about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorThe rustc-josh-sync Cronjob Bot <github-actions@github.com>2025-07-31 04:17:04 +0000
committerThe rustc-josh-sync Cronjob Bot <github-actions@github.com>2025-07-31 04:17:04 +0000
commit7cab27d73ab228be6a1eface8f0cb2f3c3b76c7c (patch)
tree876a5497b9c06e8e288e63278e19ce1397fd015f /src
parent7d378192a7d0c7829b7ee32e292d24bd89d7ca3d (diff)
parent32e7a4b92b109c24e9822c862a7c74436b50e564 (diff)
downloadrust-7cab27d73ab228be6a1eface8f0cb2f3c3b76c7c.tar.gz
rust-7cab27d73ab228be6a1eface8f0cb2f3c3b76c7c.zip
Merge ref '32e7a4b92b10' from rust-lang/rust
Pull recent changes from https://github.com/rust-lang/rust via Josh.

Upstream ref: 32e7a4b92b109c24e9822c862a7c74436b50e564
Filtered ref: d39f3479bfafb04026ed3afec68aa671d13e9c3c

This merge was created using https://github.com/rust-lang/josh-sync.
Diffstat (limited to 'src')
-rw-r--r--src/bootstrap/defaults/bootstrap.tools.toml2
-rw-r--r--src/bootstrap/src/core/build_steps/check.rs6
-rw-r--r--src/bootstrap/src/core/build_steps/test.rs30
-rw-r--r--src/bootstrap/src/core/builder/mod.rs1
-rw-r--r--src/bootstrap/src/core/config/config.rs41
-rw-r--r--src/bootstrap/src/core/config/toml/build.rs1
-rw-r--r--src/bootstrap/src/core/download.rs1108
-rw-r--r--src/bootstrap/src/utils/change_tracker.rs5
-rw-r--r--src/bootstrap/src/utils/proc_macro_deps.rs5
-rw-r--r--src/ci/docker/host-x86_64/dist-various-2/Dockerfile4
-rw-r--r--src/ci/docker/host-x86_64/pr-check-1/Dockerfile1
-rw-r--r--src/ci/docker/host-x86_64/pr-check-2/Dockerfile3
-rw-r--r--src/ci/docker/host-x86_64/test-various/Dockerfile4
-rw-r--r--src/ci/github-actions/jobs.yml15
-rwxr-xr-xsrc/ci/scripts/free-disk-space-linux.sh265
-rw-r--r--src/ci/scripts/free-disk-space-windows.ps135
-rwxr-xr-xsrc/ci/scripts/free-disk-space.sh268
-rw-r--r--src/doc/rustc-dev-guide/src/tests/directives.md13
-rw-r--r--src/doc/rustdoc/src/unstable-features.md6
-rw-r--r--src/librustdoc/clean/mod.rs51
-rw-r--r--src/librustdoc/config.rs24
-rw-r--r--src/librustdoc/core.rs2
-rw-r--r--src/librustdoc/doctest.rs84
-rw-r--r--src/librustdoc/doctest/runner.rs11
-rw-r--r--src/librustdoc/externalfiles.rs23
-rw-r--r--src/librustdoc/formats/cache.rs30
-rw-r--r--src/librustdoc/lib.rs8
-rw-r--r--src/librustdoc/scrape_examples.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/assigning_clones.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/casts/cast_ptr_alignment.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/casts/confusing_method_to_numeric_cast.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/dereference.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/implicit_saturating_sub.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/bytes_count_to_len.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/clone_on_copy.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/get_first.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/implicit_clone.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/iter_overeager_cloned.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/manual_ok_or.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/manual_str_repeat.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/map_clone.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/map_err_ignore.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/mut_mutex_lock.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/open_options.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/path_buf_push_overwrite.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/stable_sort_primitive.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/str_splitn.rs6
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/suspicious_splitn.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/unnecessary_fallible_conversions.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/unnecessary_sort_by.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/useless_asref.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/vec_resize_to_zero.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/operators/cmp_owned.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/operators/op_ref.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/ranges.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/return_self_not_must_use.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/unconditional_recursion.rs8
-rw-r--r--src/tools/clippy/clippy_lints/src/unused_io_amount.rs4
-rw-r--r--src/tools/clippy/clippy_utils/src/eager_or_lazy.rs2
-rw-r--r--src/tools/clippy/clippy_utils/src/lib.rs8
-rw-r--r--src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs2
-rw-r--r--src/tools/compiletest/src/directives.rs478
-rw-r--r--src/tools/compiletest/src/directives/auxiliary.rs41
-rw-r--r--src/tools/compiletest/src/directives/directive_names.rs289
-rw-r--r--src/tools/compiletest/src/runtest/debugger.rs8
-rw-r--r--src/tools/generate-copyright/Cargo.toml2
-rw-r--r--src/tools/generate-copyright/src/cargo_metadata.rs3
-rw-r--r--src/tools/linkchecker/Cargo.toml2
-rw-r--r--src/tools/linkchecker/main.rs118
-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/eval.rs10
-rw-r--r--src/tools/miri/src/helpers.rs232
-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_array_vs_struct.rs2
-rw-r--r--src/tools/miri/tests/fail/function_pointers/abi_mismatch_array_vs_struct.stderr2
-rw-r--r--src/tools/miri/tests/fail/function_pointers/abi_mismatch_int_vs_float.rs2
-rw-r--r--src/tools/miri/tests/fail/function_pointers/abi_mismatch_int_vs_float.stderr2
-rw-r--r--src/tools/miri/tests/fail/function_pointers/abi_mismatch_raw_pointer.rs2
-rw-r--r--src/tools/miri/tests/fail/function_pointers/abi_mismatch_raw_pointer.stderr2
-rw-r--r--src/tools/miri/tests/fail/function_pointers/abi_mismatch_repr_C.rs2
-rw-r--r--src/tools/miri/tests/fail/function_pointers/abi_mismatch_repr_C.stderr2
-rw-r--r--src/tools/miri/tests/fail/function_pointers/abi_mismatch_return_type.rs2
-rw-r--r--src/tools/miri/tests/fail/function_pointers/abi_mismatch_simple.rs2
-rw-r--r--src/tools/miri/tests/fail/function_pointers/abi_mismatch_simple.stderr2
-rw-r--r--src/tools/miri/tests/fail/function_pointers/abi_mismatch_vector.rs2
-rw-r--r--src/tools/miri/tests/fail/function_pointers/abi_mismatch_vector.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/input_arg_mismatch.rs2
-rw-r--r--src/tools/miri/tests/fail/shims/input_arg_mismatch.stderr2
-rw-r--r--src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.rs2
-rw-r--r--src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.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/miropt-test-tools/Cargo.toml2
-rw-r--r--src/tools/miropt-test-tools/src/lib.rs23
-rw-r--r--src/tools/opt-dist/src/tests.rs2
-rw-r--r--src/tools/rust-analyzer/.github/workflows/rustc-pull.yml20
-rw-r--r--src/tools/rust-analyzer/Cargo.lock49
-rw-r--r--src/tools/rust-analyzer/Cargo.toml9
-rw-r--r--src/tools/rust-analyzer/crates/base-db/src/input.rs12
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs3
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs49
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/generics.rs23
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/expr_store/path.rs2
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/hir/type_ref.rs2
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/expr.rs83
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/infer.rs26
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs6
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/lower.rs9
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs34
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/lib.rs38
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs9
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs107
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs3
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs21
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs19
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs29
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs11
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs8
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs52
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs29
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs336
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs86
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs12
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_call.rs9
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs164
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/lib.rs1
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs23
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/utils.rs147
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs69
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs119
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs24
-rwxr-xr-xsrc/tools/rust-analyzer/crates/ide/src/folding_ranges.rs27
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/lifetime.rs18
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/rename.rs75
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs2
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs89
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/grammar/types.rs5
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs4
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs8
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/err/0024_many_type_parens.rast69
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/err/0027_incomplete_where_for.rast15
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/err/0043_unexpected_for_type.rast96
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/closure_binder.rast2
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/dyn_trait_type_weak.rast14
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/for_binder_bound.rast45
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/for_binder_bound.rs1
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/for_type.rast45
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/lambda_expr.rast4
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/maybe_const_trait_bound.rast36
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/maybe_const_trait_bound.rs1
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/no_dyn_trait_leading_for.rast15
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/question_for_type_trait_bound.rast9
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/where_pred_for.rast15
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0032_where_for.rast26
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0067_where_for_pred.rast97
-rw-r--r--src/tools/rust-analyzer/crates/project-model/Cargo.toml1
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/build_dependencies.rs154
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs22
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs98
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/lib.rs19
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/sysroot.rs50
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/toolchain_info/rustc_cfg.rs1
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/workspace.rs74
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs128
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs2
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs22
-rw-r--r--src/tools/rust-analyzer/crates/syntax/rust.ungram10
-rw-r--r--src/tools/rust-analyzer/crates/syntax/src/ast.rs9
-rw-r--r--src/tools/rust-analyzer/crates/syntax/src/ast/edit.rs67
-rw-r--r--src/tools/rust-analyzer/crates/syntax/src/ast/edit_in_place.rs2
-rw-r--r--src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs112
-rw-r--r--src/tools/rust-analyzer/crates/syntax/src/ast/make.rs36
-rw-r--r--src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs8
-rw-r--r--src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory.rs2
-rw-r--r--src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs11
-rw-r--r--src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs36
-rw-r--r--src/tools/rust-analyzer/docs/book/src/contributing/README.md54
-rw-r--r--src/tools/rust-analyzer/editors/code/package-lock.json7
-rw-r--r--src/tools/rust-analyzer/editors/code/src/config.ts40
-rw-r--r--src/tools/rust-analyzer/editors/code/src/debug.ts29
-rw-r--r--src/tools/rust-analyzer/editors/code/src/run.ts15
-rw-r--r--src/tools/rust-analyzer/editors/code/src/tasks.ts10
-rw-r--r--src/tools/rust-analyzer/editors/code/src/toolchain.ts6
-rw-r--r--src/tools/rust-analyzer/josh-sync.toml2
-rw-r--r--src/tools/rust-analyzer/rust-version2
-rw-r--r--src/tools/rust-analyzer/triagebot.toml4
-rw-r--r--src/tools/rust-analyzer/xtask/Cargo.toml1
-rw-r--r--src/tools/rust-analyzer/xtask/src/flags.rs28
-rw-r--r--src/tools/rust-analyzer/xtask/src/main.rs2
-rw-r--r--src/tools/rust-analyzer/xtask/src/release.rs175
-rw-r--r--src/tools/rustbook/Cargo.lock4
m---------src/tools/rustc-perf0
-rw-r--r--src/tools/tidy/Cargo.toml2
-rw-r--r--src/tools/tidy/src/deps.rs67
-rw-r--r--src/tools/tidy/src/extra_checks/mod.rs30
-rw-r--r--src/tools/tidy/src/lib.rs55
-rw-r--r--src/tools/tidy/src/main.rs6
-rw-r--r--src/tools/tidy/src/style.rs7
-rw-r--r--src/tools/tidy/src/target_specific_tests.rs61
-rw-r--r--src/tools/tidy/src/unit_tests.rs87
298 files changed, 6843 insertions, 4300 deletions
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/build_steps/check.rs b/src/bootstrap/src/core/build_steps/check.rs
index cfe090b22dc..b4232409ba8 100644
--- a/src/bootstrap/src/core/build_steps/check.rs
+++ b/src/bootstrap/src/core/build_steps/check.rs
@@ -556,3 +556,9 @@ tool_check_step!(Compiletest {
     allow_features: COMPILETEST_ALLOW_FEATURES,
     default: false,
 });
+
+tool_check_step!(Linkchecker {
+    path: "src/tools/linkchecker",
+    mode: |_builder| Mode::ToolBootstrap,
+    default: false
+});
diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs
index 0f9268097d7..951ca73fcc4 100644
--- a/src/bootstrap/src/core/build_steps/test.rs
+++ b/src/bootstrap/src/core/build_steps/test.rs
@@ -8,6 +8,9 @@ use std::ffi::{OsStr, OsString};
 use std::path::{Path, PathBuf};
 use std::{env, fs, iter};
 
+#[cfg(feature = "tracing")]
+use tracing::instrument;
+
 use crate::core::build_steps::compile::{Std, run_cargo};
 use crate::core::build_steps::doc::DocumentationFormat;
 use crate::core::build_steps::gcc::{Gcc, add_cg_gcc_cargo_flags};
@@ -30,7 +33,7 @@ use crate::utils::helpers::{
     linker_flags, t, target_supports_cranelift_backend, up_to_date,
 };
 use crate::utils::render_tests::{add_flags_and_try_run_tests, try_run_tests};
-use crate::{CLang, DocTests, GitRepo, Mode, PathSet, envify};
+use crate::{CLang, DocTests, GitRepo, Mode, PathSet, debug, envify};
 
 const ADB_TEST_DIR: &str = "/data/local/tmp/work";
 
@@ -713,9 +716,23 @@ impl Step for CompiletestTest {
     }
 
     /// Runs `cargo test` for compiletest.
+    #[cfg_attr(
+        feature = "tracing",
+        instrument(level = "debug", name = "CompiletestTest::run", skip_all)
+    )]
     fn run(self, builder: &Builder<'_>) {
         let host = self.host;
+
+        if builder.top_stage == 0 && !builder.config.compiletest_allow_stage0 {
+            eprintln!("\
+ERROR: `--stage 0` runs compiletest self-tests against the stage0 (precompiled) compiler, not the in-tree compiler, and will almost always cause tests to fail
+NOTE: if you're sure you want to do this, please open an issue as to why. In the meantime, you can override this with `--set build.compiletest-allow-stage0=true`."
+            );
+            crate::exit!(1);
+        }
+
         let compiler = builder.compiler(builder.top_stage, host);
+        debug!(?compiler);
 
         // We need `ToolStd` for the locally-built sysroot because
         // compiletest uses unstable features of the `test` crate.
@@ -723,8 +740,8 @@ impl Step for CompiletestTest {
         let mut cargo = tool::prepare_tool_cargo(
             builder,
             compiler,
-            // compiletest uses libtest internals; make it use the in-tree std to make sure it never breaks
-            // when std sources change.
+            // compiletest uses libtest internals; make it use the in-tree std to make sure it never
+            // breaks when std sources change.
             Mode::ToolStd,
             host,
             Kind::Test,
@@ -1612,12 +1629,11 @@ impl Step for Compiletest {
             return;
         }
 
-        if builder.top_stage == 0 && env::var("COMPILETEST_FORCE_STAGE0").is_err() {
+        if builder.top_stage == 0 && !builder.config.compiletest_allow_stage0 {
             eprintln!("\
 ERROR: `--stage 0` runs compiletest on the stage0 (precompiled) compiler, not your local changes, and will almost always cause tests to fail
-HELP: to test the compiler, use `--stage 1` instead
-HELP: to test the standard library, use `--stage 0 library/std` instead
-NOTE: if you're sure you want to do this, please open an issue as to why. In the meantime, you can override this with `COMPILETEST_FORCE_STAGE0=1`."
+HELP: to test the compiler or standard library, omit the stage or explicitly use `--stage 1` instead
+NOTE: if you're sure you want to do this, please open an issue as to why. In the meantime, you can override this with `--set build.compiletest-allow-stage0=true`."
             );
             crate::exit!(1);
         }
diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs
index 923c3a9a935..020622d1c12 100644
--- a/src/bootstrap/src/core/builder/mod.rs
+++ b/src/bootstrap/src/core/builder/mod.rs
@@ -1033,6 +1033,7 @@ impl<'a> Builder<'a> {
                 check::Compiletest,
                 check::FeaturesStatusDump,
                 check::CoverageDump,
+                check::Linkchecker,
                 // This has special staging logic, it may run on stage 1 while others run on stage 0.
                 // It takes quite some time to build stage 1, so put this at the end.
                 //
diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs
index 6e04f115424..78abdd7f9b8 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};
@@ -296,8 +298,16 @@ pub struct Config {
     /// Command for visual diff display, e.g. `diff-tool --color=always`.
     pub compiletest_diff_tool: Option<String>,
 
+    /// Whether to allow running both `compiletest` self-tests and `compiletest`-managed test suites
+    /// against the stage 0 (rustc, std).
+    ///
+    /// This is only intended to be used when the stage 0 compiler is actually built from in-tree
+    /// sources.
+    pub compiletest_allow_stage0: bool,
+
     /// Whether to use the precompiled stage0 libtest with compiletest.
     pub compiletest_use_stage0_libtest: bool,
+
     /// Default value for `--extra-checks`
     pub tidy_extra_checks: Option<String>,
     pub is_running_on_ci: bool,
@@ -747,6 +757,7 @@ impl Config {
             optimized_compiler_builtins,
             jobs,
             compiletest_diff_tool,
+            compiletest_allow_stage0,
             compiletest_use_stage0_libtest,
             tidy_extra_checks,
             ccache,
@@ -795,13 +806,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 +844,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 +881,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 +899,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 +907,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 +1007,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
@@ -1012,8 +1029,12 @@ impl Config {
 
         config.optimized_compiler_builtins =
             optimized_compiler_builtins.unwrap_or(config.channel != "dev");
+
         config.compiletest_diff_tool = compiletest_diff_tool;
+
+        config.compiletest_allow_stage0 = compiletest_allow_stage0.unwrap_or(false);
         config.compiletest_use_stage0_libtest = compiletest_use_stage0_libtest.unwrap_or(true);
+
         config.tidy_extra_checks = tidy_extra_checks;
 
         let download_rustc = config.download_rustc_commit.is_some();
diff --git a/src/bootstrap/src/core/config/toml/build.rs b/src/bootstrap/src/core/config/toml/build.rs
index 4d29691f38b..728367b3972 100644
--- a/src/bootstrap/src/core/config/toml/build.rs
+++ b/src/bootstrap/src/core/config/toml/build.rs
@@ -68,6 +68,7 @@ define_config! {
         optimized_compiler_builtins: Option<bool> = "optimized-compiler-builtins",
         jobs: Option<u32> = "jobs",
         compiletest_diff_tool: Option<String> = "compiletest-diff-tool",
+        compiletest_allow_stage0: Option<bool> = "compiletest-allow-stage0",
         compiletest_use_stage0_libtest: Option<bool> = "compiletest-use-stage0-libtest",
         tidy_extra_checks: Option<String> = "tidy-extra-checks",
         ccache: Option<StringOrBool> = "ccache",
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/change_tracker.rs b/src/bootstrap/src/utils/change_tracker.rs
index 4b0c4821364..d3331b81587 100644
--- a/src/bootstrap/src/utils/change_tracker.rs
+++ b/src/bootstrap/src/utils/change_tracker.rs
@@ -486,4 +486,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[
         severity: ChangeSeverity::Warning,
         summary: "Removed `rust.description` and `llvm.ccache` as it was deprecated in #137723 and #136941 long time ago.",
     },
+    ChangeInfo {
+        change_id: 144675,
+        severity: ChangeSeverity::Warning,
+        summary: "Added `build.compiletest-allow-stage0` flag instead of `COMPILETEST_FORCE_STAGE0` env var, and reject running `compiletest` self tests against stage 0 rustc unless explicitly allowed.",
+    },
 ];
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/ci/docker/host-x86_64/dist-various-2/Dockerfile b/src/ci/docker/host-x86_64/dist-various-2/Dockerfile
index e1d83d36087..0855ea222a3 100644
--- a/src/ci/docker/host-x86_64/dist-various-2/Dockerfile
+++ b/src/ci/docker/host-x86_64/dist-various-2/Dockerfile
@@ -81,9 +81,9 @@ RUN /tmp/build-fuchsia-toolchain.sh
 COPY host-x86_64/dist-various-2/build-x86_64-fortanix-unknown-sgx-toolchain.sh /tmp/
 RUN /tmp/build-x86_64-fortanix-unknown-sgx-toolchain.sh
 
-RUN curl -L https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-linux.tar.gz | \
+RUN curl -L https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-27/wasi-sdk-27.0-x86_64-linux.tar.gz | \
   tar -xz
-ENV WASI_SDK_PATH=/tmp/wasi-sdk-25.0-x86_64-linux
+ENV WASI_SDK_PATH=/tmp/wasi-sdk-27.0-x86_64-linux
 
 COPY scripts/freebsd-toolchain.sh /tmp/
 RUN /tmp/freebsd-toolchain.sh i686
diff --git a/src/ci/docker/host-x86_64/pr-check-1/Dockerfile b/src/ci/docker/host-x86_64/pr-check-1/Dockerfile
index f7d51fba861..04ac0f33daf 100644
--- a/src/ci/docker/host-x86_64/pr-check-1/Dockerfile
+++ b/src/ci/docker/host-x86_64/pr-check-1/Dockerfile
@@ -43,7 +43,6 @@ ENV SCRIPT \
   python3 ../x.py check bootstrap && \
   /scripts/check-default-config-profiles.sh && \
   python3 ../x.py build src/tools/build-manifest && \
-  python3 ../x.py test --stage 0 src/tools/compiletest && \
   python3 ../x.py check compiletest --set build.compiletest-use-stage0-libtest=true && \
   python3 ../x.py check --target=i686-pc-windows-gnu --host=i686-pc-windows-gnu && \
   python3 ../x.py check --set build.optimized-compiler-builtins=false core alloc std --target=aarch64-unknown-linux-gnu,i686-pc-windows-msvc,i686-unknown-linux-gnu,x86_64-apple-darwin,x86_64-pc-windows-gnu,x86_64-pc-windows-msvc && \
diff --git a/src/ci/docker/host-x86_64/pr-check-2/Dockerfile b/src/ci/docker/host-x86_64/pr-check-2/Dockerfile
index ce18a181d31..f82e19bcbb4 100644
--- a/src/ci/docker/host-x86_64/pr-check-2/Dockerfile
+++ b/src/ci/docker/host-x86_64/pr-check-2/Dockerfile
@@ -30,6 +30,7 @@ ENV SCRIPT \
         python3 ../x.py check && \
         python3 ../x.py clippy ci && \
         python3 ../x.py test --stage 1 core alloc std test proc_macro && \
+        python3 ../x.py test --stage 1 src/tools/compiletest && \
         python3 ../x.py doc --stage 0 bootstrap && \
         # Build both public and internal documentation.
         RUSTDOCFLAGS=\"--document-private-items --document-hidden-items\" python3 ../x.py doc --stage 0 compiler && \
@@ -37,6 +38,6 @@ ENV SCRIPT \
         mkdir -p /checkout/obj/staging/doc && \
         cp -r build/x86_64-unknown-linux-gnu/doc /checkout/obj/staging && \
         RUSTDOCFLAGS=\"--document-private-items --document-hidden-items\" python3 ../x.py doc --stage 1 library/test && \
-        # The BOOTSTRAP_TRACING flag is added to verify whether the 
+        # The BOOTSTRAP_TRACING flag is added to verify whether the
         # bootstrap process compiles successfully with this flag enabled.
         BOOTSTRAP_TRACING=1 python3 ../x.py --help
diff --git a/src/ci/docker/host-x86_64/test-various/Dockerfile b/src/ci/docker/host-x86_64/test-various/Dockerfile
index 662a26400ce..82a820c859d 100644
--- a/src/ci/docker/host-x86_64/test-various/Dockerfile
+++ b/src/ci/docker/host-x86_64/test-various/Dockerfile
@@ -40,9 +40,9 @@ WORKDIR /
 COPY scripts/sccache.sh /scripts/
 RUN sh /scripts/sccache.sh
 
-RUN curl -L https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-linux.tar.gz | \
+RUN curl -L https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-27/wasi-sdk-27.0-x86_64-linux.tar.gz | \
   tar -xz
-ENV WASI_SDK_PATH=/wasi-sdk-25.0-x86_64-linux
+ENV WASI_SDK_PATH=/wasi-sdk-27.0-x86_64-linux
 
 ENV RUST_CONFIGURE_ARGS \
   --musl-root-x86_64=/usr/local/x86_64-linux-musl \
diff --git a/src/ci/github-actions/jobs.yml b/src/ci/github-actions/jobs.yml
index 011688487b4..48c570bfa11 100644
--- a/src/ci/github-actions/jobs.yml
+++ b/src/ci/github-actions/jobs.yml
@@ -31,20 +31,11 @@ runners:
     <<: *base-job
 
   - &job-windows
-    os: windows-2022
-    <<: *base-job
-
-  # NOTE: windows-2025 has less disk space available than windows-2022,
-  # because the D drive is missing.
-  - &job-windows-25
     os: windows-2025
+    free_disk: true
     <<: *base-job
 
   - &job-windows-8c
-    os: windows-2022-8core-32gb
-    <<: *base-job
-
-  - &job-windows-25-8c
     os: windows-2025-8core-32gb
     <<: *base-job
 
@@ -491,7 +482,7 @@ auto:
       NO_LLVM_ASSERTIONS: 1
       NO_DEBUG_ASSERTIONS: 1
       NO_OVERFLOW_CHECKS: 1
-    <<: *job-macos
+    <<: *job-macos-m1
 
   - name: x86_64-apple-1
     env:
@@ -668,7 +659,7 @@ auto:
       SCRIPT: python x.py build --set rust.debug=true opt-dist && PGO_HOST=x86_64-pc-windows-msvc ./build/x86_64-pc-windows-msvc/stage0-tools-bin/opt-dist windows-ci -- python x.py dist bootstrap --include-default-paths
       DIST_REQUIRE_ALL_TOOLS: 1
       CODEGEN_BACKENDS: llvm,cranelift
-    <<: *job-windows-25-8c
+    <<: *job-windows-8c
 
   - name: dist-i686-msvc
     env:
diff --git a/src/ci/scripts/free-disk-space-linux.sh b/src/ci/scripts/free-disk-space-linux.sh
new file mode 100755
index 00000000000..32649fe0d9b
--- /dev/null
+++ b/src/ci/scripts/free-disk-space-linux.sh
@@ -0,0 +1,265 @@
+#!/bin/bash
+set -euo pipefail
+
+# Free disk space on Linux GitHub action runners
+# Script inspired by https://github.com/jlumbroso/free-disk-space
+
+isX86() {
+    local arch
+    arch=$(uname -m)
+    if [ "$arch" = "x86_64" ]; then
+        return 0
+    else
+        return 1
+    fi
+}
+
+# Check if we're on a GitHub hosted runner.
+# In aws codebuild, the variable RUNNER_ENVIRONMENT is "self-hosted".
+isGitHubRunner() {
+    # `:-` means "use the value of RUNNER_ENVIRONMENT if it exists, otherwise use an empty string".
+    if [[ "${RUNNER_ENVIRONMENT:-}" == "github-hosted" ]]; then
+        return 0
+    else
+        return 1
+    fi
+}
+
+# print a line of the specified character
+printSeparationLine() {
+    for ((i = 0; i < 80; i++)); do
+        printf "%s" "$1"
+    done
+    printf "\n"
+}
+
+# compute available space
+# REF: https://unix.stackexchange.com/a/42049/60849
+# REF: https://stackoverflow.com/a/450821/408734
+getAvailableSpace() {
+    df -a | awk 'NR > 1 {avail+=$4} END {print avail}'
+}
+
+# make Kb human readable (assume the input is Kb)
+# REF: https://unix.stackexchange.com/a/44087/60849
+formatByteCount() {
+    numfmt --to=iec-i --suffix=B --padding=7 "${1}000"
+}
+
+# macro to output saved space
+printSavedSpace() {
+    # Disk space before the operation
+    local before=${1}
+    local title=${2:-}
+
+    local after
+    after=$(getAvailableSpace)
+    local saved=$((after - before))
+
+    if [ "$saved" -lt 0 ]; then
+        echo "::warning::Saved space is negative: $saved. Using '0' as saved space."
+        saved=0
+    fi
+
+    echo ""
+    printSeparationLine "*"
+    if [ -n "${title}" ]; then
+        echo "=> ${title}: Saved $(formatByteCount "$saved")"
+    else
+        echo "=> Saved $(formatByteCount "$saved")"
+    fi
+    printSeparationLine "*"
+    echo ""
+}
+
+# macro to print output of df with caption
+printDF() {
+    local caption=${1}
+
+    printSeparationLine "="
+    echo "${caption}"
+    echo ""
+    echo "$ df -h"
+    echo ""
+    df -h
+    printSeparationLine "="
+}
+
+removeUnusedFilesAndDirs() {
+    local to_remove=(
+        "/usr/share/java"
+    )
+
+    if isGitHubRunner; then
+        to_remove+=(
+            "/usr/local/aws-sam-cli"
+            "/usr/local/doc/cmake"
+            "/usr/local/julia"*
+            "/usr/local/lib/android"
+            "/usr/local/share/chromedriver-"*
+            "/usr/local/share/chromium"
+            "/usr/local/share/cmake-"*
+            "/usr/local/share/edge_driver"
+            "/usr/local/share/emacs"
+            "/usr/local/share/gecko_driver"
+            "/usr/local/share/icons"
+            "/usr/local/share/powershell"
+            "/usr/local/share/vcpkg"
+            "/usr/local/share/vim"
+            "/usr/share/apache-maven-"*
+            "/usr/share/gradle-"*
+            "/usr/share/kotlinc"
+            "/usr/share/miniconda"
+            "/usr/share/php"
+            "/usr/share/ri"
+            "/usr/share/swift"
+
+            # binaries
+            "/usr/local/bin/azcopy"
+            "/usr/local/bin/bicep"
+            "/usr/local/bin/ccmake"
+            "/usr/local/bin/cmake-"*
+            "/usr/local/bin/cmake"
+            "/usr/local/bin/cpack"
+            "/usr/local/bin/ctest"
+            "/usr/local/bin/helm"
+            "/usr/local/bin/kind"
+            "/usr/local/bin/kustomize"
+            "/usr/local/bin/minikube"
+            "/usr/local/bin/packer"
+            "/usr/local/bin/phpunit"
+            "/usr/local/bin/pulumi-"*
+            "/usr/local/bin/pulumi"
+            "/usr/local/bin/stack"
+
+            # Haskell runtime
+            "/usr/local/.ghcup"
+
+            # Azure
+            "/opt/az"
+            "/usr/share/az_"*
+        )
+
+        if [ -n "${AGENT_TOOLSDIRECTORY:-}" ]; then
+            # Environment variable set by GitHub Actions
+            to_remove+=(
+                "${AGENT_TOOLSDIRECTORY}"
+            )
+        else
+            echo "::warning::AGENT_TOOLSDIRECTORY is not set. Skipping removal."
+        fi
+    else
+        # Remove folders and files present in AWS CodeBuild
+        to_remove+=(
+            # binaries
+            "/usr/local/bin/ecs-cli"
+            "/usr/local/bin/eksctl"
+            "/usr/local/bin/kubectl"
+
+            "${HOME}/.gradle"
+            "${HOME}/.dotnet"
+            "${HOME}/.goenv"
+            "${HOME}/.phpenv"
+
+        )
+    fi
+
+    for element in "${to_remove[@]}"; do
+        if [ ! -e "$element" ]; then
+            # The file or directory doesn't exist.
+            # Maybe it was removed in a newer version of the runner or it's not present in a
+            # specific architecture (e.g. ARM).
+            echo "::warning::Directory or file $element does not exist, skipping."
+        fi
+    done
+
+    # Remove all files and directories at once to save time.
+    sudo rm -rf "${to_remove[@]}"
+}
+
+execAndMeasureSpaceChange() {
+    local operation=${1} # Function to execute
+    local title=${2}
+
+    local before
+    before=$(getAvailableSpace)
+    $operation
+
+    printSavedSpace "$before" "$title"
+}
+
+# Remove large packages
+# REF: https://github.com/apache/flink/blob/master/tools/azure-pipelines/free_disk_space.sh
+cleanPackages() {
+    local packages=(
+        '^aspnetcore-.*'
+        '^dotnet-.*'
+        '^llvm-.*'
+        '^mongodb-.*'
+        'firefox'
+        'libgl1-mesa-dri'
+        'mono-devel'
+        'php.*'
+    )
+
+    if isGitHubRunner; then
+        packages+=(
+            azure-cli
+        )
+
+        if isX86; then
+            packages+=(
+                'google-chrome-stable'
+                'google-cloud-cli'
+                'google-cloud-sdk'
+                'powershell'
+            )
+        fi
+    else
+        packages+=(
+            'google-chrome-stable'
+        )
+    fi
+
+    sudo apt-get -qq remove -y --fix-missing "${packages[@]}"
+
+    sudo apt-get autoremove -y || echo "::warning::The command [sudo apt-get autoremove -y] failed"
+    sudo apt-get clean || echo "::warning::The command [sudo apt-get clean] failed failed"
+}
+
+# Remove Docker images.
+# Ubuntu 22 runners have docker images already installed.
+# They aren't present in ubuntu 24 runners.
+cleanDocker() {
+    echo "=> Removing the following docker images:"
+    sudo docker image ls
+    echo "=> Removing docker images..."
+    sudo docker image prune --all --force || true
+}
+
+# Remove Swap storage
+cleanSwap() {
+    sudo swapoff -a || true
+    sudo rm -rf /mnt/swapfile || true
+    free -h
+}
+
+# Display initial disk space stats
+
+AVAILABLE_INITIAL=$(getAvailableSpace)
+
+printDF "BEFORE CLEAN-UP:"
+echo ""
+execAndMeasureSpaceChange cleanPackages "Unused packages"
+execAndMeasureSpaceChange cleanDocker "Docker images"
+execAndMeasureSpaceChange cleanSwap "Swap storage"
+execAndMeasureSpaceChange removeUnusedFilesAndDirs "Unused files and directories"
+
+# Output saved space statistic
+echo ""
+printDF "AFTER CLEAN-UP:"
+
+echo ""
+echo ""
+
+printSavedSpace "$AVAILABLE_INITIAL" "Total saved"
diff --git a/src/ci/scripts/free-disk-space-windows.ps1 b/src/ci/scripts/free-disk-space-windows.ps1
new file mode 100644
index 00000000000..8a4677bd2ab
--- /dev/null
+++ b/src/ci/scripts/free-disk-space-windows.ps1
@@ -0,0 +1,35 @@
+# Free disk space on Windows GitHub action runners.
+
+$ErrorActionPreference = 'Stop'
+
+Get-Volume | Out-String | Write-Output
+
+$available = $(Get-Volume C).SizeRemaining
+
+$dirs = 'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\Llvm',
+'C:\rtools45', 'C:\ghcup', 'C:\Program Files (x86)\Android',
+'C:\Program Files\Google\Chrome', 'C:\Program Files (x86)\Microsoft\Edge',
+'C:\Program Files\Mozilla Firefox', 'C:\Program Files\MySQL', 'C:\Julia',
+'C:\Program Files\MongoDB', 'C:\Program Files\Azure Cosmos DB Emulator',
+'C:\Program Files\PostgreSQL', 'C:\Program Files\Unity Hub',
+'C:\Strawberry', 'C:\hostedtoolcache\windows\Java_Temurin-Hotspot_jdk'
+
+foreach ($dir in $dirs) {
+    Start-ThreadJob -InputObject $dir {
+        Remove-Item -Recurse -Force -LiteralPath $input
+    } | Out-Null
+}
+
+foreach ($job in Get-Job) {
+    Wait-Job $job  | Out-Null
+    if ($job.Error) {
+        Write-Output "::warning file=$PSCommandPath::$($job.Error)"
+    }
+    Remove-Job $job
+}
+
+Get-Volume | Out-String | Write-Output
+
+$saved = ($(Get-Volume C).SizeRemaining - $available) / 1gb
+$savedRounded = [math]::Round($saved, 3)
+Write-Output "total space saved: $savedRounded GB"
diff --git a/src/ci/scripts/free-disk-space.sh b/src/ci/scripts/free-disk-space.sh
index 173f64858b3..062ad801cd8 100755
--- a/src/ci/scripts/free-disk-space.sh
+++ b/src/ci/scripts/free-disk-space.sh
@@ -1,266 +1,10 @@
 #!/bin/bash
 set -euo pipefail
 
-# Free disk space on Linux GitHub action runners
-# Script inspired by https://github.com/jlumbroso/free-disk-space
+script_dir=$(dirname "$0")
 
-isX86() {
-    local arch
-    arch=$(uname -m)
-    if [ "$arch" = "x86_64" ]; then
-        return 0
-    else
-        return 1
-    fi
-}
-
-# Check if we're on a GitHub hosted runner.
-# In aws codebuild, the variable RUNNER_ENVIRONMENT is "self-hosted".
-isGitHubRunner() {
-    # `:-` means "use the value of RUNNER_ENVIRONMENT if it exists, otherwise use an empty string".
-    if [[ "${RUNNER_ENVIRONMENT:-}" == "github-hosted" ]]; then
-        return 0
-    else
-        return 1
-    fi
-}
-
-# print a line of the specified character
-printSeparationLine() {
-    for ((i = 0; i < 80; i++)); do
-        printf "%s" "$1"
-    done
-    printf "\n"
-}
-
-# compute available space
-# REF: https://unix.stackexchange.com/a/42049/60849
-# REF: https://stackoverflow.com/a/450821/408734
-getAvailableSpace() {
-    df -a | awk 'NR > 1 {avail+=$4} END {print avail}'
-}
-
-# make Kb human readable (assume the input is Kb)
-# REF: https://unix.stackexchange.com/a/44087/60849
-formatByteCount() {
-    numfmt --to=iec-i --suffix=B --padding=7 "${1}000"
-}
-
-# macro to output saved space
-printSavedSpace() {
-    # Disk space before the operation
-    local before=${1}
-    local title=${2:-}
-
-    local after
-    after=$(getAvailableSpace)
-    local saved=$((after - before))
-
-    if [ "$saved" -lt 0 ]; then
-        echo "::warning::Saved space is negative: $saved. Using '0' as saved space."
-        saved=0
-    fi
-
-    echo ""
-    printSeparationLine "*"
-    if [ -n "${title}" ]; then
-        echo "=> ${title}: Saved $(formatByteCount "$saved")"
-    else
-        echo "=> Saved $(formatByteCount "$saved")"
-    fi
-    printSeparationLine "*"
-    echo ""
-}
-
-# macro to print output of df with caption
-printDF() {
-    local caption=${1}
-
-    printSeparationLine "="
-    echo "${caption}"
-    echo ""
-    echo "$ df -h"
-    echo ""
-    df -h
-    printSeparationLine "="
-}
-
-removeUnusedFilesAndDirs() {
-    local to_remove=(
-        "/usr/share/java"
-    )
-
-    if isGitHubRunner; then
-        to_remove+=(
-            "/usr/local/aws-sam-cli"
-            "/usr/local/doc/cmake"
-            "/usr/local/julia"*
-            "/usr/local/lib/android"
-            "/usr/local/share/chromedriver-"*
-            "/usr/local/share/chromium"
-            "/usr/local/share/cmake-"*
-            "/usr/local/share/edge_driver"
-            "/usr/local/share/emacs"
-            "/usr/local/share/gecko_driver"
-            "/usr/local/share/icons"
-            "/usr/local/share/powershell"
-            "/usr/local/share/vcpkg"
-            "/usr/local/share/vim"
-            "/usr/share/apache-maven-"*
-            "/usr/share/gradle-"*
-            "/usr/share/kotlinc"
-            "/usr/share/miniconda"
-            "/usr/share/php"
-            "/usr/share/ri"
-            "/usr/share/swift"
-
-            # binaries
-            "/usr/local/bin/azcopy"
-            "/usr/local/bin/bicep"
-            "/usr/local/bin/ccmake"
-            "/usr/local/bin/cmake-"*
-            "/usr/local/bin/cmake"
-            "/usr/local/bin/cpack"
-            "/usr/local/bin/ctest"
-            "/usr/local/bin/helm"
-            "/usr/local/bin/kind"
-            "/usr/local/bin/kustomize"
-            "/usr/local/bin/minikube"
-            "/usr/local/bin/packer"
-            "/usr/local/bin/phpunit"
-            "/usr/local/bin/pulumi-"*
-            "/usr/local/bin/pulumi"
-            "/usr/local/bin/stack"
-
-            # Haskell runtime
-            "/usr/local/.ghcup"
-
-            # Azure
-            "/opt/az"
-            "/usr/share/az_"*
-        )
-
-        if [ -n "${AGENT_TOOLSDIRECTORY:-}" ]; then
-            # Environment variable set by GitHub Actions
-            to_remove+=(
-                "${AGENT_TOOLSDIRECTORY}"
-            )
-        else
-            echo "::warning::AGENT_TOOLSDIRECTORY is not set. Skipping removal."
-        fi
-    else
-        # Remove folders and files present in AWS CodeBuild
-        to_remove+=(
-            # binaries
-            "/usr/local/bin/ecs-cli"
-            "/usr/local/bin/eksctl"
-            "/usr/local/bin/kubectl"
-
-            "${HOME}/.gradle"
-            "${HOME}/.dotnet"
-            "${HOME}/.goenv"
-            "${HOME}/.phpenv"
-
-        )
-    fi
-
-    for element in "${to_remove[@]}"; do
-        if [ ! -e "$element" ]; then
-            # The file or directory doesn't exist.
-            # Maybe it was removed in a newer version of the runner or it's not present in a
-            # specific architecture (e.g. ARM).
-            echo "::warning::Directory or file $element does not exist, skipping."
-        fi
-    done
-
-    # Remove all files and directories at once to save time.
-    sudo rm -rf "${to_remove[@]}"
-}
-
-execAndMeasureSpaceChange() {
-    local operation=${1} # Function to execute
-    local title=${2}
-
-    local before
-    before=$(getAvailableSpace)
-    $operation
-
-    printSavedSpace "$before" "$title"
-}
-
-# Remove large packages
-# REF: https://github.com/apache/flink/blob/master/tools/azure-pipelines/free_disk_space.sh
-cleanPackages() {
-    local packages=(
-        '^aspnetcore-.*'
-        '^dotnet-.*'
-        '^llvm-.*'
-        '^mongodb-.*'
-        'firefox'
-        'libgl1-mesa-dri'
-        'mono-devel'
-        'php.*'
-    )
-
-    if isGitHubRunner; then
-        packages+=(
-            azure-cli
-        )
-
-        if isX86; then
-            packages+=(
-                'google-chrome-stable'
-                'google-cloud-cli'
-                'google-cloud-sdk'
-                'powershell'
-            )
-        fi
-    else
-        packages+=(
-            'google-chrome-stable'
-        )
-    fi
-
-    sudo apt-get -qq remove -y --fix-missing "${packages[@]}"
-
-    sudo apt-get autoremove -y || echo "::warning::The command [sudo apt-get autoremove -y] failed"
-    sudo apt-get clean || echo "::warning::The command [sudo apt-get clean] failed failed"
-}
-
-# Remove Docker images.
-# Ubuntu 22 runners have docker images already installed.
-# They aren't present in ubuntu 24 runners.
-cleanDocker() {
-    echo "=> Removing the following docker images:"
-    sudo docker image ls
-    echo "=> Removing docker images..."
-    sudo docker image prune --all --force || true
-}
-
-# Remove Swap storage
-cleanSwap() {
-    sudo swapoff -a || true
-    sudo rm -rf /mnt/swapfile || true
-    free -h
-}
-
-# Display initial disk space stats
-
-AVAILABLE_INITIAL=$(getAvailableSpace)
-
-printDF "BEFORE CLEAN-UP:"
-echo ""
-
-execAndMeasureSpaceChange cleanPackages "Unused packages"
-execAndMeasureSpaceChange cleanDocker "Docker images"
-execAndMeasureSpaceChange cleanSwap "Swap storage"
-execAndMeasureSpaceChange removeUnusedFilesAndDirs "Unused files and directories"
-
-# Output saved space statistic
-echo ""
-printDF "AFTER CLEAN-UP:"
-
-echo ""
-echo ""
-
-printSavedSpace "$AVAILABLE_INITIAL" "Total saved"
+if [[ "${RUNNER_OS:-}" == "Windows" ]]; then
+    pwsh $script_dir/free-disk-space-windows.ps1
+else
+    $script_dir/free-disk-space-linux.sh
+fi
diff --git a/src/doc/rustc-dev-guide/src/tests/directives.md b/src/doc/rustc-dev-guide/src/tests/directives.md
index 5c3ae359ba0..89e4d3e9b58 100644
--- a/src/doc/rustc-dev-guide/src/tests/directives.md
+++ b/src/doc/rustc-dev-guide/src/tests/directives.md
@@ -293,8 +293,6 @@ See [Pretty-printer](compiletest.md#pretty-printer-tests).
 
 - `no-auto-check-cfg` — disable auto check-cfg (only for `--check-cfg` tests)
 - [`revisions`](compiletest.md#revisions) — compile multiple times
-- [`unused-revision-names`](compiletest.md#ignoring-unused-revision-names) -
-      suppress tidy checks for mentioning unknown revision names
 -[`forbid-output`](compiletest.md#incremental-tests) — incremental cfail rejects
       output pattern
 - [`should-ice`](compiletest.md#incremental-tests) — incremental cfail should
@@ -315,6 +313,17 @@ test suites that use those tools:
 - `llvm-cov-flags` adds extra flags when running LLVM's `llvm-cov` tool.
   - Used by [coverage tests](compiletest.md#coverage-tests) in `coverage-run` mode.
 
+### Tidy specific directives
+
+The following directives control how the [tidy script](../conventions.md#formatting)
+verifies tests.
+
+- `ignore-tidy-target-specific-tests` disables checking that the appropriate
+  LLVM component is required (via a `needs-llvm-components` directive) when a
+  test is compiled for a specific target (via the `--target` flag in a
+  `compile-flag` directive).
+- [`unused-revision-names`](compiletest.md#ignoring-unused-revision-names) -
+      suppress tidy checks for mentioning unknown revision names.
 
 ## Substitutions
 
diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md
index 27910ad0ab7..7bd2970eee7 100644
--- a/src/doc/rustdoc/src/unstable-features.md
+++ b/src/doc/rustdoc/src/unstable-features.md
@@ -395,6 +395,12 @@ flags to control that behavior. When the `--extern-html-root-url` flag is given
 one of your dependencies, rustdoc use that URL for those docs. Keep in mind that if those docs exist
 in the output directory, those local docs will still override this flag.
 
+The names in this flag are first matched against the names given in the `--extern name=` flags,
+which allows selecting between multiple crates with the same name (e.g. multiple versions of
+the same crate). For transitive dependencies that haven't been loaded via an `--extern` flag, matching
+falls backs to using crate names only, without ability to distinguish between multiple crates with
+the same name.
+
 ## `-Z force-unstable-if-unmarked`
 
 Using this flag looks like this:
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index 1265a39d27b..14295ce0a31 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -36,6 +36,7 @@ use std::mem;
 
 use rustc_ast::token::{Token, TokenKind};
 use rustc_ast::tokenstream::{TokenStream, TokenTree};
+use rustc_attr_data_structures::{AttributeKind, find_attr};
 use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet, IndexEntry};
 use rustc_errors::codes::*;
 use rustc_errors::{FatalError, struct_span_code_err};
@@ -987,28 +988,17 @@ fn clean_proc_macro<'tcx>(
     kind: MacroKind,
     cx: &mut DocContext<'tcx>,
 ) -> ItemKind {
-    let attrs = cx.tcx.hir_attrs(item.hir_id());
-    if kind == MacroKind::Derive
-        && let Some(derive_name) =
-            hir_attr_lists(attrs, sym::proc_macro_derive).find_map(|mi| mi.ident())
-    {
-        *name = derive_name.name;
+    if kind != MacroKind::Derive {
+        return ProcMacroItem(ProcMacro { kind, helpers: vec![] });
     }
+    let attrs = cx.tcx.hir_attrs(item.hir_id());
+    let Some((trait_name, helper_attrs)) = find_attr!(attrs, AttributeKind::ProcMacroDerive { trait_name, helper_attrs, ..} => (*trait_name, helper_attrs))
+    else {
+        return ProcMacroItem(ProcMacro { kind, helpers: vec![] });
+    };
+    *name = trait_name;
+    let helpers = helper_attrs.iter().copied().collect();
 
-    let mut helpers = Vec::new();
-    for mi in hir_attr_lists(attrs, sym::proc_macro_derive) {
-        if !mi.has_name(sym::attributes) {
-            continue;
-        }
-
-        if let Some(list) = mi.meta_item_list() {
-            for inner_mi in list {
-                if let Some(ident) = inner_mi.ident() {
-                    helpers.push(ident.name);
-                }
-            }
-        }
-    }
     ProcMacroItem(ProcMacro { kind, helpers })
 }
 
@@ -1021,17 +1011,16 @@ fn clean_fn_or_proc_macro<'tcx>(
     cx: &mut DocContext<'tcx>,
 ) -> ItemKind {
     let attrs = cx.tcx.hir_attrs(item.hir_id());
-    let macro_kind = attrs.iter().find_map(|a| {
-        if a.has_name(sym::proc_macro) {
-            Some(MacroKind::Bang)
-        } else if a.has_name(sym::proc_macro_derive) {
-            Some(MacroKind::Derive)
-        } else if a.has_name(sym::proc_macro_attribute) {
-            Some(MacroKind::Attr)
-        } else {
-            None
-        }
-    });
+    let macro_kind = if find_attr!(attrs, AttributeKind::ProcMacro(..)) {
+        Some(MacroKind::Bang)
+    } else if find_attr!(attrs, AttributeKind::ProcMacroDerive { .. }) {
+        Some(MacroKind::Derive)
+    } else if find_attr!(attrs, AttributeKind::ProcMacroAttribute(..)) {
+        Some(MacroKind::Attr)
+    } else {
+        None
+    };
+
     match macro_kind {
         Some(kind) => clean_proc_macro(item, name, kind, cx),
         None => {
diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs
index 986390dbaa0..fed4296fa22 100644
--- a/src/librustdoc/config.rs
+++ b/src/librustdoc/config.rs
@@ -173,6 +173,9 @@ pub(crate) struct Options {
 
     /// Arguments to be used when compiling doctests.
     pub(crate) doctest_build_args: Vec<String>,
+
+    /// Target modifiers.
+    pub(crate) target_modifiers: BTreeMap<OptionsTargetModifiers, String>,
 }
 
 impl fmt::Debug for Options {
@@ -377,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());
 
@@ -640,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();
@@ -687,6 +693,7 @@ impl Options {
                     ))
                     .emit();
                 }
+                loaded_paths.push(theme_file.clone());
                 themes.push(StylePath { path: theme_file });
             }
         }
@@ -705,6 +712,7 @@ impl Options {
             &mut id_map,
             edition,
             &None,
+            &mut loaded_paths,
         ) else {
             dcx.fatal("`ExternalHtml::load` failed");
         };
@@ -796,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 =
@@ -846,6 +855,7 @@ impl Options {
             unstable_features,
             expanded_args: args,
             doctest_build_args,
+            target_modifiers,
         };
         let render_options = RenderOptions {
             output,
@@ -881,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/core.rs b/src/librustdoc/core.rs
index bd57bb21e63..e89733b2f6d 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -214,6 +214,7 @@ pub(crate) fn create_config(
         scrape_examples_options,
         expanded_args,
         remap_path_prefix,
+        target_modifiers,
         ..
     }: RustdocOptions,
     render_options: &RenderOptions,
@@ -277,6 +278,7 @@ pub(crate) fn create_config(
         } else {
             OutputTypes::new(&[])
         },
+        target_modifiers,
         ..Options::default()
     };
 
diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs
index a32c2f7fb18..0bef091468f 100644
--- a/src/librustdoc/doctest.rs
+++ b/src/librustdoc/doctest.rs
@@ -11,7 +11,8 @@ use std::path::{Path, PathBuf};
 use std::process::{self, Command, Stdio};
 use std::sync::atomic::{AtomicUsize, Ordering};
 use std::sync::{Arc, Mutex};
-use std::{panic, str};
+use std::time::{Duration, Instant};
+use std::{fmt, panic, str};
 
 pub(crate) use make::{BuildDocTestBuilder, DocTestBuilder};
 pub(crate) use markdown::test as test_markdown;
@@ -36,6 +37,50 @@ use crate::config::{Options as RustdocOptions, OutputFormat};
 use crate::html::markdown::{ErrorCodes, Ignore, LangString, MdRelLine};
 use crate::lint::init_lints;
 
+/// Type used to display times (compilation and total) information for merged doctests.
+struct MergedDoctestTimes {
+    total_time: Instant,
+    /// Total time spent compiling all merged doctests.
+    compilation_time: Duration,
+    /// This field is used to keep track of how many merged doctests we (tried to) compile.
+    added_compilation_times: usize,
+}
+
+impl MergedDoctestTimes {
+    fn new() -> Self {
+        Self {
+            total_time: Instant::now(),
+            compilation_time: Duration::default(),
+            added_compilation_times: 0,
+        }
+    }
+
+    fn add_compilation_time(&mut self, duration: Duration) {
+        self.compilation_time += duration;
+        self.added_compilation_times += 1;
+    }
+
+    fn display_times(&self) {
+        // If no merged doctest was compiled, then there is nothing to display since the numbers
+        // displayed by `libtest` for standalone tests are already accurate (they include both
+        // compilation and runtime).
+        if self.added_compilation_times > 0 {
+            println!("{self}");
+        }
+    }
+}
+
+impl fmt::Display for MergedDoctestTimes {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(
+            f,
+            "all doctests ran in {:.2}s; merged doctests compilation took {:.2}s",
+            self.total_time.elapsed().as_secs_f64(),
+            self.compilation_time.as_secs_f64(),
+        )
+    }
+}
+
 /// Options that apply to all doctests in a crate or Markdown file (for `rustdoc foo.md`).
 #[derive(Clone)]
 pub(crate) struct GlobalTestOptions {
@@ -295,6 +340,7 @@ pub(crate) fn run_tests(
 
     let mut nb_errors = 0;
     let mut ran_edition_tests = 0;
+    let mut times = MergedDoctestTimes::new();
     let target_str = rustdoc_options.target.to_string();
 
     for (MergeableTestKey { edition, global_crate_attrs_hash }, mut doctests) in mergeable_tests {
@@ -314,13 +360,15 @@ pub(crate) fn run_tests(
         for (doctest, scraped_test) in &doctests {
             tests_runner.add_test(doctest, scraped_test, &target_str);
         }
-        if let Ok(success) = tests_runner.run_merged_tests(
+        let (duration, ret) = tests_runner.run_merged_tests(
             rustdoc_test_options,
             edition,
             &opts,
             &test_args,
             rustdoc_options,
-        ) {
+        );
+        times.add_compilation_time(duration);
+        if let Ok(success) = ret {
             ran_edition_tests += 1;
             if !success {
                 nb_errors += 1;
@@ -354,11 +402,13 @@ pub(crate) fn run_tests(
         test::test_main_with_exit_callback(&test_args, standalone_tests, None, || {
             // We ensure temp dir destructor is called.
             std::mem::drop(temp_dir.take());
+            times.display_times();
         });
     }
     if nb_errors != 0 {
         // We ensure temp dir destructor is called.
         std::mem::drop(temp_dir);
+        times.display_times();
         // libtest::ERROR_EXIT_CODE is not public but it's the same value.
         std::process::exit(101);
     }
@@ -496,16 +546,19 @@ impl RunnableDocTest {
 ///
 /// This is the function that calculates the compiler command line, invokes the compiler, then
 /// invokes the test or tests in a separate executable (if applicable).
+///
+/// Returns a tuple containing the `Duration` of the compilation and the `Result` of the test.
 fn run_test(
     doctest: RunnableDocTest,
     rustdoc_options: &RustdocOptions,
     supports_color: bool,
     report_unused_externs: impl Fn(UnusedExterns),
-) -> Result<(), TestFailure> {
+) -> (Duration, Result<(), TestFailure>) {
     let langstr = &doctest.langstr;
     // Make sure we emit well-formed executable names for our target.
     let rust_out = add_exe_suffix("rust_out".to_owned(), &rustdoc_options.target);
     let output_file = doctest.test_opts.outdir.path().join(rust_out);
+    let instant = Instant::now();
 
     // Common arguments used for compiling the doctest runner.
     // On merged doctests, the compiler is invoked twice: once for the test code itself,
@@ -589,7 +642,7 @@ fn run_test(
         if std::fs::write(&input_file, &doctest.full_test_code).is_err() {
             // If we cannot write this file for any reason, we leave. All combined tests will be
             // tested as standalone tests.
-            return Err(TestFailure::CompileError);
+            return (Duration::default(), Err(TestFailure::CompileError));
         }
         if !rustdoc_options.nocapture {
             // If `nocapture` is disabled, then we don't display rustc's output when compiling
@@ -660,7 +713,7 @@ fn run_test(
         if std::fs::write(&runner_input_file, merged_test_code).is_err() {
             // If we cannot write this file for any reason, we leave. All combined tests will be
             // tested as standalone tests.
-            return Err(TestFailure::CompileError);
+            return (instant.elapsed(), Err(TestFailure::CompileError));
         }
         if !rustdoc_options.nocapture {
             // If `nocapture` is disabled, then we don't display rustc's output when compiling
@@ -713,7 +766,7 @@ fn run_test(
     let _bomb = Bomb(&out);
     match (output.status.success(), langstr.compile_fail) {
         (true, true) => {
-            return Err(TestFailure::UnexpectedCompilePass);
+            return (instant.elapsed(), Err(TestFailure::UnexpectedCompilePass));
         }
         (true, false) => {}
         (false, true) => {
@@ -729,17 +782,18 @@ fn run_test(
                     .collect();
 
                 if !missing_codes.is_empty() {
-                    return Err(TestFailure::MissingErrorCodes(missing_codes));
+                    return (instant.elapsed(), Err(TestFailure::MissingErrorCodes(missing_codes)));
                 }
             }
         }
         (false, false) => {
-            return Err(TestFailure::CompileError);
+            return (instant.elapsed(), Err(TestFailure::CompileError));
         }
     }
 
+    let duration = instant.elapsed();
     if doctest.no_run {
-        return Ok(());
+        return (duration, Ok(()));
     }
 
     // Run the code!
@@ -771,17 +825,17 @@ fn run_test(
         cmd.output()
     };
     match result {
-        Err(e) => return Err(TestFailure::ExecutionError(e)),
+        Err(e) => return (duration, Err(TestFailure::ExecutionError(e))),
         Ok(out) => {
             if langstr.should_panic && out.status.success() {
-                return Err(TestFailure::UnexpectedRunPass);
+                return (duration, Err(TestFailure::UnexpectedRunPass));
             } else if !langstr.should_panic && !out.status.success() {
-                return Err(TestFailure::ExecutionFailure(out));
+                return (duration, Err(TestFailure::ExecutionFailure(out)));
             }
         }
     }
 
-    Ok(())
+    (duration, Ok(()))
 }
 
 /// Converts a path intended to use as a command to absolute if it is
@@ -1071,7 +1125,7 @@ fn doctest_run_fn(
         no_run: scraped_test.no_run(&rustdoc_options),
         merged_test_code: None,
     };
-    let res =
+    let (_, res) =
         run_test(runnable_test, &rustdoc_options, doctest.supports_color, report_unused_externs);
 
     if let Err(err) = res {
diff --git a/src/librustdoc/doctest/runner.rs b/src/librustdoc/doctest/runner.rs
index f0914474c79..fcfa424968e 100644
--- a/src/librustdoc/doctest/runner.rs
+++ b/src/librustdoc/doctest/runner.rs
@@ -1,4 +1,5 @@
 use std::fmt::Write;
+use std::time::Duration;
 
 use rustc_data_structures::fx::FxIndexSet;
 use rustc_span::edition::Edition;
@@ -67,6 +68,10 @@ impl DocTestRunner {
         self.nb_tests += 1;
     }
 
+    /// Returns a tuple containing the `Duration` of the compilation and the `Result` of the test.
+    ///
+    /// If compilation failed, it will return `Err`, otherwise it will return `Ok` containing if
+    /// the test ran successfully.
     pub(crate) fn run_merged_tests(
         &mut self,
         test_options: IndividualTestOptions,
@@ -74,7 +79,7 @@ impl DocTestRunner {
         opts: &GlobalTestOptions,
         test_args: &[String],
         rustdoc_options: &RustdocOptions,
-    ) -> Result<bool, ()> {
+    ) -> (Duration, Result<bool, ()>) {
         let mut code = "\
 #![allow(unused_extern_crates)]
 #![allow(internal_features)]
@@ -204,9 +209,9 @@ std::process::Termination::report(test::test_main(test_args, tests, None))
             no_run: false,
             merged_test_code: Some(code),
         };
-        let ret =
+        let (duration, ret) =
             run_test(runnable_test, rustdoc_options, self.supports_color, |_: UnusedExterns| {});
-        if let Err(TestFailure::CompileError) = ret { Err(()) } else { Ok(ret.is_ok()) }
+        (duration, if let Err(TestFailure::CompileError) = ret { Err(()) } else { Ok(ret.is_ok()) })
     }
 }
 
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/formats/cache.rs b/src/librustdoc/formats/cache.rs
index 5191120ebdb..e28cc3a542e 100644
--- a/src/librustdoc/formats/cache.rs
+++ b/src/librustdoc/formats/cache.rs
@@ -4,6 +4,7 @@ use rustc_ast::join_path_syms;
 use rustc_attr_data_structures::StabilityLevel;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet};
 use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, DefIdSet};
+use rustc_metadata::creader::CStore;
 use rustc_middle::ty::{self, TyCtxt};
 use rustc_span::Symbol;
 use tracing::debug;
@@ -158,18 +159,33 @@ impl Cache {
         assert!(cx.external_traits.is_empty());
         cx.cache.traits = mem::take(&mut krate.external_traits);
 
+        let render_options = &cx.render_options;
+        let extern_url_takes_precedence = render_options.extern_html_root_takes_precedence;
+        let dst = &render_options.output;
+
+        // Make `--extern-html-root-url` support the same names as `--extern` whenever possible
+        let cstore = CStore::from_tcx(tcx);
+        for (name, extern_url) in &render_options.extern_html_root_urls {
+            if let Some(crate_num) = cstore.resolved_extern_crate(Symbol::intern(name)) {
+                let e = ExternalCrate { crate_num };
+                let location = e.location(Some(extern_url), extern_url_takes_precedence, dst, tcx);
+                cx.cache.extern_locations.insert(e.crate_num, location);
+            }
+        }
+
         // Cache where all our extern crates are located
-        // FIXME: this part is specific to HTML so it'd be nice to remove it from the common code
+        // This is also used in the JSON output.
         for &crate_num in tcx.crates(()) {
             let e = ExternalCrate { crate_num };
 
             let name = e.name(tcx);
-            let render_options = &cx.render_options;
-            let extern_url = render_options.extern_html_root_urls.get(name.as_str()).map(|u| &**u);
-            let extern_url_takes_precedence = render_options.extern_html_root_takes_precedence;
-            let dst = &render_options.output;
-            let location = e.location(extern_url, extern_url_takes_precedence, dst, tcx);
-            cx.cache.extern_locations.insert(e.crate_num, location);
+            cx.cache.extern_locations.entry(e.crate_num).or_insert_with(|| {
+                // falls back to matching by crates' own names, because
+                // transitive dependencies and injected crates may be loaded without `--extern`
+                let extern_url =
+                    render_options.extern_html_root_urls.get(name.as_str()).map(|u| &**u);
+                e.location(extern_url, extern_url_takes_precedence, dst, tcx)
+            });
             cx.cache.external_paths.insert(e.def_id(), (vec![name], ItemType::Module));
         }
 
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/clippy/clippy_lints/src/assigning_clones.rs b/src/tools/clippy/clippy_lints/src/assigning_clones.rs
index 8b8b42bbf72..52287be34c7 100644
--- a/src/tools/clippy/clippy_lints/src/assigning_clones.rs
+++ b/src/tools/clippy/clippy_lints/src/assigning_clones.rs
@@ -98,7 +98,7 @@ impl<'tcx> LateLintPass<'tcx> for AssigningClones {
             // That is overly conservative - the lint should fire even if there was no initializer,
             // but the variable has been initialized before `lhs` was evaluated.
             && path_to_local(lhs).is_none_or(|lhs| local_is_initialized(cx, lhs))
-            && let Some(resolved_impl) = cx.tcx.impl_of_method(resolved_fn.def_id())
+            && let Some(resolved_impl) = cx.tcx.impl_of_assoc(resolved_fn.def_id())
             // Derived forms don't implement `clone_from`/`clone_into`.
             // See https://github.com/rust-lang/rust/pull/98445#issuecomment-1190681305
             && !cx.tcx.is_builtin_derived(resolved_impl)
diff --git a/src/tools/clippy/clippy_lints/src/casts/cast_ptr_alignment.rs b/src/tools/clippy/clippy_lints/src/casts/cast_ptr_alignment.rs
index e4dafde0f9d..a1543cabd2f 100644
--- a/src/tools/clippy/clippy_lints/src/casts/cast_ptr_alignment.rs
+++ b/src/tools/clippy/clippy_lints/src/casts/cast_ptr_alignment.rs
@@ -63,7 +63,7 @@ fn is_used_as_unaligned(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
         ExprKind::MethodCall(name, self_arg, ..) if self_arg.hir_id == e.hir_id => {
             if matches!(name.ident.name, sym::read_unaligned | sym::write_unaligned)
                 && let Some(def_id) = cx.typeck_results().type_dependent_def_id(parent.hir_id)
-                && let Some(def_id) = cx.tcx.impl_of_method(def_id)
+                && let Some(def_id) = cx.tcx.impl_of_assoc(def_id)
                 && cx.tcx.type_of(def_id).instantiate_identity().is_raw_ptr()
             {
                 true
diff --git a/src/tools/clippy/clippy_lints/src/casts/confusing_method_to_numeric_cast.rs b/src/tools/clippy/clippy_lints/src/casts/confusing_method_to_numeric_cast.rs
index 849de22cfba..73347e7141e 100644
--- a/src/tools/clippy/clippy_lints/src/casts/confusing_method_to_numeric_cast.rs
+++ b/src/tools/clippy/clippy_lints/src/casts/confusing_method_to_numeric_cast.rs
@@ -37,7 +37,7 @@ fn get_const_name_and_ty_name(
         } else {
             return None;
         }
-    } else if let Some(impl_id) = cx.tcx.impl_of_method(method_def_id)
+    } else if let Some(impl_id) = cx.tcx.impl_of_assoc(method_def_id)
         && let Some(ty_name) = get_primitive_ty_name(cx.tcx.type_of(impl_id).instantiate_identity())
         && matches!(
             method_name,
diff --git a/src/tools/clippy/clippy_lints/src/dereference.rs b/src/tools/clippy/clippy_lints/src/dereference.rs
index 5099df3fa02..995a1209595 100644
--- a/src/tools/clippy/clippy_lints/src/dereference.rs
+++ b/src/tools/clippy/clippy_lints/src/dereference.rs
@@ -364,7 +364,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
                                 // * `&self` methods on `&T` can have auto-borrow, but `&self` methods on `T` will take
                                 //   priority.
                                 if let Some(fn_id) = typeck.type_dependent_def_id(hir_id)
-                                    && let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
+                                    && let Some(trait_id) = cx.tcx.trait_of_assoc(fn_id)
                                     && let arg_ty = cx.tcx.erase_regions(adjusted_ty)
                                     && let ty::Ref(_, sub_ty, _) = *arg_ty.kind()
                                     && let args =
diff --git a/src/tools/clippy/clippy_lints/src/implicit_saturating_sub.rs b/src/tools/clippy/clippy_lints/src/implicit_saturating_sub.rs
index c743501da25..c634c12e187 100644
--- a/src/tools/clippy/clippy_lints/src/implicit_saturating_sub.rs
+++ b/src/tools/clippy/clippy_lints/src/implicit_saturating_sub.rs
@@ -339,7 +339,7 @@ fn check_with_condition<'tcx>(
             ExprKind::Path(QPath::TypeRelative(_, name)) => {
                 if name.ident.name == sym::MIN
                     && let Some(const_id) = cx.typeck_results().type_dependent_def_id(cond_num_val.hir_id)
-                    && let Some(impl_id) = cx.tcx.impl_of_method(const_id)
+                    && let Some(impl_id) = cx.tcx.impl_of_assoc(const_id)
                     && let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl
                     && cx.tcx.type_of(impl_id).instantiate_identity().is_integral()
                 {
@@ -350,7 +350,7 @@ fn check_with_condition<'tcx>(
                 if let ExprKind::Path(QPath::TypeRelative(_, name)) = func.kind
                     && name.ident.name == sym::min_value
                     && let Some(func_id) = cx.typeck_results().type_dependent_def_id(func.hir_id)
-                    && let Some(impl_id) = cx.tcx.impl_of_method(func_id)
+                    && let Some(impl_id) = cx.tcx.impl_of_assoc(func_id)
                     && let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl
                     && cx.tcx.type_of(impl_id).instantiate_identity().is_integral()
                 {
diff --git a/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs b/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs
index 972b0b110e0..7bb684d65bb 100644
--- a/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs
+++ b/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs
@@ -317,7 +317,7 @@ impl<'tcx> Visitor<'tcx> for VarVisitor<'_, 'tcx> {
                 .cx
                 .typeck_results()
                 .type_dependent_def_id(expr.hir_id)
-                .and_then(|def_id| self.cx.tcx.trait_of_item(def_id))
+                .and_then(|def_id| self.cx.tcx.trait_of_assoc(def_id))
             && ((meth.ident.name == sym::index && self.cx.tcx.lang_items().index_trait() == Some(trait_id))
                 || (meth.ident.name == sym::index_mut && self.cx.tcx.lang_items().index_mut_trait() == Some(trait_id)))
             && !self.check(args_1, args_0, expr)
diff --git a/src/tools/clippy/clippy_lints/src/methods/bytes_count_to_len.rs b/src/tools/clippy/clippy_lints/src/methods/bytes_count_to_len.rs
index a9f6a41c235..b8cc5ddd845 100644
--- a/src/tools/clippy/clippy_lints/src/methods/bytes_count_to_len.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/bytes_count_to_len.rs
@@ -14,7 +14,7 @@ pub(super) fn check<'tcx>(
     bytes_recv: &'tcx hir::Expr<'_>,
 ) {
     if let Some(bytes_id) = cx.typeck_results().type_dependent_def_id(count_recv.hir_id)
-        && let Some(impl_id) = cx.tcx.impl_of_method(bytes_id)
+        && let Some(impl_id) = cx.tcx.impl_of_assoc(bytes_id)
         && cx.tcx.type_of(impl_id).instantiate_identity().is_str()
         && let ty = cx.typeck_results().expr_ty(bytes_recv).peel_refs()
         && (ty.is_str() || is_type_lang_item(cx, ty, hir::LangItem::String))
diff --git a/src/tools/clippy/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs b/src/tools/clippy/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs
index 292fa08b598..6f9702f6c6c 100644
--- a/src/tools/clippy/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs
@@ -30,7 +30,7 @@ pub(super) fn check<'tcx>(
     }
 
     if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
-        && let Some(impl_id) = cx.tcx.impl_of_method(method_id)
+        && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id)
         && cx.tcx.type_of(impl_id).instantiate_identity().is_str()
         && let ExprKind::Lit(Spanned {
             node: LitKind::Str(ext_literal, ..),
diff --git a/src/tools/clippy/clippy_lints/src/methods/clone_on_copy.rs b/src/tools/clippy/clippy_lints/src/methods/clone_on_copy.rs
index 2ecf3eb8979..0a456d1057a 100644
--- a/src/tools/clippy/clippy_lints/src/methods/clone_on_copy.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/clone_on_copy.rs
@@ -28,7 +28,7 @@ pub(super) fn check(
     if cx
         .typeck_results()
         .type_dependent_def_id(expr.hir_id)
-        .and_then(|id| cx.tcx.trait_of_item(id))
+        .and_then(|id| cx.tcx.trait_of_assoc(id))
         .zip(cx.tcx.lang_items().clone_trait())
         .is_none_or(|(x, y)| x != y)
     {
diff --git a/src/tools/clippy/clippy_lints/src/methods/get_first.rs b/src/tools/clippy/clippy_lints/src/methods/get_first.rs
index f4465e654c2..2e1d71ce284 100644
--- a/src/tools/clippy/clippy_lints/src/methods/get_first.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/get_first.rs
@@ -18,7 +18,7 @@ pub(super) fn check<'tcx>(
     arg: &'tcx hir::Expr<'_>,
 ) {
     if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
-        && let Some(impl_id) = cx.tcx.impl_of_method(method_id)
+        && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id)
         && let identity = cx.tcx.type_of(impl_id).instantiate_identity()
         && let hir::ExprKind::Lit(Spanned {
             node: LitKind::Int(Pu128(0), _),
diff --git a/src/tools/clippy/clippy_lints/src/methods/implicit_clone.rs b/src/tools/clippy/clippy_lints/src/methods/implicit_clone.rs
index 9724463f0c0..efa8cee58df 100644
--- a/src/tools/clippy/clippy_lints/src/methods/implicit_clone.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/implicit_clone.rs
@@ -50,7 +50,7 @@ pub fn is_clone_like(cx: &LateContext<'_>, method_name: Symbol, method_def_id: h
         sym::to_path_buf => is_diag_item_method(cx, method_def_id, sym::Path),
         sym::to_vec => cx
             .tcx
-            .impl_of_method(method_def_id)
+            .impl_of_assoc(method_def_id)
             .filter(|&impl_did| {
                 cx.tcx.type_of(impl_did).instantiate_identity().is_slice() && cx.tcx.impl_trait_ref(impl_did).is_none()
             })
diff --git a/src/tools/clippy/clippy_lints/src/methods/iter_overeager_cloned.rs b/src/tools/clippy/clippy_lints/src/methods/iter_overeager_cloned.rs
index f5fe4316eb0..f851ebe91f3 100644
--- a/src/tools/clippy/clippy_lints/src/methods/iter_overeager_cloned.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/iter_overeager_cloned.rs
@@ -44,9 +44,9 @@ pub(super) fn check<'tcx>(
     let typeck = cx.typeck_results();
     if let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator)
         && let Some(method_id) = typeck.type_dependent_def_id(expr.hir_id)
-        && cx.tcx.trait_of_item(method_id) == Some(iter_id)
+        && cx.tcx.trait_of_assoc(method_id) == Some(iter_id)
         && let Some(method_id) = typeck.type_dependent_def_id(cloned_call.hir_id)
-        && cx.tcx.trait_of_item(method_id) == Some(iter_id)
+        && cx.tcx.trait_of_assoc(method_id) == Some(iter_id)
         && let cloned_recv_ty = typeck.expr_ty_adjusted(cloned_recv)
         && let Some(iter_assoc_ty) = cx.get_associated_type(cloned_recv_ty, iter_id, sym::Item)
         && matches!(*iter_assoc_ty.kind(), ty::Ref(_, ty, _) if !is_copy(cx, ty))
diff --git a/src/tools/clippy/clippy_lints/src/methods/manual_ok_or.rs b/src/tools/clippy/clippy_lints/src/methods/manual_ok_or.rs
index c286c5faaed..077957fa44d 100644
--- a/src/tools/clippy/clippy_lints/src/methods/manual_ok_or.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/manual_ok_or.rs
@@ -18,7 +18,7 @@ pub(super) fn check<'tcx>(
     map_expr: &'tcx Expr<'_>,
 ) {
     if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
-        && let Some(impl_id) = cx.tcx.impl_of_method(method_id)
+        && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id)
         && is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id).instantiate_identity(), sym::Option)
         && let ExprKind::Call(err_path, [err_arg]) = or_expr.kind
         && is_res_lang_ctor(cx, path_res(cx, err_path), ResultErr)
diff --git a/src/tools/clippy/clippy_lints/src/methods/manual_str_repeat.rs b/src/tools/clippy/clippy_lints/src/methods/manual_str_repeat.rs
index 8167e4f9605..a811dd1cee1 100644
--- a/src/tools/clippy/clippy_lints/src/methods/manual_str_repeat.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/manual_str_repeat.rs
@@ -59,7 +59,7 @@ pub(super) fn check(
         && is_type_lang_item(cx, cx.typeck_results().expr_ty(collect_expr), LangItem::String)
         && let Some(take_id) = cx.typeck_results().type_dependent_def_id(take_expr.hir_id)
         && let Some(iter_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator)
-        && cx.tcx.trait_of_item(take_id) == Some(iter_trait_id)
+        && cx.tcx.trait_of_assoc(take_id) == Some(iter_trait_id)
         && let Some(repeat_kind) = parse_repeat_arg(cx, repeat_arg)
         && let ctxt = collect_expr.span.ctxt()
         && ctxt == take_expr.span.ctxt()
diff --git a/src/tools/clippy/clippy_lints/src/methods/map_clone.rs b/src/tools/clippy/clippy_lints/src/methods/map_clone.rs
index 333a33f7527..748be9bfcc6 100644
--- a/src/tools/clippy/clippy_lints/src/methods/map_clone.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/map_clone.rs
@@ -23,7 +23,7 @@ fn should_run_lint(cx: &LateContext<'_>, e: &hir::Expr<'_>, method_id: DefId) ->
         return true;
     }
     // We check if it's an `Option` or a `Result`.
-    if let Some(id) = cx.tcx.impl_of_method(method_id) {
+    if let Some(id) = cx.tcx.impl_of_assoc(method_id) {
         let identity = cx.tcx.type_of(id).instantiate_identity();
         if !is_type_diagnostic_item(cx, identity, sym::Option) && !is_type_diagnostic_item(cx, identity, sym::Result) {
             return false;
@@ -69,7 +69,7 @@ pub(super) fn check(cx: &LateContext<'_>, e: &hir::Expr<'_>, recv: &hir::Expr<'_
                             hir::ExprKind::MethodCall(method, obj, [], _) => {
                                 if ident_eq(name, obj) && method.ident.name == sym::clone
                                 && let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id)
-                                && let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
+                                && let Some(trait_id) = cx.tcx.trait_of_assoc(fn_id)
                                 && cx.tcx.lang_items().clone_trait() == Some(trait_id)
                                 // no autoderefs
                                 && !cx.typeck_results().expr_adjustments(obj).iter()
diff --git a/src/tools/clippy/clippy_lints/src/methods/map_err_ignore.rs b/src/tools/clippy/clippy_lints/src/methods/map_err_ignore.rs
index 5d0d4dae35f..41beda9c5cb 100644
--- a/src/tools/clippy/clippy_lints/src/methods/map_err_ignore.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/map_err_ignore.rs
@@ -8,7 +8,7 @@ use super::MAP_ERR_IGNORE;
 
 pub(super) fn check(cx: &LateContext<'_>, e: &Expr<'_>, arg: &Expr<'_>) {
     if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
-        && let Some(impl_id) = cx.tcx.impl_of_method(method_id)
+        && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id)
         && is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id).instantiate_identity(), sym::Result)
         && let ExprKind::Closure(&Closure {
             capture_clause: CaptureBy::Ref,
diff --git a/src/tools/clippy/clippy_lints/src/methods/mut_mutex_lock.rs b/src/tools/clippy/clippy_lints/src/methods/mut_mutex_lock.rs
index 320523aceb6..4235af882b0 100644
--- a/src/tools/clippy/clippy_lints/src/methods/mut_mutex_lock.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/mut_mutex_lock.rs
@@ -13,7 +13,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'tcx>, recv: &'
         && let (_, ref_depth, Mutability::Mut) = peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(recv))
         && ref_depth >= 1
         && let Some(method_id) = cx.typeck_results().type_dependent_def_id(ex.hir_id)
-        && let Some(impl_id) = cx.tcx.impl_of_method(method_id)
+        && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id)
         && is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id).instantiate_identity(), sym::Mutex)
     {
         span_lint_and_sugg(
diff --git a/src/tools/clippy/clippy_lints/src/methods/open_options.rs b/src/tools/clippy/clippy_lints/src/methods/open_options.rs
index 9b5f138295c..37a8e25bef9 100644
--- a/src/tools/clippy/clippy_lints/src/methods/open_options.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/open_options.rs
@@ -18,7 +18,7 @@ fn is_open_options(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
 
 pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) {
     if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
-        && let Some(impl_id) = cx.tcx.impl_of_method(method_id)
+        && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id)
         && is_open_options(cx, cx.tcx.type_of(impl_id).instantiate_identity())
     {
         let mut options = Vec::new();
@@ -111,7 +111,7 @@ fn get_open_options(
                     // This might be a user defined extension trait with a method like `truncate_write`
                     // which would be a false positive
                     if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(argument.hir_id)
-                        && cx.tcx.trait_of_item(method_def_id).is_some()
+                        && cx.tcx.trait_of_assoc(method_def_id).is_some()
                     {
                         return false;
                     }
diff --git a/src/tools/clippy/clippy_lints/src/methods/path_buf_push_overwrite.rs b/src/tools/clippy/clippy_lints/src/methods/path_buf_push_overwrite.rs
index 38d9c5f1677..32752ef7435 100644
--- a/src/tools/clippy/clippy_lints/src/methods/path_buf_push_overwrite.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/path_buf_push_overwrite.rs
@@ -11,7 +11,7 @@ use super::PATH_BUF_PUSH_OVERWRITE;
 
 pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'tcx Expr<'_>) {
     if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
-        && let Some(impl_id) = cx.tcx.impl_of_method(method_id)
+        && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id)
         && is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id).instantiate_identity(), sym::PathBuf)
         && let ExprKind::Lit(lit) = arg.kind
         && let LitKind::Str(ref path_lit, _) = lit.node
diff --git a/src/tools/clippy/clippy_lints/src/methods/stable_sort_primitive.rs b/src/tools/clippy/clippy_lints/src/methods/stable_sort_primitive.rs
index aef14435d8a..17d1a6abde0 100644
--- a/src/tools/clippy/clippy_lints/src/methods/stable_sort_primitive.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/stable_sort_primitive.rs
@@ -9,7 +9,7 @@ use super::STABLE_SORT_PRIMITIVE;
 
 pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) {
     if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
-        && let Some(impl_id) = cx.tcx.impl_of_method(method_id)
+        && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id)
         && cx.tcx.type_of(impl_id).instantiate_identity().is_slice()
         && let Some(slice_type) = is_slice_of_primitives(cx, recv)
     {
diff --git a/src/tools/clippy/clippy_lints/src/methods/str_splitn.rs b/src/tools/clippy/clippy_lints/src/methods/str_splitn.rs
index 6f78d6c6128..51dd4ac313a 100644
--- a/src/tools/clippy/clippy_lints/src/methods/str_splitn.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/str_splitn.rs
@@ -286,7 +286,7 @@ fn parse_iter_usage<'tcx>(
             let iter_id = cx.tcx.get_diagnostic_item(sym::Iterator)?;
 
             match (name.ident.name, args) {
-                (sym::next, []) if cx.tcx.trait_of_item(did) == Some(iter_id) => (IterUsageKind::Nth(0), e.span),
+                (sym::next, []) if cx.tcx.trait_of_assoc(did) == Some(iter_id) => (IterUsageKind::Nth(0), e.span),
                 (sym::next_tuple, []) => {
                     return if paths::ITERTOOLS_NEXT_TUPLE.matches(cx, did)
                         && let ty::Adt(adt_def, subs) = cx.typeck_results().expr_ty(e).kind()
@@ -303,7 +303,7 @@ fn parse_iter_usage<'tcx>(
                         None
                     };
                 },
-                (sym::nth | sym::skip, [idx_expr]) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
+                (sym::nth | sym::skip, [idx_expr]) if cx.tcx.trait_of_assoc(did) == Some(iter_id) => {
                     if let Some(Constant::Int(idx)) = ConstEvalCtxt::new(cx).eval(idx_expr) {
                         let span = if name.ident.as_str() == "nth" {
                             e.span
@@ -312,7 +312,7 @@ fn parse_iter_usage<'tcx>(
                             && next_name.ident.name == sym::next
                             && next_expr.span.ctxt() == ctxt
                             && let Some(next_id) = cx.typeck_results().type_dependent_def_id(next_expr.hir_id)
-                            && cx.tcx.trait_of_item(next_id) == Some(iter_id)
+                            && cx.tcx.trait_of_assoc(next_id) == Some(iter_id)
                         {
                             next_expr.span
                         } else {
diff --git a/src/tools/clippy/clippy_lints/src/methods/suspicious_splitn.rs b/src/tools/clippy/clippy_lints/src/methods/suspicious_splitn.rs
index f8b6d4349fb..9876681ddbb 100644
--- a/src/tools/clippy/clippy_lints/src/methods/suspicious_splitn.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/suspicious_splitn.rs
@@ -10,7 +10,7 @@ use super::SUSPICIOUS_SPLITN;
 pub(super) fn check(cx: &LateContext<'_>, method_name: Symbol, expr: &Expr<'_>, self_arg: &Expr<'_>, count: u128) {
     if count <= 1
         && let Some(call_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
-        && let Some(impl_id) = cx.tcx.impl_of_method(call_id)
+        && let Some(impl_id) = cx.tcx.impl_of_assoc(call_id)
         && cx.tcx.impl_trait_ref(impl_id).is_none()
         && let self_ty = cx.tcx.type_of(impl_id).instantiate_identity()
         && (self_ty.is_slice() || self_ty.is_str())
diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_fallible_conversions.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_fallible_conversions.rs
index ce81282ddfe..0ec2d8b4fc3 100644
--- a/src/tools/clippy/clippy_lints/src/methods/unnecessary_fallible_conversions.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_fallible_conversions.rs
@@ -165,7 +165,7 @@ pub(super) fn check_method(cx: &LateContext<'_>, expr: &Expr<'_>) {
 pub(super) fn check_function(cx: &LateContext<'_>, expr: &Expr<'_>, callee: &Expr<'_>) {
     if let ExprKind::Path(ref qpath) = callee.kind
         && let Some(item_def_id) = cx.qpath_res(qpath, callee.hir_id).opt_def_id()
-        && let Some(trait_def_id) = cx.tcx.trait_of_item(item_def_id)
+        && let Some(trait_def_id) = cx.tcx.trait_of_assoc(item_def_id)
     {
         let qpath_spans = match qpath {
             QPath::Resolved(_, path) => {
diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_sort_by.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_sort_by.rs
index dbff08bc51c..1de9f6ab497 100644
--- a/src/tools/clippy/clippy_lints/src/methods/unnecessary_sort_by.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_sort_by.rs
@@ -114,7 +114,7 @@ fn mirrored_exprs(a_expr: &Expr<'_>, a_ident: &Ident, b_expr: &Expr<'_>, b_ident
 
 fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) -> Option<LintTrigger> {
     if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
-        && let Some(impl_id) = cx.tcx.impl_of_method(method_id)
+        && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id)
         && cx.tcx.type_of(impl_id).instantiate_identity().is_slice()
         && let ExprKind::Closure(&Closure { body, .. }) = arg.kind
         && let closure_body = cx.tcx.hir_body(body)
diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs
index 769526d131b..54f45263275 100644
--- a/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs
@@ -694,7 +694,7 @@ fn check_if_applicable_to_argument<'tcx>(cx: &LateContext<'tcx>, arg: &Expr<'tcx
             sym::to_string => cx.tcx.is_diagnostic_item(sym::to_string_method, method_def_id),
             sym::to_vec => cx
                 .tcx
-                .impl_of_method(method_def_id)
+                .impl_of_assoc(method_def_id)
                 .filter(|&impl_did| cx.tcx.type_of(impl_did).instantiate_identity().is_slice())
                 .is_some(),
             _ => false,
@@ -734,7 +734,7 @@ fn check_if_applicable_to_argument<'tcx>(cx: &LateContext<'tcx>, arg: &Expr<'tcx
 fn check_borrow_predicate<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
     if let ExprKind::MethodCall(_, caller, &[arg], _) = expr.kind
         && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
-        && cx.tcx.trait_of_item(method_def_id).is_none()
+        && cx.tcx.trait_of_assoc(method_def_id).is_none()
         && let Some(borrow_id) = cx.tcx.get_diagnostic_item(sym::Borrow)
         && cx.tcx.predicates_of(method_def_id).predicates.iter().any(|(pred, _)| {
             if let ClauseKind::Trait(trait_pred) = pred.kind().skip_binder()
diff --git a/src/tools/clippy/clippy_lints/src/methods/useless_asref.rs b/src/tools/clippy/clippy_lints/src/methods/useless_asref.rs
index d30c12e0c48..38fad239f67 100644
--- a/src/tools/clippy/clippy_lints/src/methods/useless_asref.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/useless_asref.rs
@@ -79,7 +79,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: Symbo
                 applicability,
             );
         }
-    } else if let Some(impl_id) = cx.tcx.impl_of_method(def_id)
+    } else if let Some(impl_id) = cx.tcx.impl_of_assoc(def_id)
         && let Some(adt) = cx.tcx.type_of(impl_id).instantiate_identity().ty_adt_def()
         && matches!(cx.tcx.get_diagnostic_name(adt.did()), Some(sym::Option | sym::Result))
     {
@@ -131,7 +131,7 @@ fn is_calling_clone(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
                 hir::ExprKind::MethodCall(method, obj, [], _) => {
                     if method.ident.name == sym::clone
                         && let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id)
-                        && let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
+                        && let Some(trait_id) = cx.tcx.trait_of_assoc(fn_id)
                         // We check it's the `Clone` trait.
                         && cx.tcx.lang_items().clone_trait().is_some_and(|id| id == trait_id)
                         // no autoderefs
diff --git a/src/tools/clippy/clippy_lints/src/methods/vec_resize_to_zero.rs b/src/tools/clippy/clippy_lints/src/methods/vec_resize_to_zero.rs
index 5ea4ada128a..bfb481f4fc0 100644
--- a/src/tools/clippy/clippy_lints/src/methods/vec_resize_to_zero.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/vec_resize_to_zero.rs
@@ -18,7 +18,7 @@ pub(super) fn check<'tcx>(
     name_span: Span,
 ) {
     if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
-        && let Some(impl_id) = cx.tcx.impl_of_method(method_id)
+        && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id)
         && is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id).instantiate_identity(), sym::Vec)
         && let ExprKind::Lit(Spanned {
             node: LitKind::Int(Pu128(0), _),
diff --git a/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs b/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs
index 2006a824402..7b057998063 100644
--- a/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs
+++ b/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs
@@ -312,9 +312,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
 /// Functions marked with these attributes must have the exact signature.
 pub(crate) fn requires_exact_signature(attrs: &[Attribute]) -> bool {
     attrs.iter().any(|attr| {
-        [sym::proc_macro, sym::proc_macro_attribute, sym::proc_macro_derive]
-            .iter()
-            .any(|&allow| attr.has_name(allow))
+        attr.is_proc_macro_attr()
     })
 }
 
diff --git a/src/tools/clippy/clippy_lints/src/operators/cmp_owned.rs b/src/tools/clippy/clippy_lints/src/operators/cmp_owned.rs
index 9b2cfd91b85..22ec4fe60fb 100644
--- a/src/tools/clippy/clippy_lints/src/operators/cmp_owned.rs
+++ b/src/tools/clippy/clippy_lints/src/operators/cmp_owned.rs
@@ -41,7 +41,7 @@ fn check_op(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: bool)
         ExprKind::MethodCall(_, arg, [], _)
             if typeck
                 .type_dependent_def_id(expr.hir_id)
-                .and_then(|id| cx.tcx.trait_of_item(id))
+                .and_then(|id| cx.tcx.trait_of_assoc(id))
                 .is_some_and(|id| matches!(cx.tcx.get_diagnostic_name(id), Some(sym::ToString | sym::ToOwned))) =>
         {
             (arg, arg.span)
diff --git a/src/tools/clippy/clippy_lints/src/operators/op_ref.rs b/src/tools/clippy/clippy_lints/src/operators/op_ref.rs
index 21e1ab0f4f2..0a1f2625f4c 100644
--- a/src/tools/clippy/clippy_lints/src/operators/op_ref.rs
+++ b/src/tools/clippy/clippy_lints/src/operators/op_ref.rs
@@ -179,7 +179,7 @@ fn in_impl<'tcx>(
     bin_op: DefId,
 ) -> Option<(&'tcx rustc_hir::Ty<'tcx>, &'tcx rustc_hir::Ty<'tcx>)> {
     if let Some(block) = get_enclosing_block(cx, e.hir_id)
-        && let Some(impl_def_id) = cx.tcx.impl_of_method(block.hir_id.owner.to_def_id())
+        && let Some(impl_def_id) = cx.tcx.impl_of_assoc(block.hir_id.owner.to_def_id())
         && let item = cx.tcx.hir_expect_item(impl_def_id.expect_local())
         && let ItemKind::Impl(item) = &item.kind
         && let Some(of_trait) = &item.of_trait
diff --git a/src/tools/clippy/clippy_lints/src/ranges.rs b/src/tools/clippy/clippy_lints/src/ranges.rs
index 9281678b3d8..03d00ba849f 100644
--- a/src/tools/clippy/clippy_lints/src/ranges.rs
+++ b/src/tools/clippy/clippy_lints/src/ranges.rs
@@ -380,7 +380,7 @@ fn can_switch_ranges<'tcx>(
     if let ExprKind::MethodCall(_, receiver, _, _) = parent_expr.kind
         && receiver.hir_id == use_ctxt.child_id
         && let Some(method_did) = cx.typeck_results().type_dependent_def_id(parent_expr.hir_id)
-        && let Some(trait_did) = cx.tcx.trait_of_item(method_did)
+        && let Some(trait_did) = cx.tcx.trait_of_assoc(method_did)
         && matches!(
             cx.tcx.get_diagnostic_name(trait_did),
             Some(sym::Iterator | sym::IntoIterator | sym::RangeBounds)
diff --git a/src/tools/clippy/clippy_lints/src/return_self_not_must_use.rs b/src/tools/clippy/clippy_lints/src/return_self_not_must_use.rs
index 25929b853af..3497216d1c5 100644
--- a/src/tools/clippy/clippy_lints/src/return_self_not_must_use.rs
+++ b/src/tools/clippy/clippy_lints/src/return_self_not_must_use.rs
@@ -113,7 +113,7 @@ impl<'tcx> LateLintPass<'tcx> for ReturnSelfNotMustUse {
     ) {
         if matches!(kind, FnKind::Method(_, _))
             // We are only interested in methods, not in functions or associated functions.
-            && let Some(impl_def) = cx.tcx.impl_of_method(fn_def.to_def_id())
+            && let Some(impl_def) = cx.tcx.impl_of_assoc(fn_def.to_def_id())
             // We don't want this method to be te implementation of a trait because the
             // `#[must_use]` should be put on the trait definition directly.
             && cx.tcx.trait_id_of_impl(impl_def).is_none()
diff --git a/src/tools/clippy/clippy_lints/src/unconditional_recursion.rs b/src/tools/clippy/clippy_lints/src/unconditional_recursion.rs
index d321c48f6af..dcddff557d1 100644
--- a/src/tools/clippy/clippy_lints/src/unconditional_recursion.rs
+++ b/src/tools/clippy/clippy_lints/src/unconditional_recursion.rs
@@ -206,7 +206,7 @@ fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: Loca
                 let arg_ty = cx.typeck_results().expr_ty_adjusted(arg);
 
                 if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
-                    && let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
+                    && let Some(trait_id) = cx.tcx.trait_of_assoc(fn_id)
                     && trait_id == trait_def_id
                     && matches_ty(receiver_ty, arg_ty, self_arg, other_arg)
                 {
@@ -250,7 +250,7 @@ fn check_to_string(cx: &LateContext<'_>, method_span: Span, method_def_id: Local
         let is_bad = match expr.kind {
             ExprKind::MethodCall(segment, _receiver, &[_arg], _) if segment.ident.name == name.name => {
                 if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
-                    && let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
+                    && let Some(trait_id) = cx.tcx.trait_of_assoc(fn_id)
                     && trait_id == trait_def_id
                 {
                     true
@@ -318,7 +318,7 @@ where
             && let ExprKind::Path(qpath) = f.kind
             && is_default_method_on_current_ty(self.cx.tcx, qpath, self.implemented_ty_id)
             && let Some(method_def_id) = path_def_id(self.cx, f)
-            && let Some(trait_def_id) = self.cx.tcx.trait_of_item(method_def_id)
+            && let Some(trait_def_id) = self.cx.tcx.trait_of_assoc(method_def_id)
             && self.cx.tcx.is_diagnostic_item(sym::Default, trait_def_id)
         {
             span_error(self.cx, self.method_span, expr);
@@ -426,7 +426,7 @@ fn check_from(cx: &LateContext<'_>, method_span: Span, method_def_id: LocalDefId
     if let Some((fn_def_id, node_args)) = fn_def_id_with_node_args(cx, expr)
         && let [s1, s2] = **node_args
         && let (Some(s1), Some(s2)) = (s1.as_type(), s2.as_type())
-        && let Some(trait_def_id) = cx.tcx.trait_of_item(fn_def_id)
+        && let Some(trait_def_id) = cx.tcx.trait_of_assoc(fn_def_id)
         && cx.tcx.is_diagnostic_item(sym::Into, trait_def_id)
         && get_impl_trait_def_id(cx, method_def_id) == cx.tcx.get_diagnostic_item(sym::From)
         && s1 == sig.inputs()[0]
diff --git a/src/tools/clippy/clippy_lints/src/unused_io_amount.rs b/src/tools/clippy/clippy_lints/src/unused_io_amount.rs
index 12cc1093899..f3cd3f1bb28 100644
--- a/src/tools/clippy/clippy_lints/src/unused_io_amount.rs
+++ b/src/tools/clippy/clippy_lints/src/unused_io_amount.rs
@@ -84,7 +84,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount {
     /// get desugared to match.
     fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'tcx>) {
         let fn_def_id = block.hir_id.owner.to_def_id();
-        if let Some(impl_id) = cx.tcx.impl_of_method(fn_def_id)
+        if let Some(impl_id) = cx.tcx.impl_of_assoc(fn_def_id)
             && let Some(trait_id) = cx.tcx.trait_id_of_impl(impl_id)
         {
             // We don't want to lint inside io::Read or io::Write implementations, as the author has more
@@ -300,7 +300,7 @@ fn check_io_mode(cx: &LateContext<'_>, call: &hir::Expr<'_>) -> Option<IoOp> {
     };
 
     if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(call.hir_id)
-        && let Some(trait_def_id) = cx.tcx.trait_of_item(method_def_id)
+        && let Some(trait_def_id) = cx.tcx.trait_of_assoc(method_def_id)
     {
         if let Some(diag_name) = cx.tcx.get_diagnostic_name(trait_def_id) {
             match diag_name {
diff --git a/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs b/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs
index 9d38672efad..eb3f442ac75 100644
--- a/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs
+++ b/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs
@@ -51,7 +51,7 @@ impl ops::BitOrAssign for EagernessSuggestion {
 fn fn_eagerness(cx: &LateContext<'_>, fn_id: DefId, name: Symbol, have_one_arg: bool) -> EagernessSuggestion {
     use EagernessSuggestion::{Eager, Lazy, NoChange};
 
-    let ty = match cx.tcx.impl_of_method(fn_id) {
+    let ty = match cx.tcx.impl_of_assoc(fn_id) {
         Some(id) => cx.tcx.type_of(id).instantiate_identity(),
         None => return Lazy,
     };
diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs
index ce5af4d2f48..67e09e772a7 100644
--- a/src/tools/clippy/clippy_utils/src/lib.rs
+++ b/src/tools/clippy/clippy_utils/src/lib.rs
@@ -349,7 +349,7 @@ pub fn is_ty_alias(qpath: &QPath<'_>) -> bool {
 /// Checks if the given method call expression calls an inherent method.
 pub fn is_inherent_method_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
     if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
-        cx.tcx.trait_of_item(method_id).is_none()
+        cx.tcx.trait_of_assoc(method_id).is_none()
     } else {
         false
     }
@@ -357,7 +357,7 @@ pub fn is_inherent_method_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
 
 /// Checks if a method is defined in an impl of a diagnostic item
 pub fn is_diag_item_method(cx: &LateContext<'_>, def_id: DefId, diag_item: Symbol) -> bool {
-    if let Some(impl_did) = cx.tcx.impl_of_method(def_id)
+    if let Some(impl_did) = cx.tcx.impl_of_assoc(def_id)
         && let Some(adt) = cx.tcx.type_of(impl_did).instantiate_identity().ty_adt_def()
     {
         return cx.tcx.is_diagnostic_item(diag_item, adt.did());
@@ -367,7 +367,7 @@ pub fn is_diag_item_method(cx: &LateContext<'_>, def_id: DefId, diag_item: Symbo
 
 /// Checks if a method is in a diagnostic item trait
 pub fn is_diag_trait_item(cx: &LateContext<'_>, def_id: DefId, diag_item: Symbol) -> bool {
-    if let Some(trait_did) = cx.tcx.trait_of_item(def_id) {
+    if let Some(trait_did) = cx.tcx.trait_of_assoc(def_id) {
         return cx.tcx.is_diagnostic_item(diag_item, trait_did);
     }
     false
@@ -620,7 +620,7 @@ fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath<
 
     if let QPath::TypeRelative(_, method) = path
         && method.ident.name == sym::new
-        && let Some(impl_did) = cx.tcx.impl_of_method(def_id)
+        && let Some(impl_did) = cx.tcx.impl_of_assoc(def_id)
         && let Some(adt) = cx.tcx.type_of(impl_did).instantiate_identity().ty_adt_def()
     {
         return std_types_symbols.iter().any(|&symbol| {
diff --git a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs
index b3356450d38..11c17a77b15 100644
--- a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs
+++ b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs
@@ -420,7 +420,7 @@ pub fn is_stable_const_fn(cx: &LateContext<'_>, def_id: DefId, msrv: Msrv) -> bo
             .lookup_const_stability(def_id)
             .or_else(|| {
                 cx.tcx
-                    .trait_of_item(def_id)
+                    .trait_of_assoc(def_id)
                     .and_then(|trait_def_id| cx.tcx.lookup_const_stability(trait_def_id))
             })
             .is_none_or(|const_stab| {
diff --git a/src/tools/compiletest/src/directives.rs b/src/tools/compiletest/src/directives.rs
index a1f76a07556..54511f4fd08 100644
--- a/src/tools/compiletest/src/directives.rs
+++ b/src/tools/compiletest/src/directives.rs
@@ -12,6 +12,9 @@ use tracing::*;
 use crate::common::{CodegenBackend, Config, Debugger, FailMode, PassMode, RunFailMode, TestMode};
 use crate::debuggers::{extract_cdb_version, extract_gdb_version};
 use crate::directives::auxiliary::{AuxProps, parse_and_update_aux};
+use crate::directives::directive_names::{
+    KNOWN_DIRECTIVE_NAMES, KNOWN_HTMLDOCCK_DIRECTIVE_NAMES, KNOWN_JSONDOCCK_DIRECTIVE_NAMES,
+};
 use crate::directives::needs::CachedNeedsConditions;
 use crate::errors::ErrorKind;
 use crate::executor::{CollectedTestDesc, ShouldPanic};
@@ -20,6 +23,7 @@ use crate::util::static_regex;
 
 pub(crate) mod auxiliary;
 mod cfg;
+mod directive_names;
 mod needs;
 #[cfg(test)]
 mod tests;
@@ -59,9 +63,9 @@ impl EarlyProps {
             &mut poisoned,
             testfile,
             rdr,
-            &mut |DirectiveLine { raw_directive: ln, .. }| {
-                parse_and_update_aux(config, ln, &mut props.aux);
-                config.parse_and_update_revisions(testfile, ln, &mut props.revisions);
+            &mut |DirectiveLine { line_number, raw_directive: ln, .. }| {
+                parse_and_update_aux(config, ln, testfile, line_number, &mut props.aux);
+                config.parse_and_update_revisions(testfile, line_number, ln, &mut props.revisions);
             },
         );
 
@@ -351,7 +355,7 @@ impl TestProps {
                 &mut poisoned,
                 testfile,
                 file,
-                &mut |directive @ DirectiveLine { raw_directive: ln, .. }| {
+                &mut |directive @ DirectiveLine { line_number, raw_directive: ln, .. }| {
                     if !directive.applies_to_test_revision(test_revision) {
                         return;
                     }
@@ -361,17 +365,28 @@ impl TestProps {
                     config.push_name_value_directive(
                         ln,
                         ERROR_PATTERN,
+                        testfile,
+                        line_number,
                         &mut self.error_patterns,
                         |r| r,
                     );
                     config.push_name_value_directive(
                         ln,
                         REGEX_ERROR_PATTERN,
+                        testfile,
+                        line_number,
                         &mut self.regex_error_patterns,
                         |r| r,
                     );
 
-                    config.push_name_value_directive(ln, DOC_FLAGS, &mut self.doc_flags, |r| r);
+                    config.push_name_value_directive(
+                        ln,
+                        DOC_FLAGS,
+                        testfile,
+                        line_number,
+                        &mut self.doc_flags,
+                        |r| r,
+                    );
 
                     fn split_flags(flags: &str) -> Vec<String> {
                         // Individual flags can be single-quoted to preserve spaces; see
@@ -386,7 +401,9 @@ impl TestProps {
                             .collect::<Vec<_>>()
                     }
 
-                    if let Some(flags) = config.parse_name_value_directive(ln, COMPILE_FLAGS) {
+                    if let Some(flags) =
+                        config.parse_name_value_directive(ln, COMPILE_FLAGS, testfile, line_number)
+                    {
                         let flags = split_flags(&flags);
                         for flag in &flags {
                             if flag == "--edition" || flag.starts_with("--edition=") {
@@ -395,25 +412,40 @@ impl TestProps {
                         }
                         self.compile_flags.extend(flags);
                     }
-                    if config.parse_name_value_directive(ln, INCORRECT_COMPILER_FLAGS).is_some() {
+                    if config
+                        .parse_name_value_directive(
+                            ln,
+                            INCORRECT_COMPILER_FLAGS,
+                            testfile,
+                            line_number,
+                        )
+                        .is_some()
+                    {
                         panic!("`compiler-flags` directive should be spelled `compile-flags`");
                     }
 
-                    if let Some(edition) = config.parse_edition(ln) {
+                    if let Some(edition) = config.parse_edition(ln, testfile, line_number) {
                         // The edition is added at the start, since flags from //@compile-flags must
                         // be passed to rustc last.
                         self.compile_flags.insert(0, format!("--edition={}", edition.trim()));
                         has_edition = true;
                     }
 
-                    config.parse_and_update_revisions(testfile, ln, &mut self.revisions);
+                    config.parse_and_update_revisions(
+                        testfile,
+                        line_number,
+                        ln,
+                        &mut self.revisions,
+                    );
 
-                    if let Some(flags) = config.parse_name_value_directive(ln, RUN_FLAGS) {
+                    if let Some(flags) =
+                        config.parse_name_value_directive(ln, RUN_FLAGS, testfile, line_number)
+                    {
                         self.run_flags.extend(split_flags(&flags));
                     }
 
                     if self.pp_exact.is_none() {
-                        self.pp_exact = config.parse_pp_exact(ln, testfile);
+                        self.pp_exact = config.parse_pp_exact(ln, testfile, line_number);
                     }
 
                     config.set_name_directive(ln, SHOULD_ICE, &mut self.should_ice);
@@ -435,7 +467,9 @@ impl TestProps {
                     );
                     config.set_name_directive(ln, NO_PREFER_DYNAMIC, &mut self.no_prefer_dynamic);
 
-                    if let Some(m) = config.parse_name_value_directive(ln, PRETTY_MODE) {
+                    if let Some(m) =
+                        config.parse_name_value_directive(ln, PRETTY_MODE, testfile, line_number)
+                    {
                         self.pretty_mode = m;
                     }
 
@@ -446,35 +480,45 @@ impl TestProps {
                     );
 
                     // Call a helper method to deal with aux-related directives.
-                    parse_and_update_aux(config, ln, &mut self.aux);
+                    parse_and_update_aux(config, ln, testfile, line_number, &mut self.aux);
 
                     config.push_name_value_directive(
                         ln,
                         EXEC_ENV,
+                        testfile,
+                        line_number,
                         &mut self.exec_env,
                         Config::parse_env,
                     );
                     config.push_name_value_directive(
                         ln,
                         UNSET_EXEC_ENV,
+                        testfile,
+                        line_number,
                         &mut self.unset_exec_env,
                         |r| r.trim().to_owned(),
                     );
                     config.push_name_value_directive(
                         ln,
                         RUSTC_ENV,
+                        testfile,
+                        line_number,
                         &mut self.rustc_env,
                         Config::parse_env,
                     );
                     config.push_name_value_directive(
                         ln,
                         UNSET_RUSTC_ENV,
+                        testfile,
+                        line_number,
                         &mut self.unset_rustc_env,
                         |r| r.trim().to_owned(),
                     );
                     config.push_name_value_directive(
                         ln,
                         FORBID_OUTPUT,
+                        testfile,
+                        line_number,
                         &mut self.forbid_output,
                         |r| r,
                     );
@@ -510,7 +554,7 @@ impl TestProps {
                     }
 
                     if let Some(code) = config
-                        .parse_name_value_directive(ln, FAILURE_STATUS)
+                        .parse_name_value_directive(ln, FAILURE_STATUS, testfile, line_number)
                         .and_then(|code| code.trim().parse::<i32>().ok())
                     {
                         self.failure_status = Some(code);
@@ -531,6 +575,8 @@ impl TestProps {
                     config.set_name_value_directive(
                         ln,
                         ASSEMBLY_OUTPUT,
+                        testfile,
+                        line_number,
                         &mut self.assembly_output,
                         |r| r.trim().to_string(),
                     );
@@ -543,7 +589,9 @@ impl TestProps {
 
                     // Unlike the other `name_value_directive`s this needs to be handled manually,
                     // because it sets a `bool` flag.
-                    if let Some(known_bug) = config.parse_name_value_directive(ln, KNOWN_BUG) {
+                    if let Some(known_bug) =
+                        config.parse_name_value_directive(ln, KNOWN_BUG, testfile, line_number)
+                    {
                         let known_bug = known_bug.trim();
                         if known_bug == "unknown"
                             || known_bug.split(',').all(|issue_ref| {
@@ -571,16 +619,25 @@ impl TestProps {
                     config.set_name_value_directive(
                         ln,
                         TEST_MIR_PASS,
+                        testfile,
+                        line_number,
                         &mut self.mir_unit_test,
                         |s| s.trim().to_string(),
                     );
                     config.set_name_directive(ln, REMAP_SRC_BASE, &mut self.remap_src_base);
 
-                    if let Some(flags) = config.parse_name_value_directive(ln, LLVM_COV_FLAGS) {
+                    if let Some(flags) =
+                        config.parse_name_value_directive(ln, LLVM_COV_FLAGS, testfile, line_number)
+                    {
                         self.llvm_cov_flags.extend(split_flags(&flags));
                     }
 
-                    if let Some(flags) = config.parse_name_value_directive(ln, FILECHECK_FLAGS) {
+                    if let Some(flags) = config.parse_name_value_directive(
+                        ln,
+                        FILECHECK_FLAGS,
+                        testfile,
+                        line_number,
+                    ) {
                         self.filecheck_flags.extend(split_flags(&flags));
                     }
 
@@ -588,9 +645,12 @@ impl TestProps {
 
                     self.update_add_core_stubs(ln, config);
 
-                    if let Some(err_kind) =
-                        config.parse_name_value_directive(ln, DONT_REQUIRE_ANNOTATIONS)
-                    {
+                    if let Some(err_kind) = config.parse_name_value_directive(
+                        ln,
+                        DONT_REQUIRE_ANNOTATIONS,
+                        testfile,
+                        line_number,
+                    ) {
                         self.dont_require_annotations
                             .insert(ErrorKind::expect_from_user_str(err_kind.trim()));
                     }
@@ -769,296 +829,6 @@ fn line_directive<'line>(
     Some(DirectiveLine { line_number, revision, raw_directive })
 }
 
-/// This was originally generated by collecting directives from ui tests and then extracting their
-/// directive names. This is **not** an exhaustive list of all possible directives. Instead, this is
-/// a best-effort approximation for diagnostics. Add new directives to this list when needed.
-const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
-    // tidy-alphabetical-start
-    "add-core-stubs",
-    "assembly-output",
-    "aux-bin",
-    "aux-build",
-    "aux-codegen-backend",
-    "aux-crate",
-    "build-aux-docs",
-    "build-fail",
-    "build-pass",
-    "check-fail",
-    "check-pass",
-    "check-run-results",
-    "check-stdout",
-    "check-test-line-numbers-match",
-    "compile-flags",
-    "doc-flags",
-    "dont-check-compiler-stderr",
-    "dont-check-compiler-stdout",
-    "dont-check-failure-status",
-    "dont-require-annotations",
-    "edition",
-    "error-pattern",
-    "exact-llvm-major-version",
-    "exec-env",
-    "failure-status",
-    "filecheck-flags",
-    "forbid-output",
-    "force-host",
-    "ignore-16bit",
-    "ignore-32bit",
-    "ignore-64bit",
-    "ignore-aarch64",
-    "ignore-aarch64-pc-windows-msvc",
-    "ignore-aarch64-unknown-linux-gnu",
-    "ignore-aix",
-    "ignore-android",
-    "ignore-apple",
-    "ignore-arm",
-    "ignore-arm-unknown-linux-gnueabi",
-    "ignore-arm-unknown-linux-gnueabihf",
-    "ignore-arm-unknown-linux-musleabi",
-    "ignore-arm-unknown-linux-musleabihf",
-    "ignore-auxiliary",
-    "ignore-avr",
-    "ignore-backends",
-    "ignore-beta",
-    "ignore-cdb",
-    "ignore-compare-mode-next-solver",
-    "ignore-compare-mode-polonius",
-    "ignore-coverage-map",
-    "ignore-coverage-run",
-    "ignore-cross-compile",
-    "ignore-eabi",
-    "ignore-elf",
-    "ignore-emscripten",
-    "ignore-endian-big",
-    "ignore-enzyme",
-    "ignore-freebsd",
-    "ignore-fuchsia",
-    "ignore-gdb",
-    "ignore-gdb-version",
-    "ignore-gnu",
-    "ignore-haiku",
-    "ignore-horizon",
-    "ignore-i686-pc-windows-gnu",
-    "ignore-i686-pc-windows-msvc",
-    "ignore-illumos",
-    "ignore-ios",
-    "ignore-linux",
-    "ignore-lldb",
-    "ignore-llvm-version",
-    "ignore-loongarch32",
-    "ignore-loongarch64",
-    "ignore-macabi",
-    "ignore-macos",
-    "ignore-msp430",
-    "ignore-msvc",
-    "ignore-musl",
-    "ignore-netbsd",
-    "ignore-nightly",
-    "ignore-none",
-    "ignore-nto",
-    "ignore-nvptx64",
-    "ignore-nvptx64-nvidia-cuda",
-    "ignore-openbsd",
-    "ignore-pass",
-    "ignore-powerpc",
-    "ignore-powerpc64",
-    "ignore-remote",
-    "ignore-riscv64",
-    "ignore-rustc-debug-assertions",
-    "ignore-rustc_abi-x86-sse2",
-    "ignore-s390x",
-    "ignore-sgx",
-    "ignore-sparc64",
-    "ignore-spirv",
-    "ignore-stable",
-    "ignore-stage1",
-    "ignore-stage2",
-    "ignore-std-debug-assertions",
-    "ignore-test",
-    "ignore-thumb",
-    "ignore-thumbv8m.base-none-eabi",
-    "ignore-thumbv8m.main-none-eabi",
-    "ignore-tvos",
-    "ignore-unix",
-    "ignore-unknown",
-    "ignore-uwp",
-    "ignore-visionos",
-    "ignore-vxworks",
-    "ignore-wasi",
-    "ignore-wasm",
-    "ignore-wasm32",
-    "ignore-wasm32-bare",
-    "ignore-wasm64",
-    "ignore-watchos",
-    "ignore-windows",
-    "ignore-windows-gnu",
-    "ignore-windows-msvc",
-    "ignore-x32",
-    "ignore-x86",
-    "ignore-x86_64",
-    "ignore-x86_64-apple-darwin",
-    "ignore-x86_64-pc-windows-gnu",
-    "ignore-x86_64-unknown-linux-gnu",
-    "incremental",
-    "known-bug",
-    "llvm-cov-flags",
-    "max-llvm-major-version",
-    "min-cdb-version",
-    "min-gdb-version",
-    "min-lldb-version",
-    "min-llvm-version",
-    "min-system-llvm-version",
-    "needs-asm-support",
-    "needs-backends",
-    "needs-crate-type",
-    "needs-deterministic-layouts",
-    "needs-dlltool",
-    "needs-dynamic-linking",
-    "needs-enzyme",
-    "needs-force-clang-based-tests",
-    "needs-git-hash",
-    "needs-llvm-components",
-    "needs-llvm-zstd",
-    "needs-profiler-runtime",
-    "needs-relocation-model-pic",
-    "needs-run-enabled",
-    "needs-rust-lld",
-    "needs-rustc-debug-assertions",
-    "needs-sanitizer-address",
-    "needs-sanitizer-cfi",
-    "needs-sanitizer-dataflow",
-    "needs-sanitizer-hwaddress",
-    "needs-sanitizer-kcfi",
-    "needs-sanitizer-leak",
-    "needs-sanitizer-memory",
-    "needs-sanitizer-memtag",
-    "needs-sanitizer-safestack",
-    "needs-sanitizer-shadow-call-stack",
-    "needs-sanitizer-support",
-    "needs-sanitizer-thread",
-    "needs-std-debug-assertions",
-    "needs-subprocess",
-    "needs-symlink",
-    "needs-target-has-atomic",
-    "needs-target-std",
-    "needs-threads",
-    "needs-unwind",
-    "needs-wasmtime",
-    "needs-xray",
-    "no-auto-check-cfg",
-    "no-prefer-dynamic",
-    "normalize-stderr",
-    "normalize-stderr-32bit",
-    "normalize-stderr-64bit",
-    "normalize-stdout",
-    "only-16bit",
-    "only-32bit",
-    "only-64bit",
-    "only-aarch64",
-    "only-aarch64-apple-darwin",
-    "only-aarch64-unknown-linux-gnu",
-    "only-apple",
-    "only-arm",
-    "only-avr",
-    "only-beta",
-    "only-bpf",
-    "only-cdb",
-    "only-dist",
-    "only-elf",
-    "only-emscripten",
-    "only-gnu",
-    "only-i686-pc-windows-gnu",
-    "only-i686-pc-windows-msvc",
-    "only-i686-unknown-linux-gnu",
-    "only-ios",
-    "only-linux",
-    "only-loongarch32",
-    "only-loongarch64",
-    "only-loongarch64-unknown-linux-gnu",
-    "only-macos",
-    "only-mips",
-    "only-mips64",
-    "only-msp430",
-    "only-msvc",
-    "only-musl",
-    "only-nightly",
-    "only-nvptx64",
-    "only-powerpc",
-    "only-riscv64",
-    "only-rustc_abi-x86-sse2",
-    "only-s390x",
-    "only-sparc",
-    "only-sparc64",
-    "only-stable",
-    "only-thumb",
-    "only-tvos",
-    "only-uefi",
-    "only-unix",
-    "only-visionos",
-    "only-wasm32",
-    "only-wasm32-bare",
-    "only-wasm32-wasip1",
-    "only-watchos",
-    "only-windows",
-    "only-windows-gnu",
-    "only-windows-msvc",
-    "only-x86",
-    "only-x86_64",
-    "only-x86_64-apple-darwin",
-    "only-x86_64-fortanix-unknown-sgx",
-    "only-x86_64-pc-windows-gnu",
-    "only-x86_64-pc-windows-msvc",
-    "only-x86_64-unknown-linux-gnu",
-    "pp-exact",
-    "pretty-compare-only",
-    "pretty-mode",
-    "proc-macro",
-    "reference",
-    "regex-error-pattern",
-    "remap-src-base",
-    "revisions",
-    "run-crash",
-    "run-fail",
-    "run-fail-or-crash",
-    "run-flags",
-    "run-pass",
-    "run-rustfix",
-    "rustc-env",
-    "rustfix-only-machine-applicable",
-    "should-fail",
-    "should-ice",
-    "stderr-per-bitwidth",
-    "test-mir-pass",
-    "unique-doc-out-dir",
-    "unset-exec-env",
-    "unset-rustc-env",
-    // Used by the tidy check `unknown_revision`.
-    "unused-revision-names",
-    // tidy-alphabetical-end
-];
-
-const KNOWN_HTMLDOCCK_DIRECTIVE_NAMES: &[&str] = &[
-    "count",
-    "!count",
-    "files",
-    "!files",
-    "has",
-    "!has",
-    "has-dir",
-    "!has-dir",
-    "hasraw",
-    "!hasraw",
-    "matches",
-    "!matches",
-    "matchesraw",
-    "!matchesraw",
-    "snapshot",
-    "!snapshot",
-];
-
-const KNOWN_JSONDOCCK_DIRECTIVE_NAMES: &[&str] =
-    &["count", "!count", "has", "!has", "is", "!is", "ismany", "!ismany", "set", "!set"];
-
 /// The (partly) broken-down contents of a line containing a test directive,
 /// which [`iter_directives`] passes to its callback function.
 ///
@@ -1206,6 +976,7 @@ impl Config {
     fn parse_and_update_revisions(
         &self,
         testfile: &Utf8Path,
+        line_number: usize,
         line: &str,
         existing: &mut Vec<String>,
     ) {
@@ -1219,7 +990,8 @@ impl Config {
         const FILECHECK_FORBIDDEN_REVISION_NAMES: [&str; 9] =
             ["CHECK", "COM", "NEXT", "SAME", "EMPTY", "NOT", "COUNT", "DAG", "LABEL"];
 
-        if let Some(raw) = self.parse_name_value_directive(line, "revisions") {
+        if let Some(raw) = self.parse_name_value_directive(line, "revisions", testfile, line_number)
+        {
             if self.mode == TestMode::RunMake {
                 panic!("`run-make` tests do not support revisions: {}", testfile);
             }
@@ -1264,8 +1036,13 @@ impl Config {
         (name.to_owned(), value.to_owned())
     }
 
-    fn parse_pp_exact(&self, line: &str, testfile: &Utf8Path) -> Option<Utf8PathBuf> {
-        if let Some(s) = self.parse_name_value_directive(line, "pp-exact") {
+    fn parse_pp_exact(
+        &self,
+        line: &str,
+        testfile: &Utf8Path,
+        line_number: usize,
+    ) -> Option<Utf8PathBuf> {
+        if let Some(s) = self.parse_name_value_directive(line, "pp-exact", testfile, line_number) {
             Some(Utf8PathBuf::from(&s))
         } else if self.parse_name_directive(line, "pp-exact") {
             testfile.file_name().map(Utf8PathBuf::from)
@@ -1306,19 +1083,31 @@ impl Config {
         line.starts_with("no-") && self.parse_name_directive(&line[3..], directive)
     }
 
-    pub fn parse_name_value_directive(&self, line: &str, directive: &str) -> Option<String> {
+    pub fn parse_name_value_directive(
+        &self,
+        line: &str,
+        directive: &str,
+        testfile: &Utf8Path,
+        line_number: usize,
+    ) -> Option<String> {
         let colon = directive.len();
         if line.starts_with(directive) && line.as_bytes().get(colon) == Some(&b':') {
             let value = line[(colon + 1)..].to_owned();
             debug!("{}: {}", directive, value);
-            Some(expand_variables(value, self))
+            let value = expand_variables(value, self);
+            if value.is_empty() {
+                error!("{testfile}:{line_number}: empty value for directive `{directive}`");
+                help!("expected syntax is: `{directive}: value`");
+                panic!("empty directive value detected");
+            }
+            Some(value)
         } else {
             None
         }
     }
 
-    fn parse_edition(&self, line: &str) -> Option<String> {
-        self.parse_name_value_directive(line, "edition")
+    fn parse_edition(&self, line: &str, testfile: &Utf8Path, line_number: usize) -> Option<String> {
+        self.parse_name_value_directive(line, "edition", testfile, line_number)
     }
 
     fn set_name_directive(&self, line: &str, directive: &str, value: &mut bool) {
@@ -1340,11 +1129,14 @@ impl Config {
         &self,
         line: &str,
         directive: &str,
+        testfile: &Utf8Path,
+        line_number: usize,
         value: &mut Option<T>,
         parse: impl FnOnce(String) -> T,
     ) {
         if value.is_none() {
-            *value = self.parse_name_value_directive(line, directive).map(parse);
+            *value =
+                self.parse_name_value_directive(line, directive, testfile, line_number).map(parse);
         }
     }
 
@@ -1352,10 +1144,14 @@ impl Config {
         &self,
         line: &str,
         directive: &str,
+        testfile: &Utf8Path,
+        line_number: usize,
         values: &mut Vec<T>,
         parse: impl FnOnce(String) -> T,
     ) {
-        if let Some(value) = self.parse_name_value_directive(line, directive).map(parse) {
+        if let Some(value) =
+            self.parse_name_value_directive(line, directive, testfile, line_number).map(parse)
+        {
             values.push(value);
         }
     }
@@ -1672,9 +1468,9 @@ pub(crate) fn make_test_description<R: Read>(
             decision!(cfg::handle_ignore(config, ln));
             decision!(cfg::handle_only(config, ln));
             decision!(needs::handle_needs(&cache.needs, config, ln));
-            decision!(ignore_llvm(config, path, ln));
-            decision!(ignore_backends(config, path, ln));
-            decision!(needs_backends(config, path, ln));
+            decision!(ignore_llvm(config, path, ln, line_number));
+            decision!(ignore_backends(config, path, ln, line_number));
+            decision!(needs_backends(config, path, ln, line_number));
             decision!(ignore_cdb(config, ln));
             decision!(ignore_gdb(config, ln));
             decision!(ignore_lldb(config, ln));
@@ -1801,8 +1597,15 @@ fn ignore_lldb(config: &Config, line: &str) -> IgnoreDecision {
     IgnoreDecision::Continue
 }
 
-fn ignore_backends(config: &Config, path: &Utf8Path, line: &str) -> IgnoreDecision {
-    if let Some(backends_to_ignore) = config.parse_name_value_directive(line, "ignore-backends") {
+fn ignore_backends(
+    config: &Config,
+    path: &Utf8Path,
+    line: &str,
+    line_number: usize,
+) -> IgnoreDecision {
+    if let Some(backends_to_ignore) =
+        config.parse_name_value_directive(line, "ignore-backends", path, line_number)
+    {
         for backend in backends_to_ignore.split_whitespace().map(|backend| {
             match CodegenBackend::try_from(backend) {
                 Ok(backend) => backend,
@@ -1821,8 +1624,15 @@ fn ignore_backends(config: &Config, path: &Utf8Path, line: &str) -> IgnoreDecisi
     IgnoreDecision::Continue
 }
 
-fn needs_backends(config: &Config, path: &Utf8Path, line: &str) -> IgnoreDecision {
-    if let Some(needed_backends) = config.parse_name_value_directive(line, "needs-backends") {
+fn needs_backends(
+    config: &Config,
+    path: &Utf8Path,
+    line: &str,
+    line_number: usize,
+) -> IgnoreDecision {
+    if let Some(needed_backends) =
+        config.parse_name_value_directive(line, "needs-backends", path, line_number)
+    {
         if !needed_backends
             .split_whitespace()
             .map(|backend| match CodegenBackend::try_from(backend) {
@@ -1844,9 +1654,9 @@ fn needs_backends(config: &Config, path: &Utf8Path, line: &str) -> IgnoreDecisio
     IgnoreDecision::Continue
 }
 
-fn ignore_llvm(config: &Config, path: &Utf8Path, line: &str) -> IgnoreDecision {
+fn ignore_llvm(config: &Config, path: &Utf8Path, line: &str, line_number: usize) -> IgnoreDecision {
     if let Some(needed_components) =
-        config.parse_name_value_directive(line, "needs-llvm-components")
+        config.parse_name_value_directive(line, "needs-llvm-components", path, line_number)
     {
         let components: HashSet<_> = config.llvm_components.split_whitespace().collect();
         if let Some(missing_component) = needed_components
@@ -1867,7 +1677,9 @@ fn ignore_llvm(config: &Config, path: &Utf8Path, line: &str) -> IgnoreDecision {
     if let Some(actual_version) = &config.llvm_version {
         // Note that these `min` versions will check for not just major versions.
 
-        if let Some(version_string) = config.parse_name_value_directive(line, "min-llvm-version") {
+        if let Some(version_string) =
+            config.parse_name_value_directive(line, "min-llvm-version", path, line_number)
+        {
             let min_version = extract_llvm_version(&version_string);
             // Ignore if actual version is smaller than the minimum required version.
             if *actual_version < min_version {
@@ -1878,7 +1690,7 @@ fn ignore_llvm(config: &Config, path: &Utf8Path, line: &str) -> IgnoreDecision {
                 };
             }
         } else if let Some(version_string) =
-            config.parse_name_value_directive(line, "max-llvm-major-version")
+            config.parse_name_value_directive(line, "max-llvm-major-version", path, line_number)
         {
             let max_version = extract_llvm_version(&version_string);
             // Ignore if actual major version is larger than the maximum required major version.
@@ -1892,7 +1704,7 @@ fn ignore_llvm(config: &Config, path: &Utf8Path, line: &str) -> IgnoreDecision {
                 };
             }
         } else if let Some(version_string) =
-            config.parse_name_value_directive(line, "min-system-llvm-version")
+            config.parse_name_value_directive(line, "min-system-llvm-version", path, line_number)
         {
             let min_version = extract_llvm_version(&version_string);
             // Ignore if using system LLVM and actual version
@@ -1905,7 +1717,7 @@ fn ignore_llvm(config: &Config, path: &Utf8Path, line: &str) -> IgnoreDecision {
                 };
             }
         } else if let Some(version_range) =
-            config.parse_name_value_directive(line, "ignore-llvm-version")
+            config.parse_name_value_directive(line, "ignore-llvm-version", path, line_number)
         {
             // Syntax is: "ignore-llvm-version: <version1> [- <version2>]"
             let (v_min, v_max) =
@@ -1931,7 +1743,7 @@ fn ignore_llvm(config: &Config, path: &Utf8Path, line: &str) -> IgnoreDecision {
                 }
             }
         } else if let Some(version_string) =
-            config.parse_name_value_directive(line, "exact-llvm-major-version")
+            config.parse_name_value_directive(line, "exact-llvm-major-version", path, line_number)
         {
             // Syntax is "exact-llvm-major-version: <version>"
             let version = extract_llvm_version(&version_string);
diff --git a/src/tools/compiletest/src/directives/auxiliary.rs b/src/tools/compiletest/src/directives/auxiliary.rs
index cdb75f6ffa9..7c1ed2e7006 100644
--- a/src/tools/compiletest/src/directives/auxiliary.rs
+++ b/src/tools/compiletest/src/directives/auxiliary.rs
@@ -3,6 +3,8 @@
 
 use std::iter;
 
+use camino::Utf8Path;
+
 use super::directives::{AUX_BIN, AUX_BUILD, AUX_CODEGEN_BACKEND, AUX_CRATE, PROC_MACRO};
 use crate::common::Config;
 
@@ -41,17 +43,42 @@ impl AuxProps {
 
 /// If the given test directive line contains an `aux-*` directive, parse it
 /// and update [`AuxProps`] accordingly.
-pub(super) fn parse_and_update_aux(config: &Config, ln: &str, aux: &mut AuxProps) {
+pub(super) fn parse_and_update_aux(
+    config: &Config,
+    ln: &str,
+    testfile: &Utf8Path,
+    line_number: usize,
+    aux: &mut AuxProps,
+) {
     if !(ln.starts_with("aux-") || ln.starts_with("proc-macro")) {
         return;
     }
 
-    config.push_name_value_directive(ln, AUX_BUILD, &mut aux.builds, |r| r.trim().to_string());
-    config.push_name_value_directive(ln, AUX_BIN, &mut aux.bins, |r| r.trim().to_string());
-    config.push_name_value_directive(ln, AUX_CRATE, &mut aux.crates, parse_aux_crate);
-    config
-        .push_name_value_directive(ln, PROC_MACRO, &mut aux.proc_macros, |r| r.trim().to_string());
-    if let Some(r) = config.parse_name_value_directive(ln, AUX_CODEGEN_BACKEND) {
+    config.push_name_value_directive(ln, AUX_BUILD, testfile, line_number, &mut aux.builds, |r| {
+        r.trim().to_string()
+    });
+    config.push_name_value_directive(ln, AUX_BIN, testfile, line_number, &mut aux.bins, |r| {
+        r.trim().to_string()
+    });
+    config.push_name_value_directive(
+        ln,
+        AUX_CRATE,
+        testfile,
+        line_number,
+        &mut aux.crates,
+        parse_aux_crate,
+    );
+    config.push_name_value_directive(
+        ln,
+        PROC_MACRO,
+        testfile,
+        line_number,
+        &mut aux.proc_macros,
+        |r| r.trim().to_string(),
+    );
+    if let Some(r) =
+        config.parse_name_value_directive(ln, AUX_CODEGEN_BACKEND, testfile, line_number)
+    {
         aux.codegen_backend = Some(r.trim().to_owned());
     }
 }
diff --git a/src/tools/compiletest/src/directives/directive_names.rs b/src/tools/compiletest/src/directives/directive_names.rs
new file mode 100644
index 00000000000..7fc76a42e0c
--- /dev/null
+++ b/src/tools/compiletest/src/directives/directive_names.rs
@@ -0,0 +1,289 @@
+/// This was originally generated by collecting directives from ui tests and then extracting their
+/// directive names. This is **not** an exhaustive list of all possible directives. Instead, this is
+/// a best-effort approximation for diagnostics. Add new directives to this list when needed.
+pub(crate) const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
+    // tidy-alphabetical-start
+    "add-core-stubs",
+    "assembly-output",
+    "aux-bin",
+    "aux-build",
+    "aux-codegen-backend",
+    "aux-crate",
+    "build-aux-docs",
+    "build-fail",
+    "build-pass",
+    "check-fail",
+    "check-pass",
+    "check-run-results",
+    "check-stdout",
+    "check-test-line-numbers-match",
+    "compile-flags",
+    "doc-flags",
+    "dont-check-compiler-stderr",
+    "dont-check-compiler-stdout",
+    "dont-check-failure-status",
+    "dont-require-annotations",
+    "edition",
+    "error-pattern",
+    "exact-llvm-major-version",
+    "exec-env",
+    "failure-status",
+    "filecheck-flags",
+    "forbid-output",
+    "force-host",
+    "ignore-16bit",
+    "ignore-32bit",
+    "ignore-64bit",
+    "ignore-aarch64",
+    "ignore-aarch64-pc-windows-msvc",
+    "ignore-aarch64-unknown-linux-gnu",
+    "ignore-aix",
+    "ignore-android",
+    "ignore-apple",
+    "ignore-arm",
+    "ignore-arm-unknown-linux-gnueabi",
+    "ignore-arm-unknown-linux-gnueabihf",
+    "ignore-arm-unknown-linux-musleabi",
+    "ignore-arm-unknown-linux-musleabihf",
+    "ignore-auxiliary",
+    "ignore-avr",
+    "ignore-backends",
+    "ignore-beta",
+    "ignore-cdb",
+    "ignore-compare-mode-next-solver",
+    "ignore-compare-mode-polonius",
+    "ignore-coverage-map",
+    "ignore-coverage-run",
+    "ignore-cross-compile",
+    "ignore-eabi",
+    "ignore-elf",
+    "ignore-emscripten",
+    "ignore-endian-big",
+    "ignore-enzyme",
+    "ignore-freebsd",
+    "ignore-fuchsia",
+    "ignore-gdb",
+    "ignore-gdb-version",
+    "ignore-gnu",
+    "ignore-haiku",
+    "ignore-horizon",
+    "ignore-i686-pc-windows-gnu",
+    "ignore-i686-pc-windows-msvc",
+    "ignore-illumos",
+    "ignore-ios",
+    "ignore-linux",
+    "ignore-lldb",
+    "ignore-llvm-version",
+    "ignore-loongarch32",
+    "ignore-loongarch64",
+    "ignore-macabi",
+    "ignore-macos",
+    "ignore-msp430",
+    "ignore-msvc",
+    "ignore-musl",
+    "ignore-netbsd",
+    "ignore-nightly",
+    "ignore-none",
+    "ignore-nto",
+    "ignore-nvptx64",
+    "ignore-nvptx64-nvidia-cuda",
+    "ignore-openbsd",
+    "ignore-pass",
+    "ignore-powerpc",
+    "ignore-powerpc64",
+    "ignore-remote",
+    "ignore-riscv64",
+    "ignore-rustc-debug-assertions",
+    "ignore-rustc_abi-x86-sse2",
+    "ignore-s390x",
+    "ignore-sgx",
+    "ignore-sparc64",
+    "ignore-spirv",
+    "ignore-stable",
+    "ignore-stage1",
+    "ignore-stage2",
+    "ignore-std-debug-assertions",
+    "ignore-test",
+    "ignore-thumb",
+    "ignore-thumbv8m.base-none-eabi",
+    "ignore-thumbv8m.main-none-eabi",
+    "ignore-tvos",
+    "ignore-unix",
+    "ignore-unknown",
+    "ignore-uwp",
+    "ignore-visionos",
+    "ignore-vxworks",
+    "ignore-wasi",
+    "ignore-wasm",
+    "ignore-wasm32",
+    "ignore-wasm32-bare",
+    "ignore-wasm64",
+    "ignore-watchos",
+    "ignore-windows",
+    "ignore-windows-gnu",
+    "ignore-windows-msvc",
+    "ignore-x32",
+    "ignore-x86",
+    "ignore-x86_64",
+    "ignore-x86_64-apple-darwin",
+    "ignore-x86_64-pc-windows-gnu",
+    "ignore-x86_64-unknown-linux-gnu",
+    "incremental",
+    "known-bug",
+    "llvm-cov-flags",
+    "max-llvm-major-version",
+    "min-cdb-version",
+    "min-gdb-version",
+    "min-lldb-version",
+    "min-llvm-version",
+    "min-system-llvm-version",
+    "needs-asm-support",
+    "needs-backends",
+    "needs-crate-type",
+    "needs-deterministic-layouts",
+    "needs-dlltool",
+    "needs-dynamic-linking",
+    "needs-enzyme",
+    "needs-force-clang-based-tests",
+    "needs-git-hash",
+    "needs-llvm-components",
+    "needs-llvm-zstd",
+    "needs-profiler-runtime",
+    "needs-relocation-model-pic",
+    "needs-run-enabled",
+    "needs-rust-lld",
+    "needs-rustc-debug-assertions",
+    "needs-sanitizer-address",
+    "needs-sanitizer-cfi",
+    "needs-sanitizer-dataflow",
+    "needs-sanitizer-hwaddress",
+    "needs-sanitizer-kcfi",
+    "needs-sanitizer-leak",
+    "needs-sanitizer-memory",
+    "needs-sanitizer-memtag",
+    "needs-sanitizer-safestack",
+    "needs-sanitizer-shadow-call-stack",
+    "needs-sanitizer-support",
+    "needs-sanitizer-thread",
+    "needs-std-debug-assertions",
+    "needs-subprocess",
+    "needs-symlink",
+    "needs-target-has-atomic",
+    "needs-target-std",
+    "needs-threads",
+    "needs-unwind",
+    "needs-wasmtime",
+    "needs-xray",
+    "no-auto-check-cfg",
+    "no-prefer-dynamic",
+    "normalize-stderr",
+    "normalize-stderr-32bit",
+    "normalize-stderr-64bit",
+    "normalize-stdout",
+    "only-16bit",
+    "only-32bit",
+    "only-64bit",
+    "only-aarch64",
+    "only-aarch64-apple-darwin",
+    "only-aarch64-unknown-linux-gnu",
+    "only-apple",
+    "only-arm",
+    "only-avr",
+    "only-beta",
+    "only-bpf",
+    "only-cdb",
+    "only-dist",
+    "only-elf",
+    "only-emscripten",
+    "only-gnu",
+    "only-i686-pc-windows-gnu",
+    "only-i686-pc-windows-msvc",
+    "only-i686-unknown-linux-gnu",
+    "only-ios",
+    "only-linux",
+    "only-loongarch32",
+    "only-loongarch64",
+    "only-loongarch64-unknown-linux-gnu",
+    "only-macos",
+    "only-mips",
+    "only-mips64",
+    "only-msp430",
+    "only-msvc",
+    "only-musl",
+    "only-nightly",
+    "only-nvptx64",
+    "only-powerpc",
+    "only-riscv64",
+    "only-rustc_abi-x86-sse2",
+    "only-s390x",
+    "only-sparc",
+    "only-sparc64",
+    "only-stable",
+    "only-thumb",
+    "only-tvos",
+    "only-uefi",
+    "only-unix",
+    "only-visionos",
+    "only-wasm32",
+    "only-wasm32-bare",
+    "only-wasm32-wasip1",
+    "only-watchos",
+    "only-windows",
+    "only-windows-gnu",
+    "only-windows-msvc",
+    "only-x86",
+    "only-x86_64",
+    "only-x86_64-apple-darwin",
+    "only-x86_64-fortanix-unknown-sgx",
+    "only-x86_64-pc-windows-gnu",
+    "only-x86_64-pc-windows-msvc",
+    "only-x86_64-unknown-linux-gnu",
+    "pp-exact",
+    "pretty-compare-only",
+    "pretty-mode",
+    "proc-macro",
+    "reference",
+    "regex-error-pattern",
+    "remap-src-base",
+    "revisions",
+    "run-crash",
+    "run-fail",
+    "run-fail-or-crash",
+    "run-flags",
+    "run-pass",
+    "run-rustfix",
+    "rustc-env",
+    "rustfix-only-machine-applicable",
+    "should-fail",
+    "should-ice",
+    "stderr-per-bitwidth",
+    "test-mir-pass",
+    "unique-doc-out-dir",
+    "unset-exec-env",
+    "unset-rustc-env",
+    // Used by the tidy check `unknown_revision`.
+    "unused-revision-names",
+    // tidy-alphabetical-end
+];
+
+pub(crate) const KNOWN_HTMLDOCCK_DIRECTIVE_NAMES: &[&str] = &[
+    "count",
+    "!count",
+    "files",
+    "!files",
+    "has",
+    "!has",
+    "has-dir",
+    "!has-dir",
+    "hasraw",
+    "!hasraw",
+    "matches",
+    "!matches",
+    "matchesraw",
+    "!matchesraw",
+    "snapshot",
+    "!snapshot",
+];
+
+pub(crate) const KNOWN_JSONDOCCK_DIRECTIVE_NAMES: &[&str] =
+    &["count", "!count", "has", "!has", "is", "!is", "ismany", "!ismany", "set", "!set"];
diff --git a/src/tools/compiletest/src/runtest/debugger.rs b/src/tools/compiletest/src/runtest/debugger.rs
index a4103c5b4a9..ba824124e87 100644
--- a/src/tools/compiletest/src/runtest/debugger.rs
+++ b/src/tools/compiletest/src/runtest/debugger.rs
@@ -47,10 +47,14 @@ impl DebuggerCommands {
                 continue;
             };
 
-            if let Some(command) = config.parse_name_value_directive(&line, &command_directive) {
+            if let Some(command) =
+                config.parse_name_value_directive(&line, &command_directive, file, line_no)
+            {
                 commands.push(command);
             }
-            if let Some(pattern) = config.parse_name_value_directive(&line, &check_directive) {
+            if let Some(pattern) =
+                config.parse_name_value_directive(&line, &check_directive, file, line_no)
+            {
                 check_lines.push((line_no, pattern));
             }
         }
diff --git a/src/tools/generate-copyright/Cargo.toml b/src/tools/generate-copyright/Cargo.toml
index e420a450d42..bcb3165de45 100644
--- a/src/tools/generate-copyright/Cargo.toml
+++ b/src/tools/generate-copyright/Cargo.toml
@@ -9,7 +9,7 @@ description = "Produces a manifest of all the copyrighted materials in the Rust
 [dependencies]
 anyhow = "1.0.65"
 askama = "0.14.0"
-cargo_metadata = "0.18.1"
+cargo_metadata = "0.21"
 serde = { version = "1.0.147", features = ["derive"] }
 serde_json = "1.0.85"
 thiserror = "1"
diff --git a/src/tools/generate-copyright/src/cargo_metadata.rs b/src/tools/generate-copyright/src/cargo_metadata.rs
index 3fae26bda47..87cd85c8def 100644
--- a/src/tools/generate-copyright/src/cargo_metadata.rs
+++ b/src/tools/generate-copyright/src/cargo_metadata.rs
@@ -92,7 +92,8 @@ pub fn get_metadata(
                 continue;
             }
             // otherwise it's an out-of-tree dependency
-            let package_id = Package { name: package.name, version: package.version.to_string() };
+            let package_id =
+                Package { name: package.name.to_string(), version: package.version.to_string() };
             output.insert(
                 package_id,
                 PackageMetadata {
diff --git a/src/tools/linkchecker/Cargo.toml b/src/tools/linkchecker/Cargo.toml
index 7123d43eb56..fb5bff3fe63 100644
--- a/src/tools/linkchecker/Cargo.toml
+++ b/src/tools/linkchecker/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 name = "linkchecker"
 version = "0.1.0"
-edition = "2021"
+edition = "2024"
 
 [[bin]]
 name = "linkchecker"
diff --git a/src/tools/linkchecker/main.rs b/src/tools/linkchecker/main.rs
index 84cba3f8c44..1dc45728c90 100644
--- a/src/tools/linkchecker/main.rs
+++ b/src/tools/linkchecker/main.rs
@@ -17,12 +17,13 @@
 //! should catch the majority of "broken link" cases.
 
 use std::cell::{Cell, RefCell};
+use std::collections::hash_map::Entry;
 use std::collections::{HashMap, HashSet};
-use std::io::ErrorKind;
+use std::fs;
+use std::iter::once;
 use std::path::{Component, Path, PathBuf};
 use std::rc::Rc;
 use std::time::Instant;
-use std::{env, fs};
 
 use html5ever::tendril::ByteTendril;
 use html5ever::tokenizer::{
@@ -110,10 +111,25 @@ macro_rules! t {
     };
 }
 
+struct Cli {
+    docs: PathBuf,
+    link_targets_dirs: Vec<PathBuf>,
+}
+
 fn main() {
-    let docs = env::args_os().nth(1).expect("doc path should be first argument");
-    let docs = env::current_dir().unwrap().join(docs);
-    let mut checker = Checker { root: docs.clone(), cache: HashMap::new() };
+    let cli = match parse_cli() {
+        Ok(cli) => cli,
+        Err(err) => {
+            eprintln!("error: {err}");
+            usage_and_exit(1);
+        }
+    };
+
+    let mut checker = Checker {
+        root: cli.docs.clone(),
+        link_targets_dirs: cli.link_targets_dirs,
+        cache: HashMap::new(),
+    };
     let mut report = Report {
         errors: 0,
         start: Instant::now(),
@@ -125,7 +141,7 @@ fn main() {
         intra_doc_exceptions: 0,
         has_broken_urls: false,
     };
-    checker.walk(&docs, &mut report);
+    checker.walk(&cli.docs, &mut report);
     report.report();
     if report.errors != 0 {
         println!("found some broken links");
@@ -133,8 +149,50 @@ fn main() {
     }
 }
 
+fn parse_cli() -> Result<Cli, String> {
+    fn to_absolute_path(arg: &str) -> Result<PathBuf, String> {
+        std::path::absolute(arg).map_err(|e| format!("could not convert to absolute {arg}: {e}"))
+    }
+
+    let mut verbatim = false;
+    let mut docs = None;
+    let mut link_targets_dirs = Vec::new();
+
+    let mut args = std::env::args().skip(1);
+    while let Some(arg) = args.next() {
+        if !verbatim && arg == "--" {
+            verbatim = true;
+        } else if !verbatim && (arg == "-h" || arg == "--help") {
+            usage_and_exit(0)
+        } else if !verbatim && arg == "--link-targets-dir" {
+            link_targets_dirs.push(to_absolute_path(
+                &args.next().ok_or("missing value for --link-targets-dir")?,
+            )?);
+        } else if !verbatim && let Some(value) = arg.strip_prefix("--link-targets-dir=") {
+            link_targets_dirs.push(to_absolute_path(value)?);
+        } else if !verbatim && arg.starts_with('-') {
+            return Err(format!("unknown flag: {arg}"));
+        } else if docs.is_none() {
+            docs = Some(arg);
+        } else {
+            return Err("too many positional arguments".into());
+        }
+    }
+
+    Ok(Cli {
+        docs: to_absolute_path(&docs.ok_or("missing first positional argument")?)?,
+        link_targets_dirs,
+    })
+}
+
+fn usage_and_exit(code: i32) -> ! {
+    eprintln!("usage: linkchecker PATH [--link-targets-dir=PATH ...]");
+    std::process::exit(code)
+}
+
 struct Checker {
     root: PathBuf,
+    link_targets_dirs: Vec<PathBuf>,
     cache: Cache,
 }
 
@@ -420,37 +478,34 @@ impl Checker {
 
     /// Load a file from disk, or from the cache if available.
     fn load_file(&mut self, file: &Path, report: &mut Report) -> (String, &FileEntry) {
-        // https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
-        #[cfg(windows)]
-        const ERROR_INVALID_NAME: i32 = 123;
-
         let pretty_path =
             file.strip_prefix(&self.root).unwrap_or(file).to_str().unwrap().to_string();
 
-        let entry =
-            self.cache.entry(pretty_path.clone()).or_insert_with(|| match fs::metadata(file) {
+        for base in once(&self.root).chain(self.link_targets_dirs.iter()) {
+            let entry = self.cache.entry(pretty_path.clone());
+            if let Entry::Occupied(e) = &entry
+                && !matches!(e.get(), FileEntry::Missing)
+            {
+                break;
+            }
+
+            let file = base.join(&pretty_path);
+            entry.insert_entry(match fs::metadata(&file) {
                 Ok(metadata) if metadata.is_dir() => FileEntry::Dir,
                 Ok(_) => {
                     if file.extension().and_then(|s| s.to_str()) != Some("html") {
                         FileEntry::OtherFile
                     } else {
                         report.html_files += 1;
-                        load_html_file(file, report)
+                        load_html_file(&file, report)
                     }
                 }
-                Err(e) if e.kind() == ErrorKind::NotFound => FileEntry::Missing,
-                Err(e) => {
-                    // If a broken intra-doc link contains `::`, on windows, it will cause `ERROR_INVALID_NAME` rather than `NotFound`.
-                    // Explicitly check for that so that the broken link can be allowed in `LINKCHECK_EXCEPTIONS`.
-                    #[cfg(windows)]
-                    if e.raw_os_error() == Some(ERROR_INVALID_NAME)
-                        && file.as_os_str().to_str().map_or(false, |s| s.contains("::"))
-                    {
-                        return FileEntry::Missing;
-                    }
-                    panic!("unexpected read error for {}: {}", file.display(), e);
-                }
+                Err(e) if is_not_found_error(&file, &e) => FileEntry::Missing,
+                Err(e) => panic!("unexpected read error for {}: {}", file.display(), e),
             });
+        }
+
+        let entry = self.cache.get(&pretty_path).unwrap();
         (pretty_path, entry)
     }
 }
@@ -629,3 +684,16 @@ fn parse_ids(ids: &mut HashSet<String>, file: &str, source: &str, report: &mut R
         ids.insert(encoded);
     }
 }
+
+fn is_not_found_error(path: &Path, error: &std::io::Error) -> bool {
+    // https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
+    const WINDOWS_ERROR_INVALID_NAME: i32 = 123;
+
+    error.kind() == std::io::ErrorKind::NotFound
+        // If a broken intra-doc link contains `::`, on windows, it will cause `ERROR_INVALID_NAME`
+        // rather than `NotFound`. Explicitly check for that so that the broken link can be allowed
+        // in `LINKCHECK_EXCEPTIONS`.
+        || (cfg!(windows)
+            && error.raw_os_error() == Some(WINDOWS_ERROR_INVALID_NAME)
+            && path.as_os_str().to_str().map_or(false, |s| s.contains("::")))
+}
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/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 ccfff7fa94b..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,161 +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 {
-                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();
@@ -1257,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(())
         })?;
@@ -1317,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",
@@ -1465,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_array_vs_struct.rs b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_array_vs_struct.rs
index 4468eb299f3..26f2e73dd75 100644
--- a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_array_vs_struct.rs
+++ b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_array_vs_struct.rs
@@ -17,5 +17,5 @@ fn main() {
     // These two types have the same size but are still not compatible.
     let g = unsafe { std::mem::transmute::<fn(S), fn(A)>(f) };
 
-    g(Default::default()) //~ ERROR: calling a function with argument of type S passing data of type [i32; 4]
+    g(Default::default()) //~ ERROR: type S passing argument of type [i32; 4]
 }
diff --git a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_array_vs_struct.stderr b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_array_vs_struct.stderr
index cabefa8bee9..f793abb0b62 100644
--- a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_array_vs_struct.stderr
+++ b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_array_vs_struct.stderr
@@ -1,4 +1,4 @@
-error: Undefined Behavior: calling a function with argument of type S passing data of type [i32; 4]
+error: Undefined Behavior: calling a function whose parameter #1 has type S passing argument of type [i32; 4]
   --> tests/fail/function_pointers/abi_mismatch_array_vs_struct.rs:LL:CC
    |
 LL |     g(Default::default())
diff --git a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_int_vs_float.rs b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_int_vs_float.rs
index a1fda329e8d..0cca4a13233 100644
--- a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_int_vs_float.rs
+++ b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_int_vs_float.rs
@@ -3,5 +3,5 @@ fn main() {
 
     let g = unsafe { std::mem::transmute::<fn(f32), fn(i32)>(f) };
 
-    g(42) //~ ERROR: calling a function with argument of type f32 passing data of type i32
+    g(42) //~ ERROR: type f32 passing argument of type i32
 }
diff --git a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_int_vs_float.stderr b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_int_vs_float.stderr
index 52cc48d58ce..3651fc9b3f7 100644
--- a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_int_vs_float.stderr
+++ b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_int_vs_float.stderr
@@ -1,4 +1,4 @@
-error: Undefined Behavior: calling a function with argument of type f32 passing data of type i32
+error: Undefined Behavior: calling a function whose parameter #1 has type f32 passing argument of type i32
   --> tests/fail/function_pointers/abi_mismatch_int_vs_float.rs:LL:CC
    |
 LL |     g(42)
diff --git a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_raw_pointer.rs b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_raw_pointer.rs
index f0ea5ccfe0f..053a4a5f284 100644
--- a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_raw_pointer.rs
+++ b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_raw_pointer.rs
@@ -3,5 +3,5 @@ fn main() {
 
     let g = unsafe { std::mem::transmute::<fn(*const [i32]), fn(*const i32)>(f) };
 
-    g(&42 as *const i32) //~ ERROR: calling a function with argument of type *const [i32] passing data of type *const i32
+    g(&42 as *const i32) //~ ERROR: type *const [i32] passing argument of type *const i32
 }
diff --git a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_raw_pointer.stderr b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_raw_pointer.stderr
index 2fbb0408c59..88345a0688c 100644
--- a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_raw_pointer.stderr
+++ b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_raw_pointer.stderr
@@ -1,4 +1,4 @@
-error: Undefined Behavior: calling a function with argument of type *const [i32] passing data of type *const i32
+error: Undefined Behavior: calling a function whose parameter #1 has type *const [i32] passing argument of type *const i32
   --> tests/fail/function_pointers/abi_mismatch_raw_pointer.rs:LL:CC
    |
 LL |     g(&42 as *const i32)
diff --git a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_repr_C.rs b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_repr_C.rs
index c5900489b4c..f3dffcc4e86 100644
--- a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_repr_C.rs
+++ b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_repr_C.rs
@@ -12,5 +12,5 @@ fn main() {
     let fnptr: fn(S2) = callee;
     let fnptr: fn(S1) = unsafe { std::mem::transmute(fnptr) };
     fnptr(S1(NonZero::new(1).unwrap()));
-    //~^ ERROR: calling a function with argument of type S2 passing data of type S1
+    //~^ ERROR: type S2 passing argument of type S1
 }
diff --git a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_repr_C.stderr b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_repr_C.stderr
index 2c1ac0ee702..47658395132 100644
--- a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_repr_C.stderr
+++ b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_repr_C.stderr
@@ -1,4 +1,4 @@
-error: Undefined Behavior: calling a function with argument of type S2 passing data of type S1
+error: Undefined Behavior: calling a function whose parameter #1 has type S2 passing argument of type S1
   --> tests/fail/function_pointers/abi_mismatch_repr_C.rs:LL:CC
    |
 LL |     fnptr(S1(NonZero::new(1).unwrap()));
diff --git a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_return_type.rs b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_return_type.rs
index 0fdab49b94b..05b645cf75a 100644
--- a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_return_type.rs
+++ b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_return_type.rs
@@ -5,5 +5,5 @@ fn main() {
 
     let g = unsafe { std::mem::transmute::<fn() -> u32, fn()>(f) };
 
-    g() //~ ERROR: calling a function with return type u32 passing return place of type ()
+    g() //~ ERROR: type u32 passing return place of type ()
 }
diff --git a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_simple.rs b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_simple.rs
index 20384f0965b..ca43c06008f 100644
--- a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_simple.rs
+++ b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_simple.rs
@@ -3,5 +3,5 @@ fn main() {
 
     let g = unsafe { std::mem::transmute::<fn((i32, i32)), fn(i32)>(f) };
 
-    g(42) //~ ERROR: calling a function with argument of type (i32, i32) passing data of type i32
+    g(42) //~ ERROR: type (i32, i32) passing argument of type i32
 }
diff --git a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_simple.stderr b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_simple.stderr
index e45ad12ec05..2ed9ac2e6da 100644
--- a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_simple.stderr
+++ b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_simple.stderr
@@ -1,4 +1,4 @@
-error: Undefined Behavior: calling a function with argument of type (i32, i32) passing data of type i32
+error: Undefined Behavior: calling a function whose parameter #1 has type (i32, i32) passing argument of type i32
   --> tests/fail/function_pointers/abi_mismatch_simple.rs:LL:CC
    |
 LL |     g(42)
diff --git a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_vector.rs b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_vector.rs
index 80f357b61ba..dedcaac6142 100644
--- a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_vector.rs
+++ b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_vector.rs
@@ -7,5 +7,5 @@ fn main() {
     // These two vector types have the same size but are still not compatible.
     let g = unsafe { std::mem::transmute::<fn(simd::u32x8), fn(simd::u64x4)>(f) };
 
-    g(Default::default()) //~ ERROR: calling a function with argument of type std::simd::Simd<u32, 8> passing data of type std::simd::Simd<u64, 4>
+    g(Default::default()) //~ ERROR: type std::simd::Simd<u32, 8> passing argument of type std::simd::Simd<u64, 4>
 }
diff --git a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_vector.stderr b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_vector.stderr
index bad2495cb39..b13e8d936db 100644
--- a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_vector.stderr
+++ b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_vector.stderr
@@ -1,4 +1,4 @@
-error: Undefined Behavior: calling a function with argument of type std::simd::Simd<u32, 8> passing data of type std::simd::Simd<u64, 4>
+error: Undefined Behavior: calling a function whose parameter #1 has type std::simd::Simd<u32, 8> passing argument of type std::simd::Simd<u64, 4>
   --> tests/fail/function_pointers/abi_mismatch_vector.rs:LL:CC
    |
 LL |     g(Default::default())
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/input_arg_mismatch.rs b/src/tools/miri/tests/fail/shims/input_arg_mismatch.rs
index eb8de04dcc4..77699776aea 100644
--- a/src/tools/miri/tests/fail/shims/input_arg_mismatch.rs
+++ b/src/tools/miri/tests/fail/shims/input_arg_mismatch.rs
@@ -16,6 +16,6 @@ fn main() {
     } as u32;
     let _ = unsafe {
         close(fd);
-        //~^ ERROR: calling a function with argument of type i32 passing data of type u32
+        //~^ ERROR: type i32 passing argument of type u32
     };
 }
diff --git a/src/tools/miri/tests/fail/shims/input_arg_mismatch.stderr b/src/tools/miri/tests/fail/shims/input_arg_mismatch.stderr
index ce00b624a42..ec27fd5ebb8 100644
--- a/src/tools/miri/tests/fail/shims/input_arg_mismatch.stderr
+++ b/src/tools/miri/tests/fail/shims/input_arg_mismatch.stderr
@@ -1,4 +1,4 @@
-error: Undefined Behavior: calling a function with argument of type i32 passing data of type u32
+error: Undefined Behavior: calling a function whose parameter #1 has type i32 passing argument of type u32
   --> tests/fail/shims/input_arg_mismatch.rs:LL:CC
    |
 LL |         close(fd);
diff --git a/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.rs b/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.rs
index 6df132d3255..36bd1e99cfb 100644
--- a/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.rs
+++ b/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.rs
@@ -6,7 +6,7 @@ fn main() {
     //   the error should point to `become g(x)`,
     //   but tail calls mess up the backtrace it seems like...
     f(0);
-    //~^ error: Undefined Behavior: calling a function with argument of type i32 passing data of type u32
+    //~^ error: type i32 passing argument of type u32
 }
 
 fn f(x: u32) {
diff --git a/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.stderr b/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.stderr
index fbb0d3d565d..cabea5df85d 100644
--- a/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.stderr
+++ b/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.stderr
@@ -1,4 +1,4 @@
-error: Undefined Behavior: calling a function with argument of type i32 passing data of type u32
+error: Undefined Behavior: calling a function whose parameter #1 has type i32 passing argument of type u32
   --> tests/fail/tail_calls/signature-mismatch-arg.rs:LL:CC
    |
 LL |     f(0);
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/miropt-test-tools/Cargo.toml b/src/tools/miropt-test-tools/Cargo.toml
index 09b4c7d16dc..3eb5020968d 100644
--- a/src/tools/miropt-test-tools/Cargo.toml
+++ b/src/tools/miropt-test-tools/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "miropt-test-tools"
 version = "0.1.0"
-edition = "2021"
+edition = "2024"
 
 [dependencies]
diff --git a/src/tools/miropt-test-tools/src/lib.rs b/src/tools/miropt-test-tools/src/lib.rs
index 41b53d2ad0e..10769c9c8ab 100644
--- a/src/tools/miropt-test-tools/src/lib.rs
+++ b/src/tools/miropt-test-tools/src/lib.rs
@@ -34,7 +34,7 @@ fn output_file_suffix(testfile: &Path, bit_width: u32, panic_strategy: PanicStra
 
     let mut suffix = String::new();
     if each_bit_width {
-        suffix.push_str(&format!(".{}bit", bit_width));
+        suffix.push_str(&format!(".{bit_width}bit"));
     }
     if each_panic_strategy {
         match panic_strategy {
@@ -51,7 +51,7 @@ pub fn files_for_miropt_test(
     panic_strategy: PanicStrategy,
 ) -> MiroptTest {
     let mut out = Vec::new();
-    let test_file_contents = fs::read_to_string(&testfile).unwrap();
+    let test_file_contents = fs::read_to_string(testfile).unwrap();
 
     let test_dir = testfile.parent().unwrap();
     let test_crate = testfile.file_stem().unwrap().to_str().unwrap().replace('-', "_");
@@ -76,10 +76,10 @@ pub fn files_for_miropt_test(
 
             if test_name.ends_with(".diff") {
                 let trimmed = test_name.trim_end_matches(".diff");
-                passes.push(trimmed.split('.').last().unwrap().to_owned());
-                let test_against = format!("{}.after.mir", trimmed);
-                from_file = format!("{}.before.mir", trimmed);
-                expected_file = format!("{}{}.diff", trimmed, suffix);
+                passes.push(trimmed.split('.').next_back().unwrap().to_owned());
+                let test_against = format!("{trimmed}.after.mir");
+                from_file = format!("{trimmed}.before.mir");
+                expected_file = format!("{trimmed}{suffix}.diff");
                 assert!(test_names.next().is_none(), "two mir pass names specified for MIR diff");
                 to_file = Some(test_against);
             } else if let Some(first_pass) = test_names.next() {
@@ -92,10 +92,9 @@ pub fn files_for_miropt_test(
                 }
                 assert!(test_names.next().is_none(), "three mir pass names specified for MIR diff");
 
-                expected_file =
-                    format!("{}{}.{}-{}.diff", test_name, suffix, first_pass, second_pass);
-                let second_file = format!("{}.{}.mir", test_name, second_pass);
-                from_file = format!("{}.{}.mir", test_name, first_pass);
+                expected_file = format!("{test_name}{suffix}.{first_pass}-{second_pass}.diff");
+                let second_file = format!("{test_name}.{second_pass}.mir");
+                from_file = format!("{test_name}.{first_pass}.mir");
                 to_file = Some(second_file);
             } else {
                 // Allow-list for file extensions that can be produced by MIR dumps.
@@ -112,7 +111,7 @@ pub fn files_for_miropt_test(
                     )
                 }
 
-                expected_file = format!("{}{}.{}", test_name_wo_ext, suffix, test_name_ext);
+                expected_file = format!("{test_name_wo_ext}{suffix}.{test_name_ext}");
                 from_file = test_name.to_string();
                 assert!(test_names.next().is_none(), "two mir pass names specified for MIR dump");
                 to_file = None;
@@ -123,7 +122,7 @@ pub fn files_for_miropt_test(
                 );
             };
             if !expected_file.starts_with(&test_crate) {
-                expected_file = format!("{}.{}", test_crate, expected_file);
+                expected_file = format!("{test_crate}.{expected_file}");
             }
             let expected_file = test_dir.join(expected_file);
 
diff --git a/src/tools/opt-dist/src/tests.rs b/src/tools/opt-dist/src/tests.rs
index c9a21fc6fb2..d5121b8c786 100644
--- a/src/tools/opt-dist/src/tests.rs
+++ b/src/tools/opt-dist/src/tests.rs
@@ -79,6 +79,7 @@ lld = false
 rustc = "{rustc}"
 cargo = "{cargo}"
 local-rebuild = true
+compiletest-allow-stage0=true
 
 [target.{host_triple}]
 llvm-config = "{llvm_config}"
@@ -117,7 +118,6 @@ llvm-config = "{llvm_config}"
             args.extend(["--skip", test_path]);
         }
         cmd(&args)
-            .env("COMPILETEST_FORCE_STAGE0", "1")
             // Also run dist-only tests
             .env("COMPILETEST_ENABLE_DIST_TESTS", "1")
             .run()
diff --git a/src/tools/rust-analyzer/.github/workflows/rustc-pull.yml b/src/tools/rust-analyzer/.github/workflows/rustc-pull.yml
new file mode 100644
index 00000000000..2a842f3b311
--- /dev/null
+++ b/src/tools/rust-analyzer/.github/workflows/rustc-pull.yml
@@ -0,0 +1,20 @@
+name: rustc-pull
+
+on:
+  workflow_dispatch:
+  schedule:
+    # Run at 04:00 UTC every Monday and Thursday
+    - cron: '0 4 * * 1,4'
+
+jobs:
+  pull:
+    if: github.repository == 'rust-lang/rust-analyzer'
+    uses: rust-lang/josh-sync/.github/workflows/rustc-pull.yml@main
+    with:
+      zulip-stream-id: 185405
+      zulip-bot-email:  "rust-analyzer-ci-bot@rust-lang.zulipchat.com"
+      pr-base-branch: master
+      branch-name: rustc-pull
+    secrets:
+      zulip-api-token: ${{ secrets.ZULIP_API_TOKEN }}
+      token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock
index c471234bbe3..7d03300c221 100644
--- a/src/tools/rust-analyzer/Cargo.lock
+++ b/src/tools/rust-analyzer/Cargo.lock
@@ -396,15 +396,6 @@ dependencies = [
 ]
 
 [[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"
 version = "6.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1268,7 +1259,7 @@ dependencies = [
  "expect-test",
  "intern",
  "parser",
- "ra-ap-rustc_lexer 0.122.0",
+ "ra-ap-rustc_lexer 0.123.0",
  "rustc-hash 2.1.1",
  "smallvec",
  "span",
@@ -1504,7 +1495,7 @@ dependencies = [
  "drop_bomb",
  "edition",
  "expect-test",
- "ra-ap-rustc_lexer 0.122.0",
+ "ra-ap-rustc_lexer 0.123.0",
  "rustc-literal-escaper",
  "stdx",
  "tracing",
@@ -1614,7 +1605,7 @@ dependencies = [
  "object",
  "paths",
  "proc-macro-test",
- "ra-ap-rustc_lexer 0.122.0",
+ "ra-ap-rustc_lexer 0.123.0",
  "span",
  "syntax-bridge",
  "tt",
@@ -1688,6 +1679,7 @@ dependencies = [
  "serde_json",
  "span",
  "stdx",
+ "temp-dir",
  "toolchain",
  "tracing",
  "triomphe",
@@ -1756,9 +1748,9 @@ dependencies = [
 
 [[package]]
 name = "ra-ap-rustc_abi"
-version = "0.122.0"
+version = "0.123.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb01e1fec578003c85481c1cad4ff8cd8195b07c2dc85ae3f716108507ae15d5"
+checksum = "f18c877575c259d127072e9bfc41d985202262fb4d6bfdae3d1252147c2562c2"
 dependencies = [
  "bitflags 2.9.1",
  "ra-ap-rustc_hashes",
@@ -1768,18 +1760,18 @@ dependencies = [
 
 [[package]]
 name = "ra-ap-rustc_hashes"
-version = "0.122.0"
+version = "0.123.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0ec056e72a472ffef8761ce96ece6c626eb07368c09d0105b6df30d27d07673"
+checksum = "2439ed1df3472443133b66949f81080dff88089b42f825761455463709ee1cad"
 dependencies = [
  "rustc-stable-hash",
 ]
 
 [[package]]
 name = "ra-ap-rustc_index"
-version = "0.122.0"
+version = "0.123.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fcdd1001db0295e59052e9f53aeda588bbe81e362534f4687d41bd44777b5a7"
+checksum = "57a24fe0be21be1f8ebc21dcb40129214fb4cefb0f2753f3d46b6dbe656a1a45"
 dependencies = [
  "ra-ap-rustc_index_macros",
  "smallvec",
@@ -1787,9 +1779,9 @@ dependencies = [
 
 [[package]]
 name = "ra-ap-rustc_index_macros"
-version = "0.122.0"
+version = "0.123.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "728d64dd98e25530b32e3f7c7c1e844e52722b269360daa1cdeba9dff9727a26"
+checksum = "844a27ddcad0116facae2df8e741fd788662cf93dc13029cd864f2b8013b81f9"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1809,9 +1801,9 @@ dependencies = [
 
 [[package]]
 name = "ra-ap-rustc_lexer"
-version = "0.122.0"
+version = "0.123.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "415f0821f512608d825b3215489a6a6a2c18ed9f0045953d514e7ec23d4b90ab"
+checksum = "2b734cfcb577d09877799a22742f1bd398be6c00bc428d9de56d48d11ece5771"
 dependencies = [
  "memchr",
  "unicode-properties",
@@ -1830,9 +1822,9 @@ dependencies = [
 
 [[package]]
 name = "ra-ap-rustc_pattern_analysis"
-version = "0.122.0"
+version = "0.123.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4657fcfdfe06e2a02ec8180d4e7c95aecf4811ba50367e363d1a2300b7623284"
+checksum = "75b0ee1f059b9dea0818c6c7267478926eee95ba4c7dcf89c8db32fa165d3904"
 dependencies = [
  "ra-ap-rustc_index",
  "rustc-hash 2.1.1",
@@ -2294,6 +2286,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "temp-dir"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83176759e9416cf81ee66cb6508dbfe9c96f20b8b56265a39917551c23c70964"
+
+[[package]]
 name = "tenthash"
 version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2592,7 +2590,7 @@ version = "0.0.0"
 dependencies = [
  "arrayvec",
  "intern",
- "ra-ap-rustc_lexer 0.122.0",
+ "ra-ap-rustc_lexer 0.123.0",
  "stdx",
  "text-size",
 ]
@@ -3105,7 +3103,6 @@ name = "xtask"
 version = "0.1.0"
 dependencies = [
  "anyhow",
- "directories",
  "edition",
  "either",
  "flate2",
diff --git a/src/tools/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/Cargo.toml
index 700c116ec18..e7cf0212bf2 100644
--- a/src/tools/rust-analyzer/Cargo.toml
+++ b/src/tools/rust-analyzer/Cargo.toml
@@ -89,11 +89,11 @@ vfs-notify = { path = "./crates/vfs-notify", version = "0.0.0" }
 vfs = { path = "./crates/vfs", version = "0.0.0" }
 edition = { path = "./crates/edition", version = "0.0.0" }
 
-ra-ap-rustc_lexer = { version = "0.122", default-features = false }
+ra-ap-rustc_lexer = { version = "0.123", default-features = false }
 ra-ap-rustc_parse_format = { version = "0.121", default-features = false }
-ra-ap-rustc_index = { version = "0.122", default-features = false }
-ra-ap-rustc_abi = { version = "0.122", default-features = false }
-ra-ap-rustc_pattern_analysis = { version = "0.122", default-features = false }
+ra-ap-rustc_index = { version = "0.123", default-features = false }
+ra-ap-rustc_abi = { version = "0.123", default-features = false }
+ra-ap-rustc_pattern_analysis = { version = "0.123", default-features = false }
 
 # local crates that aren't published to crates.io. These should not have versions.
 
@@ -156,6 +156,7 @@ smallvec = { version = "1.15.1", features = [
   "const_generics",
 ] }
 smol_str = "0.3.2"
+temp-dir = "0.1.16"
 text-size = "1.1.1"
 tracing = "0.1.41"
 tracing-tree = "0.4.0"
diff --git a/src/tools/rust-analyzer/crates/base-db/src/input.rs b/src/tools/rust-analyzer/crates/base-db/src/input.rs
index 8c9393bcc93..0bf4fbdfbd6 100644
--- a/src/tools/rust-analyzer/crates/base-db/src/input.rs
+++ b/src/tools/rust-analyzer/crates/base-db/src/input.rs
@@ -30,6 +30,7 @@ pub type ProcMacroPaths =
 pub enum ProcMacroLoadingError {
     Disabled,
     FailedToBuild,
+    ExpectedProcMacroArtifact,
     MissingDylibPath,
     NotYetBuilt,
     NoProcMacros,
@@ -39,7 +40,8 @@ impl ProcMacroLoadingError {
     pub fn is_hard_error(&self) -> bool {
         match self {
             ProcMacroLoadingError::Disabled | ProcMacroLoadingError::NotYetBuilt => false,
-            ProcMacroLoadingError::FailedToBuild
+            ProcMacroLoadingError::ExpectedProcMacroArtifact
+            | ProcMacroLoadingError::FailedToBuild
             | ProcMacroLoadingError::MissingDylibPath
             | ProcMacroLoadingError::NoProcMacros
             | ProcMacroLoadingError::ProcMacroSrvError(_) => true,
@@ -51,10 +53,16 @@ impl Error for ProcMacroLoadingError {}
 impl fmt::Display for ProcMacroLoadingError {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
+            ProcMacroLoadingError::ExpectedProcMacroArtifact => {
+                write!(f, "proc-macro crate did not build proc-macro artifact")
+            }
             ProcMacroLoadingError::Disabled => write!(f, "proc-macro expansion is disabled"),
             ProcMacroLoadingError::FailedToBuild => write!(f, "proc-macro failed to build"),
             ProcMacroLoadingError::MissingDylibPath => {
-                write!(f, "proc-macro crate build data is missing a dylib path")
+                write!(
+                    f,
+                    "proc-macro crate built but the dylib path is missing, this indicates a problem with your build system."
+                )
             }
             ProcMacroLoadingError::NotYetBuilt => write!(f, "proc-macro not yet built"),
             ProcMacroLoadingError::NoProcMacros => {
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs
index d3dfc05eb29..5695ab7ed00 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs
@@ -16,7 +16,7 @@ use std::{
 
 use cfg::{CfgExpr, CfgOptions};
 use either::Either;
-use hir_expand::{ExpandError, InFile, MacroCallId, mod_path::ModPath, name::Name};
+use hir_expand::{InFile, MacroCallId, mod_path::ModPath, name::Name};
 use la_arena::{Arena, ArenaMap};
 use rustc_hash::FxHashMap;
 use smallvec::SmallVec;
@@ -281,7 +281,6 @@ struct FormatTemplate {
 #[derive(Debug, Eq, PartialEq)]
 pub enum ExpressionStoreDiagnostics {
     InactiveCode { node: InFile<SyntaxNodePtr>, cfg: CfgExpr, opts: CfgOptions },
-    MacroError { node: InFile<MacroCallPtr>, err: ExpandError },
     UnresolvedMacroCall { node: InFile<MacroCallPtr>, path: ModPath },
     UnreachableLabel { node: InFile<AstPtr<ast::Lifetime>>, name: Name },
     AwaitOutsideOfAsync { node: InFile<AstPtr<ast::AwaitExpr>>, location: String },
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs
index 4e877748ca2..abd1382801d 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs
@@ -960,38 +960,29 @@ impl ExprCollector<'_> {
         impl_trait_lower_fn: ImplTraitLowerFn<'_>,
     ) -> TypeBound {
         match node.kind() {
-            ast::TypeBoundKind::PathType(path_type) => {
+            ast::TypeBoundKind::PathType(binder, path_type) => {
+                let binder = match binder.and_then(|it| it.generic_param_list()) {
+                    Some(gpl) => gpl
+                        .lifetime_params()
+                        .flat_map(|lp| lp.lifetime().map(|lt| Name::new_lifetime(&lt.text())))
+                        .collect(),
+                    None => ThinVec::default(),
+                };
                 let m = match node.question_mark_token() {
                     Some(_) => TraitBoundModifier::Maybe,
                     None => TraitBoundModifier::None,
                 };
                 self.lower_path_type(&path_type, impl_trait_lower_fn)
                     .map(|p| {
-                        TypeBound::Path(self.alloc_path(p, AstPtr::new(&path_type).upcast()), m)
+                        let path = self.alloc_path(p, AstPtr::new(&path_type).upcast());
+                        if binder.is_empty() {
+                            TypeBound::Path(path, m)
+                        } else {
+                            TypeBound::ForLifetime(binder, path)
+                        }
                     })
                     .unwrap_or(TypeBound::Error)
             }
-            ast::TypeBoundKind::ForType(for_type) => {
-                let lt_refs = match for_type.generic_param_list() {
-                    Some(gpl) => gpl
-                        .lifetime_params()
-                        .flat_map(|lp| lp.lifetime().map(|lt| Name::new_lifetime(&lt.text())))
-                        .collect(),
-                    None => ThinVec::default(),
-                };
-                let path = for_type.ty().and_then(|ty| match &ty {
-                    ast::Type::PathType(path_type) => {
-                        self.lower_path_type(path_type, impl_trait_lower_fn).map(|p| (p, ty))
-                    }
-                    _ => None,
-                });
-                match path {
-                    Some((p, ty)) => {
-                        TypeBound::ForLifetime(lt_refs, self.alloc_path(p, AstPtr::new(&ty)))
-                    }
-                    None => TypeBound::Error,
-                }
-            }
             ast::TypeBoundKind::Use(gal) => TypeBound::Use(
                 gal.use_bound_generic_args()
                     .map(|p| match p {
@@ -1981,13 +1972,7 @@ impl ExprCollector<'_> {
                 return collector(self, None);
             }
         };
-        if record_diagnostics {
-            if let Some(err) = res.err {
-                self.store
-                    .diagnostics
-                    .push(ExpressionStoreDiagnostics::MacroError { node: macro_call_ptr, err });
-            }
-        }
+        // No need to push macro and parsing errors as they'll be recreated from `macro_calls()`.
 
         match res.value {
             Some((mark, expansion)) => {
@@ -1997,10 +1982,6 @@ impl ExprCollector<'_> {
                     self.store.expansions.insert(macro_call_ptr, macro_file);
                 }
 
-                if record_diagnostics {
-                    // FIXME: Report parse errors here
-                }
-
                 let id = collector(self, expansion.map(|it| it.tree()));
                 self.expander.exit(mark);
                 id
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/generics.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/generics.rs
index 02a1d274fb5..c570df42b2f 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/generics.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/generics.rs
@@ -180,17 +180,18 @@ impl GenericParamsCollector {
                 continue;
             };
 
-            let lifetimes: Option<Box<_>> = pred.generic_param_list().map(|param_list| {
-                // Higher-Ranked Trait Bounds
-                param_list
-                    .lifetime_params()
-                    .map(|lifetime_param| {
-                        lifetime_param
-                            .lifetime()
-                            .map_or_else(Name::missing, |lt| Name::new_lifetime(&lt.text()))
-                    })
-                    .collect()
-            });
+            let lifetimes: Option<Box<_>> =
+                pred.for_binder().and_then(|it| it.generic_param_list()).map(|param_list| {
+                    // Higher-Ranked Trait Bounds
+                    param_list
+                        .lifetime_params()
+                        .map(|lifetime_param| {
+                            lifetime_param
+                                .lifetime()
+                                .map_or_else(Name::missing, |lt| Name::new_lifetime(&lt.text()))
+                        })
+                        .collect()
+                });
             for bound in pred.type_bound_list().iter().flat_map(|l| l.bounds()) {
                 self.lower_type_bound_as_predicate(ec, bound, lifetimes.as_deref(), target);
             }
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/path.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/path.rs
index 19c7ce0ce04..55e738b58bd 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/path.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/path.rs
@@ -27,7 +27,7 @@ pub enum Path {
 }
 
 // This type is being used a lot, make sure it doesn't grow unintentionally.
-#[cfg(target_arch = "x86_64")]
+#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
 const _: () = {
     assert!(size_of::<Path>() == 24);
     assert!(size_of::<Option<Path>>() == 24);
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/hir/type_ref.rs b/src/tools/rust-analyzer/crates/hir-def/src/hir/type_ref.rs
index eacc3f3cedf..da0f058a9cb 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/hir/type_ref.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/hir/type_ref.rs
@@ -148,7 +148,7 @@ pub enum TypeRef {
     Error,
 }
 
-#[cfg(target_arch = "x86_64")]
+#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
 const _: () = assert!(size_of::<TypeRef>() == 24);
 
 pub type TypeRefId = Idx<TypeRef>;
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/expr.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/expr.rs
index 5ae6bf6dffd..cc531f076dd 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/expr.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/expr.rs
@@ -175,8 +175,9 @@ impl ExprValidator {
                 });
             }
 
-            let receiver_ty = self.infer[*receiver].clone();
-            checker.prev_receiver_ty = Some(receiver_ty);
+            if let Some(receiver_ty) = self.infer.type_of_expr_with_adjust(*receiver) {
+                checker.prev_receiver_ty = Some(receiver_ty.clone());
+            }
         }
     }
 
@@ -187,7 +188,9 @@ impl ExprValidator {
         arms: &[MatchArm],
         db: &dyn HirDatabase,
     ) {
-        let scrut_ty = &self.infer[scrutinee_expr];
+        let Some(scrut_ty) = self.infer.type_of_expr_with_adjust(scrutinee_expr) else {
+            return;
+        };
         if scrut_ty.contains_unknown() {
             return;
         }
@@ -200,7 +203,7 @@ impl ExprValidator {
         // Note: Skipping the entire diagnostic rather than just not including a faulty match arm is
         // preferred to avoid the chance of false positives.
         for arm in arms {
-            let Some(pat_ty) = self.infer.type_of_pat.get(arm.pat) else {
+            let Some(pat_ty) = self.infer.type_of_pat_with_adjust(arm.pat) else {
                 return;
             };
             if pat_ty.contains_unknown() {
@@ -328,7 +331,7 @@ impl ExprValidator {
                 continue;
             }
             let Some(initializer) = initializer else { continue };
-            let ty = &self.infer[initializer];
+            let Some(ty) = self.infer.type_of_expr_with_adjust(initializer) else { continue };
             if ty.contains_unknown() {
                 continue;
             }
@@ -433,44 +436,44 @@ impl ExprValidator {
                     Statement::Expr { expr, .. } => Some(*expr),
                     _ => None,
                 });
-                if let Some(last_then_expr) = last_then_expr {
-                    let last_then_expr_ty = &self.infer[last_then_expr];
-                    if last_then_expr_ty.is_never() {
-                        // Only look at sources if the then branch diverges and we have an else branch.
-                        let source_map = db.body_with_source_map(self.owner).1;
-                        let Ok(source_ptr) = source_map.expr_syntax(id) else {
-                            return;
-                        };
-                        let root = source_ptr.file_syntax(db);
-                        let either::Left(ast::Expr::IfExpr(if_expr)) =
-                            source_ptr.value.to_node(&root)
-                        else {
+                if let Some(last_then_expr) = last_then_expr
+                    && let Some(last_then_expr_ty) =
+                        self.infer.type_of_expr_with_adjust(last_then_expr)
+                    && last_then_expr_ty.is_never()
+                {
+                    // Only look at sources if the then branch diverges and we have an else branch.
+                    let source_map = db.body_with_source_map(self.owner).1;
+                    let Ok(source_ptr) = source_map.expr_syntax(id) else {
+                        return;
+                    };
+                    let root = source_ptr.file_syntax(db);
+                    let either::Left(ast::Expr::IfExpr(if_expr)) = source_ptr.value.to_node(&root)
+                    else {
+                        return;
+                    };
+                    let mut top_if_expr = if_expr;
+                    loop {
+                        let parent = top_if_expr.syntax().parent();
+                        let has_parent_expr_stmt_or_stmt_list =
+                            parent.as_ref().is_some_and(|node| {
+                                ast::ExprStmt::can_cast(node.kind())
+                                    | ast::StmtList::can_cast(node.kind())
+                            });
+                        if has_parent_expr_stmt_or_stmt_list {
+                            // Only emit diagnostic if parent or direct ancestor is either
+                            // an expr stmt or a stmt list.
+                            break;
+                        }
+                        let Some(parent_if_expr) = parent.and_then(ast::IfExpr::cast) else {
+                            // Bail if parent is neither an if expr, an expr stmt nor a stmt list.
                             return;
                         };
-                        let mut top_if_expr = if_expr;
-                        loop {
-                            let parent = top_if_expr.syntax().parent();
-                            let has_parent_expr_stmt_or_stmt_list =
-                                parent.as_ref().is_some_and(|node| {
-                                    ast::ExprStmt::can_cast(node.kind())
-                                        | ast::StmtList::can_cast(node.kind())
-                                });
-                            if has_parent_expr_stmt_or_stmt_list {
-                                // Only emit diagnostic if parent or direct ancestor is either
-                                // an expr stmt or a stmt list.
-                                break;
-                            }
-                            let Some(parent_if_expr) = parent.and_then(ast::IfExpr::cast) else {
-                                // Bail if parent is neither an if expr, an expr stmt nor a stmt list.
-                                return;
-                            };
-                            // Check parent if expr.
-                            top_if_expr = parent_if_expr;
-                        }
-
-                        self.diagnostics
-                            .push(BodyValidationDiagnostic::RemoveUnnecessaryElse { if_expr: id })
+                        // Check parent if expr.
+                        top_if_expr = parent_if_expr;
                     }
+
+                    self.diagnostics
+                        .push(BodyValidationDiagnostic::RemoveUnnecessaryElse { if_expr: id })
                 }
             }
         }
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs
index e880438e3a7..7c39afa0ef8 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs
@@ -561,6 +561,32 @@ impl InferenceResult {
             ExprOrPatId::PatId(id) => self.type_of_pat.get(id),
         }
     }
+    pub fn type_of_expr_with_adjust(&self, id: ExprId) -> Option<&Ty> {
+        match self.expr_adjustments.get(&id).and_then(|adjustments| {
+            adjustments
+                .iter()
+                .filter(|adj| {
+                    // https://github.com/rust-lang/rust/blob/67819923ac8ea353aaa775303f4c3aacbf41d010/compiler/rustc_mir_build/src/thir/cx/expr.rs#L140
+                    !matches!(
+                        adj,
+                        Adjustment {
+                            kind: Adjust::NeverToAny,
+                            target,
+                        } if target.is_never()
+                    )
+                })
+                .next_back()
+        }) {
+            Some(adjustment) => Some(&adjustment.target),
+            None => self.type_of_expr.get(id),
+        }
+    }
+    pub fn type_of_pat_with_adjust(&self, id: PatId) -> Option<&Ty> {
+        match self.pat_adjustments.get(&id).and_then(|adjustments| adjustments.last()) {
+            adjusted @ Some(_) => adjusted,
+            None => self.type_of_pat.get(id),
+        }
+    }
     pub fn is_erroneous(&self) -> bool {
         self.has_errors && self.type_of_expr.iter().count() == 0
     }
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs
index 372a9dfc43d..3f310c26ec1 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs
@@ -3,9 +3,9 @@
 use std::{cmp, ops::Bound};
 
 use hir_def::{
+    AdtId, VariantId,
     layout::{Integer, ReprOptions, TargetDataLayout},
     signatures::{StructFlags, VariantFields},
-    AdtId, VariantId,
 };
 use intern::sym;
 use rustc_index::IndexVec;
@@ -13,9 +13,9 @@ use smallvec::SmallVec;
 use triomphe::Arc;
 
 use crate::{
-    db::HirDatabase,
-    layout::{field_ty, Layout, LayoutError},
     Substitution, TraitEnvironment,
+    db::HirDatabase,
+    layout::{Layout, LayoutError, field_ty},
 };
 
 use super::LayoutCx;
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs
index f32b6af4d85..d61e7de6672 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs
@@ -590,9 +590,14 @@ impl<'a> TyLoweringContext<'a> {
                         .resolve_trait(ctx.ty_ctx().db, ctx.ty_ctx().resolver.krate());
                     let pointee_sized = LangItem::PointeeSized
                         .resolve_trait(ctx.ty_ctx().db, ctx.ty_ctx().resolver.krate());
-                    if meta_sized.is_some_and(|it| it == trait_ref.hir_trait_id()) {
+                    let destruct = LangItem::Destruct
+                        .resolve_trait(ctx.ty_ctx().db, ctx.ty_ctx().resolver.krate());
+                    let hir_trait_id = trait_ref.hir_trait_id();
+                    if meta_sized.is_some_and(|it| it == hir_trait_id)
+                        || destruct.is_some_and(|it| it == hir_trait_id)
+                    {
                         // Ignore this bound
-                    } else if pointee_sized.is_some_and(|it| it == trait_ref.hir_trait_id()) {
+                    } else if pointee_sized.is_some_and(|it| it == hir_trait_id) {
                         // Regard this as `?Sized` bound
                         ctx.ty_ctx().unsized_types.insert(self_ty);
                     } else {
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs
index 238753e12e4..c4c17a93c9c 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs
@@ -2349,3 +2349,37 @@ fn test() {
         "#]],
     );
 }
+
+#[test]
+fn rust_destruct_option_clone() {
+    check_types(
+        r#"
+//- minicore: option, drop
+fn test(o: &Option<i32>) {
+    o.my_clone();
+  //^^^^^^^^^^^^ Option<i32>
+}
+pub trait MyClone: Sized {
+    fn my_clone(&self) -> Self;
+}
+impl<T> const MyClone for Option<T>
+where
+    T: ~const MyClone + ~const Destruct,
+{
+    fn my_clone(&self) -> Self {
+        match self {
+            Some(x) => Some(x.my_clone()),
+            None => None,
+        }
+    }
+}
+impl const MyClone for i32 {
+    fn my_clone(&self) -> Self {
+        *self
+    }
+}
+#[lang = "destruct"]
+pub trait Destruct {}
+"#,
+    );
+}
diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs
index 1b2b76999f7..4ddb04b24f7 100644
--- a/src/tools/rust-analyzer/crates/hir/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs
@@ -1922,10 +1922,6 @@ impl DefWithBody {
             Module { id: def_map.module_id(DefMap::ROOT) }.diagnostics(db, acc, style_lints);
         }
 
-        source_map
-            .macro_calls()
-            .for_each(|(_ast_id, call_id)| macro_call_diagnostics(db, call_id, acc));
-
         expr_store_diagnostics(db, acc, &source_map);
 
         let infer = db.infer(self.into());
@@ -2130,9 +2126,9 @@ impl DefWithBody {
     }
 }
 
-fn expr_store_diagnostics(
-    db: &dyn HirDatabase,
-    acc: &mut Vec<AnyDiagnostic<'_>>,
+fn expr_store_diagnostics<'db>(
+    db: &'db dyn HirDatabase,
+    acc: &mut Vec<AnyDiagnostic<'db>>,
     source_map: &ExpressionStoreSourceMap,
 ) {
     for diag in source_map.diagnostics() {
@@ -2140,30 +2136,6 @@ fn expr_store_diagnostics(
             ExpressionStoreDiagnostics::InactiveCode { node, cfg, opts } => {
                 InactiveCode { node: *node, cfg: cfg.clone(), opts: opts.clone() }.into()
             }
-            ExpressionStoreDiagnostics::MacroError { node, err } => {
-                let RenderedExpandError { message, error, kind } = err.render_to_string(db);
-
-                let editioned_file_id = EditionedFileId::from_span(db, err.span().anchor.file_id);
-                let precise_location = if editioned_file_id == node.file_id {
-                    Some(
-                        err.span().range
-                            + db.ast_id_map(editioned_file_id.into())
-                                .get_erased(err.span().anchor.ast_id)
-                                .text_range()
-                                .start(),
-                    )
-                } else {
-                    None
-                };
-                MacroError {
-                    node: (node).map(|it| it.into()),
-                    precise_location,
-                    message,
-                    error,
-                    kind,
-                }
-                .into()
-            }
             ExpressionStoreDiagnostics::UnresolvedMacroCall { node, path } => UnresolvedMacroCall {
                 macro_call: (*node).map(|ast_ptr| ast_ptr.into()),
                 precise_location: None,
@@ -2182,6 +2154,10 @@ fn expr_store_diagnostics(
             }
         });
     }
+
+    source_map
+        .macro_calls()
+        .for_each(|(_ast_id, call_id)| macro_call_diagnostics(db, call_id, acc));
 }
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 pub struct Function {
diff --git a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs
index ecc6e5f3d03..0b554a9d4e3 100644
--- a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs
+++ b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs
@@ -441,7 +441,7 @@ impl<'db> SourceAnalyzer<'db> {
     ) -> Option<GenericSubstitution<'db>> {
         let body = self.store()?;
         if let Expr::Field { expr: object_expr, name: _ } = body[field_expr] {
-            let (adt, subst) = type_of_expr_including_adjust(infer, object_expr)?.as_adt()?;
+            let (adt, subst) = infer.type_of_expr_with_adjust(object_expr)?.as_adt()?;
             return Some(GenericSubstitution::new(
                 adt.into(),
                 subst.clone(),
@@ -1780,10 +1780,3 @@ pub(crate) fn name_hygiene(db: &dyn HirDatabase, name: InFile<&SyntaxNode>) -> H
     let ctx = span_map.span_at(name.value.text_range().start()).ctx;
     HygieneId::new(ctx.opaque_and_semitransparent(db))
 }
-
-fn type_of_expr_including_adjust(infer: &InferenceResult, id: ExprId) -> Option<&Ty> {
-    match infer.expr_adjustment(id).and_then(|adjustments| adjustments.last()) {
-        Some(adjustment) => Some(&adjustment.target),
-        None => Some(&infer[id]),
-    }
-}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs
index 9f9d21923ff..ab183ac7089 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs
@@ -2,6 +2,7 @@ use hir::HasSource;
 use syntax::{
     Edition,
     ast::{self, AstNode, make},
+    syntax_editor::{Position, SyntaxEditor},
 };
 
 use crate::{
@@ -147,45 +148,78 @@ fn add_missing_impl_members_inner(
 
     let target = impl_def.syntax().text_range();
     acc.add(AssistId::quick_fix(assist_id), label, target, |edit| {
-        let new_impl_def = edit.make_mut(impl_def.clone());
-        let first_new_item = add_trait_assoc_items_to_impl(
+        let new_item = add_trait_assoc_items_to_impl(
             &ctx.sema,
             ctx.config,
             &missing_items,
             trait_,
-            &new_impl_def,
+            &impl_def,
             &target_scope,
         );
 
+        let Some((first_new_item, other_items)) = new_item.split_first() else {
+            return;
+        };
+
+        let mut first_new_item = if let DefaultMethods::No = mode
+            && let ast::AssocItem::Fn(func) = &first_new_item
+            && let Some(body) = try_gen_trait_body(
+                ctx,
+                func,
+                trait_ref,
+                &impl_def,
+                target_scope.krate().edition(ctx.sema.db),
+            )
+            && let Some(func_body) = func.body()
+        {
+            let mut func_editor = SyntaxEditor::new(first_new_item.syntax().clone_subtree());
+            func_editor.replace(func_body.syntax(), body.syntax());
+            ast::AssocItem::cast(func_editor.finish().new_root().clone())
+        } else {
+            Some(first_new_item.clone())
+        };
+
+        let new_assoc_items = first_new_item
+            .clone()
+            .into_iter()
+            .chain(other_items.iter().cloned())
+            .map(either::Either::Right)
+            .collect::<Vec<_>>();
+
+        let mut editor = edit.make_editor(impl_def.syntax());
+        if let Some(assoc_item_list) = impl_def.assoc_item_list() {
+            let items = new_assoc_items.into_iter().filter_map(either::Either::right).collect();
+            assoc_item_list.add_items(&mut editor, items);
+        } else {
+            let assoc_item_list = make::assoc_item_list(Some(new_assoc_items)).clone_for_update();
+            editor.insert_all(
+                Position::after(impl_def.syntax()),
+                vec![make::tokens::whitespace(" ").into(), assoc_item_list.syntax().clone().into()],
+            );
+            first_new_item = assoc_item_list.assoc_items().next();
+        }
+
         if let Some(cap) = ctx.config.snippet_cap {
             let mut placeholder = None;
             if let DefaultMethods::No = mode {
-                if let ast::AssocItem::Fn(func) = &first_new_item {
-                    if try_gen_trait_body(
-                        ctx,
-                        func,
-                        trait_ref,
-                        &impl_def,
-                        target_scope.krate().edition(ctx.sema.db),
-                    )
-                    .is_none()
+                if let Some(ast::AssocItem::Fn(func)) = &first_new_item {
+                    if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast)
+                        && m.syntax().text() == "todo!()"
                     {
-                        if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast)
-                        {
-                            if m.syntax().text() == "todo!()" {
-                                placeholder = Some(m);
-                            }
-                        }
+                        placeholder = Some(m);
                     }
                 }
             }
 
             if let Some(macro_call) = placeholder {
-                edit.add_placeholder_snippet(cap, macro_call);
-            } else {
-                edit.add_tabstop_before(cap, first_new_item);
+                let placeholder = edit.make_placeholder_snippet(cap);
+                editor.add_annotation(macro_call.syntax(), placeholder);
+            } else if let Some(first_new_item) = first_new_item {
+                let tabstop = edit.make_tabstop_before(cap);
+                editor.add_annotation(first_new_item.syntax(), tabstop);
             };
         };
+        edit.add_file_edits(ctx.vfs_file_id(), editor);
     })
 }
 
@@ -195,7 +229,7 @@ fn try_gen_trait_body(
     trait_ref: hir::TraitRef<'_>,
     impl_def: &ast::Impl,
     edition: Edition,
-) -> Option<()> {
+) -> Option<ast::BlockExpr> {
     let trait_path = make::ext::ident_path(
         &trait_ref.trait_().name(ctx.db()).display(ctx.db(), edition).to_string(),
     );
@@ -322,7 +356,7 @@ impl Foo for S {
     }
 
     #[test]
-    fn test_impl_def_without_braces() {
+    fn test_impl_def_without_braces_macro() {
         check_assist(
             add_missing_impl_members,
             r#"
@@ -341,6 +375,33 @@ impl Foo for S {
     }
 
     #[test]
+    fn test_impl_def_without_braces_tabstop_first_item() {
+        check_assist(
+            add_missing_impl_members,
+            r#"
+trait Foo {
+    type Output;
+    fn foo(&self);
+}
+struct S;
+impl Foo for S { $0 }"#,
+            r#"
+trait Foo {
+    type Output;
+    fn foo(&self);
+}
+struct S;
+impl Foo for S {
+    $0type Output;
+
+    fn foo(&self) {
+        todo!()
+    }
+}"#,
+        );
+    }
+
+    #[test]
     fn fill_in_type_params_1() {
         check_assist(
             add_missing_impl_members,
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs
index bcd06c1ef72..d7b7e8d9cad 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs
@@ -228,8 +228,7 @@ pub(crate) fn convert_bool_then_to_if(acc: &mut Assists, ctx: &AssistContext<'_>
                     closure_body,
                     Some(ast::ElseBranch::Block(make.block_expr(None, Some(none_path)))),
                 )
-                .indent(mcall.indent_level())
-                .clone_for_update();
+                .indent(mcall.indent_level());
             editor.replace(mcall.syntax().clone(), if_expr.syntax().clone());
 
             editor.add_mappings(make.finish_with_mappings());
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs
index 71a61f2db00..2ea032fb62b 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs
@@ -13,7 +13,6 @@ use syntax::{
         edit::{AstNodeEdit, IndentLevel},
         make,
     },
-    ted,
 };
 
 use crate::{
@@ -117,7 +116,7 @@ fn if_expr_to_guarded_return(
 
     then_block.syntax().last_child_or_token().filter(|t| t.kind() == T!['}'])?;
 
-    let then_block_items = then_block.dedent(IndentLevel(1)).clone_for_update();
+    let then_block_items = then_block.dedent(IndentLevel(1));
 
     let end_of_then = then_block_items.syntax().last_child_or_token()?;
     let end_of_then = if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
@@ -132,7 +131,6 @@ fn if_expr_to_guarded_return(
         "Convert to guarded return",
         target,
         |edit| {
-            let if_expr = edit.make_mut(if_expr);
             let if_indent_level = IndentLevel::from_node(if_expr.syntax());
             let replacement = match if_let_pat {
                 None => {
@@ -143,7 +141,7 @@ fn if_expr_to_guarded_return(
                         let cond = invert_boolean_expression_legacy(cond_expr);
                         make::expr_if(cond, then_branch, None).indent(if_indent_level)
                     };
-                    new_expr.syntax().clone_for_update()
+                    new_expr.syntax().clone()
                 }
                 Some(pat) => {
                     // If-let.
@@ -154,7 +152,7 @@ fn if_expr_to_guarded_return(
                         ast::make::tail_only_block_expr(early_expression),
                     );
                     let let_else_stmt = let_else_stmt.indent(if_indent_level);
-                    let_else_stmt.syntax().clone_for_update()
+                    let_else_stmt.syntax().clone()
                 }
             };
 
@@ -168,8 +166,9 @@ fn if_expr_to_guarded_return(
                         .take_while(|i| *i != end_of_then),
                 )
                 .collect();
-
-            ted::replace_with_many(if_expr.syntax(), then_statements)
+            let mut editor = edit.make_editor(if_expr.syntax());
+            editor.replace_with_many(if_expr.syntax(), then_statements);
+            edit.add_file_edits(ctx.vfs_file_id(), editor);
         },
     )
 }
@@ -214,7 +213,6 @@ fn let_stmt_to_guarded_return(
         "Convert to guarded return",
         target,
         |edit| {
-            let let_stmt = edit.make_mut(let_stmt);
             let let_indent_level = IndentLevel::from_node(let_stmt.syntax());
 
             let replacement = {
@@ -225,10 +223,11 @@ fn let_stmt_to_guarded_return(
                     ast::make::tail_only_block_expr(early_expression),
                 );
                 let let_else_stmt = let_else_stmt.indent(let_indent_level);
-                let_else_stmt.syntax().clone_for_update()
+                let_else_stmt.syntax().clone()
             };
-
-            ted::replace(let_stmt.syntax(), replacement)
+            let mut editor = edit.make_editor(let_stmt.syntax());
+            editor.replace(let_stmt.syntax(), replacement);
+            edit.add_file_edits(ctx.vfs_file_id(), editor);
         },
     )
 }
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs
index 54699a9454f..cdc0e967101 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs
@@ -8,8 +8,7 @@ use syntax::{
     AstNode, AstToken, NodeOrToken,
     SyntaxKind::WHITESPACE,
     T,
-    ast::{self, make},
-    ted,
+    ast::{self, make, syntax_factory::SyntaxFactory},
 };
 
 // Assist: extract_expressions_from_format_string
@@ -58,8 +57,6 @@ pub(crate) fn extract_expressions_from_format_string(
         "Extract format expressions",
         tt.syntax().text_range(),
         |edit| {
-            let tt = edit.make_mut(tt);
-
             // Extract existing arguments in macro
             let tokens = tt.token_trees_and_tokens().collect_vec();
 
@@ -131,8 +128,10 @@ pub(crate) fn extract_expressions_from_format_string(
             }
 
             // Insert new args
-            let new_tt = make::token_tree(tt_delimiter, new_tt_bits).clone_for_update();
-            ted::replace(tt.syntax(), new_tt.syntax());
+            let make = SyntaxFactory::with_mappings();
+            let new_tt = make.token_tree(tt_delimiter, new_tt_bits);
+            let mut editor = edit.make_editor(tt.syntax());
+            editor.replace(tt.syntax(), new_tt.syntax());
 
             if let Some(cap) = ctx.config.snippet_cap {
                 // Add placeholder snippets over placeholder args
@@ -145,15 +144,19 @@ pub(crate) fn extract_expressions_from_format_string(
                     };
 
                     if stdx::always!(placeholder.kind() == T![_]) {
-                        edit.add_placeholder_snippet_token(cap, placeholder);
+                        let annotation = edit.make_placeholder_snippet(cap);
+                        editor.add_annotation(placeholder, annotation);
                     }
                 }
 
                 // Add the final tabstop after the format literal
                 if let Some(NodeOrToken::Token(literal)) = new_tt.token_trees_and_tokens().nth(1) {
-                    edit.add_tabstop_after_token(cap, literal);
+                    let annotation = edit.make_tabstop_after(cap);
+                    editor.add_annotation(literal, annotation);
                 }
             }
+            editor.add_mappings(make.finish_with_mappings());
+            edit.add_file_edits(ctx.vfs_file_id(), editor);
         },
     );
 
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs
index b9c42285d25..9095b1825f5 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -16,8 +16,9 @@ use syntax::{
     SyntaxKind::*,
     SyntaxNode, T,
     ast::{
-        self, AstNode, HasAttrs, HasGenericParams, HasName, HasVisibility, edit::IndentLevel,
-        edit_in_place::Indent, make,
+        self, AstNode, HasAttrs, HasGenericParams, HasName, HasVisibility,
+        edit::{AstNodeEdit, IndentLevel},
+        make,
     },
     match_ast, ted,
 };
@@ -110,20 +111,30 @@ pub(crate) fn extract_struct_from_enum_variant(
             let generics = generic_params.as_ref().map(|generics| generics.clone_for_update());
 
             // resolve GenericArg in field_list to actual type
-            let field_list = field_list.clone_for_update();
-            if let Some((target_scope, source_scope)) =
+            let field_list = if let Some((target_scope, source_scope)) =
                 ctx.sema.scope(enum_ast.syntax()).zip(ctx.sema.scope(field_list.syntax()))
             {
-                PathTransform::generic_transformation(&target_scope, &source_scope)
-                    .apply(field_list.syntax());
-            }
+                let field_list = field_list.reset_indent();
+                let field_list =
+                    PathTransform::generic_transformation(&target_scope, &source_scope)
+                        .apply(field_list.syntax());
+                match_ast! {
+                    match field_list {
+                        ast::RecordFieldList(field_list) => Either::Left(field_list),
+                        ast::TupleFieldList(field_list) => Either::Right(field_list),
+                        _ => unreachable!(),
+                    }
+                }
+            } else {
+                field_list.clone_for_update()
+            };
 
             let def =
                 create_struct_def(variant_name.clone(), &variant, &field_list, generics, &enum_ast);
 
             let enum_ast = variant.parent_enum();
             let indent = enum_ast.indent_level();
-            def.reindent_to(indent);
+            let def = def.indent(indent);
 
             ted::insert_all(
                 ted::Position::before(enum_ast.syntax()),
@@ -279,7 +290,7 @@ fn create_struct_def(
             field_list.clone().into()
         }
     };
-    field_list.reindent_to(IndentLevel::single());
+    let field_list = field_list.indent(IndentLevel::single());
 
     let strukt = make::struct_(enum_vis, name, generics, field_list).clone_for_update();
 
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs
index 31e84e9adcf..db2d316d58e 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs
@@ -7,7 +7,9 @@ use syntax::{
     NodeOrToken, SyntaxKind, SyntaxNode, T,
     algo::ancestors_at_offset,
     ast::{
-        self, AstNode, edit::IndentLevel, edit_in_place::Indent, make,
+        self, AstNode,
+        edit::{AstNodeEdit, IndentLevel},
+        make,
         syntax_factory::SyntaxFactory,
     },
     syntax_editor::Position,
@@ -253,12 +255,11 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
                             // `expr_replace` is a descendant of `to_wrap`, so we just replace it with `name_expr`.
                             editor.replace(expr_replace, name_expr.syntax());
                             make.block_expr([new_stmt], Some(to_wrap.clone()))
-                        };
+                        }
+                        // fixup indentation of block
+                        .indent_with_mapping(indent_to, &make);
 
                         editor.replace(to_wrap.syntax(), block.syntax());
-
-                        // fixup indentation of block
-                        block.indent(indent_to);
                     }
                 }
 
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs
index ca66cb69dcc..60638980760 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs
@@ -114,9 +114,13 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<'
                         let source_scope = ctx.sema.scope(v.syntax());
                         let target_scope = ctx.sema.scope(strukt.syntax());
                         if let (Some(s), Some(t)) = (source_scope, target_scope) {
-                            PathTransform::generic_transformation(&t, &s).apply(v.syntax());
+                            ast::Fn::cast(
+                                PathTransform::generic_transformation(&t, &s).apply(v.syntax()),
+                            )
+                            .unwrap_or(v)
+                        } else {
+                            v
                         }
-                        v
                     }
                     None => return,
                 };
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs
index 848c63810a4..e96250f3c50 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs
@@ -255,7 +255,6 @@ fn generate_impl(
     delegee: &Delegee,
     edition: Edition,
 ) -> Option<ast::Impl> {
-    let delegate: ast::Impl;
     let db = ctx.db();
     let ast_strukt = &strukt.strukt;
     let strukt_ty = make::ty_path(make::ext::ident_path(&strukt.name.to_string()));
@@ -266,7 +265,7 @@ fn generate_impl(
             let bound_def = ctx.sema.source(delegee.to_owned())?.value;
             let bound_params = bound_def.generic_param_list();
 
-            delegate = make::impl_trait(
+            let delegate = make::impl_trait(
                 delegee.is_unsafe(db),
                 bound_params.clone(),
                 bound_params.map(|params| params.to_generic_args()),
@@ -304,7 +303,7 @@ fn generate_impl(
             let target_scope = ctx.sema.scope(strukt.strukt.syntax())?;
             let source_scope = ctx.sema.scope(bound_def.syntax())?;
             let transform = PathTransform::generic_transformation(&target_scope, &source_scope);
-            transform.apply(delegate.syntax());
+            ast::Impl::cast(transform.apply(delegate.syntax()))
         }
         Delegee::Impls(trait_, old_impl) => {
             let old_impl = ctx.sema.source(old_impl.to_owned())?.value;
@@ -358,20 +357,28 @@ fn generate_impl(
 
             // 2.3) Instantiate generics with `transform_impl`, this step also
             // remove unused params.
-            let mut trait_gen_args = old_impl.trait_()?.generic_arg_list();
-            if let Some(trait_args) = &mut trait_gen_args {
-                *trait_args = trait_args.clone_for_update();
-                transform_impl(ctx, ast_strukt, &old_impl, &transform_args, trait_args.syntax())?;
-            }
+            let trait_gen_args = old_impl.trait_()?.generic_arg_list().and_then(|trait_args| {
+                let trait_args = &mut trait_args.clone_for_update();
+                if let Some(new_args) = transform_impl(
+                    ctx,
+                    ast_strukt,
+                    &old_impl,
+                    &transform_args,
+                    trait_args.clone_subtree(),
+                ) {
+                    *trait_args = new_args.clone_subtree();
+                    Some(new_args)
+                } else {
+                    None
+                }
+            });
 
             let type_gen_args = strukt_params.clone().map(|params| params.to_generic_args());
-
             let path_type =
                 make::ty(&trait_.name(db).display_no_db(edition).to_smolstr()).clone_for_update();
-            transform_impl(ctx, ast_strukt, &old_impl, &transform_args, path_type.syntax())?;
-
+            let path_type = transform_impl(ctx, ast_strukt, &old_impl, &transform_args, path_type)?;
             // 3) Generate delegate trait impl
-            delegate = make::impl_trait(
+            let delegate = make::impl_trait(
                 trait_.is_unsafe(db),
                 trait_gen_params,
                 trait_gen_args,
@@ -385,7 +392,6 @@ fn generate_impl(
                 None,
             )
             .clone_for_update();
-
             // Goto link : https://doc.rust-lang.org/reference/paths.html#qualified-paths
             let qualified_path_type =
                 make::path_from_text(&format!("<{} as {}>", field_ty, delegate.trait_()?));
@@ -398,7 +404,7 @@ fn generate_impl(
                 .filter(|item| matches!(item, AssocItem::MacroCall(_)).not())
             {
                 let item = item.clone_for_update();
-                transform_impl(ctx, ast_strukt, &old_impl, &transform_args, item.syntax())?;
+                let item = transform_impl(ctx, ast_strukt, &old_impl, &transform_args, item)?;
 
                 let assoc = process_assoc_item(item, qualified_path_type.clone(), field_name)?;
                 delegate_assoc_items.add_item(assoc);
@@ -408,19 +414,18 @@ fn generate_impl(
             if let Some(wc) = delegate.where_clause() {
                 remove_useless_where_clauses(&delegate.trait_()?, &delegate.self_ty()?, wc);
             }
+            Some(delegate)
         }
     }
-
-    Some(delegate)
 }
 
-fn transform_impl(
+fn transform_impl<N: ast::AstNode>(
     ctx: &AssistContext<'_>,
     strukt: &ast::Struct,
     old_impl: &ast::Impl,
     args: &Option<GenericArgList>,
-    syntax: &syntax::SyntaxNode,
-) -> Option<()> {
+    syntax: N,
+) -> Option<N> {
     let source_scope = ctx.sema.scope(old_impl.self_ty()?.syntax())?;
     let target_scope = ctx.sema.scope(strukt.syntax())?;
     let hir_old_impl = ctx.sema.to_impl_def(old_impl)?;
@@ -437,8 +442,7 @@ fn transform_impl(
         },
     );
 
-    transform.apply(syntax);
-    Some(())
+    N::cast(transform.apply(syntax.syntax()))
 }
 
 fn remove_instantiated_params(
@@ -570,9 +574,7 @@ where
     let scope = ctx.sema.scope(item.syntax())?;
 
     let transform = PathTransform::adt_transformation(&scope, &scope, hir_adt, args.clone());
-    transform.apply(item.syntax());
-
-    Some(item)
+    N::cast(transform.apply(item.syntax()))
 }
 
 fn has_self_type(trait_: hir::Trait, ctx: &AssistContext<'_>) -> Option<()> {
@@ -767,7 +769,7 @@ fn func_assoc_item(
     )
     .clone_for_update();
 
-    Some(AssocItem::Fn(func.indent(edit::IndentLevel(1)).clone_for_update()))
+    Some(AssocItem::Fn(func.indent(edit::IndentLevel(1))))
 }
 
 fn ty_assoc_item(item: syntax::ast::TypeAlias, qual_path_ty: Path) -> Option<AssocItem> {
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs
index 78ae815dc87..3290a70e1c6 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs
@@ -743,17 +743,30 @@ fn fn_generic_params(
     let where_preds: Vec<ast::WherePred> =
         where_preds.into_iter().map(|it| it.node.clone_for_update()).collect();
 
-    // 4. Rewrite paths
-    if let Some(param) = generic_params.first() {
-        let source_scope = ctx.sema.scope(param.syntax())?;
-        let target_scope = ctx.sema.scope(&target.parent())?;
-        if source_scope.module() != target_scope.module() {
+    let (generic_params, where_preds): (Vec<ast::GenericParam>, Vec<ast::WherePred>) =
+        if let Some(param) = generic_params.first()
+            && let source_scope = ctx.sema.scope(param.syntax())?
+            && let target_scope = ctx.sema.scope(&target.parent())?
+            && source_scope.module() != target_scope.module()
+        {
+            // 4. Rewrite paths
             let transform = PathTransform::generic_transformation(&target_scope, &source_scope);
             let generic_params = generic_params.iter().map(|it| it.syntax());
             let where_preds = where_preds.iter().map(|it| it.syntax());
-            transform.apply_all(generic_params.chain(where_preds));
-        }
-    }
+            transform
+                .apply_all(generic_params.chain(where_preds))
+                .into_iter()
+                .filter_map(|it| {
+                    if let Some(it) = ast::GenericParam::cast(it.clone()) {
+                        Some(either::Either::Left(it))
+                    } else {
+                        ast::WherePred::cast(it).map(either::Either::Right)
+                    }
+                })
+                .partition_map(|it| it)
+        } else {
+            (generic_params, where_preds)
+        };
 
     let generic_param_list = make::generic_param_list(generic_params);
     let where_clause =
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs
index 14601ca0207..31cadcf5ea8 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs
@@ -1,12 +1,17 @@
 use syntax::{
-    ast::{self, AstNode, HasName, edit_in_place::Indent, make},
+    ast::{self, AstNode, HasGenericParams, HasName, edit_in_place::Indent, make},
     syntax_editor::{Position, SyntaxEditor},
 };
 
-use crate::{AssistContext, AssistId, Assists, utils};
+use crate::{
+    AssistContext, AssistId, Assists,
+    utils::{self, DefaultMethods, IgnoreAssocItems},
+};
 
-fn insert_impl(editor: &mut SyntaxEditor, impl_: &ast::Impl, nominal: &ast::Adt) {
+fn insert_impl(editor: &mut SyntaxEditor, impl_: &ast::Impl, nominal: &impl Indent) {
     let indent = nominal.indent_level();
+
+    impl_.indent(indent);
     editor.insert_all(
         Position::after(nominal.syntax()),
         vec![
@@ -120,6 +125,126 @@ pub(crate) fn generate_trait_impl(acc: &mut Assists, ctx: &AssistContext<'_>) ->
     )
 }
 
+// Assist: generate_impl_trait
+//
+// Adds this trait impl for a type.
+//
+// ```
+// trait $0Foo {
+//     fn foo(&self) -> i32;
+// }
+// ```
+// ->
+// ```
+// trait Foo {
+//     fn foo(&self) -> i32;
+// }
+//
+// impl Foo for ${1:_} {
+//     fn foo(&self) -> i32 {
+//         $0todo!()
+//     }
+// }
+// ```
+pub(crate) fn generate_impl_trait(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+    let name = ctx.find_node_at_offset::<ast::Name>()?;
+    let trait_ = ast::Trait::cast(name.syntax().parent()?)?;
+    let target_scope = ctx.sema.scope(trait_.syntax())?;
+    let hir_trait = ctx.sema.to_def(&trait_)?;
+
+    let target = trait_.syntax().text_range();
+    acc.add(
+        AssistId::generate("generate_impl_trait"),
+        format!("Generate `{name}` impl for type"),
+        target,
+        |edit| {
+            let mut editor = edit.make_editor(trait_.syntax());
+
+            let holder_arg = ast::GenericArg::TypeArg(make::type_arg(make::ty_placeholder()));
+            let missing_items = utils::filter_assoc_items(
+                &ctx.sema,
+                &hir_trait.items(ctx.db()),
+                DefaultMethods::No,
+                IgnoreAssocItems::DocHiddenAttrPresent,
+            );
+
+            let trait_gen_args = trait_.generic_param_list().map(|list| {
+                make::generic_arg_list(list.generic_params().map(|_| holder_arg.clone()))
+            });
+
+            let make_impl_ = |body| {
+                make::impl_trait(
+                    trait_.unsafe_token().is_some(),
+                    None,
+                    trait_gen_args.clone(),
+                    None,
+                    None,
+                    false,
+                    make::ty(&name.text()),
+                    make::ty_placeholder(),
+                    None,
+                    None,
+                    body,
+                )
+                .clone_for_update()
+            };
+
+            let impl_ = if missing_items.is_empty() {
+                make_impl_(None)
+            } else {
+                let impl_ = make_impl_(None);
+                let assoc_items = utils::add_trait_assoc_items_to_impl(
+                    &ctx.sema,
+                    ctx.config,
+                    &missing_items,
+                    hir_trait,
+                    &impl_,
+                    &target_scope,
+                );
+                let assoc_items = assoc_items.into_iter().map(either::Either::Right).collect();
+                let assoc_item_list = make::assoc_item_list(Some(assoc_items));
+                make_impl_(Some(assoc_item_list))
+            };
+
+            if let Some(cap) = ctx.config.snippet_cap {
+                if let Some(generics) = impl_.trait_().and_then(|it| it.generic_arg_list()) {
+                    for generic in generics.generic_args() {
+                        let placeholder = edit.make_placeholder_snippet(cap);
+                        editor.add_annotation(generic.syntax(), placeholder);
+                    }
+                }
+
+                if let Some(ty) = impl_.self_ty() {
+                    let placeholder = edit.make_placeholder_snippet(cap);
+                    editor.add_annotation(ty.syntax(), placeholder);
+                }
+
+                if let Some(expr) =
+                    impl_.assoc_item_list().and_then(|it| it.assoc_items().find_map(extract_expr))
+                {
+                    let tabstop = edit.make_tabstop_before(cap);
+                    editor.add_annotation(expr.syntax(), tabstop);
+                } else if let Some(l_curly) =
+                    impl_.assoc_item_list().and_then(|it| it.l_curly_token())
+                {
+                    let tabstop = edit.make_tabstop_after(cap);
+                    editor.add_annotation(l_curly, tabstop);
+                }
+            }
+
+            insert_impl(&mut editor, &impl_, &trait_);
+            edit.add_file_edits(ctx.vfs_file_id(), editor);
+        },
+    )
+}
+
+fn extract_expr(item: ast::AssocItem) -> Option<ast::Expr> {
+    let ast::AssocItem::Fn(f) = item else {
+        return None;
+    };
+    f.body()?.tail_expr()
+}
+
 #[cfg(test)]
 mod tests {
     use crate::tests::{check_assist, check_assist_target};
@@ -492,4 +617,209 @@ mod tests {
             "#,
         );
     }
+
+    #[test]
+    fn test_add_impl_trait() {
+        check_assist(
+            generate_impl_trait,
+            r#"
+                trait $0Foo {
+                    fn foo(&self) -> i32;
+
+                    fn bar(&self) -> i32 {
+                        self.foo()
+                    }
+                }
+            "#,
+            r#"
+                trait Foo {
+                    fn foo(&self) -> i32;
+
+                    fn bar(&self) -> i32 {
+                        self.foo()
+                    }
+                }
+
+                impl Foo for ${1:_} {
+                    fn foo(&self) -> i32 {
+                        $0todo!()
+                    }
+                }
+            "#,
+        );
+    }
+
+    #[test]
+    fn test_add_impl_trait_use_generic() {
+        check_assist(
+            generate_impl_trait,
+            r#"
+                trait $0Foo<T> {
+                    fn foo(&self) -> T;
+
+                    fn bar(&self) -> T {
+                        self.foo()
+                    }
+                }
+            "#,
+            r#"
+                trait Foo<T> {
+                    fn foo(&self) -> T;
+
+                    fn bar(&self) -> T {
+                        self.foo()
+                    }
+                }
+
+                impl Foo<${1:_}> for ${2:_} {
+                    fn foo(&self) -> _ {
+                        $0todo!()
+                    }
+                }
+            "#,
+        );
+        check_assist(
+            generate_impl_trait,
+            r#"
+                trait $0Foo<T, U> {
+                    fn foo(&self) -> T;
+
+                    fn bar(&self) -> T {
+                        self.foo()
+                    }
+                }
+            "#,
+            r#"
+                trait Foo<T, U> {
+                    fn foo(&self) -> T;
+
+                    fn bar(&self) -> T {
+                        self.foo()
+                    }
+                }
+
+                impl Foo<${1:_}, ${2:_}> for ${3:_} {
+                    fn foo(&self) -> _ {
+                        $0todo!()
+                    }
+                }
+            "#,
+        );
+    }
+
+    #[test]
+    fn test_add_impl_trait_docs() {
+        check_assist(
+            generate_impl_trait,
+            r#"
+                /// foo
+                trait $0Foo {
+                    /// foo method
+                    fn foo(&self) -> i32;
+
+                    fn bar(&self) -> i32 {
+                        self.foo()
+                    }
+                }
+            "#,
+            r#"
+                /// foo
+                trait Foo {
+                    /// foo method
+                    fn foo(&self) -> i32;
+
+                    fn bar(&self) -> i32 {
+                        self.foo()
+                    }
+                }
+
+                impl Foo for ${1:_} {
+                    fn foo(&self) -> i32 {
+                        $0todo!()
+                    }
+                }
+            "#,
+        );
+    }
+
+    #[test]
+    fn test_add_impl_trait_assoc_types() {
+        check_assist(
+            generate_impl_trait,
+            r#"
+                trait $0Foo {
+                    type Output;
+
+                    fn foo(&self) -> Self::Output;
+                }
+            "#,
+            r#"
+                trait Foo {
+                    type Output;
+
+                    fn foo(&self) -> Self::Output;
+                }
+
+                impl Foo for ${1:_} {
+                    type Output;
+
+                    fn foo(&self) -> Self::Output {
+                        $0todo!()
+                    }
+                }
+            "#,
+        );
+    }
+
+    #[test]
+    fn test_add_impl_trait_indent() {
+        check_assist(
+            generate_impl_trait,
+            r#"
+                mod foo {
+                    mod bar {
+                        trait $0Foo {
+                            type Output;
+
+                            fn foo(&self) -> Self::Output;
+                        }
+                    }
+                }
+            "#,
+            r#"
+                mod foo {
+                    mod bar {
+                        trait Foo {
+                            type Output;
+
+                            fn foo(&self) -> Self::Output;
+                        }
+
+                        impl Foo for ${1:_} {
+                            type Output;
+
+                            fn foo(&self) -> Self::Output {
+                                $0todo!()
+                            }
+                        }
+                    }
+                }
+            "#,
+        );
+    }
+
+    #[test]
+    fn test_add_impl_trait_empty() {
+        check_assist(
+            generate_impl_trait,
+            r#"
+                trait $0Foo {}
+            "#,
+            r#"
+                trait Foo {}
+
+                impl Foo for ${1:_} {$0}
+            "#,
+        );
+    }
 }
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs
index dc26ec79a74..9c4bcdd4030 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs
@@ -94,7 +94,7 @@ pub(crate) fn generate_mut_trait_impl(acc: &mut Assists, ctx: &AssistContext<'_>
     })?;
     let _ = process_ref_mut(&fn_);
 
-    let assoc_list = make::assoc_item_list().clone_for_update();
+    let assoc_list = make::assoc_item_list(None).clone_for_update();
     ted::replace(impl_def.assoc_item_list()?.syntax(), assoc_list.syntax());
     impl_def.get_or_create_assoc_item_list().add_item(syntax::ast::AssocItem::Fn(fn_));
 
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs
index 51c2f65e025..5bda1226cda 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs
@@ -4,12 +4,12 @@ use ide_db::{
 };
 use syntax::{
     ast::{self, AstNode, HasName, HasVisibility, StructKind, edit_in_place::Indent, make},
-    ted,
+    syntax_editor::Position,
 };
 
 use crate::{
     AssistContext, AssistId, Assists,
-    utils::{find_struct_impl, generate_impl},
+    utils::{find_struct_impl, generate_impl_with_item},
 };
 
 // Assist: generate_new
@@ -149,7 +149,53 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
         .clone_for_update();
         fn_.indent(1.into());
 
-        if let Some(cap) = ctx.config.snippet_cap {
+        let mut editor = builder.make_editor(strukt.syntax());
+
+        // Get the node for set annotation
+        let contain_fn = if let Some(impl_def) = impl_def {
+            fn_.indent(impl_def.indent_level());
+
+            if let Some(l_curly) = impl_def.assoc_item_list().and_then(|list| list.l_curly_token())
+            {
+                editor.insert_all(
+                    Position::after(l_curly),
+                    vec![
+                        make::tokens::whitespace(&format!("\n{}", impl_def.indent_level() + 1))
+                            .into(),
+                        fn_.syntax().clone().into(),
+                        make::tokens::whitespace("\n").into(),
+                    ],
+                );
+                fn_.syntax().clone()
+            } else {
+                let items = vec![either::Either::Right(ast::AssocItem::Fn(fn_))];
+                let list = make::assoc_item_list(Some(items));
+                editor.insert(Position::after(impl_def.syntax()), list.syntax());
+                list.syntax().clone()
+            }
+        } else {
+            // Generate a new impl to add the method to
+            let indent_level = strukt.indent_level();
+            let body = vec![either::Either::Right(ast::AssocItem::Fn(fn_))];
+            let list = make::assoc_item_list(Some(body));
+            let impl_def = generate_impl_with_item(&ast::Adt::Struct(strukt.clone()), Some(list));
+
+            impl_def.indent(strukt.indent_level());
+
+            // Insert it after the adt
+            editor.insert_all(
+                Position::after(strukt.syntax()),
+                vec![
+                    make::tokens::whitespace(&format!("\n\n{indent_level}")).into(),
+                    impl_def.syntax().clone().into(),
+                ],
+            );
+            impl_def.syntax().clone()
+        };
+
+        if let Some(fn_) = contain_fn.descendants().find_map(ast::Fn::cast)
+            && let Some(cap) = ctx.config.snippet_cap
+        {
             match strukt.kind() {
                 StructKind::Tuple(_) => {
                     let struct_args = fn_
@@ -168,8 +214,8 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
                         for (struct_arg, fn_param) in struct_args.zip(fn_params.params()) {
                             if let Some(fn_pat) = fn_param.pat() {
                                 let fn_pat = fn_pat.syntax().clone();
-                                builder
-                                    .add_placeholder_snippet_group(cap, vec![struct_arg, fn_pat]);
+                                let placeholder = builder.make_placeholder_snippet(cap);
+                                editor.add_annotation_all(vec![struct_arg, fn_pat], placeholder)
                             }
                         }
                     }
@@ -179,36 +225,12 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
 
             // Add a tabstop before the name
             if let Some(name) = fn_.name() {
-                builder.add_tabstop_before(cap, name);
+                let tabstop_before = builder.make_tabstop_before(cap);
+                editor.add_annotation(name.syntax(), tabstop_before);
             }
         }
 
-        // Get the mutable version of the impl to modify
-        let impl_def = if let Some(impl_def) = impl_def {
-            fn_.indent(impl_def.indent_level());
-            builder.make_mut(impl_def)
-        } else {
-            // Generate a new impl to add the method to
-            let impl_def = generate_impl(&ast::Adt::Struct(strukt.clone()));
-            let indent_level = strukt.indent_level();
-            fn_.indent(indent_level);
-
-            // Insert it after the adt
-            let strukt = builder.make_mut(strukt.clone());
-
-            ted::insert_all_raw(
-                ted::Position::after(strukt.syntax()),
-                vec![
-                    make::tokens::whitespace(&format!("\n\n{indent_level}")).into(),
-                    impl_def.syntax().clone().into(),
-                ],
-            );
-
-            impl_def
-        };
-
-        // Add the `new` method at the start of the impl
-        impl_def.get_or_create_assoc_item_list().add_item_at_start(fn_.into());
+        builder.add_file_edits(ctx.vfs_file_id(), editor);
     })
 }
 
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs
index 154b502e1bf..92a4bd35b3e 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs
@@ -3,7 +3,7 @@ use ide_db::assists::AssistId;
 use syntax::{
     AstNode, SyntaxKind, T,
     ast::{
-        self, HasGenericParams, HasName,
+        self, HasGenericParams, HasName, HasVisibility,
         edit_in_place::{HasVisibilityEdit, Indent},
         make,
     },
@@ -164,6 +164,12 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_
 /// `E0449` Trait items always share the visibility of their trait
 fn remove_items_visibility(item: &ast::AssocItem) {
     if let Some(has_vis) = ast::AnyHasVisibility::cast(item.syntax().clone()) {
+        if let Some(vis) = has_vis.visibility()
+            && let Some(token) = vis.syntax().next_sibling_or_token()
+            && token.kind() == SyntaxKind::WHITESPACE
+        {
+            ted::remove(token);
+        }
         has_vis.set_visibility(None);
     }
 }
@@ -333,11 +339,11 @@ impl F$0oo {
 struct Foo;
 
 trait NewTrait {
-     fn a_func() -> Option<()>;
+    fn a_func() -> Option<()>;
 }
 
 impl NewTrait for Foo {
-     fn a_func() -> Option<()> {
+    fn a_func() -> Option<()> {
         Some(())
     }
 }"#,
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_call.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_call.rs
index b7b8bc604a5..1549b414dcc 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_call.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_call.rs
@@ -537,8 +537,13 @@ fn inline(
     if let Some(generic_arg_list) = generic_arg_list.clone() {
         if let Some((target, source)) = &sema.scope(node.syntax()).zip(sema.scope(fn_body.syntax()))
         {
-            PathTransform::function_call(target, source, function, generic_arg_list)
-                .apply(body.syntax());
+            body.reindent_to(IndentLevel(0));
+            if let Some(new_body) = ast::BlockExpr::cast(
+                PathTransform::function_call(target, source, function, generic_arg_list)
+                    .apply(body.syntax()),
+            ) {
+                body = new_body;
+            }
         }
     }
 
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
index 806c8fba9ea..45bb6ce9129 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
@@ -5,12 +5,12 @@ use syntax::{
     SyntaxKind::WHITESPACE,
     T,
     ast::{self, AstNode, HasName, make},
-    ted::{self, Position},
+    syntax_editor::{Position, SyntaxEditor},
 };
 
 use crate::{
     AssistConfig, AssistId,
-    assist_context::{AssistContext, Assists, SourceChangeBuilder},
+    assist_context::{AssistContext, Assists},
     utils::{
         DefaultMethods, IgnoreAssocItems, add_trait_assoc_items_to_impl, filter_assoc_items,
         gen_trait_fn_body, generate_trait_impl,
@@ -126,98 +126,56 @@ fn add_assist(
     let label = format!("Convert to manual `impl {replace_trait_path} for {annotated_name}`");
 
     acc.add(AssistId::refactor("replace_derive_with_manual_impl"), label, target, |builder| {
-        let insert_after = ted::Position::after(builder.make_mut(adt.clone()).syntax());
+        let insert_after = Position::after(adt.syntax());
         let impl_is_unsafe = trait_.map(|s| s.is_unsafe(ctx.db())).unwrap_or(false);
-        let impl_def_with_items = impl_def_from_trait(
+        let impl_def = impl_def_from_trait(
             &ctx.sema,
             ctx.config,
             adt,
             &annotated_name,
             trait_,
             replace_trait_path,
+            impl_is_unsafe,
         );
-        update_attribute(builder, old_derives, old_tree, old_trait_path, attr);
 
-        let trait_path = make::ty_path(replace_trait_path.clone());
+        let mut editor = builder.make_editor(attr.syntax());
+        update_attribute(&mut editor, old_derives, old_tree, old_trait_path, attr);
 
-        match (ctx.config.snippet_cap, impl_def_with_items) {
-            (None, None) => {
-                let impl_def = generate_trait_impl(adt, trait_path);
-                if impl_is_unsafe {
-                    ted::insert(
-                        Position::first_child_of(impl_def.syntax()),
-                        make::token(T![unsafe]),
-                    );
-                }
+        let trait_path = make::ty_path(replace_trait_path.clone());
 
-                ted::insert_all(
-                    insert_after,
-                    vec![make::tokens::blank_line().into(), impl_def.syntax().clone().into()],
-                );
-            }
-            (None, Some((impl_def, _))) => {
-                if impl_is_unsafe {
-                    ted::insert(
-                        Position::first_child_of(impl_def.syntax()),
-                        make::token(T![unsafe]),
-                    );
-                }
-                ted::insert_all(
-                    insert_after,
-                    vec![make::tokens::blank_line().into(), impl_def.syntax().clone().into()],
-                );
-            }
-            (Some(cap), None) => {
-                let impl_def = generate_trait_impl(adt, trait_path);
-
-                if impl_is_unsafe {
-                    ted::insert(
-                        Position::first_child_of(impl_def.syntax()),
-                        make::token(T![unsafe]),
-                    );
-                }
+        let (impl_def, first_assoc_item) = if let Some(impl_def) = impl_def {
+            (
+                impl_def.clone(),
+                impl_def.assoc_item_list().and_then(|list| list.assoc_items().next()),
+            )
+        } else {
+            (generate_trait_impl(impl_is_unsafe, adt, trait_path), None)
+        };
 
-                if let Some(l_curly) = impl_def.assoc_item_list().and_then(|it| it.l_curly_token())
+        if let Some(cap) = ctx.config.snippet_cap {
+            if let Some(first_assoc_item) = first_assoc_item {
+                if let ast::AssocItem::Fn(ref func) = first_assoc_item
+                    && let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast)
+                    && m.syntax().text() == "todo!()"
                 {
-                    builder.add_tabstop_after_token(cap, l_curly);
-                }
-
-                ted::insert_all(
-                    insert_after,
-                    vec![make::tokens::blank_line().into(), impl_def.syntax().clone().into()],
-                );
-            }
-            (Some(cap), Some((impl_def, first_assoc_item))) => {
-                let mut added_snippet = false;
-
-                if impl_is_unsafe {
-                    ted::insert(
-                        Position::first_child_of(impl_def.syntax()),
-                        make::token(T![unsafe]),
-                    );
-                }
-
-                if let ast::AssocItem::Fn(ref func) = first_assoc_item {
-                    if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast) {
-                        if m.syntax().text() == "todo!()" {
-                            // Make the `todo!()` a placeholder
-                            builder.add_placeholder_snippet(cap, m);
-                            added_snippet = true;
-                        }
-                    }
-                }
-
-                if !added_snippet {
+                    // Make the `todo!()` a placeholder
+                    builder.add_placeholder_snippet(cap, m);
+                } else {
                     // If we haven't already added a snippet, add a tabstop before the generated function
                     builder.add_tabstop_before(cap, first_assoc_item);
                 }
-
-                ted::insert_all(
-                    insert_after,
-                    vec![make::tokens::blank_line().into(), impl_def.syntax().clone().into()],
-                );
+            } else if let Some(l_curly) =
+                impl_def.assoc_item_list().and_then(|it| it.l_curly_token())
+            {
+                builder.add_tabstop_after_token(cap, l_curly);
             }
-        };
+        }
+
+        editor.insert_all(
+            insert_after,
+            vec![make::tokens::blank_line().into(), impl_def.syntax().clone().into()],
+        );
+        builder.add_file_edits(ctx.vfs_file_id(), editor);
     })
 }
 
@@ -228,7 +186,8 @@ fn impl_def_from_trait(
     annotated_name: &ast::Name,
     trait_: Option<hir::Trait>,
     trait_path: &ast::Path,
-) -> Option<(ast::Impl, ast::AssocItem)> {
+    impl_is_unsafe: bool,
+) -> Option<ast::Impl> {
     let trait_ = trait_?;
     let target_scope = sema.scope(annotated_name.syntax())?;
 
@@ -245,21 +204,43 @@ fn impl_def_from_trait(
     if trait_items.is_empty() {
         return None;
     }
-    let impl_def = generate_trait_impl(adt, make::ty_path(trait_path.clone()));
+    let impl_def = generate_trait_impl(impl_is_unsafe, adt, make::ty_path(trait_path.clone()));
 
-    let first_assoc_item =
+    let assoc_items =
         add_trait_assoc_items_to_impl(sema, config, &trait_items, trait_, &impl_def, &target_scope);
+    let assoc_item_list = if let Some((first, other)) =
+        assoc_items.split_first().map(|(first, other)| (first.clone_subtree(), other))
+    {
+        let first_item = if let ast::AssocItem::Fn(ref func) = first
+            && let Some(body) = gen_trait_fn_body(func, trait_path, adt, None)
+            && let Some(func_body) = func.body()
+        {
+            let mut editor = SyntaxEditor::new(first.syntax().clone());
+            editor.replace(func_body.syntax(), body.syntax());
+            ast::AssocItem::cast(editor.finish().new_root().clone())
+        } else {
+            Some(first.clone())
+        };
+        let items = first_item
+            .into_iter()
+            .chain(other.iter().cloned())
+            .map(either::Either::Right)
+            .collect();
+        make::assoc_item_list(Some(items))
+    } else {
+        make::assoc_item_list(None)
+    }
+    .clone_for_update();
 
-    // Generate a default `impl` function body for the derived trait.
-    if let ast::AssocItem::Fn(ref func) = first_assoc_item {
-        let _ = gen_trait_fn_body(func, trait_path, adt, None);
-    };
-
-    Some((impl_def, first_assoc_item))
+    let impl_def = impl_def.clone_subtree();
+    let mut editor = SyntaxEditor::new(impl_def.syntax().clone());
+    editor.replace(impl_def.assoc_item_list()?.syntax(), assoc_item_list.syntax());
+    let impl_def = ast::Impl::cast(editor.finish().new_root().clone())?;
+    Some(impl_def)
 }
 
 fn update_attribute(
-    builder: &mut SourceChangeBuilder,
+    editor: &mut SyntaxEditor,
     old_derives: &[ast::Path],
     old_tree: &ast::TokenTree,
     old_trait_path: &ast::Path,
@@ -272,8 +253,6 @@ fn update_attribute(
     let has_more_derives = !new_derives.is_empty();
 
     if has_more_derives {
-        let old_tree = builder.make_mut(old_tree.clone());
-
         // Make the paths into flat lists of tokens in a vec
         let tt = new_derives.iter().map(|path| path.syntax().clone()).map(|node| {
             node.descendants_with_tokens()
@@ -288,18 +267,17 @@ fn update_attribute(
         let tt = tt.collect::<Vec<_>>();
 
         let new_tree = make::token_tree(T!['('], tt).clone_for_update();
-        ted::replace(old_tree.syntax(), new_tree.syntax());
+        editor.replace(old_tree.syntax(), new_tree.syntax());
     } else {
         // Remove the attr and any trailing whitespace
-        let attr = builder.make_mut(attr.clone());
 
         if let Some(line_break) =
             attr.syntax().next_sibling_or_token().filter(|t| t.kind() == WHITESPACE)
         {
-            ted::remove(line_break)
+            editor.delete(line_break)
         }
 
-        ted::remove(attr.syntax())
+        editor.delete(attr.syntax())
     }
 }
 
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs
index cde0d875e0d..4682c047323 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs
@@ -302,6 +302,7 @@ mod handlers {
             generate_function::generate_function,
             generate_impl::generate_impl,
             generate_impl::generate_trait_impl,
+            generate_impl::generate_impl_trait,
             generate_is_empty_from_len::generate_is_empty_from_len,
             generate_mut_trait_impl::generate_mut_trait_impl,
             generate_new::generate_new,
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs
index fc1c6928ff3..91348be97eb 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs
@@ -1881,6 +1881,29 @@ impl<T: Clone> Ctx<T> {$0}
 }
 
 #[test]
+fn doctest_generate_impl_trait() {
+    check_doc_test(
+        "generate_impl_trait",
+        r#####"
+trait $0Foo {
+    fn foo(&self) -> i32;
+}
+"#####,
+        r#####"
+trait Foo {
+    fn foo(&self) -> i32;
+}
+
+impl Foo for ${1:_} {
+    fn foo(&self) -> i32 {
+        $0todo!()
+    }
+}
+"#####,
+    )
+}
+
+#[test]
 fn doctest_generate_is_empty_from_len() {
     check_doc_test(
         "generate_is_empty_from_len",
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs
index fbce1d31eae..15c7a6a3fc2 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs
@@ -23,10 +23,11 @@ use syntax::{
     ast::{
         self, HasArgList, HasAttrs, HasGenericParams, HasName, HasTypeBounds, Whitespace,
         edit::{AstNodeEdit, IndentLevel},
-        edit_in_place::{AttrsOwnerEdit, Indent, Removable},
+        edit_in_place::{AttrsOwnerEdit, Removable},
         make,
         syntax_factory::SyntaxFactory,
     },
+    syntax_editor::SyntaxEditor,
     ted,
 };
 
@@ -178,6 +179,7 @@ pub fn filter_assoc_items(
 /// [`filter_assoc_items()`]), clones each item for update and applies path transformation to it,
 /// then inserts into `impl_`. Returns the modified `impl_` and the first associated item that got
 /// inserted.
+#[must_use]
 pub fn add_trait_assoc_items_to_impl(
     sema: &Semantics<'_, RootDatabase>,
     config: &AssistConfig,
@@ -185,71 +187,66 @@ pub fn add_trait_assoc_items_to_impl(
     trait_: hir::Trait,
     impl_: &ast::Impl,
     target_scope: &hir::SemanticsScope<'_>,
-) -> ast::AssocItem {
+) -> Vec<ast::AssocItem> {
     let new_indent_level = IndentLevel::from_node(impl_.syntax()) + 1;
-    let items = original_items.iter().map(|InFile { file_id, value: original_item }| {
-        let cloned_item = {
-            if let Some(macro_file) = file_id.macro_file() {
-                let span_map = sema.db.expansion_span_map(macro_file);
-                let item_prettified = prettify_macro_expansion(
-                    sema.db,
-                    original_item.syntax().clone(),
-                    &span_map,
-                    target_scope.krate().into(),
-                );
-                if let Some(formatted) = ast::AssocItem::cast(item_prettified) {
-                    return formatted;
-                } else {
-                    stdx::never!("formatted `AssocItem` could not be cast back to `AssocItem`");
+    original_items
+        .iter()
+        .map(|InFile { file_id, value: original_item }| {
+            let mut cloned_item = {
+                if let Some(macro_file) = file_id.macro_file() {
+                    let span_map = sema.db.expansion_span_map(macro_file);
+                    let item_prettified = prettify_macro_expansion(
+                        sema.db,
+                        original_item.syntax().clone(),
+                        &span_map,
+                        target_scope.krate().into(),
+                    );
+                    if let Some(formatted) = ast::AssocItem::cast(item_prettified) {
+                        return formatted;
+                    } else {
+                        stdx::never!("formatted `AssocItem` could not be cast back to `AssocItem`");
+                    }
                 }
+                original_item.clone_for_update()
             }
-            original_item.clone_for_update()
-        };
-
-        if let Some(source_scope) = sema.scope(original_item.syntax()) {
-            // FIXME: Paths in nested macros are not handled well. See
-            // `add_missing_impl_members::paths_in_nested_macro_should_get_transformed` test.
-            let transform =
-                PathTransform::trait_impl(target_scope, &source_scope, trait_, impl_.clone());
-            transform.apply(cloned_item.syntax());
-        }
-        cloned_item.remove_attrs_and_docs();
-        cloned_item.reindent_to(new_indent_level);
-        cloned_item
-    });
-
-    let assoc_item_list = impl_.get_or_create_assoc_item_list();
-
-    let mut first_item = None;
-    for item in items {
-        first_item.get_or_insert_with(|| item.clone());
-        match &item {
-            ast::AssocItem::Fn(fn_) if fn_.body().is_none() => {
-                let body = AstNodeEdit::indent(
-                    &make::block_expr(
-                        None,
-                        Some(match config.expr_fill_default {
-                            ExprFillDefaultMode::Todo => make::ext::expr_todo(),
-                            ExprFillDefaultMode::Underscore => make::ext::expr_underscore(),
-                            ExprFillDefaultMode::Default => make::ext::expr_todo(),
-                        }),
-                    ),
-                    new_indent_level,
-                );
-                ted::replace(fn_.get_or_create_body().syntax(), body.clone_for_update().syntax())
+            .reset_indent();
+
+            if let Some(source_scope) = sema.scope(original_item.syntax()) {
+                // FIXME: Paths in nested macros are not handled well. See
+                // `add_missing_impl_members::paths_in_nested_macro_should_get_transformed` test.
+                let transform =
+                    PathTransform::trait_impl(target_scope, &source_scope, trait_, impl_.clone());
+                cloned_item = ast::AssocItem::cast(transform.apply(cloned_item.syntax())).unwrap();
             }
-            ast::AssocItem::TypeAlias(type_alias) => {
-                if let Some(type_bound_list) = type_alias.type_bound_list() {
-                    type_bound_list.remove()
+            cloned_item.remove_attrs_and_docs();
+            cloned_item
+        })
+        .map(|item| {
+            match &item {
+                ast::AssocItem::Fn(fn_) if fn_.body().is_none() => {
+                    let body = AstNodeEdit::indent(
+                        &make::block_expr(
+                            None,
+                            Some(match config.expr_fill_default {
+                                ExprFillDefaultMode::Todo => make::ext::expr_todo(),
+                                ExprFillDefaultMode::Underscore => make::ext::expr_underscore(),
+                                ExprFillDefaultMode::Default => make::ext::expr_todo(),
+                            }),
+                        ),
+                        IndentLevel::single(),
+                    );
+                    ted::replace(fn_.get_or_create_body().syntax(), body.syntax());
                 }
+                ast::AssocItem::TypeAlias(type_alias) => {
+                    if let Some(type_bound_list) = type_alias.type_bound_list() {
+                        type_bound_list.remove()
+                    }
+                }
+                _ => {}
             }
-            _ => {}
-        }
-
-        assoc_item_list.add_item(item)
-    }
-
-    first_item.unwrap()
+            AstNodeEdit::indent(&item, new_indent_level)
+        })
+        .collect()
 }
 
 pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize {
@@ -334,7 +331,7 @@ fn invert_special_case(make: &SyntaxFactory, expr: &ast::Expr) -> Option<ast::Ex
 fn invert_special_case_legacy(expr: &ast::Expr) -> Option<ast::Expr> {
     match expr {
         ast::Expr::BinExpr(bin) => {
-            let bin = bin.clone_for_update();
+            let bin = bin.clone_subtree();
             let op_token = bin.op_token()?;
             let rev_token = match op_token.kind() {
                 T![==] => T![!=],
@@ -350,8 +347,9 @@ fn invert_special_case_legacy(expr: &ast::Expr) -> Option<ast::Expr> {
                     );
                 }
             };
-            ted::replace(op_token, make::token(rev_token));
-            Some(bin.into())
+            let mut bin_editor = SyntaxEditor::new(bin.syntax().clone());
+            bin_editor.replace(op_token, make::token(rev_token));
+            ast::Expr::cast(bin_editor.finish().new_root().clone())
         }
         ast::Expr::MethodCallExpr(mce) => {
             let receiver = mce.receiver()?;
@@ -664,16 +662,23 @@ fn generate_impl_text_inner(
 
 /// Generates the corresponding `impl Type {}` including type and lifetime
 /// parameters.
+pub(crate) fn generate_impl_with_item(
+    adt: &ast::Adt,
+    body: Option<ast::AssocItemList>,
+) -> ast::Impl {
+    generate_impl_inner(false, adt, None, true, body)
+}
+
 pub(crate) fn generate_impl(adt: &ast::Adt) -> ast::Impl {
-    generate_impl_inner(adt, None, true)
+    generate_impl_inner(false, adt, None, true, None)
 }
 
 /// Generates the corresponding `impl <trait> for Type {}` including type
 /// and lifetime parameters, with `<trait>` appended to `impl`'s generic parameters' bounds.
 ///
 /// This is useful for traits like `PartialEq`, since `impl<T> PartialEq for U<T>` often requires `T: PartialEq`.
-pub(crate) fn generate_trait_impl(adt: &ast::Adt, trait_: ast::Type) -> ast::Impl {
-    generate_impl_inner(adt, Some(trait_), true)
+pub(crate) fn generate_trait_impl(is_unsafe: bool, adt: &ast::Adt, trait_: ast::Type) -> ast::Impl {
+    generate_impl_inner(is_unsafe, adt, Some(trait_), true, None)
 }
 
 /// Generates the corresponding `impl <trait> for Type {}` including type
@@ -681,13 +686,15 @@ pub(crate) fn generate_trait_impl(adt: &ast::Adt, trait_: ast::Type) -> ast::Imp
 ///
 /// This is useful for traits like `From<T>`, since `impl<T> From<T> for U<T>` doesn't require `T: From<T>`.
 pub(crate) fn generate_trait_impl_intransitive(adt: &ast::Adt, trait_: ast::Type) -> ast::Impl {
-    generate_impl_inner(adt, Some(trait_), false)
+    generate_impl_inner(false, adt, Some(trait_), false, None)
 }
 
 fn generate_impl_inner(
+    is_unsafe: bool,
     adt: &ast::Adt,
     trait_: Option<ast::Type>,
     trait_is_transitive: bool,
+    body: Option<ast::AssocItemList>,
 ) -> ast::Impl {
     // Ensure lifetime params are before type & const params
     let generic_params = adt.generic_param_list().map(|generic_params| {
@@ -727,7 +734,7 @@ fn generate_impl_inner(
 
     let impl_ = match trait_ {
         Some(trait_) => make::impl_trait(
-            false,
+            is_unsafe,
             None,
             None,
             generic_params,
@@ -737,9 +744,9 @@ fn generate_impl_inner(
             ty,
             None,
             adt.where_clause(),
-            None,
+            body,
         ),
-        None => make::impl_(generic_params, generic_args, ty, adt.where_clause(), None),
+        None => make::impl_(generic_params, generic_args, ty, adt.where_clause(), body),
     }
     .clone_for_update();
 
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs
index c58bdd9e8ed..87e90e85193 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs
@@ -1,10 +1,7 @@
 //! This module contains functions to generate default trait impl function bodies where possible.
 
 use hir::TraitRef;
-use syntax::{
-    ast::{self, AstNode, BinaryOp, CmpOp, HasName, LogicOp, edit::AstNodeEdit, make},
-    ted,
-};
+use syntax::ast::{self, AstNode, BinaryOp, CmpOp, HasName, LogicOp, edit::AstNodeEdit, make};
 
 /// Generate custom trait bodies without default implementation where possible.
 ///
@@ -18,21 +15,33 @@ pub(crate) fn gen_trait_fn_body(
     trait_path: &ast::Path,
     adt: &ast::Adt,
     trait_ref: Option<TraitRef<'_>>,
-) -> Option<()> {
+) -> Option<ast::BlockExpr> {
+    let _ = func.body()?;
     match trait_path.segment()?.name_ref()?.text().as_str() {
-        "Clone" => gen_clone_impl(adt, func),
-        "Debug" => gen_debug_impl(adt, func),
-        "Default" => gen_default_impl(adt, func),
-        "Hash" => gen_hash_impl(adt, func),
-        "PartialEq" => gen_partial_eq(adt, func, trait_ref),
-        "PartialOrd" => gen_partial_ord(adt, func, trait_ref),
+        "Clone" => {
+            stdx::always!(func.name().is_some_and(|name| name.text() == "clone"));
+            gen_clone_impl(adt)
+        }
+        "Debug" => gen_debug_impl(adt),
+        "Default" => gen_default_impl(adt),
+        "Hash" => {
+            stdx::always!(func.name().is_some_and(|name| name.text() == "hash"));
+            gen_hash_impl(adt)
+        }
+        "PartialEq" => {
+            stdx::always!(func.name().is_some_and(|name| name.text() == "eq"));
+            gen_partial_eq(adt, trait_ref)
+        }
+        "PartialOrd" => {
+            stdx::always!(func.name().is_some_and(|name| name.text() == "partial_cmp"));
+            gen_partial_ord(adt, trait_ref)
+        }
         _ => None,
     }
 }
 
 /// Generate a `Clone` impl based on the fields and members of the target type.
-fn gen_clone_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
-    stdx::always!(func.name().is_some_and(|name| name.text() == "clone"));
+fn gen_clone_impl(adt: &ast::Adt) -> Option<ast::BlockExpr> {
     fn gen_clone_call(target: ast::Expr) -> ast::Expr {
         let method = make::name_ref("clone");
         make::expr_method_call(target, method, make::arg_list(None)).into()
@@ -139,12 +148,11 @@ fn gen_clone_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
         }
     };
     let body = make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1));
-    ted::replace(func.body()?.syntax(), body.clone_for_update().syntax());
-    Some(())
+    Some(body)
 }
 
 /// Generate a `Debug` impl based on the fields and members of the target type.
-fn gen_debug_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
+fn gen_debug_impl(adt: &ast::Adt) -> Option<ast::BlockExpr> {
     let annotated_name = adt.name()?;
     match adt {
         // `Debug` cannot be derived for unions, so no default impl can be provided.
@@ -248,8 +256,7 @@ fn gen_debug_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
 
             let body = make::block_expr(None, Some(match_expr.into()));
             let body = body.indent(ast::edit::IndentLevel(1));
-            ted::replace(func.body()?.syntax(), body.clone_for_update().syntax());
-            Some(())
+            Some(body)
         }
 
         ast::Adt::Struct(strukt) => {
@@ -296,14 +303,13 @@ fn gen_debug_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
             let method = make::name_ref("finish");
             let expr = make::expr_method_call(expr, method, make::arg_list(None)).into();
             let body = make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1));
-            ted::replace(func.body()?.syntax(), body.clone_for_update().syntax());
-            Some(())
+            Some(body)
         }
     }
 }
 
 /// Generate a `Debug` impl based on the fields and members of the target type.
-fn gen_default_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
+fn gen_default_impl(adt: &ast::Adt) -> Option<ast::BlockExpr> {
     fn gen_default_call() -> Option<ast::Expr> {
         let fn_name = make::ext::path_from_idents(["Default", "default"])?;
         Some(make::expr_call(make::expr_path(fn_name), make::arg_list(None)).into())
@@ -342,15 +348,13 @@ fn gen_default_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
                 }
             };
             let body = make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1));
-            ted::replace(func.body()?.syntax(), body.clone_for_update().syntax());
-            Some(())
+            Some(body)
         }
     }
 }
 
 /// Generate a `Hash` impl based on the fields and members of the target type.
-fn gen_hash_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
-    stdx::always!(func.name().is_some_and(|name| name.text() == "hash"));
+fn gen_hash_impl(adt: &ast::Adt) -> Option<ast::BlockExpr> {
     fn gen_hash_call(target: ast::Expr) -> ast::Stmt {
         let method = make::name_ref("hash");
         let arg = make::expr_path(make::ext::ident_path("state"));
@@ -400,13 +404,11 @@ fn gen_hash_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
         },
     };
 
-    ted::replace(func.body()?.syntax(), body.clone_for_update().syntax());
-    Some(())
+    Some(body)
 }
 
 /// Generate a `PartialEq` impl based on the fields and members of the target type.
-fn gen_partial_eq(adt: &ast::Adt, func: &ast::Fn, trait_ref: Option<TraitRef<'_>>) -> Option<()> {
-    stdx::always!(func.name().is_some_and(|name| name.text() == "eq"));
+fn gen_partial_eq(adt: &ast::Adt, trait_ref: Option<TraitRef<'_>>) -> Option<ast::BlockExpr> {
     fn gen_eq_chain(expr: Option<ast::Expr>, cmp: ast::Expr) -> Option<ast::Expr> {
         match expr {
             Some(expr) => Some(make::expr_bin_op(expr, BinaryOp::LogicOp(LogicOp::And), cmp)),
@@ -595,12 +597,10 @@ fn gen_partial_eq(adt: &ast::Adt, func: &ast::Fn, trait_ref: Option<TraitRef<'_>
         },
     };
 
-    ted::replace(func.body()?.syntax(), body.clone_for_update().syntax());
-    Some(())
+    Some(body)
 }
 
-fn gen_partial_ord(adt: &ast::Adt, func: &ast::Fn, trait_ref: Option<TraitRef<'_>>) -> Option<()> {
-    stdx::always!(func.name().is_some_and(|name| name.text() == "partial_cmp"));
+fn gen_partial_ord(adt: &ast::Adt, trait_ref: Option<TraitRef<'_>>) -> Option<ast::BlockExpr> {
     fn gen_partial_eq_match(match_target: ast::Expr) -> Option<ast::Stmt> {
         let mut arms = vec![];
 
@@ -686,8 +686,7 @@ fn gen_partial_ord(adt: &ast::Adt, func: &ast::Fn, trait_ref: Option<TraitRef<'_
         },
     };
 
-    ted::replace(func.body()?.syntax(), body.clone_for_update().syntax());
-    Some(())
+    Some(body)
 }
 
 fn make_discriminant() -> Option<ast::Expr> {
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs
index 975c2f02259..bcf8c0ec527 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs
@@ -276,7 +276,7 @@ fn get_transformed_assoc_item(
     let assoc_item = assoc_item.clone_for_update();
     // FIXME: Paths in nested macros are not handled well. See
     // `macro_generated_assoc_item2` test.
-    transform.apply(assoc_item.syntax());
+    let assoc_item = ast::AssocItem::cast(transform.apply(assoc_item.syntax()))?;
     assoc_item.remove_attrs_and_docs();
     Some(assoc_item)
 }
@@ -301,7 +301,7 @@ fn get_transformed_fn(
     let fn_ = fn_.clone_for_update();
     // FIXME: Paths in nested macros are not handled well. See
     // `macro_generated_assoc_item2` test.
-    transform.apply(fn_.syntax());
+    let fn_ = ast::Fn::cast(transform.apply(fn_.syntax()))?;
     fn_.remove_attrs_and_docs();
     match async_ {
         AsyncSugaring::Desugar => {
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs b/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs
index 0ab880bcfe7..b7432d89c7b 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs
+++ b/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs
@@ -12,15 +12,16 @@ use span::Edition;
 use syntax::{
     NodeOrToken, SyntaxNode,
     ast::{self, AstNode, HasGenericArgs, make},
-    ted,
+    syntax_editor::{self, SyntaxEditor},
 };
 
-#[derive(Default)]
+#[derive(Default, Debug)]
 struct AstSubsts {
     types_and_consts: Vec<TypeOrConst>,
     lifetimes: Vec<ast::LifetimeArg>,
 }
 
+#[derive(Debug)]
 enum TypeOrConst {
     Either(ast::TypeArg), // indistinguishable type or const param
     Const(ast::ConstArg),
@@ -128,15 +129,18 @@ impl<'a> PathTransform<'a> {
         }
     }
 
-    pub fn apply(&self, syntax: &SyntaxNode) {
+    #[must_use]
+    pub fn apply(&self, syntax: &SyntaxNode) -> SyntaxNode {
         self.build_ctx().apply(syntax)
     }
 
-    pub fn apply_all<'b>(&self, nodes: impl IntoIterator<Item = &'b SyntaxNode>) {
+    #[must_use]
+    pub fn apply_all<'b>(
+        &self,
+        nodes: impl IntoIterator<Item = &'b SyntaxNode>,
+    ) -> Vec<SyntaxNode> {
         let ctx = self.build_ctx();
-        for node in nodes {
-            ctx.apply(node);
-        }
+        nodes.into_iter().map(|node| ctx.apply(&node.clone())).collect()
     }
 
     fn prettify_target_node(&self, node: SyntaxNode) -> SyntaxNode {
@@ -236,7 +240,7 @@ impl<'a> PathTransform<'a> {
                 Some((k.name(db).display(db, target_edition).to_string(), v.lifetime()?))
             })
             .collect();
-        let ctx = Ctx {
+        let mut ctx = Ctx {
             type_substs,
             const_substs,
             lifetime_substs,
@@ -272,42 +276,75 @@ fn preorder_rev(item: &SyntaxNode) -> impl Iterator<Item = SyntaxNode> {
 }
 
 impl Ctx<'_> {
-    fn apply(&self, item: &SyntaxNode) {
+    fn apply(&self, item: &SyntaxNode) -> SyntaxNode {
         // `transform_path` may update a node's parent and that would break the
         // tree traversal. Thus all paths in the tree are collected into a vec
         // so that such operation is safe.
-        let paths = preorder_rev(item).filter_map(ast::Path::cast).collect::<Vec<_>>();
-        for path in paths {
-            self.transform_path(path);
-        }
-
-        preorder_rev(item).filter_map(ast::Lifetime::cast).for_each(|lifetime| {
+        let item = self.transform_path(item).clone_subtree();
+        let mut editor = SyntaxEditor::new(item.clone());
+        preorder_rev(&item).filter_map(ast::Lifetime::cast).for_each(|lifetime| {
             if let Some(subst) = self.lifetime_substs.get(&lifetime.syntax().text().to_string()) {
-                ted::replace(lifetime.syntax(), subst.clone_subtree().clone_for_update().syntax());
+                editor
+                    .replace(lifetime.syntax(), subst.clone_subtree().clone_for_update().syntax());
             }
         });
+
+        editor.finish().new_root().clone()
     }
 
-    fn transform_default_values(&self, defaulted_params: Vec<DefaultedParam>) {
+    fn transform_default_values(&mut self, defaulted_params: Vec<DefaultedParam>) {
         // By now the default values are simply copied from where they are declared
         // and should be transformed. As any value is allowed to refer to previous
         // generic (both type and const) parameters, they should be all iterated left-to-right.
         for param in defaulted_params {
-            let value = match param {
-                Either::Left(k) => self.type_substs.get(&k).unwrap().syntax(),
-                Either::Right(k) => self.const_substs.get(&k).unwrap(),
+            let value = match &param {
+                Either::Left(k) => self.type_substs.get(k).unwrap().syntax(),
+                Either::Right(k) => self.const_substs.get(k).unwrap(),
             };
             // `transform_path` may update a node's parent and that would break the
             // tree traversal. Thus all paths in the tree are collected into a vec
             // so that such operation is safe.
-            let paths = preorder_rev(value).filter_map(ast::Path::cast).collect::<Vec<_>>();
-            for path in paths {
-                self.transform_path(path);
+            let new_value = self.transform_path(value);
+            match param {
+                Either::Left(k) => {
+                    self.type_substs.insert(k, ast::Type::cast(new_value.clone()).unwrap());
+                }
+                Either::Right(k) => {
+                    self.const_substs.insert(k, new_value.clone());
+                }
             }
         }
     }
 
-    fn transform_path(&self, path: ast::Path) -> Option<()> {
+    fn transform_path(&self, path: &SyntaxNode) -> SyntaxNode {
+        fn find_child_paths(root_path: &SyntaxNode) -> Vec<ast::Path> {
+            let mut result = Vec::new();
+            for child in root_path.children() {
+                if let Some(child_path) = ast::Path::cast(child.clone()) {
+                    result.push(child_path);
+                } else {
+                    result.extend(find_child_paths(&child));
+                }
+            }
+            result
+        }
+        let root_path = path.clone_subtree();
+        let result = find_child_paths(&root_path);
+        let mut editor = SyntaxEditor::new(root_path.clone());
+        for sub_path in result {
+            let new = self.transform_path(sub_path.syntax());
+            editor.replace(sub_path.syntax(), new);
+        }
+        let update_sub_item = editor.finish().new_root().clone().clone_subtree();
+        let item = find_child_paths(&update_sub_item);
+        let mut editor = SyntaxEditor::new(update_sub_item);
+        for sub_path in item {
+            self.transform_path_(&mut editor, &sub_path);
+        }
+        editor.finish().new_root().clone()
+    }
+
+    fn transform_path_(&self, editor: &mut SyntaxEditor, path: &ast::Path) -> Option<()> {
         if path.qualifier().is_some() {
             return None;
         }
@@ -319,8 +356,7 @@ impl Ctx<'_> {
             // don't try to qualify sole `self` either, they are usually locals, but are returned as modules due to namespace clashing
             return None;
         }
-
-        let resolution = self.source_scope.speculative_resolve(&path)?;
+        let resolution = self.source_scope.speculative_resolve(path)?;
 
         match resolution {
             hir::PathResolution::TypeParam(tp) => {
@@ -360,12 +396,12 @@ impl Ctx<'_> {
 
                         let segment = make::path_segment_ty(subst.clone(), trait_ref);
                         let qualified = make::path_from_segments(std::iter::once(segment), false);
-                        ted::replace(path.syntax(), qualified.clone_for_update().syntax());
+                        editor.replace(path.syntax(), qualified.clone_for_update().syntax());
                     } else if let Some(path_ty) = ast::PathType::cast(parent) {
                         let old = path_ty.syntax();
 
                         if old.parent().is_some() {
-                            ted::replace(old, subst.clone_subtree().clone_for_update().syntax());
+                            editor.replace(old, subst.clone_subtree().clone_for_update().syntax());
                         } else {
                             // Some `path_ty` has no parent, especially ones made for default value
                             // of type parameters.
@@ -377,13 +413,13 @@ impl Ctx<'_> {
                             }
                             let start = path_ty.syntax().first_child().map(NodeOrToken::Node)?;
                             let end = path_ty.syntax().last_child().map(NodeOrToken::Node)?;
-                            ted::replace_all(
+                            editor.replace_all(
                                 start..=end,
                                 new.syntax().children().map(NodeOrToken::Node).collect::<Vec<_>>(),
                             );
                         }
                     } else {
-                        ted::replace(
+                        editor.replace(
                             path.syntax(),
                             subst.clone_subtree().clone_for_update().syntax(),
                         );
@@ -409,17 +445,28 @@ impl Ctx<'_> {
                 };
                 let found_path = self.target_module.find_path(self.source_scope.db, def, cfg)?;
                 let res = mod_path_to_ast(&found_path, self.target_edition).clone_for_update();
+                let mut res_editor = SyntaxEditor::new(res.syntax().clone_subtree());
                 if let Some(args) = path.segment().and_then(|it| it.generic_arg_list()) {
                     if let Some(segment) = res.segment() {
-                        let old = segment.get_or_create_generic_arg_list();
-                        ted::replace(old.syntax(), args.clone_subtree().syntax().clone_for_update())
+                        if let Some(old) = segment.generic_arg_list() {
+                            res_editor.replace(
+                                old.syntax(),
+                                args.clone_subtree().syntax().clone_for_update(),
+                            )
+                        } else {
+                            res_editor.insert(
+                                syntax_editor::Position::last_child_of(segment.syntax()),
+                                args.clone_subtree().syntax().clone_for_update(),
+                            );
+                        }
                     }
                 }
-                ted::replace(path.syntax(), res.syntax())
+                let res = res_editor.finish().new_root().clone();
+                editor.replace(path.syntax().clone(), res);
             }
             hir::PathResolution::ConstParam(cp) => {
                 if let Some(subst) = self.const_substs.get(&cp) {
-                    ted::replace(path.syntax(), subst.clone_subtree().clone_for_update());
+                    editor.replace(path.syntax(), subst.clone_subtree().clone_for_update());
                 }
             }
             hir::PathResolution::SelfType(imp) => {
@@ -456,13 +503,13 @@ impl Ctx<'_> {
                             mod_path_to_ast(&found_path, self.target_edition).qualifier()
                         {
                             let res = make::path_concat(qual, path_ty.path()?).clone_for_update();
-                            ted::replace(path.syntax(), res.syntax());
+                            editor.replace(path.syntax(), res.syntax());
                             return Some(());
                         }
                     }
                 }
 
-                ted::replace(path.syntax(), ast_ty.syntax());
+                editor.replace(path.syntax(), ast_ty.syntax());
             }
             hir::PathResolution::Local(_)
             | hir::PathResolution::Def(_)
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs
index f20b6dea122..e31367f3b14 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs
@@ -131,4 +131,28 @@ fn foo(v: Enum<()>) {
         "#,
         );
     }
+
+    #[test]
+    fn regression_20259() {
+        check_diagnostics(
+            r#"
+//- minicore: deref
+use core::ops::Deref;
+
+struct Foo<T>(T);
+
+impl<T> Deref for Foo<T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+fn test(x: Foo<(i32, bool)>) {
+    let (_a, _b): &(i32, bool) = &x;
+}
+"#,
+        );
+    }
 }
diff --git a/src/tools/rust-analyzer/crates/ide/src/folding_ranges.rs b/src/tools/rust-analyzer/crates/ide/src/folding_ranges.rs
index 698fd147789..1901bcc797e 100755
--- a/src/tools/rust-analyzer/crates/ide/src/folding_ranges.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/folding_ranges.rs
@@ -73,11 +73,13 @@ pub(crate) fn folding_ranges(file: &SourceFile) -> Vec<Fold> {
                             }
 
                             if fn_node.body().is_some() {
+                                // Get the actual start of the function (excluding doc comments)
+                                let fn_start = fn_node
+                                    .fn_token()
+                                    .map(|token| token.text_range().start())
+                                    .unwrap_or(node.text_range().start());
                                 res.push(Fold {
-                                    range: TextRange::new(
-                                        node.text_range().start(),
-                                        node.text_range().end(),
-                                    ),
+                                    range: TextRange::new(fn_start, node.text_range().end()),
                                     kind: FoldKind::Function,
                                 });
                                 continue;
@@ -688,4 +690,21 @@ type Foo<T, U> = foo<fold arglist><
 "#,
         )
     }
+
+    #[test]
+    fn test_fold_doc_comments_with_multiline_paramlist_function() {
+        check(
+            r#"
+<fold comment>/// A very very very very very very very very very very very very very very very
+/// very very very long description</fold>
+<fold function>fn foo<fold arglist>(
+    very_long_parameter_name: u32,
+    another_very_long_parameter_name: u32,
+    third_very_long_param: u32,
+)</fold> <fold block>{
+    todo!()
+}</fold></fold>
+"#,
+        );
+    }
 }
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/lifetime.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/lifetime.rs
index 0069452e7b9..49fec0a793c 100644
--- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/lifetime.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/lifetime.rs
@@ -77,17 +77,18 @@ pub(super) fn fn_ptr_hints(
         return None;
     }
 
-    let parent_for_type = func
+    let parent_for_binder = func
         .syntax()
         .ancestors()
         .skip(1)
         .take_while(|it| matches!(it.kind(), SyntaxKind::PAREN_TYPE | SyntaxKind::FOR_TYPE))
-        .find_map(ast::ForType::cast);
+        .find_map(ast::ForType::cast)
+        .and_then(|it| it.for_binder());
 
     let param_list = func.param_list()?;
-    let generic_param_list = parent_for_type.as_ref().and_then(|it| it.generic_param_list());
+    let generic_param_list = parent_for_binder.as_ref().and_then(|it| it.generic_param_list());
     let ret_type = func.ret_type();
-    let for_kw = parent_for_type.as_ref().and_then(|it| it.for_token());
+    let for_kw = parent_for_binder.as_ref().and_then(|it| it.for_token());
     hints_(
         acc,
         ctx,
@@ -143,15 +144,16 @@ pub(super) fn fn_path_hints(
 
     // FIXME: Support general path types
     let (param_list, ret_type) = func.path().as_ref().and_then(path_as_fn)?;
-    let parent_for_type = func
+    let parent_for_binder = func
         .syntax()
         .ancestors()
         .skip(1)
         .take_while(|it| matches!(it.kind(), SyntaxKind::PAREN_TYPE | SyntaxKind::FOR_TYPE))
-        .find_map(ast::ForType::cast);
+        .find_map(ast::ForType::cast)
+        .and_then(|it| it.for_binder());
 
-    let generic_param_list = parent_for_type.as_ref().and_then(|it| it.generic_param_list());
-    let for_kw = parent_for_type.as_ref().and_then(|it| it.for_token());
+    let generic_param_list = parent_for_binder.as_ref().and_then(|it| it.generic_param_list());
+    let for_kw = parent_for_binder.as_ref().and_then(|it| it.for_token());
     hints_(
         acc,
         ctx,
diff --git a/src/tools/rust-analyzer/crates/ide/src/rename.rs b/src/tools/rust-analyzer/crates/ide/src/rename.rs
index fb84e8e6b47..a07c647c2cb 100644
--- a/src/tools/rust-analyzer/crates/ide/src/rename.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/rename.rs
@@ -12,6 +12,7 @@ use ide_db::{
     source_change::SourceChangeBuilder,
 };
 use itertools::Itertools;
+use std::fmt::Write;
 use stdx::{always, never};
 use syntax::{AstNode, SyntaxKind, SyntaxNode, TextRange, TextSize, ast};
 
@@ -459,35 +460,22 @@ fn rename_self_to_param(
 }
 
 fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: String) -> Option<TextEdit> {
-    fn target_type_name(impl_def: &ast::Impl) -> Option<String> {
-        if let Some(ast::Type::PathType(p)) = impl_def.self_ty() {
-            return Some(p.path()?.segment()?.name_ref()?.text().to_string());
-        }
-        None
-    }
+    let mut replacement_text = new_name;
+    replacement_text.push_str(": ");
 
-    match self_param.syntax().ancestors().find_map(ast::Impl::cast) {
-        Some(impl_def) => {
-            let type_name = target_type_name(&impl_def)?;
+    if self_param.amp_token().is_some() {
+        replacement_text.push('&');
+    }
+    if let Some(lifetime) = self_param.lifetime() {
+        write!(replacement_text, "{lifetime} ").unwrap();
+    }
+    if self_param.amp_token().and(self_param.mut_token()).is_some() {
+        replacement_text.push_str("mut ");
+    }
 
-            let mut replacement_text = new_name;
-            replacement_text.push_str(": ");
-            match (self_param.amp_token(), self_param.mut_token()) {
-                (Some(_), None) => replacement_text.push('&'),
-                (Some(_), Some(_)) => replacement_text.push_str("&mut "),
-                (_, _) => (),
-            };
-            replacement_text.push_str(type_name.as_str());
+    replacement_text.push_str("Self");
 
-            Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))
-        }
-        None => {
-            cov_mark::hit!(rename_self_outside_of_methods);
-            let mut replacement_text = new_name;
-            replacement_text.push_str(": _");
-            Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))
-        }
-    }
+    Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))
 }
 
 #[cfg(test)]
@@ -2069,7 +2057,7 @@ impl Foo {
 struct Foo { i: i32 }
 
 impl Foo {
-    fn f(foo: &mut Foo) -> i32 {
+    fn f(foo: &mut Self) -> i32 {
         foo.i
     }
 }
@@ -2095,7 +2083,33 @@ impl Foo {
 struct Foo { i: i32 }
 
 impl Foo {
-    fn f(foo: Foo) -> i32 {
+    fn f(foo: Self) -> i32 {
+        foo.i
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn test_owned_self_to_parameter_with_lifetime() {
+        cov_mark::check!(rename_self_to_param);
+        check(
+            "foo",
+            r#"
+struct Foo<'a> { i: &'a i32 }
+
+impl<'a> Foo<'a> {
+    fn f(&'a $0self) -> i32 {
+        self.i
+    }
+}
+"#,
+            r#"
+struct Foo<'a> { i: &'a i32 }
+
+impl<'a> Foo<'a> {
+    fn f(foo: &'a Self) -> i32 {
         foo.i
     }
 }
@@ -2105,7 +2119,6 @@ impl Foo {
 
     #[test]
     fn test_self_outside_of_methods() {
-        cov_mark::check!(rename_self_outside_of_methods);
         check(
             "foo",
             r#"
@@ -2114,7 +2127,7 @@ fn f($0self) -> i32 {
 }
 "#,
             r#"
-fn f(foo: _) -> i32 {
+fn f(foo: Self) -> i32 {
     foo.i
 }
 "#,
@@ -2159,7 +2172,7 @@ impl Foo {
 struct Foo { i: i32 }
 
 impl Foo {
-    fn f(foo: &Foo) -> i32 {
+    fn f(foo: &Self) -> i32 {
         let self_var = 1;
         foo.i
     }
diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs
index 76656567e7f..ed8a91c39c0 100644
--- a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs
@@ -572,9 +572,7 @@ fn closure_expr(p: &mut Parser<'_>) -> CompletedMarker {
     // test closure_binder
     // fn main() { for<'a> || (); }
     if p.at(T![for]) {
-        let b = p.start();
         types::for_binder(p);
-        b.complete(p, CLOSURE_BINDER);
     }
     // test const_closure
     // fn main() { let cl = const || _ = 0; }
diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs
index 55c5dc400b9..cb1b59f6497 100644
--- a/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs
@@ -13,7 +13,7 @@ pub(super) fn opt_generic_param_list(p: &mut Parser<'_>) {
 
 // test_err generic_param_list_recover
 // fn f<T: Clone,, U:, V>() {}
-fn generic_param_list(p: &mut Parser<'_>) {
+pub(super) fn generic_param_list(p: &mut Parser<'_>) {
     assert!(p.at(T![<]));
     let m = p.start();
     delimited(
@@ -147,7 +147,15 @@ fn type_bound(p: &mut Parser<'_>) -> bool {
     let has_paren = p.eat(T!['(']);
     match p.current() {
         LIFETIME_IDENT => lifetime(p),
-        T![for] => types::for_type(p, false),
+        // test for_binder_bound
+        // fn foo<T: for<'a> [const] async Trait>() {}
+        T![for] => {
+            types::for_binder(p);
+            if path_type_bound(p).is_err() {
+                m.abandon(p);
+                return false;
+            }
+        }
         // test precise_capturing
         // fn captures<'a: 'a, 'b: 'b, T>() -> impl Sized + use<'b, T, Self> {}
 
@@ -180,44 +188,8 @@ fn type_bound(p: &mut Parser<'_>) -> bool {
             p.bump_any();
             types::for_type(p, false)
         }
-        current => {
-            match current {
-                T![?] => p.bump_any(),
-                T![~] => {
-                    p.bump_any();
-                    p.expect(T![const]);
-                }
-                T!['['] => {
-                    p.bump_any();
-                    p.expect(T![const]);
-                    p.expect(T![']']);
-                }
-                // test const_trait_bound
-                // const fn foo(_: impl const Trait) {}
-                T![const] => {
-                    p.bump_any();
-                }
-                // test async_trait_bound
-                // fn async_foo(_: impl async Fn(&i32)) {}
-                T![async] => {
-                    p.bump_any();
-                }
-                _ => (),
-            }
-            if paths::is_use_path_start(p) {
-                types::path_type_bounds(p, false);
-                // test_err type_bounds_macro_call_recovery
-                // fn foo<T: T![], T: T!, T: T!{}>() -> Box<T! + T!{}> {}
-                if p.at(T![!]) {
-                    let m = p.start();
-                    p.bump(T![!]);
-                    p.error("unexpected `!` in type path, macro calls are not allowed here");
-                    if p.at_ts(TokenSet::new(&[T!['{'], T!['['], T!['(']])) {
-                        items::token_tree(p);
-                    }
-                    m.complete(p, ERROR);
-                }
-            } else {
+        _ => {
+            if path_type_bound(p).is_err() {
                 m.abandon(p);
                 return false;
             }
@@ -231,6 +203,43 @@ fn type_bound(p: &mut Parser<'_>) -> bool {
     true
 }
 
+fn path_type_bound(p: &mut Parser<'_>) -> Result<(), ()> {
+    if p.eat(T![~]) {
+        p.expect(T![const]);
+    } else if p.eat(T!['[']) {
+        // test maybe_const_trait_bound
+        // const fn foo(_: impl [const] Trait) {}
+        p.expect(T![const]);
+        p.expect(T![']']);
+    } else {
+        // test const_trait_bound
+        // const fn foo(_: impl const Trait) {}
+        p.eat(T![const]);
+    }
+    // test async_trait_bound
+    // fn async_foo(_: impl async Fn(&i32)) {}
+    p.eat(T![async]);
+    p.eat(T![?]);
+
+    if paths::is_use_path_start(p) {
+        types::path_type_bounds(p, false);
+        // test_err type_bounds_macro_call_recovery
+        // fn foo<T: T![], T: T!, T: T!{}>() -> Box<T! + T!{}> {}
+        if p.at(T![!]) {
+            let m = p.start();
+            p.bump(T![!]);
+            p.error("unexpected `!` in type path, macro calls are not allowed here");
+            if p.at_ts(TokenSet::new(&[T!['{'], T!['['], T!['(']])) {
+                items::token_tree(p);
+            }
+            m.complete(p, ERROR);
+        }
+        Ok(())
+    } else {
+        Err(())
+    }
+}
+
 // test where_clause
 // fn foo()
 // where
diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/types.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/types.rs
index 908440b5d05..a7e97c5f850 100644
--- a/src/tools/rust-analyzer/crates/parser/src/grammar/types.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/grammar/types.rs
@@ -249,13 +249,14 @@ fn fn_ptr_type(p: &mut Parser<'_>) {
 }
 
 pub(super) fn for_binder(p: &mut Parser<'_>) {
-    assert!(p.at(T![for]));
+    let m = p.start();
     p.bump(T![for]);
     if p.at(T![<]) {
-        generic_params::opt_generic_param_list(p);
+        generic_params::generic_param_list(p);
     } else {
         p.error("expected `<`");
     }
+    m.complete(p, FOR_BINDER);
 }
 
 // test for_type
diff --git a/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs b/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs
index 12a13caa4d9..3a8041d2df9 100644
--- a/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs
@@ -185,7 +185,6 @@ pub enum SyntaxKind {
     BREAK_EXPR,
     CALL_EXPR,
     CAST_EXPR,
-    CLOSURE_BINDER,
     CLOSURE_EXPR,
     CONST,
     CONST_ARG,
@@ -203,6 +202,7 @@ pub enum SyntaxKind {
     FN_PTR_TYPE,
     FORMAT_ARGS_ARG,
     FORMAT_ARGS_EXPR,
+    FOR_BINDER,
     FOR_EXPR,
     FOR_TYPE,
     GENERIC_ARG_LIST,
@@ -358,7 +358,6 @@ impl SyntaxKind {
             | BREAK_EXPR
             | CALL_EXPR
             | CAST_EXPR
-            | CLOSURE_BINDER
             | CLOSURE_EXPR
             | CONST
             | CONST_ARG
@@ -376,6 +375,7 @@ impl SyntaxKind {
             | FN_PTR_TYPE
             | FORMAT_ARGS_ARG
             | FORMAT_ARGS_EXPR
+            | FOR_BINDER
             | FOR_EXPR
             | FOR_TYPE
             | GENERIC_ARG_LIST
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs b/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs
index cef7b0ee239..c642e1a3354 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs
+++ b/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs
@@ -253,6 +253,10 @@ mod ok {
         run_and_expect_no_errors("test_data/parser/inline/ok/fn_pointer_unnamed_arg.rs");
     }
     #[test]
+    fn for_binder_bound() {
+        run_and_expect_no_errors("test_data/parser/inline/ok/for_binder_bound.rs");
+    }
+    #[test]
     fn for_expr() { run_and_expect_no_errors("test_data/parser/inline/ok/for_expr.rs"); }
     #[test]
     fn for_range_from() {
@@ -402,6 +406,10 @@ mod ok {
     #[test]
     fn match_guard() { run_and_expect_no_errors("test_data/parser/inline/ok/match_guard.rs"); }
     #[test]
+    fn maybe_const_trait_bound() {
+        run_and_expect_no_errors("test_data/parser/inline/ok/maybe_const_trait_bound.rs");
+    }
+    #[test]
     fn metas() { run_and_expect_no_errors("test_data/parser/inline/ok/metas.rs"); }
     #[test]
     fn method_call_expr() {
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0024_many_type_parens.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0024_many_type_parens.rast
index 025c12e4c2a..2fd172539e4 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0024_many_type_parens.rast
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0024_many_type_parens.rast
@@ -37,7 +37,7 @@ SOURCE_FILE
           WHITESPACE " "
           TYPE_BOUND
             L_PAREN "("
-            FOR_TYPE
+            FOR_BINDER
               FOR_KW "for"
               GENERIC_PARAM_LIST
                 L_ANGLE "<"
@@ -45,18 +45,18 @@ SOURCE_FILE
                   LIFETIME
                     LIFETIME_IDENT "'a"
                 R_ANGLE ">"
-              WHITESPACE " "
-              PATH_TYPE
-                PATH
-                  PATH_SEGMENT
-                    NAME_REF
-                      IDENT "Trait"
-                    GENERIC_ARG_LIST
-                      L_ANGLE "<"
-                      LIFETIME_ARG
-                        LIFETIME
-                          LIFETIME_IDENT "'a"
-                      R_ANGLE ">"
+            WHITESPACE " "
+            PATH_TYPE
+              PATH
+                PATH_SEGMENT
+                  NAME_REF
+                    IDENT "Trait"
+                  GENERIC_ARG_LIST
+                    L_ANGLE "<"
+                    LIFETIME_ARG
+                      LIFETIME
+                        LIFETIME_IDENT "'a"
+                    R_ANGLE ">"
             R_PAREN ")"
       R_ANGLE ">"
     PARAM_LIST
@@ -124,7 +124,7 @@ SOURCE_FILE
               WHITESPACE " "
               TYPE_BOUND
                 L_PAREN "("
-                FOR_TYPE
+                FOR_BINDER
                   FOR_KW "for"
                   GENERIC_PARAM_LIST
                     L_ANGLE "<"
@@ -132,18 +132,18 @@ SOURCE_FILE
                       LIFETIME
                         LIFETIME_IDENT "'a"
                     R_ANGLE ">"
-                  WHITESPACE " "
-                  PATH_TYPE
-                    PATH
-                      PATH_SEGMENT
-                        NAME_REF
-                          IDENT "Trait"
-                        GENERIC_ARG_LIST
-                          L_ANGLE "<"
-                          LIFETIME_ARG
-                            LIFETIME
-                              LIFETIME_IDENT "'a"
-                          R_ANGLE ">"
+                WHITESPACE " "
+                PATH_TYPE
+                  PATH
+                    PATH_SEGMENT
+                      NAME_REF
+                        IDENT "Trait"
+                      GENERIC_ARG_LIST
+                        L_ANGLE "<"
+                        LIFETIME_ARG
+                          LIFETIME
+                            LIFETIME_IDENT "'a"
+                        R_ANGLE ">"
                 R_PAREN ")"
         ERROR
           R_ANGLE ">"
@@ -186,7 +186,7 @@ SOURCE_FILE
               TUPLE_EXPR
                 L_PAREN "("
                 CLOSURE_EXPR
-                  CLOSURE_BINDER
+                  FOR_BINDER
                     FOR_KW "for"
                     GENERIC_PARAM_LIST
                       L_ANGLE "<"
@@ -243,13 +243,14 @@ SOURCE_FILE
                           PAREN_TYPE
                             L_PAREN "("
                             FOR_TYPE
-                              FOR_KW "for"
-                              GENERIC_PARAM_LIST
-                                L_ANGLE "<"
-                                LIFETIME_PARAM
-                                  LIFETIME
-                                    LIFETIME_IDENT "'a"
-                                R_ANGLE ">"
+                              FOR_BINDER
+                                FOR_KW "for"
+                                GENERIC_PARAM_LIST
+                                  L_ANGLE "<"
+                                  LIFETIME_PARAM
+                                    LIFETIME
+                                      LIFETIME_IDENT "'a"
+                                  R_ANGLE ">"
                               WHITESPACE " "
                               PATH_TYPE
                                 PATH
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0027_incomplete_where_for.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0027_incomplete_where_for.rast
index 674c8d536ca..3768a55d530 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0027_incomplete_where_for.rast
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0027_incomplete_where_for.rast
@@ -12,13 +12,14 @@ SOURCE_FILE
       WHERE_KW "where"
       WHITESPACE " "
       WHERE_PRED
-        FOR_KW "for"
-        GENERIC_PARAM_LIST
-          L_ANGLE "<"
-          LIFETIME_PARAM
-            LIFETIME
-              LIFETIME_IDENT "'a"
-          R_ANGLE ">"
+        FOR_BINDER
+          FOR_KW "for"
+          GENERIC_PARAM_LIST
+            L_ANGLE "<"
+            LIFETIME_PARAM
+              LIFETIME
+                LIFETIME_IDENT "'a"
+            R_ANGLE ">"
     WHITESPACE "\n"
     BLOCK_EXPR
       STMT_LIST
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0043_unexpected_for_type.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0043_unexpected_for_type.rast
index cb4fb1642d9..9c4ee6f712a 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0043_unexpected_for_type.rast
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0043_unexpected_for_type.rast
@@ -8,13 +8,14 @@ SOURCE_FILE
     EQ "="
     WHITESPACE " "
     FOR_TYPE
-      FOR_KW "for"
-      GENERIC_PARAM_LIST
-        L_ANGLE "<"
-        LIFETIME_PARAM
-          LIFETIME
-            LIFETIME_IDENT "'a"
-        R_ANGLE ">"
+      FOR_BINDER
+        FOR_KW "for"
+        GENERIC_PARAM_LIST
+          L_ANGLE "<"
+          LIFETIME_PARAM
+            LIFETIME
+              LIFETIME_IDENT "'a"
+          R_ANGLE ">"
       WHITESPACE " "
       REF_TYPE
         AMP "&"
@@ -37,13 +38,14 @@ SOURCE_FILE
     EQ "="
     WHITESPACE " "
     FOR_TYPE
-      FOR_KW "for"
-      GENERIC_PARAM_LIST
-        L_ANGLE "<"
-        LIFETIME_PARAM
-          LIFETIME
-            LIFETIME_IDENT "'a"
-        R_ANGLE ">"
+      FOR_BINDER
+        FOR_KW "for"
+        GENERIC_PARAM_LIST
+          L_ANGLE "<"
+          LIFETIME_PARAM
+            LIFETIME
+              LIFETIME_IDENT "'a"
+          R_ANGLE ">"
       WHITESPACE " "
       TUPLE_TYPE
         L_PAREN "("
@@ -70,13 +72,14 @@ SOURCE_FILE
     EQ "="
     WHITESPACE " "
     FOR_TYPE
-      FOR_KW "for"
-      GENERIC_PARAM_LIST
-        L_ANGLE "<"
-        LIFETIME_PARAM
-          LIFETIME
-            LIFETIME_IDENT "'a"
-        R_ANGLE ">"
+      FOR_BINDER
+        FOR_KW "for"
+        GENERIC_PARAM_LIST
+          L_ANGLE "<"
+          LIFETIME_PARAM
+            LIFETIME
+              LIFETIME_IDENT "'a"
+          R_ANGLE ">"
       WHITESPACE " "
       SLICE_TYPE
         L_BRACK "["
@@ -97,22 +100,24 @@ SOURCE_FILE
     EQ "="
     WHITESPACE " "
     FOR_TYPE
-      FOR_KW "for"
-      GENERIC_PARAM_LIST
-        L_ANGLE "<"
-        LIFETIME_PARAM
-          LIFETIME
-            LIFETIME_IDENT "'a"
-        R_ANGLE ">"
-      WHITESPACE " "
-      FOR_TYPE
+      FOR_BINDER
         FOR_KW "for"
         GENERIC_PARAM_LIST
           L_ANGLE "<"
           LIFETIME_PARAM
             LIFETIME
-              LIFETIME_IDENT "'b"
+              LIFETIME_IDENT "'a"
           R_ANGLE ">"
+      WHITESPACE " "
+      FOR_TYPE
+        FOR_BINDER
+          FOR_KW "for"
+          GENERIC_PARAM_LIST
+            L_ANGLE "<"
+            LIFETIME_PARAM
+              LIFETIME
+                LIFETIME_IDENT "'b"
+            R_ANGLE ">"
         WHITESPACE " "
         FN_PTR_TYPE
           FN_KW "fn"
@@ -164,31 +169,34 @@ SOURCE_FILE
       WHERE_KW "where"
       WHITESPACE "\n    "
       WHERE_PRED
-        FOR_KW "for"
-        GENERIC_PARAM_LIST
-          L_ANGLE "<"
-          LIFETIME_PARAM
-            LIFETIME
-              LIFETIME_IDENT "'a"
-          R_ANGLE ">"
-        WHITESPACE " "
-        FOR_TYPE
+        FOR_BINDER
           FOR_KW "for"
           GENERIC_PARAM_LIST
             L_ANGLE "<"
             LIFETIME_PARAM
               LIFETIME
-                LIFETIME_IDENT "'b"
+                LIFETIME_IDENT "'a"
             R_ANGLE ">"
-          WHITESPACE " "
-          FOR_TYPE
+        WHITESPACE " "
+        FOR_TYPE
+          FOR_BINDER
             FOR_KW "for"
             GENERIC_PARAM_LIST
               L_ANGLE "<"
               LIFETIME_PARAM
                 LIFETIME
-                  LIFETIME_IDENT "'c"
+                  LIFETIME_IDENT "'b"
               R_ANGLE ">"
+          WHITESPACE " "
+          FOR_TYPE
+            FOR_BINDER
+              FOR_KW "for"
+              GENERIC_PARAM_LIST
+                L_ANGLE "<"
+                LIFETIME_PARAM
+                  LIFETIME
+                    LIFETIME_IDENT "'c"
+                R_ANGLE ">"
             WHITESPACE " "
             FN_PTR_TYPE
               FN_KW "fn"
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/closure_binder.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/closure_binder.rast
index c04dbe1ea0a..c96ccf7c7f1 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/closure_binder.rast
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/closure_binder.rast
@@ -14,7 +14,7 @@ SOURCE_FILE
         WHITESPACE " "
         EXPR_STMT
           CLOSURE_EXPR
-            CLOSURE_BINDER
+            FOR_BINDER
               FOR_KW "for"
               GENERIC_PARAM_LIST
                 L_ANGLE "<"
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/dyn_trait_type_weak.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/dyn_trait_type_weak.rast
index dcc66dc1e2b..6578809cb0e 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/dyn_trait_type_weak.rast
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/dyn_trait_type_weak.rast
@@ -103,7 +103,7 @@ SOURCE_FILE
       WHITESPACE " "
       TYPE_BOUND_LIST
         TYPE_BOUND
-          FOR_TYPE
+          FOR_BINDER
             FOR_KW "for"
             GENERIC_PARAM_LIST
               L_ANGLE "<"
@@ -111,12 +111,12 @@ SOURCE_FILE
                 LIFETIME
                   LIFETIME_IDENT "'a"
               R_ANGLE ">"
-            WHITESPACE " "
-            PATH_TYPE
-              PATH
-                PATH_SEGMENT
-                  NAME_REF
-                    IDENT "Path"
+          WHITESPACE " "
+          PATH_TYPE
+            PATH
+              PATH_SEGMENT
+                NAME_REF
+                  IDENT "Path"
     SEMICOLON ";"
   WHITESPACE "\n"
   TYPE_ALIAS
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/for_binder_bound.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/for_binder_bound.rast
new file mode 100644
index 00000000000..17dbbf30a7b
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/for_binder_bound.rast
@@ -0,0 +1,45 @@
+SOURCE_FILE
+  FN
+    FN_KW "fn"
+    WHITESPACE " "
+    NAME
+      IDENT "foo"
+    GENERIC_PARAM_LIST
+      L_ANGLE "<"
+      TYPE_PARAM
+        NAME
+          IDENT "T"
+        COLON ":"
+        WHITESPACE " "
+        TYPE_BOUND_LIST
+          TYPE_BOUND
+            FOR_BINDER
+              FOR_KW "for"
+              GENERIC_PARAM_LIST
+                L_ANGLE "<"
+                LIFETIME_PARAM
+                  LIFETIME
+                    LIFETIME_IDENT "'a"
+                R_ANGLE ">"
+            WHITESPACE " "
+            L_BRACK "["
+            CONST_KW "const"
+            R_BRACK "]"
+            WHITESPACE " "
+            ASYNC_KW "async"
+            WHITESPACE " "
+            PATH_TYPE
+              PATH
+                PATH_SEGMENT
+                  NAME_REF
+                    IDENT "Trait"
+      R_ANGLE ">"
+    PARAM_LIST
+      L_PAREN "("
+      R_PAREN ")"
+    WHITESPACE " "
+    BLOCK_EXPR
+      STMT_LIST
+        L_CURLY "{"
+        R_CURLY "}"
+  WHITESPACE "\n"
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/for_binder_bound.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/for_binder_bound.rs
new file mode 100644
index 00000000000..427cf558710
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/for_binder_bound.rs
@@ -0,0 +1 @@
+fn foo<T: for<'a> [const] async Trait>() {}
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/for_type.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/for_type.rast
index 7600457a9b8..58623058cae 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/for_type.rast
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/for_type.rast
@@ -8,13 +8,14 @@ SOURCE_FILE
     EQ "="
     WHITESPACE " "
     FOR_TYPE
-      FOR_KW "for"
-      GENERIC_PARAM_LIST
-        L_ANGLE "<"
-        LIFETIME_PARAM
-          LIFETIME
-            LIFETIME_IDENT "'a"
-        R_ANGLE ">"
+      FOR_BINDER
+        FOR_KW "for"
+        GENERIC_PARAM_LIST
+          L_ANGLE "<"
+          LIFETIME_PARAM
+            LIFETIME
+              LIFETIME_IDENT "'a"
+          R_ANGLE ">"
       WHITESPACE " "
       FN_PTR_TYPE
         FN_KW "fn"
@@ -39,13 +40,14 @@ SOURCE_FILE
     EQ "="
     WHITESPACE " "
     FOR_TYPE
-      FOR_KW "for"
-      GENERIC_PARAM_LIST
-        L_ANGLE "<"
-        LIFETIME_PARAM
-          LIFETIME
-            LIFETIME_IDENT "'a"
-        R_ANGLE ">"
+      FOR_BINDER
+        FOR_KW "for"
+        GENERIC_PARAM_LIST
+          L_ANGLE "<"
+          LIFETIME_PARAM
+            LIFETIME
+              LIFETIME_IDENT "'a"
+          R_ANGLE ">"
       WHITESPACE " "
       FN_PTR_TYPE
         UNSAFE_KW "unsafe"
@@ -86,13 +88,14 @@ SOURCE_FILE
     EQ "="
     WHITESPACE " "
     FOR_TYPE
-      FOR_KW "for"
-      GENERIC_PARAM_LIST
-        L_ANGLE "<"
-        LIFETIME_PARAM
-          LIFETIME
-            LIFETIME_IDENT "'a"
-        R_ANGLE ">"
+      FOR_BINDER
+        FOR_KW "for"
+        GENERIC_PARAM_LIST
+          L_ANGLE "<"
+          LIFETIME_PARAM
+            LIFETIME
+              LIFETIME_IDENT "'a"
+          R_ANGLE ">"
       WHITESPACE " "
       PATH_TYPE
         PATH
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/lambda_expr.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/lambda_expr.rast
index ea401d224e6..bf24a579124 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/lambda_expr.rast
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/lambda_expr.rast
@@ -202,7 +202,7 @@ SOURCE_FILE
         WHITESPACE "\n    "
         EXPR_STMT
           CLOSURE_EXPR
-            CLOSURE_BINDER
+            FOR_BINDER
               FOR_KW "for"
               GENERIC_PARAM_LIST
                 L_ANGLE "<"
@@ -223,7 +223,7 @@ SOURCE_FILE
         WHITESPACE "\n    "
         EXPR_STMT
           CLOSURE_EXPR
-            CLOSURE_BINDER
+            FOR_BINDER
               FOR_KW "for"
               GENERIC_PARAM_LIST
                 L_ANGLE "<"
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/maybe_const_trait_bound.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/maybe_const_trait_bound.rast
new file mode 100644
index 00000000000..8d12f814c2a
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/maybe_const_trait_bound.rast
@@ -0,0 +1,36 @@
+SOURCE_FILE
+  FN
+    CONST_KW "const"
+    WHITESPACE " "
+    FN_KW "fn"
+    WHITESPACE " "
+    NAME
+      IDENT "foo"
+    PARAM_LIST
+      L_PAREN "("
+      PARAM
+        WILDCARD_PAT
+          UNDERSCORE "_"
+        COLON ":"
+        WHITESPACE " "
+        IMPL_TRAIT_TYPE
+          IMPL_KW "impl"
+          WHITESPACE " "
+          TYPE_BOUND_LIST
+            TYPE_BOUND
+              L_BRACK "["
+              CONST_KW "const"
+              R_BRACK "]"
+              WHITESPACE " "
+              PATH_TYPE
+                PATH
+                  PATH_SEGMENT
+                    NAME_REF
+                      IDENT "Trait"
+      R_PAREN ")"
+    WHITESPACE " "
+    BLOCK_EXPR
+      STMT_LIST
+        L_CURLY "{"
+        R_CURLY "}"
+  WHITESPACE "\n"
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/maybe_const_trait_bound.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/maybe_const_trait_bound.rs
new file mode 100644
index 00000000000..e1da9206098
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/maybe_const_trait_bound.rs
@@ -0,0 +1 @@
+const fn foo(_: impl [const] Trait) {}
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/no_dyn_trait_leading_for.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/no_dyn_trait_leading_for.rast
index 30a2842e538..6afa0613f39 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/no_dyn_trait_leading_for.rast
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/no_dyn_trait_leading_for.rast
@@ -11,13 +11,14 @@ SOURCE_FILE
       TYPE_BOUND_LIST
         TYPE_BOUND
           FOR_TYPE
-            FOR_KW "for"
-            GENERIC_PARAM_LIST
-              L_ANGLE "<"
-              LIFETIME_PARAM
-                LIFETIME
-                  LIFETIME_IDENT "'a"
-              R_ANGLE ">"
+            FOR_BINDER
+              FOR_KW "for"
+              GENERIC_PARAM_LIST
+                L_ANGLE "<"
+                LIFETIME_PARAM
+                  LIFETIME
+                    LIFETIME_IDENT "'a"
+                R_ANGLE ">"
             WHITESPACE " "
             PATH_TYPE
               PATH
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/question_for_type_trait_bound.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/question_for_type_trait_bound.rast
index 56e2d1095d2..cb296153c8f 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/question_for_type_trait_bound.rast
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/question_for_type_trait_bound.rast
@@ -29,10 +29,11 @@ SOURCE_FILE
           TYPE_BOUND
             QUESTION "?"
             FOR_TYPE
-              FOR_KW "for"
-              GENERIC_PARAM_LIST
-                L_ANGLE "<"
-                R_ANGLE ">"
+              FOR_BINDER
+                FOR_KW "for"
+                GENERIC_PARAM_LIST
+                  L_ANGLE "<"
+                  R_ANGLE ">"
               WHITESPACE " "
               PATH_TYPE
                 PATH
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/where_pred_for.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/where_pred_for.rast
index 0cc365efbe6..b10b953f2fb 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/where_pred_for.rast
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/where_pred_for.rast
@@ -18,13 +18,14 @@ SOURCE_FILE
       WHERE_KW "where"
       WHITESPACE "\n   "
       WHERE_PRED
-        FOR_KW "for"
-        GENERIC_PARAM_LIST
-          L_ANGLE "<"
-          LIFETIME_PARAM
-            LIFETIME
-              LIFETIME_IDENT "'a"
-          R_ANGLE ">"
+        FOR_BINDER
+          FOR_KW "for"
+          GENERIC_PARAM_LIST
+            L_ANGLE "<"
+            LIFETIME_PARAM
+              LIFETIME
+                LIFETIME_IDENT "'a"
+            R_ANGLE ">"
         WHITESPACE " "
         PATH_TYPE
           PATH
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0032_where_for.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0032_where_for.rast
index 86f6af97c73..dcaf58f7f98 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0032_where_for.rast
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0032_where_for.rast
@@ -36,7 +36,7 @@ SOURCE_FILE
           PLUS "+"
           WHITESPACE " "
           TYPE_BOUND
-            FOR_TYPE
+            FOR_BINDER
               FOR_KW "for"
               GENERIC_PARAM_LIST
                 L_ANGLE "<"
@@ -44,18 +44,18 @@ SOURCE_FILE
                   LIFETIME
                     LIFETIME_IDENT "'de"
                 R_ANGLE ">"
-              WHITESPACE " "
-              PATH_TYPE
-                PATH
-                  PATH_SEGMENT
-                    NAME_REF
-                      IDENT "Deserialize"
-                    GENERIC_ARG_LIST
-                      L_ANGLE "<"
-                      LIFETIME_ARG
-                        LIFETIME
-                          LIFETIME_IDENT "'de"
-                      R_ANGLE ">"
+            WHITESPACE " "
+            PATH_TYPE
+              PATH
+                PATH_SEGMENT
+                  NAME_REF
+                    IDENT "Deserialize"
+                  GENERIC_ARG_LIST
+                    L_ANGLE "<"
+                    LIFETIME_ARG
+                      LIFETIME
+                        LIFETIME_IDENT "'de"
+                    R_ANGLE ">"
           WHITESPACE " "
           PLUS "+"
           WHITESPACE " "
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0067_where_for_pred.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0067_where_for_pred.rast
index 8bf1090f9cf..5cef4dff062 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0067_where_for_pred.rast
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0067_where_for_pred.rast
@@ -18,13 +18,14 @@ SOURCE_FILE
       WHERE_KW "where"
       WHITESPACE "\n    "
       WHERE_PRED
-        FOR_KW "for"
-        GENERIC_PARAM_LIST
-          L_ANGLE "<"
-          LIFETIME_PARAM
-            LIFETIME
-              LIFETIME_IDENT "'a"
-          R_ANGLE ">"
+        FOR_BINDER
+          FOR_KW "for"
+          GENERIC_PARAM_LIST
+            L_ANGLE "<"
+            LIFETIME_PARAM
+              LIFETIME
+                LIFETIME_IDENT "'a"
+            R_ANGLE ">"
         WHITESPACE " "
         PATH_TYPE
           PATH
@@ -81,13 +82,14 @@ SOURCE_FILE
       WHERE_KW "where"
       WHITESPACE "\n    "
       WHERE_PRED
-        FOR_KW "for"
-        GENERIC_PARAM_LIST
-          L_ANGLE "<"
-          LIFETIME_PARAM
-            LIFETIME
-              LIFETIME_IDENT "'a"
-          R_ANGLE ">"
+        FOR_BINDER
+          FOR_KW "for"
+          GENERIC_PARAM_LIST
+            L_ANGLE "<"
+            LIFETIME_PARAM
+              LIFETIME
+                LIFETIME_IDENT "'a"
+            R_ANGLE ">"
         WHITESPACE " "
         REF_TYPE
           AMP "&"
@@ -135,13 +137,14 @@ SOURCE_FILE
       WHERE_KW "where"
       WHITESPACE "\n    "
       WHERE_PRED
-        FOR_KW "for"
-        GENERIC_PARAM_LIST
-          L_ANGLE "<"
-          LIFETIME_PARAM
-            LIFETIME
-              LIFETIME_IDENT "'a"
-          R_ANGLE ">"
+        FOR_BINDER
+          FOR_KW "for"
+          GENERIC_PARAM_LIST
+            L_ANGLE "<"
+            LIFETIME_PARAM
+              LIFETIME
+                LIFETIME_IDENT "'a"
+            R_ANGLE ">"
         WHITESPACE " "
         PAREN_TYPE
           L_PAREN "("
@@ -206,13 +209,14 @@ SOURCE_FILE
       WHERE_KW "where"
       WHITESPACE "\n    "
       WHERE_PRED
-        FOR_KW "for"
-        GENERIC_PARAM_LIST
-          L_ANGLE "<"
-          LIFETIME_PARAM
-            LIFETIME
-              LIFETIME_IDENT "'a"
-          R_ANGLE ">"
+        FOR_BINDER
+          FOR_KW "for"
+          GENERIC_PARAM_LIST
+            L_ANGLE "<"
+            LIFETIME_PARAM
+              LIFETIME
+                LIFETIME_IDENT "'a"
+            R_ANGLE ">"
         WHITESPACE " "
         SLICE_TYPE
           L_BRACK "["
@@ -276,13 +280,14 @@ SOURCE_FILE
       WHERE_KW "where"
       WHITESPACE "\n    "
       WHERE_PRED
-        FOR_KW "for"
-        GENERIC_PARAM_LIST
-          L_ANGLE "<"
-          LIFETIME_PARAM
-            LIFETIME
-              LIFETIME_IDENT "'a"
-          R_ANGLE ">"
+        FOR_BINDER
+          FOR_KW "for"
+          GENERIC_PARAM_LIST
+            L_ANGLE "<"
+            LIFETIME_PARAM
+              LIFETIME
+                LIFETIME_IDENT "'a"
+            R_ANGLE ">"
         WHITESPACE " "
         PATH_TYPE
           PATH
@@ -349,22 +354,24 @@ SOURCE_FILE
       WHERE_KW "where"
       WHITESPACE "\n    "
       WHERE_PRED
-        FOR_KW "for"
-        GENERIC_PARAM_LIST
-          L_ANGLE "<"
-          LIFETIME_PARAM
-            LIFETIME
-              LIFETIME_IDENT "'a"
-          R_ANGLE ">"
-        WHITESPACE " "
-        FOR_TYPE
+        FOR_BINDER
           FOR_KW "for"
           GENERIC_PARAM_LIST
             L_ANGLE "<"
             LIFETIME_PARAM
               LIFETIME
-                LIFETIME_IDENT "'b"
+                LIFETIME_IDENT "'a"
             R_ANGLE ">"
+        WHITESPACE " "
+        FOR_TYPE
+          FOR_BINDER
+            FOR_KW "for"
+            GENERIC_PARAM_LIST
+              L_ANGLE "<"
+              LIFETIME_PARAM
+                LIFETIME
+                  LIFETIME_IDENT "'b"
+              R_ANGLE ">"
           WHITESPACE " "
           FN_PTR_TYPE
             FN_KW "fn"
diff --git a/src/tools/rust-analyzer/crates/project-model/Cargo.toml b/src/tools/rust-analyzer/crates/project-model/Cargo.toml
index 27fe9f79bbc..0dbb309a62a 100644
--- a/src/tools/rust-analyzer/crates/project-model/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/project-model/Cargo.toml
@@ -20,6 +20,7 @@ semver.workspace = true
 serde_json.workspace = true
 serde.workspace = true
 serde_derive.workspace = true
+temp-dir.workspace = true
 tracing.workspace = true
 triomphe.workspace = true
 la-arena.workspace = true
diff --git a/src/tools/rust-analyzer/crates/project-model/src/build_dependencies.rs b/src/tools/rust-analyzer/crates/project-model/src/build_dependencies.rs
index 499caa622c4..5bea74bed7e 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/build_dependencies.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/build_dependencies.rs
@@ -16,11 +16,13 @@ use la_arena::ArenaMap;
 use paths::{AbsPath, AbsPathBuf, Utf8PathBuf};
 use rustc_hash::{FxHashMap, FxHashSet};
 use serde::Deserialize as _;
+use stdx::never;
 use toolchain::Tool;
 
 use crate::{
     CargoConfig, CargoFeatures, CargoWorkspace, InvocationStrategy, ManifestPath, Package, Sysroot,
-    TargetKind, utf8_stdout,
+    TargetKind, cargo_config_file::make_lockfile_copy,
+    cargo_workspace::MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH, utf8_stdout,
 };
 
 /// Output of the build script and proc-macro building steps for a workspace.
@@ -30,6 +32,15 @@ pub struct WorkspaceBuildScripts {
     error: Option<String>,
 }
 
+#[derive(Debug, Clone, Default, PartialEq, Eq)]
+pub enum ProcMacroDylibPath {
+    Path(AbsPathBuf),
+    DylibNotFound,
+    NotProcMacro,
+    #[default]
+    NotBuilt,
+}
+
 /// Output of the build script and proc-macro building step for a concrete package.
 #[derive(Debug, Clone, Default, PartialEq, Eq)]
 pub(crate) struct BuildScriptOutput {
@@ -43,7 +54,7 @@ pub(crate) struct BuildScriptOutput {
     /// Directory where a build script might place its output.
     pub(crate) out_dir: Option<AbsPathBuf>,
     /// Path to the proc-macro library file if this package exposes proc-macros.
-    pub(crate) proc_macro_dylib_path: Option<AbsPathBuf>,
+    pub(crate) proc_macro_dylib_path: ProcMacroDylibPath,
 }
 
 impl BuildScriptOutput {
@@ -51,7 +62,10 @@ impl BuildScriptOutput {
         self.cfgs.is_empty()
             && self.envs.is_empty()
             && self.out_dir.is_none()
-            && self.proc_macro_dylib_path.is_none()
+            && matches!(
+                self.proc_macro_dylib_path,
+                ProcMacroDylibPath::NotBuilt | ProcMacroDylibPath::NotProcMacro
+            )
     }
 }
 
@@ -67,7 +81,7 @@ impl WorkspaceBuildScripts {
         let current_dir = workspace.workspace_root();
 
         let allowed_features = workspace.workspace_features();
-        let cmd = Self::build_command(
+        let (_guard, cmd) = Self::build_command(
             config,
             &allowed_features,
             workspace.manifest_path(),
@@ -88,7 +102,7 @@ impl WorkspaceBuildScripts {
     ) -> io::Result<Vec<WorkspaceBuildScripts>> {
         assert_eq!(config.invocation_strategy, InvocationStrategy::Once);
 
-        let cmd = Self::build_command(
+        let (_guard, cmd) = Self::build_command(
             config,
             &Default::default(),
             // This is not gonna be used anyways, so just construct a dummy here
@@ -126,6 +140,8 @@ impl WorkspaceBuildScripts {
             |package, cb| {
                 if let Some(&(package, workspace)) = by_id.get(package) {
                     cb(&workspaces[workspace][package].name, &mut res[workspace].outputs[package]);
+                } else {
+                    never!("Received compiler message for unknown package: {}", package);
                 }
             },
             progress,
@@ -140,12 +156,9 @@ impl WorkspaceBuildScripts {
         if tracing::enabled!(tracing::Level::INFO) {
             for (idx, workspace) in workspaces.iter().enumerate() {
                 for package in workspace.packages() {
-                    let package_build_data = &mut res[idx].outputs[package];
+                    let package_build_data: &mut BuildScriptOutput = &mut res[idx].outputs[package];
                     if !package_build_data.is_empty() {
-                        tracing::info!(
-                            "{}: {package_build_data:?}",
-                            workspace[package].manifest.parent(),
-                        );
+                        tracing::info!("{}: {package_build_data:?}", workspace[package].manifest,);
                     }
                 }
             }
@@ -198,10 +211,33 @@ impl WorkspaceBuildScripts {
                         let path = dir_entry.path();
                         let extension = path.extension()?;
                         if extension == std::env::consts::DLL_EXTENSION {
-                            let name = path.file_stem()?.to_str()?.split_once('-')?.0.to_owned();
-                            let path = AbsPathBuf::try_from(Utf8PathBuf::from_path_buf(path).ok()?)
-                                .ok()?;
-                            return Some((name, path));
+                            let name = path
+                                .file_stem()?
+                                .to_str()?
+                                .split_once('-')?
+                                .0
+                                .trim_start_matches("lib")
+                                .to_owned();
+                            let path = match Utf8PathBuf::from_path_buf(path) {
+                                Ok(path) => path,
+                                Err(path) => {
+                                    tracing::warn!(
+                                        "Proc-macro dylib path contains non-UTF8 characters: {:?}",
+                                        path.display()
+                                    );
+                                    return None;
+                                }
+                            };
+                            return match AbsPathBuf::try_from(path) {
+                                Ok(path) => Some((name, path)),
+                                Err(path) => {
+                                    tracing::error!(
+                                        "proc-macro dylib path is not absolute: {:?}",
+                                        path
+                                    );
+                                    None
+                                }
+                            };
                         }
                     }
                     None
@@ -209,28 +245,24 @@ impl WorkspaceBuildScripts {
                 .collect();
             for p in rustc.packages() {
                 let package = &rustc[p];
-                if package
-                    .targets
-                    .iter()
-                    .any(|&it| matches!(rustc[it].kind, TargetKind::Lib { is_proc_macro: true }))
-                {
-                    if let Some((_, path)) = proc_macro_dylibs
-                        .iter()
-                        .find(|(name, _)| *name.trim_start_matches("lib") == package.name)
-                    {
-                        bs.outputs[p].proc_macro_dylib_path = Some(path.clone());
+                bs.outputs[p].proc_macro_dylib_path =
+                    if package.targets.iter().any(|&it| {
+                        matches!(rustc[it].kind, TargetKind::Lib { is_proc_macro: true })
+                    }) {
+                        match proc_macro_dylibs.iter().find(|(name, _)| *name == package.name) {
+                            Some((_, path)) => ProcMacroDylibPath::Path(path.clone()),
+                            _ => ProcMacroDylibPath::DylibNotFound,
+                        }
+                    } else {
+                        ProcMacroDylibPath::NotProcMacro
                     }
-                }
             }
 
             if tracing::enabled!(tracing::Level::INFO) {
                 for package in rustc.packages() {
                     let package_build_data = &bs.outputs[package];
                     if !package_build_data.is_empty() {
-                        tracing::info!(
-                            "{}: {package_build_data:?}",
-                            rustc[package].manifest.parent(),
-                        );
+                        tracing::info!("{}: {package_build_data:?}", rustc[package].manifest,);
                     }
                 }
             }
@@ -263,6 +295,12 @@ impl WorkspaceBuildScripts {
             |package, cb| {
                 if let Some(&package) = by_id.get(package) {
                     cb(&workspace[package].name, &mut outputs[package]);
+                } else {
+                    never!(
+                        "Received compiler message for unknown package: {}\n {}",
+                        package,
+                        by_id.keys().join(", ")
+                    );
                 }
             },
             progress,
@@ -272,10 +310,7 @@ impl WorkspaceBuildScripts {
             for package in workspace.packages() {
                 let package_build_data = &outputs[package];
                 if !package_build_data.is_empty() {
-                    tracing::info!(
-                        "{}: {package_build_data:?}",
-                        workspace[package].manifest.parent(),
-                    );
+                    tracing::info!("{}: {package_build_data:?}", workspace[package].manifest,);
                 }
             }
         }
@@ -348,15 +383,23 @@ impl WorkspaceBuildScripts {
                             progress(format!(
                                 "building compile-time-deps: proc-macro {name} built"
                             ));
-                            if message.target.kind.contains(&cargo_metadata::TargetKind::ProcMacro)
+                            if data.proc_macro_dylib_path == ProcMacroDylibPath::NotBuilt {
+                                data.proc_macro_dylib_path = ProcMacroDylibPath::NotProcMacro;
+                            }
+                            if !matches!(data.proc_macro_dylib_path, ProcMacroDylibPath::Path(_))
+                                && message
+                                    .target
+                                    .kind
+                                    .contains(&cargo_metadata::TargetKind::ProcMacro)
                             {
-                                // Skip rmeta file
-                                if let Some(filename) =
-                                    message.filenames.iter().find(|file| is_dylib(file))
-                                {
-                                    let filename = AbsPath::assert(filename);
-                                    data.proc_macro_dylib_path = Some(filename.to_owned());
-                                }
+                                data.proc_macro_dylib_path =
+                                    match message.filenames.iter().find(|file| is_dylib(file)) {
+                                        Some(filename) => {
+                                            let filename = AbsPath::assert(filename);
+                                            ProcMacroDylibPath::Path(filename.to_owned())
+                                        }
+                                        None => ProcMacroDylibPath::DylibNotFound,
+                                    };
                             }
                         });
                     }
@@ -393,14 +436,15 @@ impl WorkspaceBuildScripts {
         current_dir: &AbsPath,
         sysroot: &Sysroot,
         toolchain: Option<&semver::Version>,
-    ) -> io::Result<Command> {
+    ) -> io::Result<(Option<temp_dir::TempDir>, Command)> {
         match config.run_build_script_command.as_deref() {
             Some([program, args @ ..]) => {
                 let mut cmd = toolchain::command(program, current_dir, &config.extra_env);
                 cmd.args(args);
-                Ok(cmd)
+                Ok((None, cmd))
             }
             _ => {
+                let mut requires_unstable_options = false;
                 let mut cmd = sysroot.tool(Tool::Cargo, current_dir, &config.extra_env);
 
                 cmd.args(["check", "--quiet", "--workspace", "--message-format=json"]);
@@ -416,7 +460,19 @@ impl WorkspaceBuildScripts {
                 if let Some(target) = &config.target {
                     cmd.args(["--target", target]);
                 }
-
+                let mut temp_dir_guard = None;
+                if toolchain
+                    .is_some_and(|v| *v >= MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH)
+                {
+                    let lockfile_path =
+                        <_ as AsRef<Utf8Path>>::as_ref(manifest_path).with_extension("lock");
+                    if let Some((temp_dir, target_lockfile)) = make_lockfile_copy(&lockfile_path) {
+                        requires_unstable_options = true;
+                        temp_dir_guard = Some(temp_dir);
+                        cmd.arg("--lockfile-path");
+                        cmd.arg(target_lockfile.as_str());
+                    }
+                }
                 match &config.features {
                     CargoFeatures::All => {
                         cmd.arg("--all-features");
@@ -438,6 +494,7 @@ impl WorkspaceBuildScripts {
                 }
 
                 if manifest_path.is_rust_manifest() {
+                    requires_unstable_options = true;
                     cmd.arg("-Zscript");
                 }
 
@@ -457,8 +514,7 @@ impl WorkspaceBuildScripts {
                     toolchain.is_some_and(|v| *v >= COMP_TIME_DEPS_MIN_TOOLCHAIN_VERSION);
 
                 if cargo_comp_time_deps_available {
-                    cmd.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly");
-                    cmd.arg("-Zunstable-options");
+                    requires_unstable_options = true;
                     cmd.arg("--compile-time-deps");
                     // we can pass this unconditionally, because we won't actually build the
                     // binaries, and as such, this will succeed even on targets without libtest
@@ -481,7 +537,11 @@ impl WorkspaceBuildScripts {
                         cmd.env("RA_RUSTC_WRAPPER", "1");
                     }
                 }
-                Ok(cmd)
+                if requires_unstable_options {
+                    cmd.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly");
+                    cmd.arg("-Zunstable-options");
+                }
+                Ok((temp_dir_guard, cmd))
             }
         }
     }
diff --git a/src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs b/src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs
index 7966f74df30..a1e7ed09232 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs
@@ -1,4 +1,5 @@
 //! Read `.cargo/config.toml` as a JSON object
+use paths::{Utf8Path, Utf8PathBuf};
 use rustc_hash::FxHashMap;
 use toolchain::Tool;
 
@@ -32,3 +33,24 @@ pub(crate) fn read(
 
     Some(json)
 }
+
+pub(crate) fn make_lockfile_copy(
+    lockfile_path: &Utf8Path,
+) -> Option<(temp_dir::TempDir, Utf8PathBuf)> {
+    let temp_dir = temp_dir::TempDir::with_prefix("rust-analyzer").ok()?;
+    let target_lockfile = temp_dir.path().join("Cargo.lock").try_into().ok()?;
+    match std::fs::copy(lockfile_path, &target_lockfile) {
+        Ok(_) => {
+            tracing::debug!("Copied lock file from `{}` to `{}`", lockfile_path, target_lockfile);
+            Some((temp_dir, target_lockfile))
+        }
+        // lockfile does not yet exist, so we can just create a new one in the temp dir
+        Err(e) if e.kind() == std::io::ErrorKind::NotFound => Some((temp_dir, target_lockfile)),
+        Err(e) => {
+            tracing::warn!(
+                "Failed to copy lock file from `{lockfile_path}` to `{target_lockfile}`: {e}",
+            );
+            None
+        }
+    }
+}
diff --git a/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs
index daadcd9d79a..e613fd590c7 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs
@@ -15,16 +15,18 @@ use span::Edition;
 use stdx::process::spawn_with_streaming_output;
 use toolchain::Tool;
 
+use crate::cargo_config_file::make_lockfile_copy;
 use crate::{CfgOverrides, InvocationStrategy};
 use crate::{ManifestPath, Sysroot};
 
-const MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH: semver::Version = semver::Version {
-    major: 1,
-    minor: 82,
-    patch: 0,
-    pre: semver::Prerelease::EMPTY,
-    build: semver::BuildMetadata::EMPTY,
-};
+pub(crate) const MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH: semver::Version =
+    semver::Version {
+        major: 1,
+        minor: 82,
+        patch: 0,
+        pre: semver::Prerelease::EMPTY,
+        build: semver::BuildMetadata::EMPTY,
+    };
 
 /// [`CargoWorkspace`] represents the logical structure of, well, a Cargo
 /// workspace. It pretty closely mirrors `cargo metadata` output.
@@ -245,7 +247,7 @@ pub enum TargetKind {
 }
 
 impl TargetKind {
-    fn new(kinds: &[cargo_metadata::TargetKind]) -> TargetKind {
+    pub fn new(kinds: &[cargo_metadata::TargetKind]) -> TargetKind {
         for kind in kinds {
             return match kind {
                 cargo_metadata::TargetKind::Bin => TargetKind::Bin,
@@ -552,7 +554,10 @@ impl CargoWorkspace {
 
 pub(crate) struct FetchMetadata {
     command: cargo_metadata::MetadataCommand,
+    #[expect(dead_code)]
+    manifest_path: ManifestPath,
     lockfile_path: Option<Utf8PathBuf>,
+    #[expect(dead_code)]
     kind: &'static str,
     no_deps: bool,
     no_deps_result: anyhow::Result<cargo_metadata::Metadata>,
@@ -596,25 +601,22 @@ impl FetchMetadata {
         }
         command.current_dir(current_dir);
 
-        let mut needs_nightly = false;
         let mut other_options = vec![];
         // cargo metadata only supports a subset of flags of what cargo usually accepts, and usually
         // the only relevant flags for metadata here are unstable ones, so we pass those along
         // but nothing else
         let mut extra_args = config.extra_args.iter();
         while let Some(arg) = extra_args.next() {
-            if arg == "-Z" {
-                if let Some(arg) = extra_args.next() {
-                    needs_nightly = true;
-                    other_options.push("-Z".to_owned());
-                    other_options.push(arg.to_owned());
-                }
+            if arg == "-Z"
+                && let Some(arg) = extra_args.next()
+            {
+                other_options.push("-Z".to_owned());
+                other_options.push(arg.to_owned());
             }
         }
 
         let mut lockfile_path = None;
         if cargo_toml.is_rust_manifest() {
-            needs_nightly = true;
             other_options.push("-Zscript".to_owned());
         } else if config
             .toolchain_version
@@ -632,10 +634,6 @@ impl FetchMetadata {
 
         command.other_options(other_options.clone());
 
-        if needs_nightly {
-            command.env("RUSTC_BOOTSTRAP", "1");
-        }
-
         // Pre-fetch basic metadata using `--no-deps`, which:
         // - avoids fetching registries like crates.io,
         // - skips dependency resolution and does not modify lockfiles,
@@ -655,7 +653,15 @@ impl FetchMetadata {
         }
         .with_context(|| format!("Failed to run `{cargo_command:?}`"));
 
-        Self { command, lockfile_path, kind: config.kind, no_deps, no_deps_result, other_options }
+        Self {
+            manifest_path: cargo_toml.clone(),
+            command,
+            lockfile_path,
+            kind: config.kind,
+            no_deps,
+            no_deps_result,
+            other_options,
+        }
     }
 
     pub(crate) fn no_deps_metadata(&self) -> Option<&cargo_metadata::Metadata> {
@@ -672,40 +678,34 @@ impl FetchMetadata {
         locked: bool,
         progress: &dyn Fn(String),
     ) -> anyhow::Result<(cargo_metadata::Metadata, Option<anyhow::Error>)> {
-        let Self { mut command, lockfile_path, kind, no_deps, no_deps_result, mut other_options } =
-            self;
+        _ = target_dir;
+        let Self {
+            mut command,
+            manifest_path: _,
+            lockfile_path,
+            kind: _,
+            no_deps,
+            no_deps_result,
+            mut other_options,
+        } = self;
 
         if no_deps {
             return no_deps_result.map(|m| (m, None));
         }
 
         let mut using_lockfile_copy = false;
-        // The manifest is a rust file, so this means its a script manifest
-        if let Some(lockfile) = lockfile_path {
-            let target_lockfile =
-                target_dir.join("rust-analyzer").join("metadata").join(kind).join("Cargo.lock");
-            match std::fs::copy(&lockfile, &target_lockfile) {
-                Ok(_) => {
-                    using_lockfile_copy = true;
-                    other_options.push("--lockfile-path".to_owned());
-                    other_options.push(target_lockfile.to_string());
-                }
-                Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
-                    // There exists no lockfile yet
-                    using_lockfile_copy = true;
-                    other_options.push("--lockfile-path".to_owned());
-                    other_options.push(target_lockfile.to_string());
-                }
-                Err(e) => {
-                    tracing::warn!(
-                        "Failed to copy lock file from `{lockfile}` to `{target_lockfile}`: {e}",
-                    );
-                }
-            }
+        let mut _temp_dir_guard;
+        if let Some(lockfile) = lockfile_path
+            && let Some((temp_dir, target_lockfile)) = make_lockfile_copy(&lockfile)
+        {
+            _temp_dir_guard = temp_dir;
+            other_options.push("--lockfile-path".to_owned());
+            other_options.push(target_lockfile.to_string());
+            using_lockfile_copy = true;
         }
-        if using_lockfile_copy {
+        if using_lockfile_copy || other_options.iter().any(|it| it.starts_with("-Z")) {
+            command.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly");
             other_options.push("-Zunstable-options".to_owned());
-            command.env("RUSTC_BOOTSTRAP", "1");
         }
         // No need to lock it if we copied the lockfile, we won't modify the original after all/
         // This way cargo cannot error out on us if the lockfile requires updating.
@@ -714,13 +714,11 @@ impl FetchMetadata {
         }
         command.other_options(other_options);
 
-        // FIXME: Fetching metadata is a slow process, as it might require
-        // calling crates.io. We should be reporting progress here, but it's
-        // unclear whether cargo itself supports it.
         progress("cargo metadata: started".to_owned());
 
         let res = (|| -> anyhow::Result<(_, _)> {
             let mut errored = false;
+            tracing::debug!("Running `{:?}`", command.cargo_command());
             let output =
                 spawn_with_streaming_output(command.cargo_command(), &mut |_| (), &mut |line| {
                     errored = errored || line.starts_with("error") || line.starts_with("warning");
diff --git a/src/tools/rust-analyzer/crates/project-model/src/lib.rs b/src/tools/rust-analyzer/crates/project-model/src/lib.rs
index 3bf3d06e6b1..d39781b1506 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/lib.rs
@@ -59,7 +59,7 @@ use paths::{AbsPath, AbsPathBuf, Utf8PathBuf};
 use rustc_hash::FxHashSet;
 
 pub use crate::{
-    build_dependencies::WorkspaceBuildScripts,
+    build_dependencies::{ProcMacroDylibPath, WorkspaceBuildScripts},
     cargo_workspace::{
         CargoConfig, CargoFeatures, CargoMetadataConfig, CargoWorkspace, Package, PackageData,
         PackageDependency, RustLibSource, Target, TargetData, TargetKind,
@@ -139,21 +139,22 @@ impl ProjectManifest {
         }
 
         fn find_in_parent_dirs(path: &AbsPath, target_file_name: &str) -> Option<ManifestPath> {
-            if path.file_name().unwrap_or_default() == target_file_name {
-                if let Ok(manifest) = ManifestPath::try_from(path.to_path_buf()) {
-                    return Some(manifest);
-                }
+            if path.file_name().unwrap_or_default() == target_file_name
+                && let Ok(manifest) = ManifestPath::try_from(path.to_path_buf())
+            {
+                return Some(manifest);
             }
 
             let mut curr = Some(path);
 
             while let Some(path) = curr {
                 let candidate = path.join(target_file_name);
-                if fs::metadata(&candidate).is_ok() {
-                    if let Ok(manifest) = ManifestPath::try_from(candidate) {
-                        return Some(manifest);
-                    }
+                if fs::metadata(&candidate).is_ok()
+                    && let Ok(manifest) = ManifestPath::try_from(candidate)
+                {
+                    return Some(manifest);
                 }
+
                 curr = path.parent();
             }
 
diff --git a/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs b/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs
index 9781c46737d..c0a5009afba 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs
@@ -143,12 +143,11 @@ impl Sysroot {
             Some(root) => {
                 // special case rustc, we can look that up directly in the sysroot's bin folder
                 // as it should never invoke another cargo binary
-                if let Tool::Rustc = tool {
-                    if let Some(path) =
+                if let Tool::Rustc = tool
+                    && let Some(path) =
                         probe_for_binary(root.join("bin").join(Tool::Rustc.name()).into())
-                    {
-                        return toolchain::command(path, current_dir, envs);
-                    }
+                {
+                    return toolchain::command(path, current_dir, envs);
                 }
 
                 let mut cmd = toolchain::command(tool.prefer_proxy(), current_dir, envs);
@@ -291,29 +290,26 @@ impl Sysroot {
 
     pub fn set_workspace(&mut self, workspace: RustLibSrcWorkspace) {
         self.workspace = workspace;
-        if self.error.is_none() {
-            if let Some(src_root) = &self.rust_lib_src_root {
-                let has_core = match &self.workspace {
-                    RustLibSrcWorkspace::Workspace(ws) => {
-                        ws.packages().any(|p| ws[p].name == "core")
-                    }
-                    RustLibSrcWorkspace::Json(project_json) => project_json
-                        .crates()
-                        .filter_map(|(_, krate)| krate.display_name.clone())
-                        .any(|name| name.canonical_name().as_str() == "core"),
-                    RustLibSrcWorkspace::Stitched(stitched) => stitched.by_name("core").is_some(),
-                    RustLibSrcWorkspace::Empty => true,
+        if self.error.is_none()
+            && let Some(src_root) = &self.rust_lib_src_root
+        {
+            let has_core = match &self.workspace {
+                RustLibSrcWorkspace::Workspace(ws) => ws.packages().any(|p| ws[p].name == "core"),
+                RustLibSrcWorkspace::Json(project_json) => project_json
+                    .crates()
+                    .filter_map(|(_, krate)| krate.display_name.clone())
+                    .any(|name| name.canonical_name().as_str() == "core"),
+                RustLibSrcWorkspace::Stitched(stitched) => stitched.by_name("core").is_some(),
+                RustLibSrcWorkspace::Empty => true,
+            };
+            if !has_core {
+                let var_note = if env::var_os("RUST_SRC_PATH").is_some() {
+                    " (env var `RUST_SRC_PATH` is set and may be incorrect, try unsetting it)"
+                } else {
+                    ", try running `rustup component add rust-src` to possibly fix this"
                 };
-                if !has_core {
-                    let var_note = if env::var_os("RUST_SRC_PATH").is_some() {
-                        " (env var `RUST_SRC_PATH` is set and may be incorrect, try unsetting it)"
-                    } else {
-                        ", try running `rustup component add rust-src` to possibly fix this"
-                    };
-                    self.error = Some(format!(
-                        "sysroot at `{src_root}` is missing a `core` library{var_note}",
-                    ));
-                }
+                self.error =
+                    Some(format!("sysroot at `{src_root}` is missing a `core` library{var_note}",));
             }
         }
     }
diff --git a/src/tools/rust-analyzer/crates/project-model/src/toolchain_info/rustc_cfg.rs b/src/tools/rust-analyzer/crates/project-model/src/toolchain_info/rustc_cfg.rs
index 6e06e88bf7a..ab69c8e0e4a 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/toolchain_info/rustc_cfg.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/toolchain_info/rustc_cfg.rs
@@ -65,6 +65,7 @@ fn rustc_print_cfg(
     let (sysroot, current_dir) = match config {
         QueryConfig::Cargo(sysroot, cargo_toml, _) => {
             let mut cmd = sysroot.tool(Tool::Cargo, cargo_toml.parent(), extra_env);
+            cmd.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly");
             cmd.args(["rustc", "-Z", "unstable-options"]).args(RUSTC_ARGS);
             if let Some(target) = target {
                 cmd.args(["--target", target]);
diff --git a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs
index 677f29e3c60..5b36e10fd69 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs
@@ -24,7 +24,7 @@ use crate::{
     CargoConfig, CargoWorkspace, CfgOverrides, InvocationStrategy, ManifestPath, Package,
     ProjectJson, ProjectManifest, RustSourceWorkspaceConfig, Sysroot, TargetData, TargetKind,
     WorkspaceBuildScripts,
-    build_dependencies::BuildScriptOutput,
+    build_dependencies::{BuildScriptOutput, ProcMacroDylibPath},
     cargo_config_file,
     cargo_workspace::{CargoMetadataConfig, DepKind, FetchMetadata, PackageData, RustLibSource},
     env::{cargo_config_env, inject_cargo_env, inject_cargo_package_env, inject_rustc_tool_env},
@@ -424,12 +424,12 @@ impl ProjectWorkspace {
             sysroot.set_workspace(loaded_sysroot);
         }
 
-        if !cargo.requires_rustc_private() {
-            if let Err(e) = &mut rustc {
-                // We don't need the rustc sources here,
-                // so just discard the error.
-                _ = e.take();
-            }
+        if !cargo.requires_rustc_private()
+            && let Err(e) = &mut rustc
+        {
+            // We don't need the rustc sources here,
+            // so just discard the error.
+            _ = e.take();
         }
 
         Ok(ProjectWorkspace {
@@ -1163,17 +1163,15 @@ fn project_json_to_crate_graph(
                     crate = display_name.as_ref().map(|name| name.canonical_name().as_str()),
                     "added root to crate graph"
                 );
-                if *is_proc_macro {
-                    if let Some(path) = proc_macro_dylib_path.clone() {
-                        let node = Ok((
-                            display_name
-                                .as_ref()
-                                .map(|it| it.canonical_name().as_str().to_owned())
-                                .unwrap_or_else(|| format!("crate{}", idx.0)),
-                            path,
-                        ));
-                        proc_macros.insert(crate_graph_crate_id, node);
-                    }
+                if *is_proc_macro && let Some(path) = proc_macro_dylib_path.clone() {
+                    let node = Ok((
+                        display_name
+                            .as_ref()
+                            .map(|it| it.canonical_name().as_str().to_owned())
+                            .unwrap_or_else(|| format!("crate{}", idx.0)),
+                        path,
+                    ));
+                    proc_macros.insert(crate_graph_crate_id, node);
                 }
                 (idx, crate_graph_crate_id)
             },
@@ -1318,16 +1316,17 @@ fn cargo_to_crate_graph(
             public_deps.add_to_crate_graph(crate_graph, from);
 
             // Add dep edge of all targets to the package's lib target
-            if let Some((to, name)) = lib_tgt.clone() {
-                if to != from && kind != TargetKind::BuildScript {
-                    // (build script can not depend on its library target)
-
-                    // For root projects with dashes in their name,
-                    // cargo metadata does not do any normalization,
-                    // so we do it ourselves currently
-                    let name = CrateName::normalize_dashes(&name);
-                    add_dep(crate_graph, from, name, to);
-                }
+            if let Some((to, name)) = lib_tgt.clone()
+                && to != from
+                && kind != TargetKind::BuildScript
+            {
+                // (build script can not depend on its library target)
+
+                // For root projects with dashes in their name,
+                // cargo metadata does not do any normalization,
+                // so we do it ourselves currently
+                let name = CrateName::normalize_dashes(&name);
+                add_dep(crate_graph, from, name, to);
             }
         }
     }
@@ -1638,9 +1637,19 @@ fn add_target_crate_root(
         let proc_macro = match build_data {
             Some((BuildScriptOutput { proc_macro_dylib_path, .. }, has_errors)) => {
                 match proc_macro_dylib_path {
-                    Some(path) => Ok((cargo_name.to_owned(), path.clone())),
-                    None if has_errors => Err(ProcMacroLoadingError::FailedToBuild),
-                    None => Err(ProcMacroLoadingError::MissingDylibPath),
+                    ProcMacroDylibPath::Path(path) => Ok((cargo_name.to_owned(), path.clone())),
+                    ProcMacroDylibPath::NotBuilt => Err(ProcMacroLoadingError::NotYetBuilt),
+                    ProcMacroDylibPath::NotProcMacro | ProcMacroDylibPath::DylibNotFound
+                        if has_errors =>
+                    {
+                        Err(ProcMacroLoadingError::FailedToBuild)
+                    }
+                    ProcMacroDylibPath::NotProcMacro => {
+                        Err(ProcMacroLoadingError::ExpectedProcMacroArtifact)
+                    }
+                    ProcMacroDylibPath::DylibNotFound => {
+                        Err(ProcMacroLoadingError::MissingDylibPath)
+                    }
                 }
             }
             None => Err(ProcMacroLoadingError::NotYetBuilt),
@@ -1905,7 +1914,8 @@ fn cargo_target_dir(
     meta.manifest_path(manifest);
     // `--no-deps` doesn't (over)write lockfiles as it doesn't do any package resolve.
     // So we can use it to get `target_directory` before copying lockfiles
-    let mut other_options = vec!["--no-deps".to_owned()];
+    meta.no_deps();
+    let mut other_options = vec![];
     if manifest.is_rust_manifest() {
         meta.env("RUSTC_BOOTSTRAP", "1");
         other_options.push("-Zscript".to_owned());
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs
index fc89f486f84..4f75d14834c 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -656,22 +656,26 @@ impl flags::AnalysisStats {
         let mut sw = self.stop_watch();
         let mut all = 0;
         let mut fail = 0;
-        for &body in bodies {
-            if matches!(body, DefWithBody::Variant(_)) {
+        for &body_id in bodies {
+            if matches!(body_id, DefWithBody::Variant(_)) {
+                continue;
+            }
+            let module = body_id.module(db);
+            if !self.should_process(db, body_id, module) {
                 continue;
             }
+
             all += 1;
-            let Err(e) = db.mir_body(body.into()) else {
+            let Err(e) = db.mir_body(body_id.into()) else {
                 continue;
             };
             if verbosity.is_spammy() {
-                let full_name = body
-                    .module(db)
+                let full_name = module
                     .path_to_root(db)
                     .into_iter()
                     .rev()
                     .filter_map(|it| it.name(db))
-                    .chain(Some(body.name(db).unwrap_or_else(Name::missing)))
+                    .chain(Some(body_id.name(db).unwrap_or_else(Name::missing)))
                     .map(|it| it.display(db, Edition::LATEST).to_string())
                     .join("::");
                 bar.println(format!("Mir body for {full_name} failed due {e:?}"));
@@ -727,26 +731,9 @@ impl flags::AnalysisStats {
             let name = body_id.name(db).unwrap_or_else(Name::missing);
             let module = body_id.module(db);
             let display_target = module.krate().to_display_target(db);
-            let full_name = move || {
-                module
-                    .krate()
-                    .display_name(db)
-                    .map(|it| it.canonical_name().as_str().to_owned())
-                    .into_iter()
-                    .chain(
-                        module
-                            .path_to_root(db)
-                            .into_iter()
-                            .filter_map(|it| it.name(db))
-                            .rev()
-                            .chain(Some(body_id.name(db).unwrap_or_else(Name::missing)))
-                            .map(|it| it.display(db, Edition::LATEST).to_string()),
-                    )
-                    .join("::")
-            };
             if let Some(only_name) = self.only.as_deref() {
                 if name.display(db, Edition::LATEST).to_string() != only_name
-                    && full_name() != only_name
+                    && full_name(db, body_id, module) != only_name
                 {
                     continue;
                 }
@@ -763,12 +750,17 @@ impl flags::AnalysisStats {
                         let original_file = src.file_id.original_file(db);
                         let path = vfs.file_path(original_file.file_id(db));
                         let syntax_range = src.text_range();
-                        format!("processing: {} ({} {:?})", full_name(), path, syntax_range)
+                        format!(
+                            "processing: {} ({} {:?})",
+                            full_name(db, body_id, module),
+                            path,
+                            syntax_range
+                        )
                     } else {
-                        format!("processing: {}", full_name())
+                        format!("processing: {}", full_name(db, body_id, module))
                     }
                 } else {
-                    format!("processing: {}", full_name())
+                    format!("processing: {}", full_name(db, body_id, module))
                 }
             };
             if verbosity.is_spammy() {
@@ -781,9 +773,11 @@ impl flags::AnalysisStats {
                 Ok(inference_result) => inference_result,
                 Err(p) => {
                     if let Some(s) = p.downcast_ref::<&str>() {
-                        eprintln!("infer panicked for {}: {}", full_name(), s);
+                        eprintln!("infer panicked for {}: {}", full_name(db, body_id, module), s);
                     } else if let Some(s) = p.downcast_ref::<String>() {
-                        eprintln!("infer panicked for {}: {}", full_name(), s);
+                        eprintln!("infer panicked for {}: {}", full_name(db, body_id, module), s);
+                    } else {
+                        eprintln!("infer panicked for {}", full_name(db, body_id, module));
                     }
                     panics += 1;
                     bar.inc(1);
@@ -890,7 +884,7 @@ impl flags::AnalysisStats {
             if verbosity.is_spammy() {
                 bar.println(format!(
                     "In {}: {} exprs, {} unknown, {} partial",
-                    full_name(),
+                    full_name(db, body_id, module),
                     num_exprs - previous_exprs,
                     num_exprs_unknown - previous_unknown,
                     num_exprs_partially_unknown - previous_partially_unknown
@@ -993,7 +987,7 @@ impl flags::AnalysisStats {
             if verbosity.is_spammy() {
                 bar.println(format!(
                     "In {}: {} pats, {} unknown, {} partial",
-                    full_name(),
+                    full_name(db, body_id, module),
                     num_pats - previous_pats,
                     num_pats_unknown - previous_unknown,
                     num_pats_partially_unknown - previous_partially_unknown
@@ -1049,34 +1043,8 @@ impl flags::AnalysisStats {
         bar.tick();
         for &body_id in bodies {
             let module = body_id.module(db);
-            let full_name = move || {
-                module
-                    .krate()
-                    .display_name(db)
-                    .map(|it| it.canonical_name().as_str().to_owned())
-                    .into_iter()
-                    .chain(
-                        module
-                            .path_to_root(db)
-                            .into_iter()
-                            .filter_map(|it| it.name(db))
-                            .rev()
-                            .chain(Some(body_id.name(db).unwrap_or_else(Name::missing)))
-                            .map(|it| it.display(db, Edition::LATEST).to_string()),
-                    )
-                    .join("::")
-            };
-            if let Some(only_name) = self.only.as_deref() {
-                if body_id
-                    .name(db)
-                    .unwrap_or_else(Name::missing)
-                    .display(db, Edition::LATEST)
-                    .to_string()
-                    != only_name
-                    && full_name() != only_name
-                {
-                    continue;
-                }
+            if !self.should_process(db, body_id, module) {
+                continue;
             }
             let msg = move || {
                 if verbosity.is_verbose() {
@@ -1090,12 +1058,17 @@ impl flags::AnalysisStats {
                         let original_file = src.file_id.original_file(db);
                         let path = vfs.file_path(original_file.file_id(db));
                         let syntax_range = src.text_range();
-                        format!("processing: {} ({} {:?})", full_name(), path, syntax_range)
+                        format!(
+                            "processing: {} ({} {:?})",
+                            full_name(db, body_id, module),
+                            path,
+                            syntax_range
+                        )
                     } else {
-                        format!("processing: {}", full_name())
+                        format!("processing: {}", full_name(db, body_id, module))
                     }
                 } else {
-                    format!("processing: {}", full_name())
+                    format!("processing: {}", full_name(db, body_id, module))
                 }
             };
             if verbosity.is_spammy() {
@@ -1205,11 +1178,42 @@ impl flags::AnalysisStats {
         eprintln!("{:<20} {} ({} files)", "IDE:", ide_time, file_ids.len());
     }
 
+    fn should_process(&self, db: &RootDatabase, body_id: DefWithBody, module: hir::Module) -> bool {
+        if let Some(only_name) = self.only.as_deref() {
+            let name = body_id.name(db).unwrap_or_else(Name::missing);
+
+            if name.display(db, Edition::LATEST).to_string() != only_name
+                && full_name(db, body_id, module) != only_name
+            {
+                return false;
+            }
+        }
+        true
+    }
+
     fn stop_watch(&self) -> StopWatch {
         StopWatch::start()
     }
 }
 
+fn full_name(db: &RootDatabase, body_id: DefWithBody, module: hir::Module) -> String {
+    module
+        .krate()
+        .display_name(db)
+        .map(|it| it.canonical_name().as_str().to_owned())
+        .into_iter()
+        .chain(
+            module
+                .path_to_root(db)
+                .into_iter()
+                .filter_map(|it| it.name(db))
+                .rev()
+                .chain(Some(body_id.name(db).unwrap_or_else(Name::missing)))
+                .map(|it| it.display(db, Edition::LATEST).to_string()),
+        )
+        .join("::")
+}
+
 fn location_csv_expr(db: &RootDatabase, vfs: &Vfs, sm: &BodySourceMap, expr_id: ExprId) -> String {
     let src = match sm.expr_syntax(expr_id) {
         Ok(s) => s,
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs
index 51d4c29aa74..9456fd8809b 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs
@@ -2162,6 +2162,7 @@ impl Config {
             extra_test_bin_args: self.runnables_extraTestBinaryArgs(source_root).clone(),
             extra_env: self.extra_env(source_root).clone(),
             target_dir: self.target_dir_from_config(source_root),
+            set_test: true,
         }
     }
 
@@ -2219,6 +2220,7 @@ impl Config {
                     extra_test_bin_args: self.runnables_extraTestBinaryArgs(source_root).clone(),
                     extra_env: self.check_extra_env(source_root),
                     target_dir: self.target_dir_from_config(source_root),
+                    set_test: *self.cfg_setTest(source_root),
                 },
                 ansi_color_output: self.color_diagnostic_output(),
             },
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs
index 91d37bd7c9e..512ce0b9de3 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs
@@ -31,6 +31,7 @@ pub(crate) enum InvocationStrategy {
 pub(crate) struct CargoOptions {
     pub(crate) target_tuples: Vec<String>,
     pub(crate) all_targets: bool,
+    pub(crate) set_test: bool,
     pub(crate) no_default_features: bool,
     pub(crate) all_features: bool,
     pub(crate) features: Vec<String>,
@@ -54,7 +55,13 @@ impl CargoOptions {
             cmd.args(["--target", target.as_str()]);
         }
         if self.all_targets {
-            cmd.arg("--all-targets");
+            if self.set_test {
+                cmd.arg("--all-targets");
+            } else {
+                // No --benches unfortunately, as this implies --tests (see https://github.com/rust-lang/cargo/issues/6454),
+                // and users setting `cfg.seTest = false` probably prefer disabling benches than enabling tests.
+                cmd.args(["--lib", "--bins", "--examples"]);
+            }
         }
         if self.all_features {
             cmd.arg("--all-features");
@@ -104,7 +111,18 @@ impl fmt::Display for FlycheckConfig {
         match self {
             FlycheckConfig::CargoCommand { command, .. } => write!(f, "cargo {command}"),
             FlycheckConfig::CustomCommand { command, args, .. } => {
-                write!(f, "{command} {}", args.join(" "))
+                // Don't show `my_custom_check --foo $saved_file` literally to the user, as it
+                // looks like we've forgotten to substitute $saved_file.
+                //
+                // Instead, show `my_custom_check --foo ...`. The
+                // actual path is often too long to be worth showing
+                // in the IDE (e.g. in the VS Code status bar).
+                let display_args = args
+                    .iter()
+                    .map(|arg| if arg == SAVED_FILE_PLACEHOLDER { "..." } else { arg })
+                    .collect::<Vec<_>>();
+
+                write!(f, "{command} {}", display_args.join(" "))
             }
         }
     }
diff --git a/src/tools/rust-analyzer/crates/syntax/rust.ungram b/src/tools/rust-analyzer/crates/syntax/rust.ungram
index 4cbc88cfb5e..6d8a360d715 100644
--- a/src/tools/rust-analyzer/crates/syntax/rust.ungram
+++ b/src/tools/rust-analyzer/crates/syntax/rust.ungram
@@ -101,7 +101,7 @@ WhereClause =
   'where' predicates:(WherePred (',' WherePred)* ','?)
 
 WherePred =
-  ('for' GenericParamList)? (Lifetime | Type) ':' TypeBoundList?
+  ForBinder? (Lifetime | Type) ':' TypeBoundList?
 
 
 //*************************//
@@ -534,10 +534,10 @@ FieldExpr =
   Attr* Expr '.' NameRef
 
 ClosureExpr =
-  Attr* ClosureBinder? 'const'? 'static'? 'async'? 'gen'? 'move'?  ParamList RetType?
+  Attr* ForBinder? 'const'? 'static'? 'async'? 'gen'? 'move'?  ParamList RetType?
   body:Expr
 
-ClosureBinder =
+ForBinder =
   'for' GenericParamList
 
 IfExpr =
@@ -658,7 +658,7 @@ FnPtrType =
   'const'? 'async'? 'unsafe'? Abi? 'fn' ParamList RetType?
 
 ForType =
-  'for' GenericParamList Type
+  ForBinder Type
 
 ImplTraitType =
   'impl' TypeBoundList
@@ -671,7 +671,7 @@ TypeBoundList =
 
 TypeBound =
   Lifetime
-| ('~' 'const' | '[' 'const' ']' | 'const')? 'async'? '?'? Type
+| ForBinder? ('~' 'const' | '[' 'const' ']' | 'const')? 'async'? '?'? Type
 | 'use' UseBoundGenericArgs
 
 UseBoundGenericArgs =
diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast.rs b/src/tools/rust-analyzer/crates/syntax/src/ast.rs
index d787fd076fc..a9aeeedb654 100644
--- a/src/tools/rust-analyzer/crates/syntax/src/ast.rs
+++ b/src/tools/rust-analyzer/crates/syntax/src/ast.rs
@@ -393,8 +393,7 @@ where
     let pred = predicates.next().unwrap();
     let mut bounds = pred.type_bound_list().unwrap().bounds();
 
-    assert!(pred.for_token().is_none());
-    assert!(pred.generic_param_list().is_none());
+    assert!(pred.for_binder().is_none());
     assert_eq!("T", pred.ty().unwrap().syntax().text().to_string());
     assert_bound("Clone", bounds.next());
     assert_bound("Copy", bounds.next());
@@ -432,8 +431,10 @@ where
     let pred = predicates.next().unwrap();
     let mut bounds = pred.type_bound_list().unwrap().bounds();
 
-    assert!(pred.for_token().is_some());
-    assert_eq!("<'a>", pred.generic_param_list().unwrap().syntax().text().to_string());
+    assert_eq!(
+        "<'a>",
+        pred.for_binder().unwrap().generic_param_list().unwrap().syntax().text().to_string()
+    );
     assert_eq!("F", pred.ty().unwrap().syntax().text().to_string());
     assert_bound("Fn(&'a str)", bounds.next());
 }
diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/edit.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/edit.rs
index 37cb4a434f3..d97fdec524f 100644
--- a/src/tools/rust-analyzer/crates/syntax/src/ast/edit.rs
+++ b/src/tools/rust-analyzer/crates/syntax/src/ast/edit.rs
@@ -6,9 +6,12 @@ use std::{fmt, iter, ops};
 use crate::{
     AstToken, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxToken,
     ast::{self, AstNode, make},
+    syntax_editor::{SyntaxEditor, SyntaxMappingBuilder},
     ted,
 };
 
+use super::syntax_factory::SyntaxFactory;
+
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub struct IndentLevel(pub u8);
 
@@ -95,6 +98,24 @@ impl IndentLevel {
         }
     }
 
+    pub(super) fn clone_increase_indent(self, node: &SyntaxNode) -> SyntaxNode {
+        let node = node.clone_subtree();
+        let mut editor = SyntaxEditor::new(node.clone());
+        let tokens = node
+            .preorder_with_tokens()
+            .filter_map(|event| match event {
+                rowan::WalkEvent::Leave(NodeOrToken::Token(it)) => Some(it),
+                _ => None,
+            })
+            .filter_map(ast::Whitespace::cast)
+            .filter(|ws| ws.text().contains('\n'));
+        for ws in tokens {
+            let new_ws = make::tokens::whitespace(&format!("{}{self}", ws.syntax()));
+            editor.replace(ws.syntax(), &new_ws);
+        }
+        editor.finish().new_root().clone()
+    }
+
     pub(super) fn decrease_indent(self, node: &SyntaxNode) {
         let tokens = node.preorder_with_tokens().filter_map(|event| match event {
             rowan::WalkEvent::Leave(NodeOrToken::Token(it)) => Some(it),
@@ -111,36 +132,54 @@ impl IndentLevel {
             }
         }
     }
+
+    pub(super) fn clone_decrease_indent(self, node: &SyntaxNode) -> SyntaxNode {
+        let node = node.clone_subtree();
+        let mut editor = SyntaxEditor::new(node.clone());
+        let tokens = node
+            .preorder_with_tokens()
+            .filter_map(|event| match event {
+                rowan::WalkEvent::Leave(NodeOrToken::Token(it)) => Some(it),
+                _ => None,
+            })
+            .filter_map(ast::Whitespace::cast)
+            .filter(|ws| ws.text().contains('\n'));
+        for ws in tokens {
+            let new_ws =
+                make::tokens::whitespace(&ws.syntax().text().replace(&format!("\n{self}"), "\n"));
+            editor.replace(ws.syntax(), &new_ws);
+        }
+        editor.finish().new_root().clone()
+    }
 }
 
 fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> {
     iter::successors(Some(token), |token| token.prev_token())
 }
 
-/// Soft-deprecated in favor of mutable tree editing API `edit_in_place::Ident`.
 pub trait AstNodeEdit: AstNode + Clone + Sized {
     fn indent_level(&self) -> IndentLevel {
         IndentLevel::from_node(self.syntax())
     }
     #[must_use]
     fn indent(&self, level: IndentLevel) -> Self {
-        fn indent_inner(node: &SyntaxNode, level: IndentLevel) -> SyntaxNode {
-            let res = node.clone_subtree().clone_for_update();
-            level.increase_indent(&res);
-            res.clone_subtree()
+        Self::cast(level.clone_increase_indent(self.syntax())).unwrap()
+    }
+    #[must_use]
+    fn indent_with_mapping(&self, level: IndentLevel, make: &SyntaxFactory) -> Self {
+        let new_node = self.indent(level);
+        if let Some(mut mapping) = make.mappings() {
+            let mut builder = SyntaxMappingBuilder::new(new_node.syntax().clone());
+            for (old, new) in self.syntax().children().zip(new_node.syntax().children()) {
+                builder.map_node(old, new);
+            }
+            builder.finish(&mut mapping);
         }
-
-        Self::cast(indent_inner(self.syntax(), level)).unwrap()
+        new_node
     }
     #[must_use]
     fn dedent(&self, level: IndentLevel) -> Self {
-        fn dedent_inner(node: &SyntaxNode, level: IndentLevel) -> SyntaxNode {
-            let res = node.clone_subtree().clone_for_update();
-            level.decrease_indent(&res);
-            res.clone_subtree()
-        }
-
-        Self::cast(dedent_inner(self.syntax(), level)).unwrap()
+        Self::cast(level.clone_decrease_indent(self.syntax())).unwrap()
     }
     #[must_use]
     fn reset_indent(&self) -> Self {
diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/edit_in_place.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/edit_in_place.rs
index e902516471d..28b543ea706 100644
--- a/src/tools/rust-analyzer/crates/syntax/src/ast/edit_in_place.rs
+++ b/src/tools/rust-analyzer/crates/syntax/src/ast/edit_in_place.rs
@@ -644,7 +644,7 @@ impl Removable for ast::Use {
 impl ast::Impl {
     pub fn get_or_create_assoc_item_list(&self) -> ast::AssocItemList {
         if self.assoc_item_list().is_none() {
-            let assoc_item_list = make::assoc_item_list().clone_for_update();
+            let assoc_item_list = make::assoc_item_list(None).clone_for_update();
             ted::append_child(self.syntax(), assoc_item_list.syntax());
         }
         self.assoc_item_list().unwrap()
diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs
index 2b862465420..ceb2866ebcd 100644
--- a/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs
+++ b/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs
@@ -377,22 +377,13 @@ impl CastExpr {
     #[inline]
     pub fn as_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![as]) }
 }
-pub struct ClosureBinder {
-    pub(crate) syntax: SyntaxNode,
-}
-impl ClosureBinder {
-    #[inline]
-    pub fn generic_param_list(&self) -> Option<GenericParamList> { support::child(&self.syntax) }
-    #[inline]
-    pub fn for_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![for]) }
-}
 pub struct ClosureExpr {
     pub(crate) syntax: SyntaxNode,
 }
 impl ast::HasAttrs for ClosureExpr {}
 impl ClosureExpr {
     #[inline]
-    pub fn closure_binder(&self) -> Option<ClosureBinder> { support::child(&self.syntax) }
+    pub fn for_binder(&self) -> Option<ForBinder> { support::child(&self.syntax) }
     #[inline]
     pub fn param_list(&self) -> Option<ParamList> { support::child(&self.syntax) }
     #[inline]
@@ -615,6 +606,15 @@ impl FnPtrType {
     #[inline]
     pub fn unsafe_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![unsafe]) }
 }
+pub struct ForBinder {
+    pub(crate) syntax: SyntaxNode,
+}
+impl ForBinder {
+    #[inline]
+    pub fn generic_param_list(&self) -> Option<GenericParamList> { support::child(&self.syntax) }
+    #[inline]
+    pub fn for_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![for]) }
+}
 pub struct ForExpr {
     pub(crate) syntax: SyntaxNode,
 }
@@ -632,11 +632,9 @@ pub struct ForType {
 }
 impl ForType {
     #[inline]
-    pub fn generic_param_list(&self) -> Option<GenericParamList> { support::child(&self.syntax) }
+    pub fn for_binder(&self) -> Option<ForBinder> { support::child(&self.syntax) }
     #[inline]
     pub fn ty(&self) -> Option<Type> { support::child(&self.syntax) }
-    #[inline]
-    pub fn for_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![for]) }
 }
 pub struct FormatArgsArg {
     pub(crate) syntax: SyntaxNode,
@@ -1766,6 +1764,8 @@ pub struct TypeBound {
 }
 impl TypeBound {
     #[inline]
+    pub fn for_binder(&self) -> Option<ForBinder> { support::child(&self.syntax) }
+    #[inline]
     pub fn lifetime(&self) -> Option<Lifetime> { support::child(&self.syntax) }
     #[inline]
     pub fn ty(&self) -> Option<Type> { support::child(&self.syntax) }
@@ -1938,13 +1938,11 @@ pub struct WherePred {
 impl ast::HasTypeBounds for WherePred {}
 impl WherePred {
     #[inline]
-    pub fn generic_param_list(&self) -> Option<GenericParamList> { support::child(&self.syntax) }
+    pub fn for_binder(&self) -> Option<ForBinder> { support::child(&self.syntax) }
     #[inline]
     pub fn lifetime(&self) -> Option<Lifetime> { support::child(&self.syntax) }
     #[inline]
     pub fn ty(&self) -> Option<Type> { support::child(&self.syntax) }
-    #[inline]
-    pub fn for_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![for]) }
 }
 pub struct WhileExpr {
     pub(crate) syntax: SyntaxNode,
@@ -3239,42 +3237,6 @@ impl fmt::Debug for CastExpr {
         f.debug_struct("CastExpr").field("syntax", &self.syntax).finish()
     }
 }
-impl AstNode for ClosureBinder {
-    #[inline]
-    fn kind() -> SyntaxKind
-    where
-        Self: Sized,
-    {
-        CLOSURE_BINDER
-    }
-    #[inline]
-    fn can_cast(kind: SyntaxKind) -> bool { kind == CLOSURE_BINDER }
-    #[inline]
-    fn cast(syntax: SyntaxNode) -> Option<Self> {
-        if Self::can_cast(syntax.kind()) {
-            Some(Self { syntax })
-        } else {
-            None
-        }
-    }
-    #[inline]
-    fn syntax(&self) -> &SyntaxNode { &self.syntax }
-}
-impl hash::Hash for ClosureBinder {
-    fn hash<H: hash::Hasher>(&self, state: &mut H) { self.syntax.hash(state); }
-}
-impl Eq for ClosureBinder {}
-impl PartialEq for ClosureBinder {
-    fn eq(&self, other: &Self) -> bool { self.syntax == other.syntax }
-}
-impl Clone for ClosureBinder {
-    fn clone(&self) -> Self { Self { syntax: self.syntax.clone() } }
-}
-impl fmt::Debug for ClosureBinder {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_struct("ClosureBinder").field("syntax", &self.syntax).finish()
-    }
-}
 impl AstNode for ClosureExpr {
     #[inline]
     fn kind() -> SyntaxKind
@@ -3815,6 +3777,42 @@ impl fmt::Debug for FnPtrType {
         f.debug_struct("FnPtrType").field("syntax", &self.syntax).finish()
     }
 }
+impl AstNode for ForBinder {
+    #[inline]
+    fn kind() -> SyntaxKind
+    where
+        Self: Sized,
+    {
+        FOR_BINDER
+    }
+    #[inline]
+    fn can_cast(kind: SyntaxKind) -> bool { kind == FOR_BINDER }
+    #[inline]
+    fn cast(syntax: SyntaxNode) -> Option<Self> {
+        if Self::can_cast(syntax.kind()) {
+            Some(Self { syntax })
+        } else {
+            None
+        }
+    }
+    #[inline]
+    fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl hash::Hash for ForBinder {
+    fn hash<H: hash::Hasher>(&self, state: &mut H) { self.syntax.hash(state); }
+}
+impl Eq for ForBinder {}
+impl PartialEq for ForBinder {
+    fn eq(&self, other: &Self) -> bool { self.syntax == other.syntax }
+}
+impl Clone for ForBinder {
+    fn clone(&self) -> Self { Self { syntax: self.syntax.clone() } }
+}
+impl fmt::Debug for ForBinder {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("ForBinder").field("syntax", &self.syntax).finish()
+    }
+}
 impl AstNode for ForExpr {
     #[inline]
     fn kind() -> SyntaxKind
@@ -10146,11 +10144,6 @@ impl std::fmt::Display for CastExpr {
         std::fmt::Display::fmt(self.syntax(), f)
     }
 }
-impl std::fmt::Display for ClosureBinder {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        std::fmt::Display::fmt(self.syntax(), f)
-    }
-}
 impl std::fmt::Display for ClosureExpr {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         std::fmt::Display::fmt(self.syntax(), f)
@@ -10226,6 +10219,11 @@ impl std::fmt::Display for FnPtrType {
         std::fmt::Display::fmt(self.syntax(), f)
     }
 }
+impl std::fmt::Display for ForBinder {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        std::fmt::Display::fmt(self.syntax(), f)
+    }
+}
 impl std::fmt::Display for ForExpr {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         std::fmt::Display::fmt(self.syntax(), f)
diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs
index d67f24fda96..2a7b51c3c24 100644
--- a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs
+++ b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs
@@ -229,8 +229,18 @@ pub fn ty_fn_ptr<I: Iterator<Item = Param>>(
     }
 }
 
-pub fn assoc_item_list() -> ast::AssocItemList {
-    ast_from_text("impl C for D {}")
+pub fn assoc_item_list(
+    body: Option<Vec<either::Either<ast::Attr, ast::AssocItem>>>,
+) -> ast::AssocItemList {
+    let is_break_braces = body.is_some();
+    let body_newline = if is_break_braces { "\n".to_owned() } else { String::new() };
+    let body_indent = if is_break_braces { "    ".to_owned() } else { String::new() };
+
+    let body = match body {
+        Some(bd) => bd.iter().map(|elem| elem.to_string()).join("\n\n    "),
+        None => String::new(),
+    };
+    ast_from_text(&format!("impl C for D {{{body_newline}{body_indent}{body}{body_newline}}}"))
 }
 
 fn merge_gen_params(
@@ -273,7 +283,7 @@ pub fn impl_(
     generic_args: Option<ast::GenericArgList>,
     path_type: ast::Type,
     where_clause: Option<ast::WhereClause>,
-    body: Option<Vec<either::Either<ast::Attr, ast::AssocItem>>>,
+    body: Option<ast::AssocItemList>,
 ) -> ast::Impl {
     let gen_args = generic_args.map_or_else(String::new, |it| it.to_string());
 
@@ -281,20 +291,13 @@ pub fn impl_(
 
     let body_newline =
         if where_clause.is_some() && body.is_none() { "\n".to_owned() } else { String::new() };
-
     let where_clause = match where_clause {
         Some(pr) => format!("\n{pr}\n"),
         None => " ".to_owned(),
     };
 
-    let body = match body {
-        Some(bd) => bd.iter().map(|elem| elem.to_string()).join(""),
-        None => String::new(),
-    };
-
-    ast_from_text(&format!(
-        "impl{gen_params} {path_type}{gen_args}{where_clause}{{{body_newline}{body}}}"
-    ))
+    let body = body.map_or_else(|| format!("{{{body_newline}}}"), |it| it.to_string());
+    ast_from_text(&format!("impl{gen_params} {path_type}{gen_args}{where_clause}{body}"))
 }
 
 pub fn impl_trait(
@@ -308,7 +311,7 @@ pub fn impl_trait(
     ty: ast::Type,
     trait_where_clause: Option<ast::WhereClause>,
     ty_where_clause: Option<ast::WhereClause>,
-    body: Option<Vec<either::Either<ast::Attr, ast::AssocItem>>>,
+    body: Option<ast::AssocItemList>,
 ) -> ast::Impl {
     let is_unsafe = if is_unsafe { "unsafe " } else { "" };
 
@@ -330,13 +333,10 @@ pub fn impl_trait(
     let where_clause = merge_where_clause(ty_where_clause, trait_where_clause)
         .map_or_else(|| " ".to_owned(), |wc| format!("\n{wc}\n"));
 
-    let body = match body {
-        Some(bd) => bd.iter().map(|elem| elem.to_string()).join(""),
-        None => String::new(),
-    };
+    let body = body.map_or_else(|| format!("{{{body_newline}}}"), |it| it.to_string());
 
     ast_from_text(&format!(
-        "{is_unsafe}impl{gen_params} {is_negative}{path_type}{trait_gen_args} for {ty}{type_gen_args}{where_clause}{{{body_newline}{body}}}"
+        "{is_unsafe}impl{gen_params} {is_negative}{path_type}{trait_gen_args} for {ty}{type_gen_args}{where_clause}{body}"
     ))
 }
 
diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs
index f5530c5fffd..62a7d4df2cf 100644
--- a/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs
+++ b/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs
@@ -805,9 +805,7 @@ impl ast::SelfParam {
 #[derive(Clone, Debug, PartialEq, Eq, Hash)]
 pub enum TypeBoundKind {
     /// Trait
-    PathType(ast::PathType),
-    /// for<'a> ...
-    ForType(ast::ForType),
+    PathType(Option<ast::ForBinder>, ast::PathType),
     /// use
     Use(ast::UseBoundGenericArgs),
     /// 'a
@@ -817,9 +815,7 @@ pub enum TypeBoundKind {
 impl ast::TypeBound {
     pub fn kind(&self) -> TypeBoundKind {
         if let Some(path_type) = support::children(self.syntax()).next() {
-            TypeBoundKind::PathType(path_type)
-        } else if let Some(for_type) = support::children(self.syntax()).next() {
-            TypeBoundKind::ForType(for_type)
+            TypeBoundKind::PathType(self.for_binder(), path_type)
         } else if let Some(args) = self.use_bound_generic_args() {
             TypeBoundKind::Use(args)
         } else if let Some(lifetime) = self.lifetime() {
diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory.rs
index 7142e4f6e1b..f3ae7544cc3 100644
--- a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory.rs
+++ b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory.rs
@@ -38,7 +38,7 @@ impl SyntaxFactory {
         self.mappings.as_ref().map(|mappings| mappings.take()).unwrap_or_default()
     }
 
-    fn mappings(&self) -> Option<RefMut<'_, SyntaxMapping>> {
+    pub(crate) fn mappings(&self) -> Option<RefMut<'_, SyntaxMapping>> {
         self.mappings.as_ref().map(|it| it.borrow_mut())
     }
 }
diff --git a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs
index 3fa584850f7..5107754b182 100644
--- a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs
+++ b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs
@@ -5,7 +5,7 @@
 //! [`SyntaxEditor`]: https://github.com/dotnet/roslyn/blob/43b0b05cc4f492fd5de00f6f6717409091df8daa/src/Workspaces/Core/Portable/Editing/SyntaxEditor.cs
 
 use std::{
-    fmt,
+    fmt, iter,
     num::NonZeroU32,
     ops::RangeInclusive,
     sync::atomic::{AtomicU32, Ordering},
@@ -41,6 +41,15 @@ impl SyntaxEditor {
         self.annotations.push((element.syntax_element(), annotation))
     }
 
+    pub fn add_annotation_all(
+        &mut self,
+        elements: Vec<impl Element>,
+        annotation: SyntaxAnnotation,
+    ) {
+        self.annotations
+            .extend(elements.into_iter().map(|e| e.syntax_element()).zip(iter::repeat(annotation)));
+    }
+
     pub fn merge(&mut self, mut other: SyntaxEditor) {
         debug_assert!(
             self.root == other.root || other.root.ancestors().any(|node| node == self.root),
diff --git a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs
index d66ea8aa28c..840e7697979 100644
--- a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs
+++ b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs
@@ -92,6 +92,42 @@ fn get_or_insert_comma_after(editor: &mut SyntaxEditor, syntax: &SyntaxNode) ->
     }
 }
 
+impl ast::AssocItemList {
+    /// Adds a new associated item after all of the existing associated items.
+    ///
+    /// Attention! This function does align the first line of `item` with respect to `self`,
+    /// but it does _not_ change indentation of other lines (if any).
+    pub fn add_items(&self, editor: &mut SyntaxEditor, items: Vec<ast::AssocItem>) {
+        let (indent, position, whitespace) = match self.assoc_items().last() {
+            Some(last_item) => (
+                IndentLevel::from_node(last_item.syntax()),
+                Position::after(last_item.syntax()),
+                "\n\n",
+            ),
+            None => match self.l_curly_token() {
+                Some(l_curly) => {
+                    normalize_ws_between_braces(editor, self.syntax());
+                    (IndentLevel::from_token(&l_curly) + 1, Position::after(&l_curly), "\n")
+                }
+                None => (IndentLevel::single(), Position::last_child_of(self.syntax()), "\n"),
+            },
+        };
+
+        let elements: Vec<SyntaxElement> = items
+            .into_iter()
+            .enumerate()
+            .flat_map(|(i, item)| {
+                let whitespace = if i != 0 { "\n\n" } else { whitespace };
+                vec![
+                    make::tokens::whitespace(&format!("{whitespace}{indent}")).into(),
+                    item.syntax().clone().into(),
+                ]
+            })
+            .collect();
+        editor.insert_all(position, elements);
+    }
+}
+
 impl ast::VariantList {
     pub fn add_variant(&self, editor: &mut SyntaxEditor, variant: &ast::Variant) {
         let make = SyntaxFactory::without_mappings();
diff --git a/src/tools/rust-analyzer/docs/book/src/contributing/README.md b/src/tools/rust-analyzer/docs/book/src/contributing/README.md
index beb94cdfc41..57c7a9c5996 100644
--- a/src/tools/rust-analyzer/docs/book/src/contributing/README.md
+++ b/src/tools/rust-analyzer/docs/book/src/contributing/README.md
@@ -252,18 +252,8 @@ Release steps:
 4. Commit & push the changelog.
 5. Run `cargo xtask publish-release-notes <CHANGELOG>` -- this will convert the changelog entry in AsciiDoc to Markdown and update the body of GitHub Releases entry.
 6. Tweet.
-7. Make a new branch and run `cargo xtask rustc-pull`, open a PR, and merge it.
-   This will pull any changes from `rust-lang/rust` into `rust-analyzer`.
-8. Switch to `master`, pull, then run `cargo xtask rustc-push --rust-path ../rust-rust-analyzer --rust-fork matklad/rust`.
-   Replace `matklad/rust` with your own fork of `rust-lang/rust`.
-   You can use the token to authenticate when you get prompted for a password, since `josh` will push over HTTPS, not SSH.
-   This will push the `rust-analyzer` changes to your fork.
-   You can then open a PR against `rust-lang/rust`.
-
-Note: besides the `rust-rust-analyzer` clone, the Josh cache (stored under `~/.cache/rust-analyzer-josh`) will contain a bare clone of `rust-lang/rust`.
-This currently takes about 3.5 GB.
-
-This [HackMD](https://hackmd.io/7pOuxnkdQDaL1Y1FQr65xg) has details about how `josh` syncs work.
+7. Perform a subtree [pull](#performing-a-pull).
+8. Perform a subtree [push](#performing-a-push).
 
 If the GitHub Actions release fails because of a transient problem like a timeout, you can re-run the job from the Actions console.
 If it fails because of something that needs to be fixed, remove the release tag (if needed), fix the problem, then start over.
@@ -288,3 +278,43 @@ There are two sets of people with extra permissions:
   If you don't feel like reviewing for whatever reason, someone else will pick the review up (but please speak up if you don't feel like it)!
 * The [rust-lang](https://github.com/rust-lang) team [t-rust-analyzer-contributors]([https://github.com/orgs/rust-analyzer/teams/triage](https://github.com/rust-lang/team/blob/master/teams/rust-analyzer-contributors.toml)).
   This team has general triaging permissions allowing to label, close and re-open issues.
+
+## Synchronizing subtree changes
+`rust-analyzer` is a [josh](https://josh-project.github.io/josh/intro.html) subtree of the [rust-lang/rust](https://github.com/rust-lang/rust)
+repository. We use the [rustc-josh-sync](https://github.com/rust-lang/josh-sync) tool to perform synchronization between these two
+repositories. You can find documentation of the tool [here](https://github.com/rust-lang/josh-sync).
+
+You can install the synchronization tool using the following commands:
+```
+cargo install --locked --git https://github.com/rust-lang/josh-sync
+```
+
+Both pulls (synchronizing changes from rust-lang/rust into rust-analyzer) and pushes (synchronizing
+changes from rust-analyzer into rust-lang/rust) are performed from this repository.
+changes from rust-analyzer to rust-lang/rust) are performed from this repository.
+
+Usually we first perform a pull, wait for it to be merged, and then perform a push.
+
+### Performing a pull
+1) Checkout a new branch that will be used to create a PR against rust-analyzer
+2) Run the pull command
+    ```
+    rustc-josh-sync pull
+    ```
+3) Push the branch to your fork of `rust-analyzer` and create a PR
+  - If you have the `gh` CLI installed, `rustc-josh-sync` can create the PR for you.
+
+### Performing a push
+
+Wait for the previous pull to be merged.
+
+1) Switch to `master` and pull
+2) Run the push command to create a branch named `<branch-name>` in a `rustc` fork under the `<gh-username>` account
+    ```
+    rustc-josh-sync push <branch-name> <gh-username>
+    ```
+   - The push will ask you to download a checkout of the `rust-lang/rust` repository.
+   - If you get prompted for a password, see [this](https://github.com/rust-lang/josh-sync?tab=readme-ov-file#git-peculiarities).
+3) Create a PR from `<branch-name>` into `rust-lang/rust`
+
+> Besides the `rust` checkout, the Josh cache (stored under `~/.cache/rustc-josh`) will contain a bare clone of `rust-lang/rust`. This currently takes several GBs.
diff --git a/src/tools/rust-analyzer/editors/code/package-lock.json b/src/tools/rust-analyzer/editors/code/package-lock.json
index 57d67a69b2e..534c24be52e 100644
--- a/src/tools/rust-analyzer/editors/code/package-lock.json
+++ b/src/tools/rust-analyzer/editors/code/package-lock.json
@@ -3336,15 +3336,16 @@
             }
         },
         "node_modules/form-data": {
-            "version": "4.0.2",
-            "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
-            "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
+            "version": "4.0.4",
+            "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
+            "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
             "dev": true,
             "license": "MIT",
             "dependencies": {
                 "asynckit": "^0.4.0",
                 "combined-stream": "^1.0.8",
                 "es-set-tostringtag": "^2.1.0",
+                "hasown": "^2.0.2",
                 "mime-types": "^2.1.12"
             },
             "engines": {
diff --git a/src/tools/rust-analyzer/editors/code/src/config.ts b/src/tools/rust-analyzer/editors/code/src/config.ts
index d2dc740c09b..3b1b0768d3c 100644
--- a/src/tools/rust-analyzer/editors/code/src/config.ts
+++ b/src/tools/rust-analyzer/editors/code/src/config.ts
@@ -8,10 +8,9 @@ import type { Disposable } from "vscode";
 
 export type RunnableEnvCfgItem = {
     mask?: string;
-    env: Record<string, string>;
+    env: { [key: string]: { toString(): string } | null };
     platform?: string | string[];
 };
-export type RunnableEnvCfg = Record<string, string> | RunnableEnvCfgItem[];
 
 type ShowStatusBar = "always" | "never" | { documentSelector: vscode.DocumentSelector };
 
@@ -261,18 +260,13 @@ export class Config {
         return this.get<boolean | undefined>("testExplorer");
     }
 
-    runnablesExtraEnv(label: string): Record<string, string> | undefined {
-        // eslint-disable-next-line @typescript-eslint/no-explicit-any
-        const item = this.get<any>("runnables.extraEnv") ?? this.get<any>("runnableEnv");
-        if (!item) return undefined;
-        // eslint-disable-next-line @typescript-eslint/no-explicit-any
-        const fixRecord = (r: Record<string, any>) => {
-            for (const key in r) {
-                if (typeof r[key] !== "string") {
-                    r[key] = String(r[key]);
-                }
-            }
-        };
+    runnablesExtraEnv(label: string): Env {
+        const serverEnv = this.serverExtraEnv;
+        let extraEnv =
+            this.get<
+                RunnableEnvCfgItem[] | { [key: string]: { toString(): string } | null } | null
+            >("runnables.extraEnv") ?? {};
+        if (!extraEnv) return serverEnv;
 
         const platform = process.platform;
         const checkPlatform = (it: RunnableEnvCfgItem) => {
@@ -283,19 +277,25 @@ export class Config {
             return true;
         };
 
-        if (item instanceof Array) {
+        if (extraEnv instanceof Array) {
             const env = {};
-            for (const it of item) {
+            for (const it of extraEnv) {
                 const masked = !it.mask || new RegExp(it.mask).test(label);
                 if (masked && checkPlatform(it)) {
                     Object.assign(env, it.env);
                 }
             }
-            fixRecord(env);
-            return env;
+            extraEnv = env;
         }
-        fixRecord(item);
-        return item;
+        const runnableExtraEnv = substituteVariablesInEnv(
+            Object.fromEntries(
+                Object.entries(extraEnv).map(([k, v]) => [
+                    k,
+                    typeof v === "string" ? v : v?.toString(),
+                ]),
+            ),
+        );
+        return { ...runnableExtraEnv, ...serverEnv };
     }
 
     get restartServerOnConfigChange() {
diff --git a/src/tools/rust-analyzer/editors/code/src/debug.ts b/src/tools/rust-analyzer/editors/code/src/debug.ts
index adb75c23c70..24f8d908730 100644
--- a/src/tools/rust-analyzer/editors/code/src/debug.ts
+++ b/src/tools/rust-analyzer/editors/code/src/debug.ts
@@ -6,7 +6,14 @@ import type * as ra from "./lsp_ext";
 import { Cargo } from "./toolchain";
 import type { Ctx } from "./ctx";
 import { createTaskFromRunnable, prepareEnv } from "./run";
-import { execute, isCargoRunnableArgs, unwrapUndefinable, log, normalizeDriveLetter } from "./util";
+import {
+    execute,
+    isCargoRunnableArgs,
+    unwrapUndefinable,
+    log,
+    normalizeDriveLetter,
+    Env,
+} from "./util";
 import type { Config } from "./config";
 
 // Here we want to keep track on everything that's currently running
@@ -206,10 +213,7 @@ type SourceFileMap = {
     destination: string;
 };
 
-async function discoverSourceFileMap(
-    env: Record<string, string>,
-    cwd: string,
-): Promise<SourceFileMap | undefined> {
+async function discoverSourceFileMap(env: Env, cwd: string): Promise<SourceFileMap | undefined> {
     const sysroot = env["RUSTC_TOOLCHAIN"];
     if (sysroot) {
         // let's try to use the default toolchain
@@ -232,7 +236,7 @@ type PropertyFetcher<Config, Input, Key extends keyof Config> = (
 
 type DebugConfigProvider<Type extends string, DebugConfig extends BaseDebugConfig<Type>> = {
     executableProperty: keyof DebugConfig;
-    environmentProperty: PropertyFetcher<DebugConfig, Record<string, string>, keyof DebugConfig>;
+    environmentProperty: PropertyFetcher<DebugConfig, Env, keyof DebugConfig>;
     runnableArgsProperty: PropertyFetcher<DebugConfig, ra.CargoRunnableArgs, keyof DebugConfig>;
     sourceFileMapProperty?: keyof DebugConfig;
     type: Type;
@@ -276,7 +280,7 @@ const knownEngines: {
             "environment",
             Object.entries(env).map((entry) => ({
                 name: entry[0],
-                value: entry[1],
+                value: entry[1] ?? "",
             })),
         ],
         runnableArgsProperty: (runnableArgs: ra.CargoRunnableArgs) => [
@@ -304,10 +308,7 @@ const knownEngines: {
     },
 };
 
-async function getDebugExecutable(
-    runnableArgs: ra.CargoRunnableArgs,
-    env: Record<string, string>,
-): Promise<string> {
+async function getDebugExecutable(runnableArgs: ra.CargoRunnableArgs, env: Env): Promise<string> {
     const cargo = new Cargo(runnableArgs.workspaceRoot || ".", env);
     const executable = await cargo.executableFromArgs(runnableArgs);
 
@@ -328,7 +329,7 @@ function getDebugConfig(
     runnable: ra.Runnable,
     runnableArgs: ra.CargoRunnableArgs,
     executable: string,
-    env: Record<string, string>,
+    env: Env,
     sourceFileMap?: Record<string, string>,
 ): vscode.DebugConfiguration {
     const {
@@ -380,14 +381,14 @@ type CodeLldbDebugConfig = {
     args: string[];
     sourceMap: Record<string, string> | undefined;
     sourceLanguages: ["rust"];
-    env: Record<string, string>;
+    env: Env;
 } & BaseDebugConfig<"lldb">;
 
 type NativeDebugConfig = {
     target: string;
     // See https://github.com/WebFreak001/code-debug/issues/359
     arguments: string;
-    env: Record<string, string>;
+    env: Env;
     valuesFormatting: "prettyPrinters";
 } & BaseDebugConfig<"gdb">;
 
diff --git a/src/tools/rust-analyzer/editors/code/src/run.ts b/src/tools/rust-analyzer/editors/code/src/run.ts
index 95166c427b2..87c1d529f7e 100644
--- a/src/tools/rust-analyzer/editors/code/src/run.ts
+++ b/src/tools/rust-analyzer/editors/code/src/run.ts
@@ -7,7 +7,7 @@ import type { CtxInit } from "./ctx";
 import { makeDebugConfig } from "./debug";
 import type { Config } from "./config";
 import type { LanguageClient } from "vscode-languageclient/node";
-import { log, unwrapUndefinable, type RustEditor } from "./util";
+import { Env, log, unwrapUndefinable, type RustEditor } from "./util";
 
 const quickPickButtons = [
     { iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configuration." },
@@ -122,11 +122,8 @@ export class RunnableQuickPick implements vscode.QuickPickItem {
     }
 }
 
-export function prepareBaseEnv(
-    inheritEnv: boolean,
-    base?: Record<string, string>,
-): Record<string, string> {
-    const env: Record<string, string> = { RUST_BACKTRACE: "short" };
+export function prepareBaseEnv(inheritEnv: boolean, base?: Env): Env {
+    const env: Env = { RUST_BACKTRACE: "short" };
     if (inheritEnv) {
         Object.assign(env, process.env);
     }
@@ -136,11 +133,7 @@ export function prepareBaseEnv(
     return env;
 }
 
-export function prepareEnv(
-    inheritEnv: boolean,
-    runnableEnv?: Record<string, string>,
-    runnableEnvCfg?: Record<string, string>,
-): Record<string, string> {
+export function prepareEnv(inheritEnv: boolean, runnableEnv?: Env, runnableEnvCfg?: Env): Env {
     const env = prepareBaseEnv(inheritEnv, runnableEnv);
 
     if (runnableEnvCfg) {
diff --git a/src/tools/rust-analyzer/editors/code/src/tasks.ts b/src/tools/rust-analyzer/editors/code/src/tasks.ts
index 730ec6d1e90..eb0748a704b 100644
--- a/src/tools/rust-analyzer/editors/code/src/tasks.ts
+++ b/src/tools/rust-analyzer/editors/code/src/tasks.ts
@@ -1,6 +1,7 @@
 import * as vscode from "vscode";
 import type { Config } from "./config";
 import * as toolchain from "./toolchain";
+import { Env } from "./util";
 
 // This ends up as the `type` key in tasks.json. RLS also uses `cargo` and
 // our configuration should be compatible with it so use the same key.
@@ -117,8 +118,8 @@ export async function buildRustTask(
 export async function targetToExecution(
     definition: TaskDefinition,
     options?: {
-        env?: { [key: string]: string };
         cwd?: string;
+        env?: Env;
     },
     cargo?: string,
 ): Promise<vscode.ProcessExecution | vscode.ShellExecution> {
@@ -131,7 +132,12 @@ export async function targetToExecution(
         command = definition.command;
         args = definition.args || [];
     }
-    return new vscode.ProcessExecution(command, args, options);
+    return new vscode.ProcessExecution(command, args, {
+        cwd: options?.cwd,
+        env: Object.fromEntries(
+            Object.entries(options?.env ?? {}).map(([key, value]) => [key, value ?? ""]),
+        ),
+    });
 }
 
 export function activateTaskProvider(config: Config): vscode.Disposable {
diff --git a/src/tools/rust-analyzer/editors/code/src/toolchain.ts b/src/tools/rust-analyzer/editors/code/src/toolchain.ts
index a859ce6ff00..06f75a8f8d6 100644
--- a/src/tools/rust-analyzer/editors/code/src/toolchain.ts
+++ b/src/tools/rust-analyzer/editors/code/src/toolchain.ts
@@ -3,7 +3,7 @@ import * as os from "os";
 import * as path from "path";
 import * as readline from "readline";
 import * as vscode from "vscode";
-import { log, memoizeAsync, unwrapUndefinable } from "./util";
+import { Env, log, memoizeAsync, unwrapUndefinable } from "./util";
 import type { CargoRunnableArgs } from "./lsp_ext";
 
 interface CompilationArtifact {
@@ -37,7 +37,7 @@ interface CompilerMessage {
 export class Cargo {
     constructor(
         readonly rootFolder: string,
-        readonly env: Record<string, string>,
+        readonly env: Env,
     ) {}
 
     // Made public for testing purposes
@@ -156,7 +156,7 @@ export class Cargo {
 
 /** Mirrors `toolchain::cargo()` implementation */
 // FIXME: The server should provide this
-export function cargoPath(env?: Record<string, string>): Promise<string> {
+export function cargoPath(env?: Env): Promise<string> {
     if (env?.["RUSTC_TOOLCHAIN"]) {
         return Promise.resolve("cargo");
     }
diff --git a/src/tools/rust-analyzer/josh-sync.toml b/src/tools/rust-analyzer/josh-sync.toml
new file mode 100644
index 00000000000..51ff0d71e71
--- /dev/null
+++ b/src/tools/rust-analyzer/josh-sync.toml
@@ -0,0 +1,2 @@
+repo = "rust-analyzer"
+filter = ":rev(55d9a533b309119c8acd13061581b43ae8840823:prefix=src/tools/rust-analyzer):/src/tools/rust-analyzer"
diff --git a/src/tools/rust-analyzer/rust-version b/src/tools/rust-analyzer/rust-version
index c2b1c151b83..2178caf6396 100644
--- a/src/tools/rust-analyzer/rust-version
+++ b/src/tools/rust-analyzer/rust-version
@@ -1 +1 @@
-e05ab47e6c418fb2b9faa2eae9a7e70c65c98eaa
+733dab558992d902d6d17576de1da768094e2cf3
diff --git a/src/tools/rust-analyzer/triagebot.toml b/src/tools/rust-analyzer/triagebot.toml
index 2201b5a5e7c..27fdb672455 100644
--- a/src/tools/rust-analyzer/triagebot.toml
+++ b/src/tools/rust-analyzer/triagebot.toml
@@ -17,6 +17,7 @@ exclude_titles = [ # exclude syncs from subtree in rust-lang/rust
     "sync from downstream",
     "Sync from rust",
     "sync from rust",
+    "Rustc pull update",
 ]
 labels = ["has-merge-commits", "S-waiting-on-author"]
 
@@ -27,3 +28,6 @@ labels = ["has-merge-commits", "S-waiting-on-author"]
 
 # Prevents mentions in commits to avoid users being spammed
 [no-mentions]
+
+# Automatically close and reopen PRs made by bots to run CI on them
+[bot-pull-requests]
diff --git a/src/tools/rust-analyzer/xtask/Cargo.toml b/src/tools/rust-analyzer/xtask/Cargo.toml
index 8cd5811c0a6..9d8a1956d0a 100644
--- a/src/tools/rust-analyzer/xtask/Cargo.toml
+++ b/src/tools/rust-analyzer/xtask/Cargo.toml
@@ -8,7 +8,6 @@ rust-version.workspace = true
 
 [dependencies]
 anyhow.workspace = true
-directories = "6.0"
 flate2 = "1.1.2"
 write-json = "0.1.4"
 xshell.workspace = true
diff --git a/src/tools/rust-analyzer/xtask/src/flags.rs b/src/tools/rust-analyzer/xtask/src/flags.rs
index 2fd471b35c7..72f6215d4c3 100644
--- a/src/tools/rust-analyzer/xtask/src/flags.rs
+++ b/src/tools/rust-analyzer/xtask/src/flags.rs
@@ -59,20 +59,6 @@ xflags::xflags! {
             optional --dry-run
         }
 
-        cmd rustc-pull {
-            /// rustc commit to pull.
-            optional --commit refspec: String
-        }
-
-        cmd rustc-push {
-            /// rust local path, e.g. `../rust-rust-analyzer`.
-            required --rust-path rust_path: String
-            /// rust fork name, e.g.  `matklad/rust`.
-            required --rust-fork rust_fork: String
-            /// branch name.
-            optional --branch branch: String
-        }
-
         cmd dist {
             /// Use mimalloc allocator for server
             optional --mimalloc
@@ -121,8 +107,6 @@ pub enum XtaskCmd {
     Install(Install),
     FuzzTests(FuzzTests),
     Release(Release),
-    RustcPull(RustcPull),
-    RustcPush(RustcPush),
     Dist(Dist),
     PublishReleaseNotes(PublishReleaseNotes),
     Metrics(Metrics),
@@ -152,18 +136,6 @@ pub struct Release {
 }
 
 #[derive(Debug)]
-pub struct RustcPull {
-    pub commit: Option<String>,
-}
-
-#[derive(Debug)]
-pub struct RustcPush {
-    pub rust_path: String,
-    pub rust_fork: String,
-    pub branch: Option<String>,
-}
-
-#[derive(Debug)]
 pub struct Dist {
     pub mimalloc: bool,
     pub jemalloc: bool,
diff --git a/src/tools/rust-analyzer/xtask/src/main.rs b/src/tools/rust-analyzer/xtask/src/main.rs
index aaa8d0e1d4d..c5ad49cdcea 100644
--- a/src/tools/rust-analyzer/xtask/src/main.rs
+++ b/src/tools/rust-analyzer/xtask/src/main.rs
@@ -42,8 +42,6 @@ fn main() -> anyhow::Result<()> {
         flags::XtaskCmd::Install(cmd) => cmd.run(sh),
         flags::XtaskCmd::FuzzTests(_) => run_fuzzer(sh),
         flags::XtaskCmd::Release(cmd) => cmd.run(sh),
-        flags::XtaskCmd::RustcPull(cmd) => cmd.run(sh),
-        flags::XtaskCmd::RustcPush(cmd) => cmd.run(sh),
         flags::XtaskCmd::Dist(cmd) => cmd.run(sh),
         flags::XtaskCmd::PublishReleaseNotes(cmd) => cmd.run(sh),
         flags::XtaskCmd::Metrics(cmd) => cmd.run(sh),
diff --git a/src/tools/rust-analyzer/xtask/src/release.rs b/src/tools/rust-analyzer/xtask/src/release.rs
index e41f4ceb435..d06a25c8929 100644
--- a/src/tools/rust-analyzer/xtask/src/release.rs
+++ b/src/tools/rust-analyzer/xtask/src/release.rs
@@ -1,12 +1,5 @@
 mod changelog;
 
-use std::process::{Command, Stdio};
-use std::thread;
-use std::time::Duration;
-
-use anyhow::{Context as _, bail};
-use directories::ProjectDirs;
-use stdx::JodChild;
 use xshell::{Shell, cmd};
 
 use crate::{date_iso, flags, is_release_tag, project_root};
@@ -59,171 +52,3 @@ impl flags::Release {
         Ok(())
     }
 }
-
-// git sync implementation adapted from https://github.com/rust-lang/miri/blob/62039ac/miri-script/src/commands.rs
-impl flags::RustcPull {
-    pub(crate) fn run(self, sh: &Shell) -> anyhow::Result<()> {
-        sh.change_dir(project_root());
-        let commit = self.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::format_err!("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() {
-            bail!("working directory must be clean before running `cargo xtask pull`");
-        }
-        // This should not add any new root commits. So count those before and after merging.
-        let num_roots = || -> anyhow::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()?;
-        // Make sure josh is running.
-        let josh = start_josh()?;
-
-        // 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 any git hooks that might exist,
-        // in case they dirty the repository.
-        sh.write_file("rust-version", format!("{commit}\n"))?;
-        const PREPARING_COMMIT_MESSAGE: &str = "Preparing for merge from rust-lang/rust";
-        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 http://localhost:{JOSH_PORT}/rust-lang/rust.git@{commit}{JOSH_FILTER}.git")
-            .run()
-            .inspect_err(|_| {
-                // Try to un-do the previous `git commit`, to leave the repo in the state we found it 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)")?;
-
-        // Merge the fetched commit.
-        const MERGE_COMMIT_MESSAGE: &str = "Merge from rust-lang/rust";
-        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(())
-    }
-}
-
-impl flags::RustcPush {
-    pub(crate) fn run(self, sh: &Shell) -> anyhow::Result<()> {
-        let branch = self.branch.as_deref().unwrap_or("sync-from-ra");
-        let rust_path = self.rust_path;
-        let rust_fork = self.rust_fork;
-
-        sh.change_dir(project_root());
-        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() {
-            bail!("working directory must be clean before running `cargo xtask push`");
-        }
-        // Make sure josh is running.
-        let josh = start_josh()?;
-
-        // Find a repo we can do our preparation in.
-        sh.change_dir(rust_path);
-
-        // 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 {rust_fork} (base: {base})...");
-        if cmd!(sh, "git fetch https://github.com/{rust_fork} {branch}")
-            .ignore_stderr()
-            .read()
-            .is_ok()
-        {
-            bail!(
-                "The branch `{branch}` seems to already exist in `https://github.com/{rust_fork}`. Please delete it and try again."
-            );
-        }
-        cmd!(sh, "git fetch https://github.com/rust-lang/rust {base}").run()?;
-        cmd!(sh, "git push https://github.com/{rust_fork} {base}:refs/heads/{branch}")
-            .ignore_stdout()
-            .ignore_stderr() // silence the "create GitHub PR" message
-            .run()?;
-        println!();
-
-        // Do the actual push.
-        sh.change_dir(project_root());
-        println!("Pushing rust-analyzer changes...");
-        cmd!(
-            sh,
-            "git push http://localhost:{JOSH_PORT}/{rust_fork}.git{JOSH_FILTER}.git HEAD:{branch}"
-        )
-        .run()?;
-        println!();
-
-        // Do a round-trip check to make sure the push worked as expected.
-        cmd!(
-            sh,
-            "git fetch http://localhost:{JOSH_PORT}/{rust_fork}.git{JOSH_FILTER}.git {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 rust-analyzer properly. Please create a rustc PR:"
-        );
-        // https://github.com/github-linguist/linguist/compare/master...octocat:linguist:master
-        let fork_path = rust_fork.replace('/', ":");
-        println!(
-            "    https://github.com/rust-lang/rust/compare/{fork_path}:{branch}?quick_pull=1&title=Subtree+update+of+rust-analyzer&body=r?+@ghost"
-        );
-
-        drop(josh);
-        Ok(())
-    }
-}
-
-/// Used for rustc syncs.
-const JOSH_FILTER: &str = ":rev(55d9a533b309119c8acd13061581b43ae8840823:prefix=src/tools/rust-analyzer):/src/tools/rust-analyzer";
-const JOSH_PORT: &str = "42042";
-
-fn start_josh() -> anyhow::Result<impl Drop> {
-    // Determine cache directory.
-    let local_dir = {
-        let user_dirs = ProjectDirs::from("org", "rust-lang", "rust-analyzer-josh").unwrap();
-        user_dirs.cache_dir().to_owned()
-    };
-
-    // Start josh, silencing its output.
-    let mut cmd = Command::new("josh-proxy");
-    cmd.arg("--local").arg(local_dir);
-    cmd.arg("--remote").arg("https://github.com");
-    cmd.arg("--port").arg(JOSH_PORT);
-    cmd.arg("--no-background");
-    cmd.stdout(Stdio::null());
-    cmd.stderr(Stdio::null());
-    let josh = cmd.spawn().context("failed to start josh-proxy, make sure it is installed")?;
-    // Give it some time so hopefully the port is open. (100ms was not enough.)
-    thread::sleep(Duration::from_millis(200));
-
-    Ok(JodChild(josh))
-}
diff --git a/src/tools/rustbook/Cargo.lock b/src/tools/rustbook/Cargo.lock
index e363668d462..5f30c75732c 100644
--- a/src/tools/rustbook/Cargo.lock
+++ b/src/tools/rustbook/Cargo.lock
@@ -1343,9 +1343,9 @@ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
 
 [[package]]
 name = "redox_syscall"
-version = "0.5.13"
+version = "0.5.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6"
+checksum = "7251471db004e509f4e75a62cca9435365b5ec7bcdff530d612ac7c87c44a792"
 dependencies = [
  "bitflags 2.9.1",
 ]
diff --git a/src/tools/rustc-perf b/src/tools/rustc-perf
-Subproject 6a70166b92a1b1560cb3cf056427b011b2a1f2b
+Subproject dde879cf1087cb34a32287bd8ccc4d545bb9fee
diff --git a/src/tools/tidy/Cargo.toml b/src/tools/tidy/Cargo.toml
index d995106ae02..c1f27de7ed4 100644
--- a/src/tools/tidy/Cargo.toml
+++ b/src/tools/tidy/Cargo.toml
@@ -6,7 +6,7 @@ autobins = false
 
 [dependencies]
 build_helper = { path = "../../build_helper" }
-cargo_metadata = "0.19"
+cargo_metadata = "0.21"
 regex = "1"
 miropt-test-tools = { path = "../miropt-test-tools" }
 walkdir = "2"
diff --git a/src/tools/tidy/src/deps.rs b/src/tools/tidy/src/deps.rs
index c40572c115b..858b058cb7d 100644
--- a/src/tools/tidy/src/deps.rs
+++ b/src/tools/tidy/src/deps.rs
@@ -167,7 +167,7 @@ const EXCEPTIONS_RUSTC_PERF: ExceptionList = &[
     ("brotli-decompressor", "BSD-3-Clause/MIT"),
     ("encoding_rs", "(Apache-2.0 OR MIT) AND BSD-3-Clause"),
     ("inferno", "CDDL-1.0"),
-    ("ring", NON_STANDARD_LICENSE), // see EXCEPTIONS_NON_STANDARD_LICENSE_DEPS for more.
+    ("option-ext", "MPL-2.0"),
     ("ryu", "Apache-2.0 OR BSL-1.0"),
     ("snap", "BSD-3-Clause"),
     ("subtle", "BSD-3-Clause"),
@@ -226,20 +226,6 @@ const EXCEPTIONS_UEFI_QEMU_TEST: ExceptionList = &[
     ("r-efi", "MIT OR Apache-2.0 OR LGPL-2.1-or-later"), // LGPL is not acceptable, but we use it under MIT OR Apache-2.0
 ];
 
-/// Placeholder for non-standard license file.
-const NON_STANDARD_LICENSE: &str = "NON_STANDARD_LICENSE";
-
-/// These dependencies have non-standard licenses but are genenrally permitted.
-const EXCEPTIONS_NON_STANDARD_LICENSE_DEPS: &[&str] = &[
-    // `ring` is included because it is an optional dependency of `hyper`,
-    // which is a training data in rustc-perf for optimized build.
-    // The license of it is generally `ISC AND MIT AND OpenSSL`,
-    // though the `package.license` field is not set.
-    //
-    // See https://github.com/briansmith/ring/issues/902
-    "ring",
-];
-
 const PERMITTED_DEPS_LOCATION: &str = concat!(file!(), ":", line!());
 
 /// Crates rustc is allowed to depend on. Avoid adding to the list if possible.
@@ -599,7 +585,7 @@ pub fn check(root: &Path, cargo: &Path, bless: bool, bad: &mut bool) {
             .other_options(vec!["--locked".to_owned()]);
         let metadata = t!(cmd.exec());
 
-        check_license_exceptions(&metadata, exceptions, bad);
+        check_license_exceptions(&metadata, workspace, exceptions, bad);
         if let Some((crates, permitted_deps)) = permitted_deps {
             check_permitted_dependencies(&metadata, workspace, permitted_deps, crates, bad);
         }
@@ -633,8 +619,8 @@ fn check_proc_macro_dep_list(root: &Path, cargo: &Path, bless: bool, bad: &mut b
     proc_macro_deps.retain(|pkg| !is_proc_macro_pkg(&metadata[pkg]));
 
     let proc_macro_deps: HashSet<_> =
-        proc_macro_deps.into_iter().map(|dep| metadata[dep].name.clone()).collect();
-    let expected = proc_macro_deps::CRATES.iter().map(|s| s.to_string()).collect::<HashSet<_>>();
+        proc_macro_deps.into_iter().map(|dep| metadata[dep].name.as_ref()).collect();
+    let expected = proc_macro_deps::CRATES.iter().copied().collect::<HashSet<_>>();
 
     let needs_blessing = proc_macro_deps.difference(&expected).next().is_some()
         || expected.difference(&proc_macro_deps).next().is_some();
@@ -718,7 +704,7 @@ fn check_runtime_license_exceptions(metadata: &Metadata, bad: &mut bool) {
             // See https://github.com/rust-lang/rust/issues/62620 for more.
             // In general, these should never be added and this exception
             // should not be taken as precedent for any new target.
-            if pkg.name == "fortanix-sgx-abi" && pkg.license.as_deref() == Some("MPL-2.0") {
+            if *pkg.name == "fortanix-sgx-abi" && pkg.license.as_deref() == Some("MPL-2.0") {
                 continue;
             }
 
@@ -730,36 +716,38 @@ fn check_runtime_license_exceptions(metadata: &Metadata, bad: &mut bool) {
 /// Check that all licenses of tool dependencies are in the valid list in `LICENSES`.
 ///
 /// Packages listed in `exceptions` are allowed for tools.
-fn check_license_exceptions(metadata: &Metadata, exceptions: &[(&str, &str)], bad: &mut bool) {
+fn check_license_exceptions(
+    metadata: &Metadata,
+    workspace: &str,
+    exceptions: &[(&str, &str)],
+    bad: &mut bool,
+) {
     // Validate the EXCEPTIONS list hasn't changed.
     for (name, license) in exceptions {
         // Check that the package actually exists.
-        if !metadata.packages.iter().any(|p| p.name == *name) {
+        if !metadata.packages.iter().any(|p| *p.name == *name) {
             tidy_error!(
                 bad,
-                "could not find exception package `{}`\n\
+                "could not find exception package `{}` in workspace `{workspace}`\n\
                 Remove from EXCEPTIONS list if it is no longer used.",
                 name
             );
         }
         // Check that the license hasn't changed.
-        for pkg in metadata.packages.iter().filter(|p| p.name == *name) {
+        for pkg in metadata.packages.iter().filter(|p| *p.name == *name) {
             match &pkg.license {
                 None => {
-                    if *license == NON_STANDARD_LICENSE
-                        && EXCEPTIONS_NON_STANDARD_LICENSE_DEPS.contains(&pkg.name.as_str())
-                    {
-                        continue;
-                    }
                     tidy_error!(
                         bad,
-                        "dependency exception `{}` does not declare a license expression",
+                        "dependency exception `{}` in workspace `{workspace}` does not declare a license expression",
                         pkg.id
                     );
                 }
                 Some(pkg_license) => {
                     if pkg_license.as_str() != *license {
-                        println!("dependency exception `{name}` license has changed");
+                        println!(
+                            "dependency exception `{name}` license in workspace `{workspace}` has changed"
+                        );
                         println!("    previously `{license}` now `{pkg_license}`");
                         println!("    update EXCEPTIONS for the new license");
                         *bad = true;
@@ -783,12 +771,21 @@ fn check_license_exceptions(metadata: &Metadata, exceptions: &[(&str, &str)], ba
         let license = match &pkg.license {
             Some(license) => license,
             None => {
-                tidy_error!(bad, "dependency `{}` does not define a license expression", pkg.id);
+                tidy_error!(
+                    bad,
+                    "dependency `{}` in workspace `{workspace}` does not define a license expression",
+                    pkg.id
+                );
                 continue;
             }
         };
         if !LICENSES.contains(&license.as_str()) {
-            tidy_error!(bad, "invalid license `{}` in `{}`", license, pkg.id);
+            tidy_error!(
+                bad,
+                "invalid license `{}` for package `{}` in workspace `{workspace}`",
+                license,
+                pkg.id
+            );
         }
     }
 }
@@ -818,9 +815,9 @@ fn check_permitted_dependencies(
                 let Ok(version) = Version::parse(version) else {
                     return false;
                 };
-                pkg.name == name && pkg.version == version
+                *pkg.name == name && pkg.version == version
             } else {
-                pkg.name == permitted
+                *pkg.name == permitted
             }
         }
         if !deps.iter().any(|dep_id| compare(pkg_from_id(metadata, dep_id), permitted)) {
@@ -868,7 +865,7 @@ fn check_permitted_dependencies(
 
 /// Finds a package with the given name.
 fn pkg_from_name<'a>(metadata: &'a Metadata, name: &'static str) -> &'a Package {
-    let mut i = metadata.packages.iter().filter(|p| p.name == name);
+    let mut i = metadata.packages.iter().filter(|p| *p.name == name);
     let result =
         i.next().unwrap_or_else(|| panic!("could not find package `{name}` in package list"));
     assert!(i.next().is_none(), "more than one package found for `{name}`");
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/src/tools/tidy/src/main.rs b/src/tools/tidy/src/main.rs
index 794b0addee3..cd2567ddb64 100644
--- a/src/tools/tidy/src/main.rs
+++ b/src/tools/tidy/src/main.rs
@@ -128,9 +128,9 @@ fn main() {
         check!(pal, &library_path);
 
         // Checks that need to be done for both the compiler and std libraries.
-        check!(unit_tests, &src_path);
-        check!(unit_tests, &compiler_path);
-        check!(unit_tests, &library_path);
+        check!(unit_tests, &src_path, false);
+        check!(unit_tests, &compiler_path, false);
+        check!(unit_tests, &library_path, true);
 
         if bins::check_filesystem_support(&[&root_path], &output_directory) {
             check!(bins, &root_path);
diff --git a/src/tools/tidy/src/style.rs b/src/tools/tidy/src/style.rs
index 35ed61eacc7..fca097c091b 100644
--- a/src/tools/tidy/src/style.rs
+++ b/src/tools/tidy/src/style.rs
@@ -519,8 +519,11 @@ pub fn check(path: &Path, bad: &mut bool) {
                         .any(|directive| matches!(directive, Directive::Ignore(_)));
                 let has_alphabetical_directive = line.contains("tidy-alphabetical-start")
                     || line.contains("tidy-alphabetical-end");
-                let has_recognized_directive =
-                    has_recognized_ignore_directive || has_alphabetical_directive;
+                let has_other_tidy_ignore_directive =
+                    line.contains("ignore-tidy-target-specific-tests");
+                let has_recognized_directive = has_recognized_ignore_directive
+                    || has_alphabetical_directive
+                    || has_other_tidy_ignore_directive;
                 if contains_potential_directive && (!has_recognized_directive) {
                     err("Unrecognized tidy directive")
                 }
diff --git a/src/tools/tidy/src/target_specific_tests.rs b/src/tools/tidy/src/target_specific_tests.rs
index f4a6783abb6..b2d5f259eb2 100644
--- a/src/tools/tidy/src/target_specific_tests.rs
+++ b/src/tools/tidy/src/target_specific_tests.rs
@@ -12,12 +12,16 @@ const COMPILE_FLAGS_HEADER: &str = "compile-flags:";
 
 #[derive(Default, Debug)]
 struct RevisionInfo<'a> {
-    target_arch: Option<&'a str>,
+    target_arch: Option<Option<&'a str>>,
     llvm_components: Option<Vec<&'a str>>,
 }
 
 pub fn check(tests_path: &Path, bad: &mut bool) {
     crate::walk::walk(tests_path, |path, _is_dir| filter_not_rust(path), &mut |entry, content| {
+        if content.contains("// ignore-tidy-target-specific-tests") {
+            return;
+        }
+
         let file = entry.path().display();
         let mut header_map = BTreeMap::new();
         iter_header(content, &mut |HeaderLine { revision, directive, .. }| {
@@ -34,10 +38,11 @@ pub fn check(tests_path: &Path, bad: &mut bool) {
                 && let Some((_, v)) = compile_flags.split_once("--target")
             {
                 let v = v.trim_start_matches([' ', '=']);
-                let v = if v == "{{target}}" { Some((v, v)) } else { v.split_once("-") };
-                if let Some((arch, _)) = v {
-                    let info = header_map.entry(revision).or_insert(RevisionInfo::default());
-                    info.target_arch.replace(arch);
+                let info = header_map.entry(revision).or_insert(RevisionInfo::default());
+                if v.starts_with("{{") {
+                    info.target_arch.replace(None);
+                } else if let Some((arch, _)) = v.split_once("-") {
+                    info.target_arch.replace(Some(arch));
                 } else {
                     eprintln!("{file}: seems to have a malformed --target value");
                     *bad = true;
@@ -54,9 +59,11 @@ pub fn check(tests_path: &Path, bad: &mut bool) {
             let rev = rev.unwrap_or("[unspecified]");
             match (target_arch, llvm_components) {
                 (None, None) => {}
-                (Some(_), None) => {
+                (Some(target_arch), None) => {
+                    let llvm_component =
+                        target_arch.map_or_else(|| "<arch>".to_string(), arch_to_llvm_component);
                     eprintln!(
-                        "{file}: revision {rev} should specify `{LLVM_COMPONENTS_HEADER}` as it has `--target` set"
+                        "{file}: revision {rev} should specify `{LLVM_COMPONENTS_HEADER} {llvm_component}` as it has `--target` set"
                     );
                     *bad = true;
                 }
@@ -66,11 +73,45 @@ pub fn check(tests_path: &Path, bad: &mut bool) {
                     );
                     *bad = true;
                 }
-                (Some(_), Some(_)) => {
-                    // FIXME: check specified components against the target architectures we
-                    // gathered.
+                (Some(target_arch), Some(llvm_components)) => {
+                    if let Some(target_arch) = target_arch {
+                        let llvm_component = arch_to_llvm_component(target_arch);
+                        if !llvm_components.contains(&llvm_component.as_str()) {
+                            eprintln!(
+                                "{file}: revision {rev} should specify `{LLVM_COMPONENTS_HEADER} {llvm_component}` as it has `--target` set"
+                            );
+                            *bad = true;
+                        }
+                    }
                 }
             }
         }
     });
 }
+
+fn arch_to_llvm_component(arch: &str) -> String {
+    // NOTE: This is an *approximate* mapping of Rust's `--target` architecture to LLVM component
+    // names. It is not intended to be an authoritative source, but rather a best-effort that's good
+    // enough for the purpose of this tidy check.
+    match arch {
+        "amdgcn" => "amdgpu".into(),
+        "aarch64_be" | "arm64_32" | "arm64e" | "arm64ec" => "aarch64".into(),
+        "i386" | "i586" | "i686" | "x86" | "x86_64" | "x86_64h" => "x86".into(),
+        "loongarch32" | "loongarch64" => "loongarch".into(),
+        "nvptx64" => "nvptx".into(),
+        "s390x" => "systemz".into(),
+        "sparc64" | "sparcv9" => "sparc".into(),
+        "wasm32" | "wasm32v1" | "wasm64" => "webassembly".into(),
+        _ if arch.starts_with("armeb")
+            || arch.starts_with("armv")
+            || arch.starts_with("thumbv") =>
+        {
+            "arm".into()
+        }
+        _ if arch.starts_with("bpfe") => "bpf".into(),
+        _ if arch.starts_with("mips") => "mips".into(),
+        _ if arch.starts_with("powerpc") => "powerpc".into(),
+        _ if arch.starts_with("riscv") => "riscv".into(),
+        _ => arch.to_ascii_lowercase(),
+    }
+}
diff --git a/src/tools/tidy/src/unit_tests.rs b/src/tools/tidy/src/unit_tests.rs
index df9146b5147..3d14a467319 100644
--- a/src/tools/tidy/src/unit_tests.rs
+++ b/src/tools/tidy/src/unit_tests.rs
@@ -1,44 +1,60 @@
 //! Tidy check to ensure `#[test]` and `#[bench]` are not used directly inside
-//! `core` or `alloc`.
+//! of the standard library.
 //!
 //! `core` and `alloc` cannot be tested directly due to duplicating lang items.
 //! All tests and benchmarks must be written externally in
 //! `{coretests,alloctests}/{tests,benches}`.
 //!
-//! Outside of `core` and `alloc`, tests and benchmarks should be outlined into
-//! separate files named `tests.rs` or `benches.rs`, or directories named
+//! Outside of the standard library, tests and benchmarks should be outlined
+//! into separate files named `tests.rs` or `benches.rs`, or directories named
 //! `tests` or `benches` unconfigured during normal build.
 
 use std::path::Path;
 
 use crate::walk::{filter_dirs, walk};
 
-pub fn check(root_path: &Path, bad: &mut bool) {
-    let core = root_path.join("core");
-    let core_copy = core.clone();
-    let is_core = move |path: &Path| path.starts_with(&core);
-    let alloc = root_path.join("alloc");
-    let alloc_copy = alloc.clone();
-    let is_alloc = move |path: &Path| path.starts_with(&alloc);
-
+pub fn check(root_path: &Path, stdlib: bool, bad: &mut bool) {
     let skip = move |path: &Path, is_dir| {
         let file_name = path.file_name().unwrap_or_default();
+
+        // Skip excluded directories and non-rust files
         if is_dir {
-            filter_dirs(path)
-                || path.ends_with("src/doc")
-                || (file_name == "tests" || file_name == "benches")
-                    && !is_core(path)
-                    && !is_alloc(path)
+            if filter_dirs(path) || path.ends_with("src/doc") {
+                return true;
+            }
         } else {
             let extension = path.extension().unwrap_or_default();
-            extension != "rs"
-                || (file_name == "tests.rs" || file_name == "benches.rs")
-                    && !is_core(path)
-                    && !is_alloc(path)
-                // Tests which use non-public internals and, as such, need to
-                // have the types in the same crate as the tests themselves. See
-                // the comment in alloctests/lib.rs.
-                || path.ends_with("library/alloc/src/collections/btree/borrow/tests.rs")
+            if extension != "rs" {
+                return true;
+            }
+        }
+
+        // Tests in a separate package are always allowed
+        if is_dir && file_name != "tests" && file_name.as_encoded_bytes().ends_with(b"tests") {
+            return true;
+        }
+
+        if !stdlib {
+            // Outside of the standard library tests may also be in separate files in the same crate
+            if is_dir {
+                if file_name == "tests" || file_name == "benches" {
+                    return true;
+                }
+            } else {
+                if file_name == "tests.rs" || file_name == "benches.rs" {
+                    return true;
+                }
+            }
+        }
+
+        if is_dir {
+            // FIXME remove those exceptions once no longer necessary
+            file_name == "std_detect" || file_name == "std" || file_name == "test"
+        } else {
+            // Tests which use non-public internals and, as such, need to
+            // have the types in the same crate as the tests themselves. See
+            // the comment in alloctests/lib.rs.
+            path.ends_with("library/alloc/src/collections/btree/borrow/tests.rs")
                 || path.ends_with("library/alloc/src/collections/btree/map/tests.rs")
                 || path.ends_with("library/alloc/src/collections/btree/node/tests.rs")
                 || path.ends_with("library/alloc/src/collections/btree/set/tests.rs")
@@ -50,22 +66,29 @@ pub fn check(root_path: &Path, bad: &mut bool) {
 
     walk(root_path, skip, &mut |entry, contents| {
         let path = entry.path();
-        let is_core = path.starts_with(&core_copy);
-        let is_alloc = path.starts_with(&alloc_copy);
+        let package = path
+            .strip_prefix(root_path)
+            .unwrap()
+            .components()
+            .next()
+            .unwrap()
+            .as_os_str()
+            .to_str()
+            .unwrap();
         for (i, line) in contents.lines().enumerate() {
             let line = line.trim();
             let is_test = || line.contains("#[test]") && !line.contains("`#[test]");
             let is_bench = || line.contains("#[bench]") && !line.contains("`#[bench]");
-            let manual_skip = line.contains("//tidy:skip");
-            if !line.starts_with("//") && (is_test() || is_bench()) && !manual_skip {
-                let explanation = if is_core {
-                    "`core` unit tests and benchmarks must be placed into `coretests`"
-                } else if is_alloc {
-                    "`alloc` unit tests and benchmarks must be placed into `alloctests`"
+            if !line.starts_with("//") && (is_test() || is_bench()) {
+                let explanation = if stdlib {
+                    format!(
+                        "`{package}` unit tests and benchmarks must be placed into `{package}tests`"
+                    )
                 } else {
                     "unit tests and benchmarks must be placed into \
                          separate files or directories named \
                          `tests.rs`, `benches.rs`, `tests` or `benches`"
+                        .to_owned()
                 };
                 let name = if is_test() { "test" } else { "bench" };
                 tidy_error!(