diff --git a/Cargo.lock b/Cargo.lock
index 75c6ab37bd14f..7765353092553 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -43,7 +43,7 @@ dependencies = [
  "cfg-if",
  "once_cell",
  "version_check",
- "zerocopy",
+ "zerocopy 0.7.35",
 ]
 
 [[package]]
@@ -567,7 +567,7 @@ dependencies = [
  "termize",
  "tokio",
  "toml 0.7.8",
- "ui_test",
+ "ui_test 0.26.5",
  "walkdir",
 ]
 
@@ -1442,7 +1442,19 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
 dependencies = [
  "cfg-if",
  "libc",
- "wasi",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.13.3+wasi-0.2.2",
+ "windows-targets 0.52.6",
 ]
 
 [[package]]
@@ -2342,18 +2354,18 @@ dependencies = [
  "chrono-tz",
  "colored",
  "directories",
- "getrandom",
+ "getrandom 0.3.1",
  "libc",
  "libffi",
  "libloading",
  "measureme",
- "rand",
+ "rand 0.9.0",
  "regex",
  "rustc_version",
  "smallvec",
  "tempfile",
  "tikv-jemalloc-sys",
- "ui_test",
+ "ui_test 0.28.0",
  "windows-sys 0.52.0",
 ]
 
@@ -2782,7 +2794,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
 dependencies = [
  "phf_shared 0.10.0",
- "rand",
+ "rand 0.8.5",
 ]
 
 [[package]]
@@ -2792,7 +2804,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
 dependencies = [
  "phf_shared 0.11.3",
- "rand",
+ "rand 0.8.5",
 ]
 
 [[package]]
@@ -2860,7 +2872,7 @@ version = "0.2.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
 dependencies = [
- "zerocopy",
+ "zerocopy 0.7.35",
 ]
 
 [[package]]
@@ -2978,8 +2990,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
 dependencies = [
  "libc",
- "rand_chacha",
- "rand_core",
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
+dependencies = [
+ "rand_chacha 0.9.0",
+ "rand_core 0.9.0",
+ "zerocopy 0.8.14",
 ]
 
 [[package]]
@@ -2989,7 +3012,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
 dependencies = [
  "ppv-lite86",
- "rand_core",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.9.0",
 ]
 
 [[package]]
@@ -2998,7 +3031,17 @@ version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
 dependencies = [
- "getrandom",
+ "getrandom 0.2.15",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff"
+dependencies = [
+ "getrandom 0.3.1",
+ "zerocopy 0.8.14",
 ]
 
 [[package]]
@@ -3007,7 +3050,7 @@ version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
 dependencies = [
- "rand_core",
+ "rand_core 0.6.4",
 ]
 
 [[package]]
@@ -3045,7 +3088,7 @@ version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
 dependencies = [
- "getrandom",
+ "getrandom 0.2.15",
  "libredox",
  "thiserror 1.0.69",
 ]
@@ -3283,7 +3326,7 @@ name = "rustc_abi"
 version = "0.0.0"
 dependencies = [
  "bitflags",
- "rand",
+ "rand 0.8.5",
  "rand_xoshiro",
  "rustc_data_structures",
  "rustc_feature",
@@ -3897,7 +3940,7 @@ dependencies = [
 name = "rustc_incremental"
 version = "0.0.0"
 dependencies = [
- "rand",
+ "rand 0.8.5",
  "rustc_ast",
  "rustc_data_structures",
  "rustc_errors",
@@ -5218,7 +5261,7 @@ checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704"
 dependencies = [
  "cfg-if",
  "fastrand",
- "getrandom",
+ "getrandom 0.2.15",
  "once_cell",
  "rustix",
  "windows-sys 0.59.0",
@@ -5281,8 +5324,8 @@ version = "0.1.0"
 dependencies = [
  "indicatif",
  "num",
- "rand",
- "rand_chacha",
+ "rand 0.8.5",
+ "rand_chacha 0.3.1",
  "rayon",
 ]
 
@@ -5602,7 +5645,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
 dependencies = [
  "cfg-if",
- "rand",
+ "rand 0.8.5",
  "static_assertions",
 ]
 
@@ -5662,6 +5705,32 @@ dependencies = [
  "spanned",
 ]
 
+[[package]]
+name = "ui_test"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7484683d60d50ca1d1b6433c3dbf6c5ad71d20387acdcfb16fe79573f3fba576"
+dependencies = [
+ "annotate-snippets 0.11.5",
+ "anyhow",
+ "bstr",
+ "cargo-platform",
+ "cargo_metadata 0.18.1",
+ "color-eyre",
+ "colored",
+ "comma",
+ "crossbeam-channel",
+ "indicatif",
+ "levenshtein",
+ "prettydiff",
+ "regex",
+ "rustc_version",
+ "rustfix",
+ "serde",
+ "serde_json",
+ "spanned",
+]
+
 [[package]]
 name = "unic-langid"
 version = "0.9.5"
@@ -5843,7 +5912,7 @@ version = "1.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "744018581f9a3454a9e15beb8a33b017183f1e7c0cd170232a2d1453b23a51c4"
 dependencies = [
- "getrandom",
+ "getrandom 0.2.15",
 ]
 
 [[package]]
@@ -5880,6 +5949,15 @@ version = "0.11.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
+[[package]]
+name = "wasi"
+version = "0.13.3+wasi-0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
+dependencies = [
+ "wit-bindgen-rt",
+]
+
 [[package]]
 name = "wasi-preview1-component-adapter-provider"
 version = "29.0.1"
@@ -6475,6 +6553,15 @@ version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3ab703352da6a72f35c39a533526393725640575bb211f61987a2748323ad956"
 
+[[package]]
+name = "wit-bindgen-rt"
+version = "0.33.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
+dependencies = [
+ "bitflags",
+]
+
 [[package]]
 name = "wit-component"
 version = "0.223.0"
@@ -6584,7 +6671,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
 dependencies = [
  "byteorder",
- "zerocopy-derive",
+ "zerocopy-derive 0.7.35",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.8.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468"
+dependencies = [
+ "zerocopy-derive 0.8.14",
 ]
 
 [[package]]
@@ -6598,6 +6694,17 @@ dependencies = [
  "syn 2.0.96",
 ]
 
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
 [[package]]
 name = "zerofrom"
 version = "0.1.5"
diff --git a/src/tools/miri/Cargo.lock b/src/tools/miri/Cargo.lock
index 363b96fdff1f6..57a757f9085ca 100644
--- a/src/tools/miri/Cargo.lock
+++ b/src/tools/miri/Cargo.lock
@@ -351,7 +351,19 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
 dependencies = [
  "cfg-if",
  "libc",
- "wasi",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.13.3+wasi-0.2.2",
+ "windows-targets 0.52.6",
 ]
 
 [[package]]
@@ -529,12 +541,12 @@ dependencies = [
  "chrono-tz",
  "colored",
  "directories",
- "getrandom",
+ "getrandom 0.3.1",
  "libc",
  "libffi",
  "libloading",
  "measureme",
- "rand",
+ "rand 0.9.0",
  "regex",
  "rustc_version",
  "smallvec",
@@ -662,7 +674,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
 dependencies = [
  "phf_shared",
- "rand",
+ "rand 0.8.5",
 ]
 
 [[package]]
@@ -692,7 +704,7 @@ version = "0.2.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
 dependencies = [
- "zerocopy",
+ "zerocopy 0.7.35",
 ]
 
 [[package]]
@@ -729,19 +741,28 @@ version = "0.8.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
 dependencies = [
- "libc",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
+dependencies = [
  "rand_chacha",
- "rand_core",
+ "rand_core 0.9.0",
+ "zerocopy 0.8.14",
 ]
 
 [[package]]
 name = "rand_chacha"
-version = "0.3.1"
+version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
 dependencies = [
  "ppv-lite86",
- "rand_core",
+ "rand_core 0.9.0",
 ]
 
 [[package]]
@@ -749,8 +770,15 @@ name = "rand_core"
 version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+
+[[package]]
+name = "rand_core"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff"
 dependencies = [
- "getrandom",
+ "getrandom 0.3.1",
+ "zerocopy 0.8.14",
 ]
 
 [[package]]
@@ -768,7 +796,7 @@ version = "0.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891"
 dependencies = [
- "getrandom",
+ "getrandom 0.2.15",
  "libredox",
  "thiserror",
 ]
@@ -1051,9 +1079,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
 
 [[package]]
 name = "ui_test"
-version = "0.26.5"
+version = "0.28.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32ee4c40e5a5f9fa6864ff976473e5d6a6e9884b6ce68b40690d9f87e1994c83"
+checksum = "7484683d60d50ca1d1b6433c3dbf6c5ad71d20387acdcfb16fe79573f3fba576"
 dependencies = [
  "annotate-snippets",
  "anyhow",
@@ -1105,6 +1133,15 @@ version = "0.11.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
+[[package]]
+name = "wasi"
+version = "0.13.3+wasi-0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
+dependencies = [
+ "wit-bindgen-rt",
+]
+
 [[package]]
 name = "windows-sys"
 version = "0.48.0"
@@ -1244,6 +1281,15 @@ version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
 
+[[package]]
+name = "wit-bindgen-rt"
+version = "0.33.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
+dependencies = [
+ "bitflags",
+]
+
 [[package]]
 name = "zerocopy"
 version = "0.7.35"
@@ -1251,7 +1297,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
 dependencies = [
  "byteorder",
- "zerocopy-derive",
+ "zerocopy-derive 0.7.35",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.8.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468"
+dependencies = [
+ "zerocopy-derive 0.8.14",
 ]
 
 [[package]]
@@ -1264,3 +1319,14 @@ dependencies = [
  "quote",
  "syn",
 ]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
diff --git a/src/tools/miri/Cargo.toml b/src/tools/miri/Cargo.toml
index 6e8e270985e07..de80722fc3df1 100644
--- a/src/tools/miri/Cargo.toml
+++ b/src/tools/miri/Cargo.toml
@@ -18,8 +18,8 @@ test = false    # we have no unit tests
 doctest = false # and no doc tests
 
 [dependencies]
-getrandom = { version = "0.2", features = ["std"] }
-rand = "0.8"
+getrandom = { version = "0.3", features = ["std"] }
+rand = "0.9"
 smallvec = { version = "1.7", features = ["drain_filter"] }
 aes = { version = "0.8.3", features = ["hazmat"] }
 measureme = "11"
@@ -47,8 +47,8 @@ windows-sys = { version = "0.52", features = [
 ] }
 
 [dev-dependencies]
+ui_test = "0.28.0"
 colored = "2"
-ui_test = "0.26.5"
 rustc_version = "0.4"
 regex = "1.5.5"
 tempfile = "3"
diff --git a/src/tools/miri/ci/ci.sh b/src/tools/miri/ci/ci.sh
index fb3fc621565e3..5583030b490ae 100755
--- a/src/tools/miri/ci/ci.sh
+++ b/src/tools/miri/ci/ci.sh
@@ -14,9 +14,7 @@ function endgroup {
 begingroup "Building Miri"
 
 # Global configuration
-# We are getting some odd linker warnings on macOS, make sure they do not fail the build.
-# (See <https://github.com/rust-lang/rust/issues/136086>.)
-export RUSTFLAGS="-D warnings -A linker-messages"
+export RUSTFLAGS="-D warnings"
 export CARGO_INCREMENTAL=0
 export CARGO_EXTRA_FLAGS="--locked"
 
diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version
index 0d405f532fcd8..6e84524c8aa86 100644
--- a/src/tools/miri/rust-version
+++ b/src/tools/miri/rust-version
@@ -1 +1 @@
-2f0ad2a71e4a4528bb80bcb24bf8fa4e50cb87c2
+6dd75f0d6802f56564f5f9c947a85ded286d3986
diff --git a/src/tools/miri/src/alloc_addresses/mod.rs b/src/tools/miri/src/alloc_addresses/mod.rs
index 3d0fc5590eb81..a4f2a117b181c 100644
--- a/src/tools/miri/src/alloc_addresses/mod.rs
+++ b/src/tools/miri/src/alloc_addresses/mod.rs
@@ -217,7 +217,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // We have to pick a fresh address.
             // Leave some space to the previous allocation, to give it some chance to be less aligned.
             // We ensure that `(global_state.next_base_addr + slack) % 16` is uniformly distributed.
-            let slack = rng.gen_range(0..16);
+            let slack = rng.random_range(0..16);
             // From next_base_addr + slack, round up to adjust for alignment.
             let base_addr = global_state
                 .next_base_addr
diff --git a/src/tools/miri/src/alloc_addresses/reuse_pool.rs b/src/tools/miri/src/alloc_addresses/reuse_pool.rs
index b0c7ee7dff562..c0d24a9fbbcf9 100644
--- a/src/tools/miri/src/alloc_addresses/reuse_pool.rs
+++ b/src/tools/miri/src/alloc_addresses/reuse_pool.rs
@@ -58,7 +58,7 @@ impl ReusePool {
         // We don't remember stack addresses: there's a lot of them (so the perf impact is big),
         // and we only want to reuse stack slots within the same thread or else we'll add a lot of
         // undesired synchronization.
-        if kind == MemoryKind::Stack || !rng.gen_bool(self.address_reuse_rate) {
+        if kind == MemoryKind::Stack || !rng.random_bool(self.address_reuse_rate) {
             return;
         }
         let clock = clock();
@@ -88,10 +88,10 @@ impl ReusePool {
         thread: ThreadId,
     ) -> Option<(u64, Option<VClock>)> {
         // Determine whether we'll even attempt a reuse. As above, we don't do reuse for stack addresses.
-        if kind == MemoryKind::Stack || !rng.gen_bool(self.address_reuse_rate) {
+        if kind == MemoryKind::Stack || !rng.random_bool(self.address_reuse_rate) {
             return None;
         }
-        let cross_thread_reuse = rng.gen_bool(self.address_reuse_cross_thread_rate);
+        let cross_thread_reuse = rng.random_bool(self.address_reuse_cross_thread_rate);
         // Determine the pool to take this from.
         let subpool = self.subpool(align);
         // Let's see if we can find something of the right size. We want to find the full range of
@@ -118,7 +118,7 @@ impl ReusePool {
             return None;
         }
         // Pick a random element with the desired size.
-        let idx = rng.gen_range(begin..end);
+        let idx = rng.random_range(begin..end);
         // Remove it from the pool and return.
         let (chosen_addr, chosen_size, chosen_thread, clock) = subpool.remove(idx);
         debug_assert!(chosen_size >= size && chosen_addr % align.bytes() == 0);
diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs
index 988a0be632774..77692ed8655ea 100644
--- a/src/tools/miri/src/bin/miri.rs
+++ b/src/tools/miri/src/bin/miri.rs
@@ -723,8 +723,8 @@ fn main() {
 
     // Ensure we have parallelism for many-seeds mode.
     if many_seeds.is_some() && !rustc_args.iter().any(|arg| arg.starts_with("-Zthreads=")) {
-        // Clamp to 8 threads; things get a lot less efficient beyond that due to lock contention.
-        let threads = std::thread::available_parallelism().map_or(1, |n| n.get()).min(8);
+        // Clamp to 10 threads; things get a lot less efficient beyond that due to lock contention.
+        let threads = std::thread::available_parallelism().map_or(1, |n| n.get()).min(10);
         rustc_args.push(format!("-Zthreads={threads}"));
     }
     let many_seeds =
diff --git a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs
index bcc8668dbc122..18a5a0612bb06 100644
--- a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs
+++ b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs
@@ -865,7 +865,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         let this = self.eval_context_mut();
         let new_perm = NewPermission::from_ref_ty(val.layout.ty, kind, this);
         let cause = match kind {
-            RetagKind::TwoPhase { .. } => RetagCause::TwoPhase,
+            RetagKind::TwoPhase => RetagCause::TwoPhase,
             RetagKind::FnEntry => unreachable!(),
             RetagKind::Raw | RetagKind::Default => RetagCause::Normal,
         };
@@ -880,7 +880,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         let this = self.eval_context_mut();
         let retag_fields = this.machine.borrow_tracker.as_mut().unwrap().get_mut().retag_fields;
         let retag_cause = match kind {
-            RetagKind::TwoPhase { .. } => unreachable!(), // can only happen in `retag_ptr_value`
+            RetagKind::TwoPhase => unreachable!(), // can only happen in `retag_ptr_value`
             RetagKind::FnEntry => RetagCause::FnEntry,
             RetagKind::Default | RetagKind::Raw => RetagCause::Normal,
         };
@@ -904,10 +904,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 new_perm: NewPermission,
             ) -> InterpResult<'tcx> {
                 let val = self.ecx.read_immediate(&self.ecx.place_to_op(place)?)?;
-                let val = self.ecx.sb_retag_reference(&val, new_perm, RetagInfo {
-                    cause: self.retag_cause,
-                    in_field: self.in_field,
-                })?;
+                let val = self.ecx.sb_retag_reference(
+                    &val,
+                    new_perm,
+                    RetagInfo { cause: self.retag_cause, in_field: self.in_field },
+                )?;
                 self.ecx.write_immediate(*val, place)?;
                 interp_ok(())
             }
@@ -996,10 +997,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             access: Some(AccessKind::Write),
             protector: Some(ProtectorKind::StrongProtector),
         };
-        this.sb_retag_place(place, new_perm, RetagInfo {
-            cause: RetagCause::InPlaceFnPassing,
-            in_field: false,
-        })
+        this.sb_retag_place(
+            place,
+            new_perm,
+            RetagInfo { cause: RetagCause::InPlaceFnPassing, in_field: false },
+        )
     }
 
     /// Mark the given tag as exposed. It was found on a pointer with the given AllocId.
diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs
index 5d7c3d8c219f9..5c12ce39d10da 100644
--- a/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs
+++ b/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs
@@ -379,14 +379,18 @@ pub mod diagnostics {
     use super::*;
     impl fmt::Display for PermissionPriv {
         fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-            write!(f, "{}", match self {
-                ReservedFrz { conflicted: false } => "Reserved",
-                ReservedFrz { conflicted: true } => "Reserved (conflicted)",
-                ReservedIM => "Reserved (interior mutable)",
-                Active => "Active",
-                Frozen => "Frozen",
-                Disabled => "Disabled",
-            })
+            write!(
+                f,
+                "{}",
+                match self {
+                    ReservedFrz { conflicted: false } => "Reserved",
+                    ReservedFrz { conflicted: true } => "Reserved (conflicted)",
+                    ReservedIM => "Reserved (interior mutable)",
+                    Active => "Active",
+                    Frozen => "Frozen",
+                    Disabled => "Disabled",
+                }
+            )
         }
     }
 
diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs
index fd69278f20a7f..3389b1c602c33 100644
--- a/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs
+++ b/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs
@@ -581,15 +581,18 @@ impl Tree {
             let mut debug_info = NodeDebugInfo::new(root_tag, root_default_perm, span);
             // name the root so that all allocations contain one named pointer
             debug_info.add_name("root of the allocation");
-            nodes.insert(root_idx, Node {
-                tag: root_tag,
-                parent: None,
-                children: SmallVec::default(),
-                default_initial_perm: root_default_perm,
-                // The root may never be skipped, all accesses will be local.
-                default_initial_idempotent_foreign_access: IdempotentForeignAccess::None,
-                debug_info,
-            });
+            nodes.insert(
+                root_idx,
+                Node {
+                    tag: root_tag,
+                    parent: None,
+                    children: SmallVec::default(),
+                    default_initial_perm: root_default_perm,
+                    // The root may never be skipped, all accesses will be local.
+                    default_initial_idempotent_foreign_access: IdempotentForeignAccess::None,
+                    debug_info,
+                },
+            );
             nodes
         };
         let rperms = {
@@ -624,14 +627,17 @@ impl<'tcx> Tree {
         let parent_idx = self.tag_mapping.get(&parent_tag).unwrap();
         let strongest_idempotent = default_initial_perm.strongest_idempotent_foreign_access(prot);
         // Create the node
-        self.nodes.insert(idx, Node {
-            tag: new_tag,
-            parent: Some(parent_idx),
-            children: SmallVec::default(),
-            default_initial_perm,
-            default_initial_idempotent_foreign_access: strongest_idempotent,
-            debug_info: NodeDebugInfo::new(new_tag, default_initial_perm, span),
-        });
+        self.nodes.insert(
+            idx,
+            Node {
+                tag: new_tag,
+                parent: Some(parent_idx),
+                children: SmallVec::default(),
+                default_initial_perm,
+                default_initial_idempotent_foreign_access: strongest_idempotent,
+                debug_info: NodeDebugInfo::new(new_tag, default_initial_perm, span),
+            },
+        );
         // Register new_tag as a child of parent_tag
         self.nodes.get_mut(parent_idx).unwrap().children.push(idx);
         // Initialize perms
diff --git a/src/tools/miri/src/concurrency/data_race.rs b/src/tools/miri/src/concurrency/data_race.rs
index 4cdc9348dc9f8..b1ca434361b4a 100644
--- a/src/tools/miri/src/concurrency/data_race.rs
+++ b/src/tools/miri/src/concurrency/data_race.rs
@@ -830,7 +830,7 @@ pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> {
         let success_rate = 1.0 - this.machine.cmpxchg_weak_failure_rate;
         let cmpxchg_success = eq.to_scalar().to_bool()?
             && if can_fail_spuriously {
-                this.machine.rng.get_mut().gen_bool(success_rate)
+                this.machine.rng.get_mut().random_bool(success_rate)
             } else {
                 true
             };
diff --git a/src/tools/miri/src/concurrency/sync.rs b/src/tools/miri/src/concurrency/sync.rs
index 14c72e9398add..268268848ed2f 100644
--- a/src/tools/miri/src/concurrency/sync.rs
+++ b/src/tools/miri/src/concurrency/sync.rs
@@ -128,7 +128,7 @@ struct Condvar {
 /// The futex state.
 #[derive(Default, Debug)]
 struct Futex {
-    waiters: VecDeque<FutexWaiter>,
+    waiters: Vec<FutexWaiter>,
     /// Tracks the happens-before relationship
     /// between a futex-wake and a futex-wait
     /// during a non-spurious wake event.
@@ -140,6 +140,12 @@ struct Futex {
 #[derive(Default, Clone)]
 pub struct FutexRef(Rc<RefCell<Futex>>);
 
+impl FutexRef {
+    pub fn waiters(&self) -> usize {
+        self.0.borrow().waiters.len()
+    }
+}
+
 impl VisitProvenance for FutexRef {
     fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
         // No provenance in `Futex`.
@@ -728,25 +734,21 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         interp_ok(true)
     }
 
-    /// Wait for the futex to be signaled, or a timeout.
-    /// On a signal, `retval_succ` is written to `dest`.
-    /// On a timeout, `retval_timeout` is written to `dest` and `errno_timeout` is set as the last error.
+    /// Wait for the futex to be signaled, or a timeout. Once the thread is
+    /// unblocked, `callback` is called with the unblock reason.
     fn futex_wait(
         &mut self,
         futex_ref: FutexRef,
         bitset: u32,
         timeout: Option<(TimeoutClock, TimeoutAnchor, Duration)>,
-        retval_succ: Scalar,
-        retval_timeout: Scalar,
-        dest: MPlaceTy<'tcx>,
-        errno_timeout: IoError,
+        callback: DynUnblockCallback<'tcx>,
     ) {
         let this = self.eval_context_mut();
         let thread = this.active_thread();
         let mut futex = futex_ref.0.borrow_mut();
         let waiters = &mut futex.waiters;
         assert!(waiters.iter().all(|waiter| waiter.thread != thread), "thread is already waiting");
-        waiters.push_back(FutexWaiter { thread, bitset });
+        waiters.push(FutexWaiter { thread, bitset });
         drop(futex);
 
         this.block_thread(
@@ -755,10 +757,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             callback!(
                 @capture<'tcx> {
                     futex_ref: FutexRef,
-                    retval_succ: Scalar,
-                    retval_timeout: Scalar,
-                    dest: MPlaceTy<'tcx>,
-                    errno_timeout: IoError,
+                    callback: DynUnblockCallback<'tcx>,
                 }
                 |this, unblock: UnblockKind| {
                     match unblock {
@@ -768,29 +767,29 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                             if let Some(data_race) = &this.machine.data_race {
                                 data_race.acquire_clock(&futex.clock, &this.machine.threads);
                             }
-                            // Write the return value.
-                            this.write_scalar(retval_succ, &dest)?;
-                            interp_ok(())
                         },
                         UnblockKind::TimedOut => {
                             // Remove the waiter from the futex.
                             let thread = this.active_thread();
                             let mut futex = futex_ref.0.borrow_mut();
                             futex.waiters.retain(|waiter| waiter.thread != thread);
-                            // Set errno and write return value.
-                            this.set_last_error(errno_timeout)?;
-                            this.write_scalar(retval_timeout, &dest)?;
-                            interp_ok(())
                         },
                     }
+
+                    callback.call(this, unblock)
                 }
             ),
         );
     }
 
-    /// Wake up the first thread in the queue that matches any of the bits in the bitset.
-    /// Returns whether anything was woken.
-    fn futex_wake(&mut self, futex_ref: &FutexRef, bitset: u32) -> InterpResult<'tcx, bool> {
+    /// Wake up `count` of the threads in the queue that match any of the bits
+    /// in the bitset. Returns how many threads were woken.
+    fn futex_wake(
+        &mut self,
+        futex_ref: &FutexRef,
+        bitset: u32,
+        count: usize,
+    ) -> InterpResult<'tcx, usize> {
         let this = self.eval_context_mut();
         let mut futex = futex_ref.0.borrow_mut();
         let data_race = &this.machine.data_race;
@@ -800,13 +799,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             data_race.release_clock(&this.machine.threads, |clock| futex.clock.clone_from(clock));
         }
 
-        // Wake up the first thread in the queue that matches any of the bits in the bitset.
-        let Some(i) = futex.waiters.iter().position(|w| w.bitset & bitset != 0) else {
-            return interp_ok(false);
-        };
-        let waiter = futex.waiters.remove(i).unwrap();
+        // Remove `count` of the threads in the queue that match any of the bits in the bitset.
+        // We collect all of them before unblocking because the unblock callback may access the
+        // futex state to retrieve the remaining number of waiters on macOS.
+        let waiters: Vec<_> =
+            futex.waiters.extract_if(.., |w| w.bitset & bitset != 0).take(count).collect();
         drop(futex);
-        this.unblock_thread(waiter.thread, BlockReason::Futex)?;
-        interp_ok(true)
+
+        let woken = waiters.len();
+        for waiter in waiters {
+            this.unblock_thread(waiter.thread, BlockReason::Futex)?;
+        }
+
+        interp_ok(woken)
     }
 }
diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs
index 6d22dd8d68d93..a8a2491304dd1 100644
--- a/src/tools/miri/src/concurrency/thread.rs
+++ b/src/tools/miri/src/concurrency/thread.rs
@@ -1138,7 +1138,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         use rand::Rng as _;
 
         let this = self.eval_context_mut();
-        if this.machine.rng.get_mut().gen_bool(this.machine.preemption_rate) {
+        if this.machine.rng.get_mut().random_bool(this.machine.preemption_rate) {
             this.yield_active_thread();
         }
     }
diff --git a/src/tools/miri/src/eval.rs b/src/tools/miri/src/eval.rs
index c8f04e252072e..36b15dbf623da 100644
--- a/src/tools/miri/src/eval.rs
+++ b/src/tools/miri/src/eval.rs
@@ -558,15 +558,15 @@ where
 
                 match chars.next() {
                     Some('"') => {
-                        cmd.extend(iter::repeat('\\').take(nslashes * 2 + 1));
+                        cmd.extend(iter::repeat_n('\\', nslashes * 2 + 1));
                         cmd.push('"');
                     }
                     Some(c) => {
-                        cmd.extend(iter::repeat('\\').take(nslashes));
+                        cmd.extend(iter::repeat_n('\\', nslashes));
                         cmd.push(c);
                     }
                     None => {
-                        cmd.extend(iter::repeat('\\').take(nslashes * 2));
+                        cmd.extend(iter::repeat_n('\\', nslashes * 2));
                         break;
                     }
                 }
diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs
index c5538351d7dd1..a26f12cdfb1e2 100644
--- a/src/tools/miri/src/helpers.rs
+++ b/src/tools/miri/src/helpers.rs
@@ -421,7 +421,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
         if this.machine.communicate() {
             // Fill the buffer using the host's rng.
-            getrandom::getrandom(&mut data)
+            getrandom::fill(&mut data)
                 .map_err(|err| err_unsup_format!("host getrandom failed: {}", err))?;
         } else {
             let rng = this.machine.rng.get_mut();
@@ -678,6 +678,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         )
     }
 
+    /// Helper function used inside shims of foreign functions to check that the target OS
+    /// is one of `target_oses`. It returns an error containing the `name` of the foreign function
+    /// in a message if this is not the case.
+    fn check_target_os(&self, target_oses: &[&str], name: Symbol) -> InterpResult<'tcx> {
+        let target_os = self.eval_context_ref().tcx.sess.target.os.as_ref();
+        if !target_oses.contains(&target_os) {
+            throw_unsup_format!("`{name}` is not supported on {target_os}");
+        }
+        interp_ok(())
+    }
+
     /// Helper function used inside the shims of foreign functions to assert that the target OS
     /// is part of the UNIX family. It panics showing a message with the `name` of the foreign function
     /// if this is not the case.
@@ -991,6 +1002,22 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         check_arg_count(args)
     }
 
+    /// 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: Conv,
+        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)?;
+        check_vargarg_fixed_arg_count(link_name, abi, args)
+    }
+
     /// Mark a machine allocation that was just created as immutable.
     fn mark_immutable(&mut self, mplace: &MPlaceTy<'tcx>) {
         let this = self.eval_context_mut();
@@ -1184,8 +1211,10 @@ where
     throw_ub_format!("incorrect number of arguments: got {}, expected {}", args.len(), N)
 }
 
-/// Check that the number of args is at least the minumim what we expect.
-pub fn check_min_arg_count<'a, 'tcx, const N: usize>(
+/// Check that the number of varargs is at least the minimum what we expect.
+/// Fixed args should not be included.
+/// Use `check_vararg_fixed_arg_count` to extract the varargs slice from full function arguments.
+pub fn check_min_vararg_count<'a, 'tcx, const N: usize>(
     name: &'a str,
     args: &'a [OpTy<'tcx>],
 ) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> {
@@ -1193,7 +1222,35 @@ pub fn check_min_arg_count<'a, 'tcx, const N: usize>(
         return interp_ok(ops);
     }
     throw_ub_format!(
-        "incorrect number of arguments for `{name}`: got {}, expected at least {}",
+        "not enough variadic arguments for `{name}`: got {}, expected at least {}",
+        args.len(),
+        N
+    )
+}
+
+/// Check the number of fixed args of a vararg function.
+/// Returns a tuple that consisting of an array of fixed args, and a slice of varargs.
+fn check_vargarg_fixed_arg_count<'a, 'tcx, const N: usize>(
+    link_name: Symbol,
+    abi: &FnAbi<'tcx, Ty<'tcx>>,
+    args: &'a [OpTy<'tcx>],
+) -> InterpResult<'tcx, (&'a [OpTy<'tcx>; N], &'a [OpTy<'tcx>])> {
+    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);
+    }
+    throw_ub_format!(
+        "incorrect number of arguments for `{}`: got {}, expected at least {}",
+        link_name.as_str(),
         args.len(),
         N
     )
diff --git a/src/tools/miri/src/intrinsics/mod.rs b/src/tools/miri/src/intrinsics/mod.rs
index 9eebbc5d3631e..bce78adcaea45 100644
--- a/src/tools/miri/src/intrinsics/mod.rs
+++ b/src/tools/miri/src/intrinsics/mod.rs
@@ -141,7 +141,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 // FIXME: should we check for validity here? It's tricky because we do not have a
                 // place. Codegen does not seem to set any attributes like `noundef` for intrinsic
                 // calls, so we don't *have* to do anything.
-                let branch: bool = this.machine.rng.get_mut().gen();
+                let branch: bool = this.machine.rng.get_mut().random();
                 this.write_scalar(Scalar::from_bool(branch), dest)?;
             }
 
@@ -289,7 +289,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 let a = this.read_scalar(a)?.to_f32()?;
                 let b = this.read_scalar(b)?.to_f32()?;
                 let c = this.read_scalar(c)?.to_f32()?;
-                let fuse: bool = this.machine.rng.get_mut().gen();
+                let fuse: bool = this.machine.rng.get_mut().random();
                 let res = if fuse {
                     // FIXME: Using host floats, to work around https://github.com/rust-lang/rustc_apfloat/issues/11
                     a.to_host().mul_add(b.to_host(), c.to_host()).to_soft()
@@ -304,7 +304,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 let a = this.read_scalar(a)?.to_f64()?;
                 let b = this.read_scalar(b)?.to_f64()?;
                 let c = this.read_scalar(c)?.to_f64()?;
-                let fuse: bool = this.machine.rng.get_mut().gen();
+                let fuse: bool = this.machine.rng.get_mut().random();
                 let res = if fuse {
                     // FIXME: Using host floats, to work around https://github.com/rust-lang/rustc_apfloat/issues/11
                     a.to_host().mul_add(b.to_host(), c.to_host()).to_soft()
diff --git a/src/tools/miri/src/intrinsics/simd.rs b/src/tools/miri/src/intrinsics/simd.rs
index 63a61dcd14878..45e316b190a68 100644
--- a/src/tools/miri/src/intrinsics/simd.rs
+++ b/src/tools/miri/src/intrinsics/simd.rs
@@ -304,7 +304,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     let c = this.read_scalar(&this.project_index(&c, i)?)?;
                     let dest = this.project_index(&dest, i)?;
 
-                    let fuse: bool = intrinsic_name == "fma" || this.machine.rng.get_mut().gen();
+                    let fuse: bool = intrinsic_name == "fma" || this.machine.rng.get_mut().random();
 
                     // Works for f32 and f64.
                     // FIXME: using host floats to work around https://github.com/rust-lang/miri/issues/2468.
@@ -639,8 +639,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 let (right, right_len) = this.project_to_simd(right)?;
                 let (dest, dest_len) = this.project_to_simd(dest)?;
 
-                let index =
-                    generic_args[2].expect_const().to_value().valtree.unwrap_branch();
+                let index = generic_args[2].expect_const().to_value().valtree.unwrap_branch();
                 let index_len = index.len();
 
                 assert_eq!(left_len, right_len);
diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs
index 3ec35763a7d05..45054c37c40e9 100644
--- a/src/tools/miri/src/lib.rs
+++ b/src/tools/miri/src/lib.rs
@@ -15,6 +15,8 @@
 #![feature(unqualified_local_imports)]
 #![feature(derive_coerce_pointee)]
 #![feature(arbitrary_self_types)]
+#![feature(unsigned_is_multiple_of)]
+#![feature(extract_if)]
 // Configure clippy and other lints
 #![allow(
     clippy::collapsible_else_if,
@@ -36,6 +38,7 @@
     clippy::needless_question_mark,
     clippy::needless_lifetimes,
     clippy::too_long_first_doc_paragraph,
+    // We don't use translatable diagnostics
     rustc::diagnostic_outside_of_impl,
     // We are not implementing queries here so it's fine
     rustc::potential_query_instability,
diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs
index 3727b5f4cae4a..4735db48e81f8 100644
--- a/src/tools/miri/src/machine.rs
+++ b/src/tools/miri/src/machine.rs
@@ -1112,10 +1112,13 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
         // Call the lang item.
         let panic = ecx.tcx.lang_items().get(reason.lang_item()).unwrap();
         let panic = ty::Instance::mono(ecx.tcx.tcx, panic);
-        ecx.call_function(panic, ExternAbi::Rust, &[], None, StackPopCleanup::Goto {
-            ret: None,
-            unwind: mir::UnwindAction::Unreachable,
-        })?;
+        ecx.call_function(
+            panic,
+            ExternAbi::Rust,
+            &[],
+            None,
+            StackPopCleanup::Goto { ret: None, unwind: mir::UnwindAction::Unreachable },
+        )?;
         interp_ok(())
     }
 
@@ -1501,7 +1504,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
             catch_unwind: None,
             timing,
             is_user_relevant: ecx.machine.is_user_relevant(&frame),
-            salt: ecx.machine.rng.borrow_mut().gen::<usize>() % ADDRS_PER_ANON_GLOBAL,
+            salt: ecx.machine.rng.borrow_mut().random_range(0..ADDRS_PER_ANON_GLOBAL),
             data_race: ecx.machine.data_race.as_ref().map(|_| data_race::FrameState::default()),
         };
 
@@ -1716,7 +1719,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
         if unique {
             CTFE_ALLOC_SALT
         } else {
-            ecx.machine.rng.borrow_mut().gen::<usize>() % ADDRS_PER_ANON_GLOBAL
+            ecx.machine.rng.borrow_mut().random_range(0..ADDRS_PER_ANON_GLOBAL)
         }
     }
 
diff --git a/src/tools/miri/src/math.rs b/src/tools/miri/src/math.rs
index ed3d2d55678e2..7117f722fee89 100644
--- a/src/tools/miri/src/math.rs
+++ b/src/tools/miri/src/math.rs
@@ -1,9 +1,12 @@
 use rand::Rng as _;
-use rand::distributions::Distribution as _;
 use rustc_apfloat::Float as _;
 use rustc_apfloat::ieee::IeeeFloat;
 
-/// Disturbes a floating-point result by a relative error on the order of (-2^scale, 2^scale).
+/// Disturbes a floating-point result by a relative error in the range (-2^scale, 2^scale).
+///
+/// For a 2^N ULP error, you can use an `err_scale` of `-(F::PRECISION - 1 - N)`.
+/// In other words, a 1 ULP (absolute) error is the same as a `2^-(F::PRECISION-1)` relative error.
+/// (Subtracting 1 compensates for the integer bit.)
 pub(crate) fn apply_random_float_error<F: rustc_apfloat::Float>(
     ecx: &mut crate::MiriInterpCx<'_>,
     val: F,
@@ -11,12 +14,15 @@ pub(crate) fn apply_random_float_error<F: rustc_apfloat::Float>(
 ) -> F {
     let rng = ecx.machine.rng.get_mut();
     // Generate a random integer in the range [0, 2^PREC).
-    let dist = rand::distributions::Uniform::new(0, 1 << F::PRECISION);
-    let err = F::from_u128(dist.sample(rng))
-        .value
-        .scalbn(err_scale.strict_sub(F::PRECISION.try_into().unwrap()));
+    // (When read as binary, the position of the first `1` determines the exponent,
+    // and the remaining bits fill the mantissa. `PREC` is one plus the size of the mantissa,
+    // so this all works out.)
+    let r = F::from_u128(rng.random_range(0..(1 << F::PRECISION))).value;
+    // Multiply this with 2^(scale - PREC). The result is between 0 and
+    // 2^PREC * 2^(scale - PREC) = 2^scale.
+    let err = r.scalbn(err_scale.strict_sub(F::PRECISION.try_into().unwrap()));
     // give it a random sign
-    let err = if rng.gen::<bool>() { -err } else { err };
+    let err = if rng.random() { -err } else { err };
     // multiple the value with (1+err)
     (val * (F::from_u128(1).value + err).value).value
 }
diff --git a/src/tools/miri/src/operator.rs b/src/tools/miri/src/operator.rs
index 43c628d66d590..c588b6fc7f159 100644
--- a/src/tools/miri/src/operator.rs
+++ b/src/tools/miri/src/operator.rs
@@ -108,7 +108,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         // Pick one of the NaNs.
         let nan = nans.choose(&mut *rand).unwrap();
         // Non-deterministically flip the sign.
-        if rand.gen() {
+        if rand.random() {
             // This will properly flip even for NaN.
             -nan
         } else {
@@ -120,6 +120,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         let this = self.eval_context_ref();
         // Return one side non-deterministically.
         let mut rand = this.machine.rng.borrow_mut();
-        if rand.gen() { a } else { b }
+        if rand.random() { a } else { b }
     }
 }
diff --git a/src/tools/miri/src/shims/alloc.rs b/src/tools/miri/src/shims/alloc.rs
index 0fda13e061600..323b95d5f5f23 100644
--- a/src/tools/miri/src/shims/alloc.rs
+++ b/src/tools/miri/src/shims/alloc.rs
@@ -81,7 +81,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
     fn malloc(&mut self, size: u64, init: AllocInit) -> InterpResult<'tcx, Pointer> {
         let this = self.eval_context_mut();
         let align = this.malloc_align(size);
-        let ptr = this.allocate_ptr(Size::from_bytes(size), align, MiriMemoryKind::C.into(), init)?;
+        let ptr =
+            this.allocate_ptr(Size::from_bytes(size), align, MiriMemoryKind::C.into(), init)?;
         interp_ok(ptr.into())
     }
 
@@ -92,7 +93,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         size: &OpTy<'tcx>,
     ) -> InterpResult<'tcx, Scalar> {
         let this = self.eval_context_mut();
-        let memptr = this.deref_pointer(memptr)?;
+        let memptr = this.deref_pointer_as(memptr, this.machine.layouts.mut_raw_ptr)?;
         let align = this.read_target_usize(align)?;
         let size = this.read_target_usize(size)?;
 
@@ -105,7 +106,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 Size::from_bytes(size),
                 Align::from_bytes(align).unwrap(),
                 MiriMemoryKind::C.into(),
-                AllocInit::Uninit
+                AllocInit::Uninit,
             )?;
             this.write_pointer(ptr, &memptr)?;
             interp_ok(Scalar::from_i32(0))
@@ -138,7 +139,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     Size::from_bytes(new_size),
                     new_align,
                     MiriMemoryKind::C.into(),
-                    AllocInit::Uninit
+                    AllocInit::Uninit,
                 )?;
                 interp_ok(new_ptr.into())
             }
@@ -179,7 +180,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     Size::from_bytes(size),
                     Align::from_bytes(align).unwrap(),
                     MiriMemoryKind::C.into(),
-                    AllocInit::Uninit
+                    AllocInit::Uninit,
                 )?;
                 interp_ok(ptr.into())
             }
diff --git a/src/tools/miri/src/shims/backtrace.rs b/src/tools/miri/src/shims/backtrace.rs
index 1622ef280d25c..7e667e70a1721 100644
--- a/src/tools/miri/src/shims/backtrace.rs
+++ b/src/tools/miri/src/shims/backtrace.rs
@@ -4,7 +4,6 @@ use rustc_middle::ty::{self, Instance, Ty};
 use rustc_span::{BytePos, Loc, Symbol, hygiene};
 use rustc_target::callconv::{Conv, FnAbi};
 
-use crate::helpers::check_min_arg_count;
 use crate::*;
 
 impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
@@ -34,13 +33,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         abi: &FnAbi<'tcx, Ty<'tcx>>,
         link_name: Symbol,
         args: &[OpTy<'tcx>],
-        dest: &MPlaceTy<'tcx>,
     ) -> InterpResult<'tcx> {
         let this = self.eval_context_mut();
-        let tcx = this.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, Conv::Rust, link_name, args)?;
 
-        let [flags] = check_min_arg_count("miri_get_backtrace", args)?;
         let flags = this.read_scalar(flags)?.to_u64()?;
+        let buf_place = this.deref_pointer_as(buf, ptr_layout)?;
 
         let mut data = Vec::new();
         for frame in this.active_thread_stack().iter().rev() {
@@ -63,44 +64,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             })
             .collect();
 
-        let len: u64 = ptrs.len().try_into().unwrap();
-
-        let ptr_ty = this.machine.layouts.mut_raw_ptr.ty;
-        let array_layout = this.layout_of(Ty::new_array(tcx.tcx, ptr_ty, len)).unwrap();
-
         match flags {
-            // storage for pointers is allocated by miri
-            // deallocating the slice is undefined behavior with a custom global allocator
             0 => {
-                let [_flags] = this.check_shim(abi, Conv::Rust, link_name, args)?;
-
-                let alloc = this.allocate(array_layout, MiriMemoryKind::Rust.into())?;
-
-                // Write pointers into array
-                for (i, ptr) in ptrs.into_iter().enumerate() {
-                    let place = this.project_index(&alloc, i as u64)?;
-
-                    this.write_pointer(ptr, &place)?;
-                }
-
-                this.write_immediate(Immediate::new_slice(alloc.ptr(), len, this), dest)?;
+                throw_unsup_format!("miri_get_backtrace: v0 is not supported any more");
             }
-            // storage for pointers is allocated by the caller
-            1 => {
-                let [_flags, buf] = this.check_shim(abi, Conv::Rust, link_name, args)?;
-
-                let buf_place = this.deref_pointer(buf)?;
-
-                let ptr_layout = this.layout_of(ptr_ty)?;
-
+            1 =>
                 for (i, ptr) in ptrs.into_iter().enumerate() {
                     let offset = ptr_layout.size.checked_mul(i.try_into().unwrap(), this).unwrap();
 
                     let op_place = buf_place.offset(offset, ptr_layout, this)?;
 
                     this.write_pointer(ptr, &op_place)?;
-                }
-            }
+                },
             _ => throw_unsup_format!("unknown `miri_get_backtrace` flags {}", flags),
         };
 
diff --git a/src/tools/miri/src/shims/extern_static.rs b/src/tools/miri/src/shims/extern_static.rs
index f0aebfe169378..20dd3c8db2a3e 100644
--- a/src/tools/miri/src/shims/extern_static.rs
+++ b/src/tools/miri/src/shims/extern_static.rs
@@ -60,10 +60,10 @@ impl<'tcx> MiriMachine<'tcx> {
 
         match ecx.tcx.sess.target.os.as_ref() {
             "linux" => {
-                Self::null_ptr_extern_statics(ecx, &[
-                    "__cxa_thread_atexit_impl",
-                    "__clock_gettime64",
-                ])?;
+                Self::null_ptr_extern_statics(
+                    ecx,
+                    &["__cxa_thread_atexit_impl", "__clock_gettime64"],
+                )?;
                 Self::weak_symbol_extern_statics(ecx, &["getrandom", "statx"])?;
             }
             "freebsd" => {
diff --git a/src/tools/miri/src/shims/files.rs b/src/tools/miri/src/shims/files.rs
index 73425eee51569..6b4f4cdc922a0 100644
--- a/src/tools/miri/src/shims/files.rs
+++ b/src/tools/miri/src/shims/files.rs
@@ -1,6 +1,6 @@
 use std::any::Any;
 use std::collections::BTreeMap;
-use std::io::{IsTerminal, Read, SeekFrom, Write};
+use std::io::{IsTerminal, SeekFrom, Write};
 use std::marker::CoercePointee;
 use std::ops::Deref;
 use std::rc::{Rc, Weak};
@@ -140,8 +140,8 @@ pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
         _communicate_allowed: bool,
         _ptr: Pointer,
         _len: usize,
-        _dest: &MPlaceTy<'tcx>,
         _ecx: &mut MiriInterpCx<'tcx>,
+        _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
     ) -> InterpResult<'tcx> {
         throw_unsup_format!("cannot read from {}", self.name());
     }
@@ -154,8 +154,8 @@ pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
         _communicate_allowed: bool,
         _ptr: Pointer,
         _len: usize,
-        _dest: &MPlaceTy<'tcx>,
         _ecx: &mut MiriInterpCx<'tcx>,
+        _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
     ) -> InterpResult<'tcx> {
         throw_unsup_format!("cannot write to {}", self.name());
     }
@@ -207,19 +207,16 @@ impl FileDescription for io::Stdin {
         communicate_allowed: bool,
         ptr: Pointer,
         len: usize,
-        dest: &MPlaceTy<'tcx>,
         ecx: &mut MiriInterpCx<'tcx>,
+        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
     ) -> InterpResult<'tcx> {
-        let mut bytes = vec![0; len];
         if !communicate_allowed {
             // We want isolation mode to be deterministic, so we have to disallow all reads, even stdin.
             helpers::isolation_abort_error("`read` from stdin")?;
         }
-        let result = Read::read(&mut &*self, &mut bytes);
-        match result {
-            Ok(read_size) => ecx.return_read_success(ptr, &bytes, read_size, dest),
-            Err(e) => ecx.set_last_error_and_return(e, dest),
-        }
+
+        let result = ecx.read_from_host(&*self, len, ptr)?;
+        finish.call(ecx, result)
     }
 
     fn is_tty(&self, communicate_allowed: bool) -> bool {
@@ -237,22 +234,19 @@ impl FileDescription for io::Stdout {
         _communicate_allowed: bool,
         ptr: Pointer,
         len: usize,
-        dest: &MPlaceTy<'tcx>,
         ecx: &mut MiriInterpCx<'tcx>,
+        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
     ) -> InterpResult<'tcx> {
-        let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
-        // We allow writing to stderr even with isolation enabled.
-        let result = Write::write(&mut &*self, bytes);
+        // We allow writing to stdout even with isolation enabled.
+        let result = ecx.write_to_host(&*self, len, ptr)?;
         // Stdout is buffered, flush to make sure it appears on the
         // screen.  This is the write() syscall of the interpreted
         // program, we want it to correspond to a write() syscall on
         // the host -- there is no good in adding extra buffering
         // here.
         io::stdout().flush().unwrap();
-        match result {
-            Ok(write_size) => ecx.return_write_success(write_size, dest),
-            Err(e) => ecx.set_last_error_and_return(e, dest),
-        }
+
+        finish.call(ecx, result)
     }
 
     fn is_tty(&self, communicate_allowed: bool) -> bool {
@@ -270,17 +264,13 @@ impl FileDescription for io::Stderr {
         _communicate_allowed: bool,
         ptr: Pointer,
         len: usize,
-        dest: &MPlaceTy<'tcx>,
         ecx: &mut MiriInterpCx<'tcx>,
+        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
     ) -> InterpResult<'tcx> {
-        let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
         // We allow writing to stderr even with isolation enabled.
+        let result = ecx.write_to_host(&*self, len, ptr)?;
         // No need to flush, stderr is not buffered.
-        let result = Write::write(&mut &*self, bytes);
-        match result {
-            Ok(write_size) => ecx.return_write_success(write_size, dest),
-            Err(e) => ecx.set_last_error_and_return(e, dest),
-        }
+        finish.call(ecx, result)
     }
 
     fn is_tty(&self, communicate_allowed: bool) -> bool {
@@ -302,11 +292,11 @@ impl FileDescription for NullOutput {
         _communicate_allowed: bool,
         _ptr: Pointer,
         len: usize,
-        dest: &MPlaceTy<'tcx>,
         ecx: &mut MiriInterpCx<'tcx>,
+        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
     ) -> InterpResult<'tcx> {
         // We just don't write anything, but report to the user that we did.
-        ecx.return_write_success(len, dest)
+        finish.call(ecx, Ok(len))
     }
 }
 
@@ -405,40 +395,41 @@ impl FdTable {
 
 impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
 pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
-    /// Helper to implement `FileDescription::read`:
-    /// This is only used when `read` is successful.
-    /// `actual_read_size` should be the return value of some underlying `read` call that used
-    /// `bytes` as its output buffer.
-    /// The length of `bytes` must not exceed either the host's or the target's `isize`.
-    /// `bytes` is written to `buf` and the size is written to `dest`.
-    fn return_read_success(
+    /// Read data from a host `Read` type, store the result into machine memory,
+    /// and return whether that worked.
+    fn read_from_host(
         &mut self,
-        buf: Pointer,
-        bytes: &[u8],
-        actual_read_size: usize,
-        dest: &MPlaceTy<'tcx>,
-    ) -> InterpResult<'tcx> {
+        mut file: impl io::Read,
+        len: usize,
+        ptr: Pointer,
+    ) -> InterpResult<'tcx, Result<usize, IoError>> {
         let this = self.eval_context_mut();
-        // If reading to `bytes` did not fail, we write those bytes to the buffer.
-        // Crucially, if fewer than `bytes.len()` bytes were read, only write
-        // that much into the output buffer!
-        this.write_bytes_ptr(buf, bytes[..actual_read_size].iter().copied())?;
 
-        // The actual read size is always less than what got originally requested so this cannot fail.
-        this.write_int(u64::try_from(actual_read_size).unwrap(), dest)?;
-        interp_ok(())
+        let mut bytes = vec![0; len];
+        let result = file.read(&mut bytes);
+        match result {
+            Ok(read_size) => {
+                // If reading to `bytes` did not fail, we write those bytes to the buffer.
+                // Crucially, if fewer than `bytes.len()` bytes were read, only write
+                // that much into the output buffer!
+                this.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
+                interp_ok(Ok(read_size))
+            }
+            Err(e) => interp_ok(Err(IoError::HostError(e))),
+        }
     }
 
-    /// Helper to implement `FileDescription::write`:
-    /// This function is only used when `write` is successful, and writes `actual_write_size` to `dest`
-    fn return_write_success(
+    /// Write data to a host `Write` type, withthe bytes taken from machine memory.
+    fn write_to_host(
         &mut self,
-        actual_write_size: usize,
-        dest: &MPlaceTy<'tcx>,
-    ) -> InterpResult<'tcx> {
+        mut file: impl io::Write,
+        len: usize,
+        ptr: Pointer,
+    ) -> InterpResult<'tcx, Result<usize, IoError>> {
         let this = self.eval_context_mut();
-        // The actual write size is always less than what got originally requested so this cannot fail.
-        this.write_int(u64::try_from(actual_write_size).unwrap(), dest)?;
-        interp_ok(())
+
+        let bytes = this.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
+        let result = file.write(bytes);
+        interp_ok(result.map_err(IoError::HostError))
     }
 }
diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs
index 1ce0c209de9e2..97bfb04f1f471 100644
--- a/src/tools/miri/src/shims/foreign_items.rs
+++ b/src/tools/miri/src/shims/foreign_items.rs
@@ -357,7 +357,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Obtains a Miri backtrace. See the README for details.
             "miri_get_backtrace" => {
                 // `check_shim` happens inside `handle_miri_get_backtrace`.
-                this.handle_miri_get_backtrace(abi, link_name, args, dest)?;
+                this.handle_miri_get_backtrace(abi, link_name, args)?;
             }
             // Resolves a Miri backtrace frame. See the README for details.
             "miri_resolve_frame" => {
@@ -509,7 +509,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                         Size::from_bytes(size),
                         Align::from_bytes(align).unwrap(),
                         memory_kind.into(),
-                        AllocInit::Uninit
+                        AllocInit::Uninit,
                     )?;
 
                     ecx.write_pointer(ptr, dest)
@@ -538,7 +538,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                         Size::from_bytes(size),
                         Align::from_bytes(align).unwrap(),
                         MiriMemoryKind::Rust.into(),
-                        AllocInit::Zero
+                        AllocInit::Zero,
                     )?;
                     this.write_pointer(ptr, dest)
                 });
@@ -599,7 +599,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                         Size::from_bytes(new_size),
                         align,
                         MiriMemoryKind::Rust.into(),
-                        AllocInit::Uninit
+                        AllocInit::Uninit,
                     )?;
                     this.write_pointer(new_ptr, dest)
                 });
@@ -861,7 +861,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
             "lgammaf_r" => {
                 let [x, signp] = this.check_shim(abi, Conv::C, link_name, args)?;
                 let x = this.read_scalar(x)?.to_f32()?;
-                let signp = this.deref_pointer(signp)?;
+                let signp = this.deref_pointer_as(signp, this.machine.layouts.i32)?;
 
                 // Using host floats (but it's fine, these operations do not have guaranteed precision).
                 let (res, sign) = x.to_host().ln_gamma();
@@ -872,7 +872,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
             "lgamma_r" => {
                 let [x, signp] = this.check_shim(abi, Conv::C, link_name, args)?;
                 let x = this.read_scalar(x)?.to_f64()?;
-                let signp = this.deref_pointer(signp)?;
+                let signp = this.deref_pointer_as(signp, this.machine.layouts.i32)?;
 
                 // Using host floats (but it's fine, these operations do not have guaranteed precision).
                 let (res, sign) = x.to_host().ln_gamma();
diff --git a/src/tools/miri/src/shims/panic.rs b/src/tools/miri/src/shims/panic.rs
index 93479540009ea..83f331bb173db 100644
--- a/src/tools/miri/src/shims/panic.rs
+++ b/src/tools/miri/src/shims/panic.rs
@@ -247,10 +247,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 // Call the lang item associated with this message.
                 let fn_item = this.tcx.require_lang_item(msg.panic_function(), None);
                 let instance = ty::Instance::mono(this.tcx.tcx, fn_item);
-                this.call_function(instance, ExternAbi::Rust, &[], None, StackPopCleanup::Goto {
-                    ret: None,
-                    unwind,
-                })?;
+                this.call_function(
+                    instance,
+                    ExternAbi::Rust,
+                    &[],
+                    None,
+                    StackPopCleanup::Goto { ret: None, unwind },
+                )?;
             }
         }
         interp_ok(())
diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs
index d6c77d9c4d9a0..64b3ce6b4e494 100644
--- a/src/tools/miri/src/shims/time.rs
+++ b/src/tools/miri/src/shims/time.rs
@@ -132,16 +132,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         this.assert_target_os_is_unix("localtime_r");
         this.check_no_isolation("`localtime_r`")?;
 
-        let timep = this.deref_pointer(timep)?;
+        let time_layout = this.libc_ty_layout("time_t");
+        let timep = this.deref_pointer_as(timep, time_layout)?;
         let result = this.deref_pointer_as(result_op, this.libc_ty_layout("tm"))?;
 
         // The input "represents the number of seconds elapsed since the Epoch,
         // 1970-01-01 00:00:00 +0000 (UTC)".
-        let sec_since_epoch: i64 = this
-            .read_scalar(&timep)?
-            .to_int(this.libc_ty_layout("time_t").size)?
-            .try_into()
-            .unwrap();
+        let sec_since_epoch: i64 =
+            this.read_scalar(&timep)?.to_int(time_layout.size)?.try_into().unwrap();
         let dt_utc: DateTime<Utc> =
             DateTime::from_timestamp(sec_since_epoch, 0).expect("Invalid timestamp");
 
@@ -254,7 +252,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         let qpc = i64::try_from(duration.as_nanos()).map_err(|_| {
             err_unsup_format!("programs running longer than 2^63 nanoseconds are not supported")
         })?;
-        this.write_scalar(Scalar::from_i64(qpc), &this.deref_pointer(lpPerformanceCount_op)?)?;
+        this.write_scalar(
+            Scalar::from_i64(qpc),
+            &this.deref_pointer_as(lpPerformanceCount_op, this.machine.layouts.i64)?,
+        )?;
         interp_ok(Scalar::from_i32(-1)) // return non-zero on success
     }
 
diff --git a/src/tools/miri/src/shims/unix/android/thread.rs b/src/tools/miri/src/shims/unix/android/thread.rs
index 8d5d4a52b6efe..c7e2c4d507b22 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::{Conv, FnAbi};
 
-use crate::helpers::check_min_arg_count;
+use crate::helpers::check_min_vararg_count;
 use crate::shims::unix::thread::{EvalContextExt as _, ThreadNameResult};
 use crate::*;
 
@@ -16,18 +16,15 @@ pub fn prctl<'tcx>(
     args: &[OpTy<'tcx>],
     dest: &MPlaceTy<'tcx>,
 ) -> InterpResult<'tcx> {
-    // We do not use `check_shim` here because `prctl` is variadic. The argument
-    // count is checked bellow.
-    ecx.check_abi_and_shim_symbol_clash(abi, Conv::C, link_name)?;
+    let ([op], varargs) = ecx.check_shim_variadic(abi, Conv::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;
     let pr_get_name = 16;
 
-    let [op] = check_min_arg_count("prctl", args)?;
     let res = match ecx.read_scalar(op)?.to_i32()? {
         op if op == pr_set_name => {
-            let [_, name] = check_min_arg_count("prctl(PR_SET_NAME, ...)", args)?;
+            let [name] = check_min_vararg_count("prctl(PR_SET_NAME, ...)", varargs)?;
             let name = ecx.read_scalar(name)?;
             let thread = ecx.pthread_self()?;
             // The Linux kernel silently truncates long names.
@@ -38,7 +35,7 @@ pub fn prctl<'tcx>(
             Scalar::from_u32(0)
         }
         op if op == pr_get_name => {
-            let [_, name] = check_min_arg_count("prctl(PR_GET_NAME, ...)", args)?;
+            let [name] = check_min_vararg_count("prctl(PR_GET_NAME, ...)", varargs)?;
             let name = ecx.read_scalar(name)?;
             let thread = ecx.pthread_self()?;
             let len = Scalar::from_target_usize(TASK_COMM_LEN as u64, ecx);
diff --git a/src/tools/miri/src/shims/unix/fd.rs b/src/tools/miri/src/shims/unix/fd.rs
index 0b59490308b44..3f85b9ae9bd94 100644
--- a/src/tools/miri/src/shims/unix/fd.rs
+++ b/src/tools/miri/src/shims/unix/fd.rs
@@ -6,7 +6,7 @@ use std::io::ErrorKind;
 
 use rustc_abi::Size;
 
-use crate::helpers::check_min_arg_count;
+use crate::helpers::check_min_vararg_count;
 use crate::shims::files::FileDescription;
 use crate::shims::unix::linux_like::epoll::EpollReadyEvents;
 use crate::shims::unix::*;
@@ -30,8 +30,8 @@ pub trait UnixFileDescription: FileDescription {
         _offset: u64,
         _ptr: Pointer,
         _len: usize,
-        _dest: &MPlaceTy<'tcx>,
         _ecx: &mut MiriInterpCx<'tcx>,
+        _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
     ) -> InterpResult<'tcx> {
         throw_unsup_format!("cannot pread from {}", self.name());
     }
@@ -46,8 +46,8 @@ pub trait UnixFileDescription: FileDescription {
         _ptr: Pointer,
         _len: usize,
         _offset: u64,
-        _dest: &MPlaceTy<'tcx>,
         _ecx: &mut MiriInterpCx<'tcx>,
+        _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
     ) -> InterpResult<'tcx> {
         throw_unsup_format!("cannot pwrite to {}", self.name());
     }
@@ -127,11 +127,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
     }
 
-    fn fcntl(&mut self, args: &[OpTy<'tcx>]) -> InterpResult<'tcx, Scalar> {
+    fn fcntl(
+        &mut self,
+        fd_num: &OpTy<'tcx>,
+        cmd: &OpTy<'tcx>,
+        varargs: &[OpTy<'tcx>],
+    ) -> InterpResult<'tcx, Scalar> {
         let this = self.eval_context_mut();
 
-        let [fd_num, cmd] = check_min_arg_count("fcntl", args)?;
-
         let fd_num = this.read_scalar(fd_num)?.to_i32()?;
         let cmd = this.read_scalar(cmd)?.to_i32()?;
 
@@ -163,7 +166,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     "fcntl(fd, F_DUPFD_CLOEXEC, ...)"
                 };
 
-                let [_, _, start] = check_min_arg_count(cmd_name, args)?;
+                let [start] = check_min_vararg_count(cmd_name, varargs)?;
                 let start = this.read_scalar(start)?.to_i32()?;
 
                 if let Some(fd) = this.machine.fds.get(fd_num) {
@@ -233,7 +236,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         let count = usize::try_from(count).unwrap(); // now it fits in a `usize`
         let communicate = this.machine.communicate();
 
-        // We temporarily dup the FD to be able to retain mutable access to `this`.
+        // Get the FD.
         let Some(fd) = this.machine.fds.get(fd_num) else {
             trace!("read: FD not found");
             return this.set_last_error_and_return(LibcError("EBADF"), dest);
@@ -244,13 +247,33 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         // because it was a target's `usize`. Also we are sure that its smaller than
         // `usize::MAX` because it is bounded by the host's `isize`.
 
+        let finish = {
+            let dest = dest.clone();
+            callback!(
+                @capture<'tcx> {
+                    count: usize,
+                    dest: MPlaceTy<'tcx>,
+                }
+                |this, result: Result<usize, IoError>| {
+                    match result {
+                        Ok(read_size) => {
+                            assert!(read_size <= count);
+                            // This must fit since `count` fits.
+                            this.write_int(u64::try_from(read_size).unwrap(), &dest)
+                        }
+                        Err(e) => {
+                            this.set_last_error_and_return(e, &dest)
+                        }
+                }}
+            )
+        };
         match offset {
-            None => fd.read(communicate, buf, count, dest, this)?,
+            None => fd.read(communicate, buf, count, this, finish)?,
             Some(offset) => {
                 let Ok(offset) = u64::try_from(offset) else {
                     return this.set_last_error_and_return(LibcError("EINVAL"), dest);
                 };
-                fd.as_unix().pread(communicate, offset, buf, count, dest, this)?
+                fd.as_unix().pread(communicate, offset, buf, count, this, finish)?
             }
         };
         interp_ok(())
@@ -284,13 +307,33 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             return this.set_last_error_and_return(LibcError("EBADF"), dest);
         };
 
+        let finish = {
+            let dest = dest.clone();
+            callback!(
+                @capture<'tcx> {
+                    count: usize,
+                    dest: MPlaceTy<'tcx>,
+                }
+                |this, result: Result<usize, IoError>| {
+                    match result {
+                        Ok(write_size) => {
+                            assert!(write_size <= count);
+                            // This must fit since `count` fits.
+                            this.write_int(u64::try_from(write_size).unwrap(), &dest)
+                        }
+                        Err(e) => {
+                            this.set_last_error_and_return(e, &dest)
+                        }
+                }}
+            )
+        };
         match offset {
-            None => fd.write(communicate, buf, count, dest, this)?,
+            None => fd.write(communicate, buf, count, this, finish)?,
             Some(offset) => {
                 let Ok(offset) = u64::try_from(offset) else {
                     return this.set_last_error_and_return(LibcError("EINVAL"), dest);
                 };
-                fd.as_unix().pwrite(communicate, buf, count, offset, dest, this)?
+                fd.as_unix().pwrite(communicate, buf, count, offset, this, finish)?
             }
         };
         interp_ok(())
diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs
index 3353cf2cc59d2..d459ec7cb774c 100644
--- a/src/tools/miri/src/shims/unix/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/foreign_items.rs
@@ -205,10 +205,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_scalar(result, dest)?;
             }
             "fcntl" => {
-                // `fcntl` is variadic. The argument count is checked based on the first argument
-                // in `this.fcntl()`, so we do not use `check_shim` here.
-                this.check_abi_and_shim_symbol_clash(abi, Conv::C, link_name)?;
-                let result = this.fcntl(args)?;
+                let ([fd_num, cmd], varargs) =
+                    this.check_shim_variadic(abi, Conv::C, link_name, args)?;
+                let result = this.fcntl(fd_num, cmd, varargs)?;
                 this.write_scalar(result, dest)?;
             }
             "dup" => {
@@ -236,8 +235,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             "open" | "open64" => {
                 // `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
-                this.check_abi_and_shim_symbol_clash(abi, Conv::C, link_name)?;
-                let result = this.open(args)?;
+                let ([path_raw, flag], varargs) =
+                    this.check_shim_variadic(abi, Conv::C, link_name, args)?;
+                let result = this.open(path_raw, flag, varargs)?;
                 this.write_scalar(result, dest)?;
             }
             "unlink" => {
@@ -354,10 +354,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             }
             "pipe2" => {
                 // Currently this function does not exist on all Unixes, e.g. on macOS.
-                if !matches!(&*this.tcx.sess.target.os, "linux" | "freebsd" | "solaris" | "illumos")
-                {
-                    throw_unsup_format!("`pipe2` is not supported on {}", this.tcx.sess.target.os);
-                }
+                this.check_target_os(&["linux", "freebsd", "solaris", "illumos"], link_name)?;
                 let [pipefd, flags] = this.check_shim(abi, Conv::C, link_name, args)?;
                 let result = this.pipe2(pipefd, Some(flags))?;
                 this.write_scalar(result, dest)?;
@@ -402,12 +399,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
             "reallocarray" => {
                 // Currently this function does not exist on all Unixes, e.g. on macOS.
-                if !matches!(&*this.tcx.sess.target.os, "linux" | "freebsd" | "android") {
-                    throw_unsup_format!(
-                        "`reallocarray` is not supported on {}",
-                        this.tcx.sess.target.os
-                    );
-                }
+                this.check_target_os(&["linux", "freebsd", "android"], link_name)?;
                 let [ptr, nmemb, size] = this.check_shim(abi, Conv::C, link_name, args)?;
                 let ptr = this.read_pointer(ptr)?;
                 let nmemb = this.read_target_usize(nmemb)?;
@@ -656,13 +648,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             }
             "sched_getaffinity" => {
                 // Currently this function does not exist on all Unixes, e.g. on macOS.
-                if !matches!(&*this.tcx.sess.target.os, "linux" | "freebsd" | "android") {
-                    throw_unsup_format!(
-                        "`sched_getaffinity` is not supported on {}",
-                        this.tcx.sess.target.os
-                    );
-                }
-
+                this.check_target_os(&["linux", "freebsd", "android"], link_name)?;
                 let [pid, cpusetsize, mask] = this.check_shim(abi, Conv::C, link_name, args)?;
                 let pid = this.read_scalar(pid)?.to_u32()?;
                 let cpusetsize = this.read_target_usize(cpusetsize)?;
@@ -699,13 +685,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             }
             "sched_setaffinity" => {
                 // Currently this function does not exist on all Unixes, e.g. on macOS.
-                if !matches!(&*this.tcx.sess.target.os, "linux" | "freebsd" | "android") {
-                    throw_unsup_format!(
-                        "`sched_setaffinity` is not supported on {}",
-                        this.tcx.sess.target.os
-                    );
-                }
-
+                this.check_target_os(&["linux", "freebsd", "android"], link_name)?;
                 let [pid, cpusetsize, mask] = this.check_shim(abi, Conv::C, link_name, args)?;
                 let pid = this.read_scalar(pid)?.to_u32()?;
                 let cpusetsize = this.read_target_usize(cpusetsize)?;
@@ -761,16 +741,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             "getentropy" => {
                 // This function is non-standard but exists with the same signature and behavior on
                 // Linux, macOS, FreeBSD and Solaris/Illumos.
-                if !matches!(
-                    &*this.tcx.sess.target.os,
-                    "linux" | "macos" | "freebsd" | "illumos" | "solaris" | "android"
-                ) {
-                    throw_unsup_format!(
-                        "`getentropy` is not supported on {}",
-                        this.tcx.sess.target.os
-                    );
-                }
-
+                this.check_target_os(
+                    &["linux", "macos", "freebsd", "illumos", "solaris", "android"],
+                    link_name,
+                )?;
                 let [buf, bufsize] = this.check_shim(abi, Conv::C, link_name, args)?;
                 let buf = this.read_pointer(buf)?;
                 let bufsize = this.read_target_usize(bufsize)?;
@@ -797,15 +771,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             "getrandom" => {
                 // This function is non-standard but exists with the same signature and behavior on
                 // Linux, FreeBSD and Solaris/Illumos.
-                if !matches!(
-                    &*this.tcx.sess.target.os,
-                    "linux" | "freebsd" | "illumos" | "solaris" | "android"
-                ) {
-                    throw_unsup_format!(
-                        "`getrandom` is not supported on {}",
-                        this.tcx.sess.target.os
-                    );
-                }
+                this.check_target_os(
+                    &["linux", "freebsd", "illumos", "solaris", "android"],
+                    link_name,
+                )?;
                 let [ptr, len, flags] = this.check_shim(abi, Conv::C, link_name, args)?;
                 let ptr = this.read_pointer(ptr)?;
                 let len = this.read_target_usize(len)?;
@@ -817,12 +786,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             "arc4random_buf" => {
                 // This function is non-standard but exists with the same signature and
                 // same behavior (eg never fails) on FreeBSD and Solaris/Illumos.
-                if !matches!(&*this.tcx.sess.target.os, "freebsd" | "illumos" | "solaris") {
-                    throw_unsup_format!(
-                        "`arc4random_buf` is not supported on {}",
-                        this.tcx.sess.target.os
-                    );
-                }
+                this.check_target_os(&["freebsd", "illumos", "solaris"], link_name)?;
                 let [ptr, len] = this.check_shim(abi, Conv::C, link_name, args)?;
                 let ptr = this.read_pointer(ptr)?;
                 let len = this.read_target_usize(len)?;
@@ -842,15 +806,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 // For arm32 they did something custom, but similar enough that the same
                 // `_Unwind_RaiseException` impl in miri should work:
                 // https://github.com/ARM-software/abi-aa/blob/main/ehabi32/ehabi32.rst
-                if !matches!(
-                    &*this.tcx.sess.target.os,
-                    "linux" | "freebsd" | "illumos" | "solaris" | "android" | "macos"
-                ) {
-                    throw_unsup_format!(
-                        "`_Unwind_RaiseException` is not supported on {}",
-                        this.tcx.sess.target.os
-                    );
-                }
+                this.check_target_os(
+                    &["linux", "freebsd", "illumos", "solaris", "android", "macos"],
+                    link_name,
+                )?;
                 // This function looks and behaves excatly like miri_start_unwind.
                 let [payload] = this.check_shim(abi, Conv::C, link_name, args)?;
                 this.handle_miri_start_unwind(payload)?;
@@ -866,8 +825,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // 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, Conv::C, link_name, args)?;
-                let guard_size = this.deref_pointer(guard_size)?;
                 let guard_size_layout = this.libc_ty_layout("size_t");
+                let guard_size = this.deref_pointer_as(guard_size, guard_size_layout)?;
                 this.write_scalar(
                     Scalar::from_uint(this.machine.page_size, guard_size_layout.size),
                     &guard_size,
@@ -893,8 +852,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     this.check_shim(abi, Conv::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(addr_place)?;
-                let size_place = this.deref_pointer(size_place)?;
+                let addr_place = this.deref_pointer_as(addr_place, this.machine.layouts.usize)?;
+                let size_place = this.deref_pointer_as(size_place, this.machine.layouts.usize)?;
 
                 this.write_scalar(
                     Scalar::from_uint(this.machine.stack_addr, this.pointer_size()),
@@ -928,7 +887,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 let pwd = this.deref_pointer_as(pwd, this.libc_ty_layout("passwd"))?;
                 let buf = this.read_pointer(buf)?;
                 let buflen = this.read_target_usize(buflen)?;
-                let result = this.deref_pointer(result)?;
+                let result = this.deref_pointer_as(result, this.machine.layouts.mut_raw_ptr)?;
 
                 // Must be for "us".
                 if uid != UID {
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 03dbd931329c7..08d06fe5d4c61 100644
--- a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs
@@ -60,17 +60,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // since freebsd 12 the former form can be expected.
             "stat" | "stat@FBSD_1.0" => {
                 let [path, buf] = this.check_shim(abi, Conv::C, link_name, args)?;
-                let result = this.macos_fbsd_solaris_stat(path, buf)?;
+                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, Conv::C, link_name, args)?;
-                let result = this.macos_fbsd_solaris_lstat(path, buf)?;
+                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, Conv::C, link_name, args)?;
-                let result = this.macos_fbsd_solaris_fstat(fd, buf)?;
+                let result = this.macos_fbsd_solarish_fstat(fd, buf)?;
                 this.write_scalar(result, dest)?;
             }
             "readdir_r" | "readdir_r@FBSD_1.0" => {
diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs
index cafce62cfedbe..c7399b00d3fe2 100644
--- a/src/tools/miri/src/shims/unix/fs.rs
+++ b/src/tools/miri/src/shims/unix/fs.rs
@@ -13,7 +13,7 @@ use rustc_abi::Size;
 use rustc_data_structures::fx::FxHashMap;
 
 use self::shims::time::system_time_to_duration;
-use crate::helpers::check_min_arg_count;
+use crate::helpers::check_min_vararg_count;
 use crate::shims::files::{EvalContextExt as _, FileDescription, FileDescriptionRef};
 use crate::shims::os_str::bytes_to_os_str;
 use crate::shims::unix::fd::{FlockOp, UnixFileDescription};
@@ -35,16 +35,13 @@ impl FileDescription for FileHandle {
         communicate_allowed: bool,
         ptr: Pointer,
         len: usize,
-        dest: &MPlaceTy<'tcx>,
         ecx: &mut MiriInterpCx<'tcx>,
+        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
     ) -> InterpResult<'tcx> {
         assert!(communicate_allowed, "isolation should have prevented even opening a file");
-        let mut bytes = vec![0; len];
-        let result = (&mut &self.file).read(&mut bytes);
-        match result {
-            Ok(read_size) => ecx.return_read_success(ptr, &bytes, read_size, dest),
-            Err(e) => ecx.set_last_error_and_return(e, dest),
-        }
+
+        let result = ecx.read_from_host(&self.file, len, ptr)?;
+        finish.call(ecx, result)
     }
 
     fn write<'tcx>(
@@ -52,16 +49,13 @@ impl FileDescription for FileHandle {
         communicate_allowed: bool,
         ptr: Pointer,
         len: usize,
-        dest: &MPlaceTy<'tcx>,
         ecx: &mut MiriInterpCx<'tcx>,
+        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
     ) -> InterpResult<'tcx> {
         assert!(communicate_allowed, "isolation should have prevented even opening a file");
-        let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
-        let result = (&mut &self.file).write(bytes);
-        match result {
-            Ok(write_size) => ecx.return_write_success(write_size, dest),
-            Err(e) => ecx.set_last_error_and_return(e, dest),
-        }
+
+        let result = ecx.write_to_host(&self.file, len, ptr)?;
+        finish.call(ecx, result)
     }
 
     fn seek<'tcx>(
@@ -119,8 +113,8 @@ impl UnixFileDescription for FileHandle {
         offset: u64,
         ptr: Pointer,
         len: usize,
-        dest: &MPlaceTy<'tcx>,
         ecx: &mut MiriInterpCx<'tcx>,
+        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
     ) -> InterpResult<'tcx> {
         assert!(communicate_allowed, "isolation should have prevented even opening a file");
         let mut bytes = vec![0; len];
@@ -137,11 +131,17 @@ impl UnixFileDescription for FileHandle {
                 .expect("failed to restore file position, this shouldn't be possible");
             res
         };
-        let result = f();
-        match result {
-            Ok(read_size) => ecx.return_read_success(ptr, &bytes, read_size, dest),
-            Err(e) => ecx.set_last_error_and_return(e, dest),
-        }
+        let result = match f() {
+            Ok(read_size) => {
+                // If reading to `bytes` did not fail, we write those bytes to the buffer.
+                // Crucially, if fewer than `bytes.len()` bytes were read, only write
+                // that much into the output buffer!
+                ecx.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
+                Ok(read_size)
+            }
+            Err(e) => Err(IoError::HostError(e)),
+        };
+        finish.call(ecx, result)
     }
 
     fn pwrite<'tcx>(
@@ -150,8 +150,8 @@ impl UnixFileDescription for FileHandle {
         ptr: Pointer,
         len: usize,
         offset: u64,
-        dest: &MPlaceTy<'tcx>,
         ecx: &mut MiriInterpCx<'tcx>,
+        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
     ) -> InterpResult<'tcx> {
         assert!(communicate_allowed, "isolation should have prevented even opening a file");
         // Emulates pwrite using seek + write + seek to restore cursor position.
@@ -169,10 +169,7 @@ impl UnixFileDescription for FileHandle {
             res
         };
         let result = f();
-        match result {
-            Ok(write_size) => ecx.return_write_success(write_size, dest),
-            Err(e) => ecx.set_last_error_and_return(e, dest),
-        }
+        finish.call(ecx, result.map_err(IoError::HostError))
     }
 
     fn flock<'tcx>(
@@ -273,7 +270,7 @@ impl UnixFileDescription for FileHandle {
 
 impl<'tcx> EvalContextExtPrivate<'tcx> for crate::MiriInterpCx<'tcx> {}
 trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> {
-    fn macos_fbsd_solaris_write_buf(
+    fn macos_fbsd_solarish_write_stat_buf(
         &mut self,
         metadata: FileMetadata,
         buf_op: &OpTy<'tcx>,
@@ -321,9 +318,9 @@ trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> {
         }
 
         if matches!(&*this.tcx.sess.target.os, "solaris" | "illumos") {
-            // FIXME: write st_fstype field once libc is updated.
-            // https://github.com/rust-lang/libc/pull/4145
-            //this.write_int_fields_named(&[("st_fstype", 0)], &buf)?;
+            let st_fstype = this.project_field_named(&buf, "st_fstype")?;
+            // This is an array; write 0 into first element so that it encodes the empty string.
+            this.write_int(0, &this.project_index(&st_fstype, 0)?)?;
         }
 
         interp_ok(0)
@@ -452,9 +449,12 @@ fn maybe_sync_file(
 
 impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
 pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
-    fn open(&mut self, args: &[OpTy<'tcx>]) -> InterpResult<'tcx, Scalar> {
-        let [path_raw, flag] = check_min_arg_count("open", args)?;
-
+    fn open(
+        &mut self,
+        path_raw: &OpTy<'tcx>,
+        flag: &OpTy<'tcx>,
+        varargs: &[OpTy<'tcx>],
+    ) -> InterpResult<'tcx, Scalar> {
         let this = self.eval_context_mut();
 
         let path_raw = this.read_pointer(path_raw)?;
@@ -507,7 +507,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Get the mode.  On macOS, the argument type `mode_t` is actually `u16`, but
             // C integer promotion rules mean that on the ABI level, it gets passed as `u32`
             // (see https://github.com/rust-lang/rust/issues/71915).
-            let [_, _, mode] = check_min_arg_count("open(pathname, O_CREAT, ...)", args)?;
+            let [mode] = check_min_vararg_count("open(pathname, O_CREAT, ...)", varargs)?;
             let mode = this.read_scalar(mode)?.to_u32()?;
 
             #[cfg(unix)]
@@ -668,7 +668,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
     }
 
-    fn macos_fbsd_solaris_stat(
+    fn macos_fbsd_solarish_stat(
         &mut self,
         path_op: &OpTy<'tcx>,
         buf_op: &OpTy<'tcx>,
@@ -694,11 +694,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             Err(err) => return this.set_last_error_and_return_i32(err),
         };
 
-        interp_ok(Scalar::from_i32(this.macos_fbsd_solaris_write_buf(metadata, buf_op)?))
+        interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?))
     }
 
     // `lstat` is used to get symlink metadata.
-    fn macos_fbsd_solaris_lstat(
+    fn macos_fbsd_solarish_lstat(
         &mut self,
         path_op: &OpTy<'tcx>,
         buf_op: &OpTy<'tcx>,
@@ -726,10 +726,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             Err(err) => return this.set_last_error_and_return_i32(err),
         };
 
-        interp_ok(Scalar::from_i32(this.macos_fbsd_solaris_write_buf(metadata, buf_op)?))
+        interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?))
     }
 
-    fn macos_fbsd_solaris_fstat(
+    fn macos_fbsd_solarish_fstat(
         &mut self,
         fd_op: &OpTy<'tcx>,
         buf_op: &OpTy<'tcx>,
@@ -756,7 +756,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             Ok(metadata) => metadata,
             Err(err) => return this.set_last_error_and_return_i32(err),
         };
-        interp_ok(Scalar::from_i32(this.macos_fbsd_solaris_write_buf(metadata, buf_op)?))
+        interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?))
     }
 
     fn linux_statx(
@@ -1109,7 +1109,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     Size::from_bytes(size),
                     dirent_layout.align.abi,
                     MiriMemoryKind::Runtime.into(),
-                    AllocInit::Uninit
+                    AllocInit::Uninit,
                 )?;
                 let entry: Pointer = entry.into();
 
@@ -1169,6 +1169,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         }
 
         let dirp = this.read_target_usize(dirp_op)?;
+        let result_place = this.deref_pointer_as(result_op, this.machine.layouts.mut_raw_ptr)?;
 
         // Reject if isolation is enabled.
         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
@@ -1254,15 +1255,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     }
                     _ => unreachable!(),
                 }
-
-                let result_place = this.deref_pointer(result_op)?;
                 this.write_scalar(this.read_scalar(entry_op)?, &result_place)?;
 
                 Scalar::from_i32(0)
             }
             None => {
                 // end of stream: return 0, assign *result=NULL
-                this.write_null(&this.deref_pointer(result_op)?)?;
+                this.write_null(&result_place)?;
                 Scalar::from_i32(0)
             }
             Some(Err(e)) => {
@@ -1548,7 +1547,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         }
     }
     fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
-        use rand::seq::SliceRandom;
+        use rand::seq::IndexedRandom;
 
         // POSIX defines the template string.
         const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
diff --git a/src/tools/miri/src/shims/unix/linux/mem.rs b/src/tools/miri/src/shims/unix/linux/mem.rs
index 6418d749d3d9d..8e5a3021b1c03 100644
--- a/src/tools/miri/src/shims/unix/linux/mem.rs
+++ b/src/tools/miri/src/shims/unix/linux/mem.rs
@@ -49,7 +49,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             Size::from_bytes(new_size),
             align,
             MiriMemoryKind::Mmap.into(),
-            AllocInit::Zero
+            AllocInit::Zero,
         )?;
 
         interp_ok(Scalar::from_pointer(ptr, this))
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 4b76bbb2b4de4..936d436bd82d6 100644
--- a/src/tools/miri/src/shims/unix/linux_like/eventfd.rs
+++ b/src/tools/miri/src/shims/unix/linux_like/eventfd.rs
@@ -51,20 +51,20 @@ impl FileDescription for EventFd {
         _communicate_allowed: bool,
         ptr: Pointer,
         len: usize,
-        dest: &MPlaceTy<'tcx>,
         ecx: &mut MiriInterpCx<'tcx>,
+        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
     ) -> InterpResult<'tcx> {
         // We're treating the buffer as a `u64`.
         let ty = ecx.machine.layouts.u64;
         // Check the size of slice, and return error only if the size of the slice < 8.
         if len < ty.size.bytes_usize() {
-            return ecx.set_last_error_and_return(ErrorKind::InvalidInput, dest);
+            return finish.call(ecx, Err(ErrorKind::InvalidInput.into()));
         }
 
         // Turn the pointer into a place at the right type.
         let buf_place = ecx.ptr_to_mplace_unaligned(ptr, ty);
 
-        eventfd_read(buf_place, dest, self, ecx)
+        eventfd_read(buf_place, self, ecx, finish)
     }
 
     /// A write call adds the 8-byte integer value supplied in
@@ -84,20 +84,20 @@ impl FileDescription for EventFd {
         _communicate_allowed: bool,
         ptr: Pointer,
         len: usize,
-        dest: &MPlaceTy<'tcx>,
         ecx: &mut MiriInterpCx<'tcx>,
+        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
     ) -> InterpResult<'tcx> {
         // We're treating the buffer as a `u64`.
         let ty = ecx.machine.layouts.u64;
         // Check the size of slice, and return error only if the size of the slice < 8.
         if len < ty.layout.size.bytes_usize() {
-            return ecx.set_last_error_and_return(ErrorKind::InvalidInput, dest);
+            return finish.call(ecx, Err(ErrorKind::InvalidInput.into()));
         }
 
         // Turn the pointer into a place at the right type.
         let buf_place = ecx.ptr_to_mplace_unaligned(ptr, ty);
 
-        eventfd_write(buf_place, dest, self, ecx)
+        eventfd_write(buf_place, self, ecx, finish)
     }
 
     fn as_unix(&self) -> &dyn UnixFileDescription {
@@ -183,15 +183,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 /// else just add the user-supplied value to current counter.
 fn eventfd_write<'tcx>(
     buf_place: MPlaceTy<'tcx>,
-    dest: &MPlaceTy<'tcx>,
     eventfd: FileDescriptionRef<EventFd>,
     ecx: &mut MiriInterpCx<'tcx>,
+    finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
 ) -> InterpResult<'tcx> {
     // Figure out which value we should add.
     let num = ecx.read_scalar(&buf_place)?.to_u64()?;
     // u64::MAX as input is invalid because the maximum value of counter is u64::MAX - 1.
     if num == u64::MAX {
-        return ecx.set_last_error_and_return(ErrorKind::InvalidInput, dest);
+        return finish.call(ecx, Err(ErrorKind::InvalidInput.into()));
     }
 
     match eventfd.counter.get().checked_add(num) {
@@ -219,16 +219,14 @@ fn eventfd_write<'tcx>(
             ecx.check_and_update_readiness(eventfd)?;
 
             // Return how many bytes we consumed from the user-provided buffer.
-            return ecx.write_int(buf_place.layout.size.bytes(), dest);
+            return finish.call(ecx, Ok(buf_place.layout.size.bytes_usize()));
         }
         None | Some(u64::MAX) => {
             // We can't update the state, so we have to block.
             if eventfd.is_nonblock {
-                return ecx.set_last_error_and_return(ErrorKind::WouldBlock, dest);
+                return finish.call(ecx, Err(ErrorKind::WouldBlock.into()));
             }
 
-            let dest = dest.clone();
-
             eventfd.blocked_write_tid.borrow_mut().push(ecx.active_thread());
 
             let weak_eventfd = FileDescriptionRef::downgrade(&eventfd);
@@ -239,7 +237,7 @@ fn eventfd_write<'tcx>(
                     @capture<'tcx> {
                         num: u64,
                         buf_place: MPlaceTy<'tcx>,
-                        dest: MPlaceTy<'tcx>,
+                        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
                         weak_eventfd: WeakFileDescriptionRef<EventFd>,
                     }
                     |this, unblock: UnblockKind| {
@@ -247,7 +245,7 @@ fn eventfd_write<'tcx>(
                         // When we get unblocked, try again. We know the ref is still valid,
                         // otherwise there couldn't be a `write` that unblocks us.
                         let eventfd_ref = weak_eventfd.upgrade().unwrap();
-                        eventfd_write(buf_place, &dest, eventfd_ref, this)
+                        eventfd_write(buf_place, eventfd_ref, this, finish)
                     }
                 ),
             );
@@ -260,9 +258,9 @@ fn eventfd_write<'tcx>(
 /// else just return the current counter value to the caller and set the counter to 0.
 fn eventfd_read<'tcx>(
     buf_place: MPlaceTy<'tcx>,
-    dest: &MPlaceTy<'tcx>,
     eventfd: FileDescriptionRef<EventFd>,
     ecx: &mut MiriInterpCx<'tcx>,
+    finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
 ) -> InterpResult<'tcx> {
     // Set counter to 0, get old value.
     let counter = eventfd.counter.replace(0);
@@ -270,9 +268,8 @@ fn eventfd_read<'tcx>(
     // Block when counter == 0.
     if counter == 0 {
         if eventfd.is_nonblock {
-            return ecx.set_last_error_and_return(ErrorKind::WouldBlock, dest);
+            return finish.call(ecx, Err(ErrorKind::WouldBlock.into()));
         }
-        let dest = dest.clone();
 
         eventfd.blocked_read_tid.borrow_mut().push(ecx.active_thread());
 
@@ -283,7 +280,7 @@ fn eventfd_read<'tcx>(
             callback!(
                 @capture<'tcx> {
                     buf_place: MPlaceTy<'tcx>,
-                    dest: MPlaceTy<'tcx>,
+                    finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
                     weak_eventfd: WeakFileDescriptionRef<EventFd>,
                 }
                 |this, unblock: UnblockKind| {
@@ -291,7 +288,7 @@ fn eventfd_read<'tcx>(
                     // When we get unblocked, try again. We know the ref is still valid,
                     // otherwise there couldn't be a `write` that unblocks us.
                     let eventfd_ref = weak_eventfd.upgrade().unwrap();
-                    eventfd_read(buf_place, &dest, eventfd_ref, this)
+                    eventfd_read(buf_place, eventfd_ref, this, finish)
                 }
             ),
         );
@@ -317,7 +314,7 @@ fn eventfd_read<'tcx>(
         ecx.check_and_update_readiness(eventfd)?;
 
         // Tell userspace how many bytes we put into the buffer.
-        return ecx.write_int(buf_place.layout.size.bytes(), dest);
+        return finish.call(ecx, Ok(buf_place.layout.size.bytes_usize()));
     }
     interp_ok(())
 }
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 51124fb2a003b..280bee4800fe5 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_arg_count;
+use crate::helpers::check_min_vararg_count;
 use crate::*;
 
 struct LinuxFutex {
@@ -10,7 +10,7 @@ struct LinuxFutex {
 /// `args` is the arguments *including* the syscall number.
 pub fn futex<'tcx>(
     ecx: &mut MiriInterpCx<'tcx>,
-    args: &[OpTy<'tcx>],
+    varargs: &[OpTy<'tcx>],
     dest: &MPlaceTy<'tcx>,
 ) -> InterpResult<'tcx> {
     // The amount of arguments used depends on the type of futex operation.
@@ -21,7 +21,7 @@ pub fn futex<'tcx>(
     // may or may not be left out from the `syscall()` call.
     // Therefore we don't use `check_arg_count` here, but only check for the
     // number of arguments to fall within a range.
-    let [_, addr, op, val] = check_min_arg_count("`syscall(SYS_futex, ...)`", args)?;
+    let [addr, op, val] = check_min_vararg_count("`syscall(SYS_futex, ...)`", varargs)?;
 
     // The first three arguments (after the syscall number itself) are the same to all futex operations:
     //     (int *addr, int op, int val).
@@ -55,14 +55,16 @@ pub fn futex<'tcx>(
             let wait_bitset = op & !futex_realtime == futex_wait_bitset;
 
             let (timeout, bitset) = if wait_bitset {
-                let [_, _, _, _, timeout, uaddr2, bitset] =
-                    check_min_arg_count("`syscall(SYS_futex, FUTEX_WAIT_BITSET, ...)`", args)?;
+                let [_, _, _, timeout, uaddr2, bitset] = check_min_vararg_count(
+                    "`syscall(SYS_futex, FUTEX_WAIT_BITSET, ...)`",
+                    varargs,
+                )?;
                 let _timeout = ecx.read_pointer(timeout)?;
                 let _uaddr2 = ecx.read_pointer(uaddr2)?;
                 (timeout, ecx.read_scalar(bitset)?.to_u32()?)
             } else {
-                let [_, _, _, _, timeout] =
-                    check_min_arg_count("`syscall(SYS_futex, FUTEX_WAIT, ...)`", args)?;
+                let [_, _, _, timeout] =
+                    check_min_vararg_count("`syscall(SYS_futex, FUTEX_WAIT, ...)`", varargs)?;
                 (timeout, u32::MAX)
             };
 
@@ -156,14 +158,24 @@ pub fn futex<'tcx>(
                     .futex
                     .clone();
 
+                let dest = dest.clone();
                 ecx.futex_wait(
                     futex_ref,
                     bitset,
                     timeout,
-                    Scalar::from_target_isize(0, ecx), // retval_succ
-                    Scalar::from_target_isize(-1, ecx), // retval_timeout
-                    dest.clone(),
-                    LibcError("ETIMEDOUT"), // errno_timeout
+                    callback!(
+                        @capture<'tcx> {
+                            dest: MPlaceTy<'tcx>,
+                        }
+                        |ecx, unblock: UnblockKind| match unblock {
+                            UnblockKind::Ready => {
+                                ecx.write_int(0, &dest)
+                            }
+                            UnblockKind::TimedOut => {
+                                ecx.set_last_error_and_return(LibcError("ETIMEDOUT"), &dest)
+                            }
+                        }
+                    ),
                 );
             } else {
                 // The futex value doesn't match the expected value, so we return failure
@@ -190,8 +202,10 @@ pub fn futex<'tcx>(
             let futex_ref = futex_ref.futex.clone();
 
             let bitset = if op == futex_wake_bitset {
-                let [_, _, _, _, timeout, uaddr2, bitset] =
-                    check_min_arg_count("`syscall(SYS_futex, FUTEX_WAKE_BITSET, ...)`", args)?;
+                let [_, _, _, timeout, uaddr2, bitset] = check_min_vararg_count(
+                    "`syscall(SYS_futex, FUTEX_WAKE_BITSET, ...)`",
+                    varargs,
+                )?;
                 let _timeout = ecx.read_pointer(timeout)?;
                 let _uaddr2 = ecx.read_pointer(uaddr2)?;
                 ecx.read_scalar(bitset)?.to_u32()?
@@ -205,16 +219,8 @@ pub fn futex<'tcx>(
             // will see the latest value on addr which could be changed by our caller
             // before doing the syscall.
             ecx.atomic_fence(AtomicFenceOrd::SeqCst)?;
-            let mut n = 0;
-            #[expect(clippy::arithmetic_side_effects)]
-            for _ in 0..val {
-                if ecx.futex_wake(&futex_ref, bitset)? {
-                    n += 1;
-                } else {
-                    break;
-                }
-            }
-            ecx.write_scalar(Scalar::from_target_isize(n, ecx), dest)?;
+            let woken = ecx.futex_wake(&futex_ref, bitset, val.try_into().unwrap())?;
+            ecx.write_scalar(Scalar::from_target_isize(woken.try_into().unwrap(), ecx), dest)?;
         }
         op => throw_unsup_format!("Miri does not support `futex` syscall with op={}", op),
     }
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 5fb262e176f0b..22c6dc975070b 100644
--- a/src/tools/miri/src/shims/unix/linux_like/syscall.rs
+++ b/src/tools/miri/src/shims/unix/linux_like/syscall.rs
@@ -2,7 +2,7 @@ use rustc_middle::ty::Ty;
 use rustc_span::Symbol;
 use rustc_target::callconv::{Conv, FnAbi};
 
-use crate::helpers::check_min_arg_count;
+use crate::helpers::check_min_vararg_count;
 use crate::shims::unix::linux_like::eventfd::EvalContextExt as _;
 use crate::shims::unix::linux_like::sync::futex;
 use crate::*;
@@ -14,9 +14,7 @@ pub fn syscall<'tcx>(
     args: &[OpTy<'tcx>],
     dest: &MPlaceTy<'tcx>,
 ) -> InterpResult<'tcx> {
-    // We do not use `check_shim` here because `syscall` is variadic. The argument
-    // count is checked bellow.
-    ecx.check_abi_and_shim_symbol_clash(abi, Conv::C, link_name)?;
+    let ([op], varargs) = ecx.check_shim_variadic(abi, Conv::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
@@ -26,14 +24,13 @@ pub fn syscall<'tcx>(
     let sys_futex = ecx.eval_libc("SYS_futex").to_target_usize(ecx)?;
     let sys_eventfd2 = ecx.eval_libc("SYS_eventfd2").to_target_usize(ecx)?;
 
-    let [op] = check_min_arg_count("syscall", args)?;
     match ecx.read_target_usize(op)? {
         // `libc::syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), GRND_NONBLOCK)`
         // is called if a `HashMap` is created the regular way (e.g. HashMap<K, V>).
         num if num == sys_getrandom => {
             // Used by getrandom 0.1
             // The first argument is the syscall id, so skip over it.
-            let [_, ptr, len, flags] = check_min_arg_count("syscall(SYS_getrandom, ...)", args)?;
+            let [ptr, len, flags] = check_min_vararg_count("syscall(SYS_getrandom, ...)", varargs)?;
 
             let ptr = ecx.read_pointer(ptr)?;
             let len = ecx.read_target_usize(len)?;
@@ -47,10 +44,10 @@ pub fn syscall<'tcx>(
         }
         // `futex` is used by some synchronization primitives.
         num if num == sys_futex => {
-            futex(ecx, args, dest)?;
+            futex(ecx, varargs, dest)?;
         }
         num if num == sys_eventfd2 => {
-            let [_, initval, flags] = check_min_arg_count("syscall(SYS_evetfd2, ...)", args)?;
+            let [initval, flags] = check_min_vararg_count("syscall(SYS_evetfd2, ...)", varargs)?;
 
             let result = ecx.eventfd(initval, flags)?;
             ecx.write_int(result.to_i32()?, dest)?;
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 85c963774a159..918fd8dd52dfd 100644
--- a/src/tools/miri/src/shims/unix/macos/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs
@@ -2,13 +2,20 @@ use rustc_middle::ty::Ty;
 use rustc_span::Symbol;
 use rustc_target::callconv::{Conv, FnAbi};
 
-use super::sync::EvalContextExt as _;
-use crate::helpers::check_min_arg_count;
+use super::sync::{EvalContextExt as _, MacOsFutexTimeout};
 use crate::shims::unix::*;
 use crate::*;
 
-pub fn is_dyn_sym(_name: &str) -> bool {
-    false
+pub fn is_dyn_sym(name: &str) -> bool {
+    match name {
+        // These only became available with macOS 11.0, so std looks them up dynamically.
+        "os_sync_wait_on_address"
+        | "os_sync_wait_on_address_with_deadline"
+        | "os_sync_wait_on_address_with_timeout"
+        | "os_sync_wake_by_address_any"
+        | "os_sync_wake_by_address_all" => true,
+        _ => false,
+    }
 }
 
 impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
@@ -40,17 +47,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             }
             "stat" | "stat64" | "stat$INODE64" => {
                 let [path, buf] = this.check_shim(abi, Conv::C, link_name, args)?;
-                let result = this.macos_fbsd_solaris_stat(path, buf)?;
+                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, Conv::C, link_name, args)?;
-                let result = this.macos_fbsd_solaris_lstat(path, buf)?;
+                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, Conv::C, link_name, args)?;
-                let result = this.macos_fbsd_solaris_fstat(fd, buf)?;
+                let result = this.macos_fbsd_solarish_fstat(fd, buf)?;
                 this.write_scalar(result, dest)?;
             }
             "opendir$INODE64" => {
@@ -69,10 +76,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_scalar(result, dest)?;
             }
             "ioctl" => {
-                // `ioctl` is variadic. The argument count is checked based on the first argument
-                // in `this.ioctl()`, so we do not use `check_shim` here.
-                this.check_abi_and_shim_symbol_clash(abi, Conv::C, link_name)?;
-                let result = this.ioctl(args)?;
+                let ([fd_num, cmd], varargs) =
+                    this.check_shim_variadic(abi, Conv::C, link_name, args)?;
+                let result = this.ioctl(fd_num, cmd, varargs)?;
                 this.write_scalar(result, dest)?;
             }
 
@@ -216,6 +222,58 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_scalar(res, dest)?;
             }
 
+            // Futex primitives
+            "os_sync_wait_on_address" => {
+                let [addr_op, value_op, size_op, flags_op] =
+                    this.check_shim(abi, Conv::C, link_name, args)?;
+                this.os_sync_wait_on_address(
+                    addr_op,
+                    value_op,
+                    size_op,
+                    flags_op,
+                    MacOsFutexTimeout::None,
+                    dest,
+                )?;
+            }
+            "os_sync_wait_on_address_with_deadline" => {
+                let [addr_op, value_op, size_op, flags_op, clock_op, timeout_op] =
+                    this.check_shim(abi, Conv::C, link_name, args)?;
+                this.os_sync_wait_on_address(
+                    addr_op,
+                    value_op,
+                    size_op,
+                    flags_op,
+                    MacOsFutexTimeout::Absolute { clock_op, timeout_op },
+                    dest,
+                )?;
+            }
+            "os_sync_wait_on_address_with_timeout" => {
+                let [addr_op, value_op, size_op, flags_op, clock_op, timeout_op] =
+                    this.check_shim(abi, Conv::C, link_name, args)?;
+                this.os_sync_wait_on_address(
+                    addr_op,
+                    value_op,
+                    size_op,
+                    flags_op,
+                    MacOsFutexTimeout::Relative { clock_op, timeout_op },
+                    dest,
+                )?;
+            }
+            "os_sync_wake_by_address_any" => {
+                let [addr_op, size_op, flags_op] =
+                    this.check_shim(abi, Conv::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, Conv::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, Conv::C, link_name, args)?;
                 this.os_unfair_lock_lock(lock_op)?;
@@ -243,12 +301,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         interp_ok(EmulateItemResult::NeedsReturn)
     }
 
-    fn ioctl(&mut self, args: &[OpTy<'tcx>]) -> InterpResult<'tcx, Scalar> {
+    fn ioctl(
+        &mut self,
+        fd_num: &OpTy<'tcx>,
+        cmd: &OpTy<'tcx>,
+        _varargs: &[OpTy<'tcx>],
+    ) -> InterpResult<'tcx, Scalar> {
         let this = self.eval_context_mut();
 
         let fioclex = this.eval_libc_u64("FIOCLEX");
 
-        let [fd_num, cmd] = check_min_arg_count("ioctl", args)?;
         let fd_num = this.read_scalar(fd_num)?.to_i32()?;
         let cmd = this.read_scalar(cmd)?.to_u64()?;
 
diff --git a/src/tools/miri/src/shims/unix/macos/sync.rs b/src/tools/miri/src/shims/unix/macos/sync.rs
index 330c64f06a3e6..6ba52f2f57e40 100644
--- a/src/tools/miri/src/shims/unix/macos/sync.rs
+++ b/src/tools/miri/src/shims/unix/macos/sync.rs
@@ -10,8 +10,12 @@
 //! and we do not detect copying of the lock, but macOS doesn't guarantee anything
 //! in that case either.
 
+use std::cell::Cell;
+use std::time::Duration;
+
 use rustc_abi::Size;
 
+use crate::concurrency::sync::FutexRef;
 use crate::*;
 
 #[derive(Clone)]
@@ -20,6 +24,26 @@ enum MacOsUnfairLock {
     Active { mutex_ref: MutexRef },
 }
 
+pub enum MacOsFutexTimeout<'a, 'tcx> {
+    None,
+    Relative { clock_op: &'a OpTy<'tcx>, timeout_op: &'a OpTy<'tcx> },
+    Absolute { clock_op: &'a OpTy<'tcx>, timeout_op: &'a OpTy<'tcx> },
+}
+
+/// Metadata for a macOS futex.
+///
+/// Since macOS 11.0, Apple has exposed the previously private futex API consisting
+/// of `os_sync_wait_on_address` (and friends) and `os_sync_wake_by_address_{any, all}`.
+/// These work with different value sizes and flags, which are validated to be consistent.
+/// This structure keeps track of both the futex queue and these values.
+struct MacOsFutex {
+    futex: FutexRef,
+    /// The size in bytes of the atomic primitive underlying this futex.
+    size: Cell<u64>,
+    /// Whether the futex is shared across process boundaries.
+    shared: Cell<bool>,
+}
+
 impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {}
 trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
     fn os_unfair_lock_get_data<'a>(
@@ -30,7 +54,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
         'tcx: 'a,
     {
         let this = self.eval_context_mut();
-        let lock = this.deref_pointer(lock_ptr)?;
+        let lock = this.deref_pointer_as(lock_ptr, this.libc_ty_layout("os_unfair_lock_s"))?;
         this.lazy_sync_get_data(
             &lock,
             Size::ZERO, // offset for init tracking
@@ -54,6 +78,198 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
 impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
 pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
+    /// Implements [`os_sync_wait_on_address`], [`os_sync_wait_on_address_with_deadline`]
+    /// and [`os_sync_wait_on_address_with_timeout`].
+    ///
+    /// [`os_sync_wait_on_address`]: https://developer.apple.com/documentation/os/os_sync_wait_on_address?language=objc
+    /// [`os_sync_wait_on_address_with_deadline`]: https://developer.apple.com/documentation/os/os_sync_wait_on_address_with_deadline?language=objc
+    /// [`os_sync_wait_on_address_with_timeout`]: https://developer.apple.com/documentation/os/os_sync_wait_on_address_with_timeout?language=objc
+    fn os_sync_wait_on_address(
+        &mut self,
+        addr_op: &OpTy<'tcx>,
+        value_op: &OpTy<'tcx>,
+        size_op: &OpTy<'tcx>,
+        flags_op: &OpTy<'tcx>,
+        timeout: MacOsFutexTimeout<'_, 'tcx>,
+        dest: &MPlaceTy<'tcx>,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        let none = this.eval_libc_u32("OS_SYNC_WAIT_ON_ADDRESS_NONE");
+        let shared = this.eval_libc_u32("OS_SYNC_WAIT_ON_ADDRESS_SHARED");
+        let absolute_clock = this.eval_libc_u32("OS_CLOCK_MACH_ABSOLUTE_TIME");
+
+        let ptr = this.read_pointer(addr_op)?;
+        let value = this.read_scalar(value_op)?.to_u64()?;
+        let size = this.read_target_usize(size_op)?;
+        let flags = this.read_scalar(flags_op)?.to_u32()?;
+
+        let clock_timeout = match timeout {
+            MacOsFutexTimeout::None => None,
+            MacOsFutexTimeout::Relative { clock_op, timeout_op } => {
+                let clock = this.read_scalar(clock_op)?.to_u32()?;
+                let timeout = this.read_scalar(timeout_op)?.to_u64()?;
+                Some((clock, TimeoutAnchor::Relative, timeout))
+            }
+            MacOsFutexTimeout::Absolute { clock_op, timeout_op } => {
+                let clock = this.read_scalar(clock_op)?.to_u32()?;
+                let timeout = this.read_scalar(timeout_op)?.to_u64()?;
+                Some((clock, TimeoutAnchor::Absolute, timeout))
+            }
+        };
+
+        // Perform validation of the arguments.
+        let addr = ptr.addr().bytes();
+        if addr == 0
+            || !matches!(size, 4 | 8)
+            || !addr.is_multiple_of(size)
+            || (flags != none && flags != shared)
+            || clock_timeout
+                .is_some_and(|(clock, _, timeout)| clock != absolute_clock || timeout == 0)
+        {
+            this.set_last_error_and_return(LibcError("EINVAL"), dest)?;
+            return interp_ok(());
+        }
+
+        let is_shared = flags == shared;
+        let timeout = clock_timeout.map(|(_, anchor, timeout)| {
+            // The only clock that is currenlty supported is the monotonic clock.
+            // While the deadline argument of `os_sync_wait_on_address_with_deadline`
+            // is actually not in nanoseconds but in the units of `mach_current_time`,
+            // the two are equivalent in miri.
+            (TimeoutClock::Monotonic, anchor, Duration::from_nanos(timeout))
+        });
+
+        // See the Linux futex implementation for why this fence exists.
+        this.atomic_fence(AtomicFenceOrd::SeqCst)?;
+
+        let layout = this.machine.layouts.uint(Size::from_bytes(size)).unwrap();
+        let futex_val = this
+            .read_scalar_atomic(&this.ptr_to_mplace(ptr, layout), AtomicReadOrd::Acquire)?
+            .to_bits(Size::from_bytes(size))?;
+
+        let futex = this
+            .get_sync_or_init(ptr, |_| {
+                MacOsFutex {
+                    futex: Default::default(),
+                    size: Cell::new(size),
+                    shared: Cell::new(is_shared),
+                }
+            })
+            .unwrap();
+
+        // Detect mismatches between the flags and sizes used on this address
+        // by comparing it with the parameters used by the other waiters in
+        // the current list. If the list is currently empty, update those
+        // parameters.
+        if futex.futex.waiters() == 0 {
+            futex.size.set(size);
+            futex.shared.set(is_shared);
+        } else if futex.size.get() != size || futex.shared.get() != is_shared {
+            this.set_last_error_and_return(LibcError("EINVAL"), dest)?;
+            return interp_ok(());
+        }
+
+        if futex_val == value.into() {
+            // If the values are the same, we have to block.
+            let futex_ref = futex.futex.clone();
+            let dest = dest.clone();
+            this.futex_wait(
+                futex_ref.clone(),
+                u32::MAX, // bitset
+                timeout,
+                callback!(
+                    @capture<'tcx> {
+                        dest: MPlaceTy<'tcx>,
+                        futex_ref: FutexRef,
+                    }
+                    |this, unblock: UnblockKind| {
+                        match unblock {
+                            UnblockKind::Ready => {
+                                let remaining = futex_ref.waiters().try_into().unwrap();
+                                this.write_scalar(Scalar::from_i32(remaining), &dest)
+                            }
+                            UnblockKind::TimedOut => {
+                                this.set_last_error_and_return(LibcError("ETIMEDOUT"), &dest)
+                            }
+                        }
+                    }
+                ),
+            );
+        } else {
+            // else retrieve the current number of waiters.
+            let waiters = futex.futex.waiters().try_into().unwrap();
+            this.write_scalar(Scalar::from_i32(waiters), dest)?;
+        }
+
+        interp_ok(())
+    }
+
+    /// Implements [`os_sync_wake_by_address_all`] and [`os_sync_wake_by_address_any`].
+    ///
+    /// [`os_sync_wake_by_address_all`]: https://developer.apple.com/documentation/os/os_sync_wake_by_address_all?language=objc
+    /// [`os_sync_wake_by_address_any`]: https://developer.apple.com/documentation/os/os_sync_wake_by_address_any?language=objc
+    fn os_sync_wake_by_address(
+        &mut self,
+        addr_op: &OpTy<'tcx>,
+        size_op: &OpTy<'tcx>,
+        flags_op: &OpTy<'tcx>,
+        all: bool,
+        dest: &MPlaceTy<'tcx>,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        let none = this.eval_libc_u32("OS_SYNC_WAKE_BY_ADDRESS_NONE");
+        let shared = this.eval_libc_u32("OS_SYNC_WAKE_BY_ADDRESS_SHARED");
+
+        let ptr = this.read_pointer(addr_op)?;
+        let size = this.read_target_usize(size_op)?;
+        let flags = this.read_scalar(flags_op)?.to_u32()?;
+
+        // Perform validation of the arguments.
+        let addr = ptr.addr().bytes();
+        if addr == 0 || !matches!(size, 4 | 8) || (flags != none && flags != shared) {
+            this.set_last_error_and_return(LibcError("EINVAL"), dest)?;
+            return interp_ok(());
+        }
+
+        let is_shared = flags == shared;
+
+        let Some(futex) = this.get_sync_or_init(ptr, |_| {
+            MacOsFutex {
+                futex: Default::default(),
+                size: Cell::new(size),
+                shared: Cell::new(is_shared),
+            }
+        }) else {
+            // No AllocId, or no live allocation at that AllocId. Return an
+            // error code. (That seems nicer than silently doing something
+            // non-intuitive.) This means that if an address gets reused by a
+            // new allocation, we'll use an independent futex queue for this...
+            // that seems acceptable.
+            this.set_last_error_and_return(LibcError("ENOENT"), dest)?;
+            return interp_ok(());
+        };
+
+        if futex.futex.waiters() == 0 {
+            this.set_last_error_and_return(LibcError("ENOENT"), dest)?;
+            return interp_ok(());
+        // If there are waiters in the queue, they have all used the parameters
+        // stored in `futex` (we check this in `os_sync_wait_on_address` above).
+        // Detect mismatches between "our" parameters and the parameters used by
+        // the waiters and return an error in that case.
+        } else if futex.size.get() != size || futex.shared.get() != is_shared {
+            this.set_last_error_and_return(LibcError("EINVAL"), dest)?;
+            return interp_ok(());
+        }
+
+        let futex_ref = futex.futex.clone();
+
+        // See the Linux futex implementation for why this fence exists.
+        this.atomic_fence(AtomicFenceOrd::SeqCst)?;
+        this.futex_wake(&futex_ref, u32::MAX, if all { usize::MAX } else { 1 })?;
+        this.write_scalar(Scalar::from_i32(0), dest)?;
+        interp_ok(())
+    }
+
     fn os_unfair_lock_lock(&mut self, lock_op: &OpTy<'tcx>) -> InterpResult<'tcx> {
         let this = self.eval_context_mut();
 
diff --git a/src/tools/miri/src/shims/unix/mem.rs b/src/tools/miri/src/shims/unix/mem.rs
index 2d5d3a6471ab9..aefeee6f7a3a3 100644
--- a/src/tools/miri/src/shims/unix/mem.rs
+++ b/src/tools/miri/src/shims/unix/mem.rs
@@ -116,7 +116,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             align,
             MiriMemoryKind::Mmap.into(),
             // mmap guarantees new mappings are zero-init.
-            AllocInit::Zero
+            AllocInit::Zero,
         )?;
 
         interp_ok(Scalar::from_pointer(ptr, this))
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 f94783a390722..21d4f41f48529 100644
--- a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs
@@ -87,17 +87,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // File related shims
             "stat" | "stat64" => {
                 let [path, buf] = this.check_shim(abi, Conv::C, link_name, args)?;
-                let result = this.macos_fbsd_solaris_stat(path, buf)?;
+                let result = this.macos_fbsd_solarish_stat(path, buf)?;
                 this.write_scalar(result, dest)?;
             }
             "lstat" | "lstat64" => {
                 let [path, buf] = this.check_shim(abi, Conv::C, link_name, args)?;
-                let result = this.macos_fbsd_solaris_lstat(path, buf)?;
+                let result = this.macos_fbsd_solarish_lstat(path, buf)?;
                 this.write_scalar(result, dest)?;
             }
             "fstat" | "fstat64" => {
                 let [fd, buf] = this.check_shim(abi, Conv::C, link_name, args)?;
-                let result = this.macos_fbsd_solaris_fstat(fd, buf)?;
+                let result = this.macos_fbsd_solarish_fstat(fd, buf)?;
                 this.write_scalar(result, dest)?;
             }
             "readdir" => {
@@ -163,7 +163,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     throw_unsup_format!("pset_info is only supported with list==NULL");
                 }
 
-                let cpus = this.deref_pointer(cpus)?;
+                let cpus = this.deref_pointer_as(cpus, this.machine.layouts.u32)?;
                 this.write_scalar(Scalar::from_u32(this.machine.num_cpus), &cpus)?;
                 this.write_null(dest)?;
             }
diff --git a/src/tools/miri/src/shims/unix/sync.rs b/src/tools/miri/src/shims/unix/sync.rs
index 5b0a9398b4b6a..9f1fabfbf6494 100644
--- a/src/tools/miri/src/shims/unix/sync.rs
+++ b/src/tools/miri/src/shims/unix/sync.rs
@@ -170,7 +170,7 @@ fn mutex_create<'tcx>(
     mutex_ptr: &OpTy<'tcx>,
     kind: MutexKind,
 ) -> InterpResult<'tcx, PthreadMutex> {
-    let mutex = ecx.deref_pointer(mutex_ptr)?;
+    let mutex = ecx.deref_pointer_as(mutex_ptr, ecx.libc_ty_layout("pthread_mutex_t"))?;
     let id = ecx.machine.sync.mutex_create();
     let data = PthreadMutex { mutex_ref: id, kind };
     ecx.lazy_sync_init(&mutex, mutex_init_offset(ecx)?, data.clone())?;
@@ -186,7 +186,7 @@ fn mutex_get_data<'tcx, 'a>(
 where
     'tcx: 'a,
 {
-    let mutex = ecx.deref_pointer(mutex_ptr)?;
+    let mutex = ecx.deref_pointer_as(mutex_ptr, ecx.libc_ty_layout("pthread_mutex_t"))?;
     ecx.lazy_sync_get_data(
         &mutex,
         mutex_init_offset(ecx)?,
@@ -265,7 +265,7 @@ fn rwlock_get_data<'tcx, 'a>(
 where
     'tcx: 'a,
 {
-    let rwlock = ecx.deref_pointer(rwlock_ptr)?;
+    let rwlock = ecx.deref_pointer_as(rwlock_ptr, ecx.libc_ty_layout("pthread_rwlock_t"))?;
     ecx.lazy_sync_get_data(
         &rwlock,
         rwlock_init_offset(ecx)?,
@@ -383,7 +383,7 @@ fn cond_create<'tcx>(
     cond_ptr: &OpTy<'tcx>,
     clock: ClockId,
 ) -> InterpResult<'tcx, PthreadCondvar> {
-    let cond = ecx.deref_pointer(cond_ptr)?;
+    let cond = ecx.deref_pointer_as(cond_ptr, ecx.libc_ty_layout("pthread_cond_t"))?;
     let id = ecx.machine.sync.condvar_create();
     let data = PthreadCondvar { id, clock };
     ecx.lazy_sync_init(&cond, cond_init_offset(ecx)?, data)?;
@@ -397,7 +397,7 @@ fn cond_get_data<'tcx, 'a>(
 where
     'tcx: 'a,
 {
-    let cond = ecx.deref_pointer(cond_ptr)?;
+    let cond = ecx.deref_pointer_as(cond_ptr, ecx.libc_ty_layout("pthread_cond_t"))?;
     ecx.lazy_sync_get_data(
         &cond,
         cond_init_offset(ecx)?,
@@ -760,7 +760,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         let this = self.eval_context_mut();
 
         let clock_id = condattr_get_clock_id(this, attr_op)?;
-        this.write_scalar(Scalar::from_i32(clock_id), &this.deref_pointer(clk_id_op)?)?;
+        this.write_scalar(
+            Scalar::from_i32(clock_id),
+            &this.deref_pointer_as(clk_id_op, this.libc_ty_layout("clockid_t"))?,
+        )?;
 
         interp_ok(())
     }
diff --git a/src/tools/miri/src/shims/unix/unnamed_socket.rs b/src/tools/miri/src/shims/unix/unnamed_socket.rs
index 08515b815a900..e183bfdf0e137 100644
--- a/src/tools/miri/src/shims/unix/unnamed_socket.rs
+++ b/src/tools/miri/src/shims/unix/unnamed_socket.rs
@@ -5,9 +5,7 @@
 use std::cell::{Cell, OnceCell, RefCell};
 use std::collections::VecDeque;
 use std::io;
-use std::io::{ErrorKind, Read};
-
-use rustc_abi::Size;
+use std::io::ErrorKind;
 
 use crate::concurrency::VClock;
 use crate::shims::files::{
@@ -92,10 +90,10 @@ impl FileDescription for AnonSocket {
         _communicate_allowed: bool,
         ptr: Pointer,
         len: usize,
-        dest: &MPlaceTy<'tcx>,
         ecx: &mut MiriInterpCx<'tcx>,
+        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
     ) -> InterpResult<'tcx> {
-        anonsocket_read(self, len, ptr, dest, ecx)
+        anonsocket_read(self, ptr, len, ecx, finish)
     }
 
     fn write<'tcx>(
@@ -103,10 +101,10 @@ impl FileDescription for AnonSocket {
         _communicate_allowed: bool,
         ptr: Pointer,
         len: usize,
-        dest: &MPlaceTy<'tcx>,
         ecx: &mut MiriInterpCx<'tcx>,
+        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
     ) -> InterpResult<'tcx> {
-        anonsocket_write(self, ptr, len, dest, ecx)
+        anonsocket_write(self, ptr, len, ecx, finish)
     }
 
     fn as_unix(&self) -> &dyn UnixFileDescription {
@@ -119,25 +117,25 @@ fn anonsocket_write<'tcx>(
     self_ref: FileDescriptionRef<AnonSocket>,
     ptr: Pointer,
     len: usize,
-    dest: &MPlaceTy<'tcx>,
     ecx: &mut MiriInterpCx<'tcx>,
+    finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
 ) -> InterpResult<'tcx> {
     // Always succeed on write size 0.
     // ("If count is zero and fd refers to a file other than a regular file, the results are not specified.")
     if len == 0 {
-        return ecx.return_write_success(0, dest);
+        return finish.call(ecx, Ok(0));
     }
 
     // We are writing to our peer's readbuf.
     let Some(peer_fd) = self_ref.peer_fd().upgrade() else {
         // If the upgrade from Weak to Rc fails, it indicates that all read ends have been
         // closed. It is an error to write even if there would be space.
-        return ecx.set_last_error_and_return(ErrorKind::BrokenPipe, dest);
+        return finish.call(ecx, Err(ErrorKind::BrokenPipe.into()));
     };
 
     let Some(writebuf) = &peer_fd.readbuf else {
         // Writing to the read end of a pipe.
-        return ecx.set_last_error_and_return(IoError::LibcError("EBADF"), dest);
+        return finish.call(ecx, Err(IoError::LibcError("EBADF")));
     };
 
     // Let's see if we can write.
@@ -145,13 +143,12 @@ fn anonsocket_write<'tcx>(
     if available_space == 0 {
         if self_ref.is_nonblock {
             // Non-blocking socketpair with a full buffer.
-            return ecx.set_last_error_and_return(ErrorKind::WouldBlock, dest);
+            return finish.call(ecx, Err(ErrorKind::WouldBlock.into()));
         } else {
             self_ref.blocked_write_tid.borrow_mut().push(ecx.active_thread());
             // Blocking socketpair with a full buffer.
             // Block the current thread; only keep a weak ref for this.
             let weak_self_ref = FileDescriptionRef::downgrade(&self_ref);
-            let dest = dest.clone();
             ecx.block_thread(
                 BlockReason::UnnamedSocket,
                 None,
@@ -160,14 +157,14 @@ fn anonsocket_write<'tcx>(
                         weak_self_ref: WeakFileDescriptionRef<AnonSocket>,
                         ptr: Pointer,
                         len: usize,
-                        dest: MPlaceTy<'tcx>,
+                        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
                     }
                     |this, unblock: UnblockKind| {
                         assert_eq!(unblock, UnblockKind::Ready);
                         // If we got unblocked, then our peer successfully upgraded its weak
                         // ref to us. That means we can also upgrade our weak ref.
                         let self_ref = weak_self_ref.upgrade().unwrap();
-                        anonsocket_write(self_ref, ptr, len, &dest, this)
+                        anonsocket_write(self_ref, ptr, len, this, finish)
                     }
                 ),
             );
@@ -180,9 +177,9 @@ fn anonsocket_write<'tcx>(
             writebuf.clock.join(clock);
         });
         // Do full write / partial write based on the space available.
-        let actual_write_size = len.min(available_space);
-        let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
-        writebuf.buf.extend(&bytes[..actual_write_size]);
+        let write_size = len.min(available_space);
+        let actual_write_size = ecx.write_to_host(&mut writebuf.buf, write_size, ptr)?.unwrap();
+        assert_eq!(actual_write_size, write_size);
 
         // Need to stop accessing peer_fd so that it can be notified.
         drop(writebuf);
@@ -197,7 +194,7 @@ fn anonsocket_write<'tcx>(
         // The kernel does this even if the fd was already readable before, so we follow suit.
         ecx.check_and_update_readiness(peer_fd)?;
 
-        return ecx.return_write_success(actual_write_size, dest);
+        return finish.call(ecx, Ok(write_size));
     }
     interp_ok(())
 }
@@ -205,14 +202,14 @@ fn anonsocket_write<'tcx>(
 /// Read from AnonSocket and return the number of bytes read.
 fn anonsocket_read<'tcx>(
     self_ref: FileDescriptionRef<AnonSocket>,
-    len: usize,
     ptr: Pointer,
-    dest: &MPlaceTy<'tcx>,
+    len: usize,
     ecx: &mut MiriInterpCx<'tcx>,
+    finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
 ) -> InterpResult<'tcx> {
     // Always succeed on read size 0.
     if len == 0 {
-        return ecx.return_read_success(ptr, &[], 0, dest);
+        return finish.call(ecx, Ok(0));
     }
 
     let Some(readbuf) = &self_ref.readbuf else {
@@ -225,43 +222,41 @@ fn anonsocket_read<'tcx>(
         if self_ref.peer_fd().upgrade().is_none() {
             // Socketpair with no peer and empty buffer.
             // 0 bytes successfully read indicates end-of-file.
-            return ecx.return_read_success(ptr, &[], 0, dest);
+            return finish.call(ecx, Ok(0));
         } else if self_ref.is_nonblock {
             // Non-blocking socketpair with writer and empty buffer.
             // https://linux.die.net/man/2/read
             // EAGAIN or EWOULDBLOCK can be returned for socket,
             // POSIX.1-2001 allows either error to be returned for this case.
             // Since there is no ErrorKind for EAGAIN, WouldBlock is used.
-            return ecx.set_last_error_and_return(ErrorKind::WouldBlock, dest);
+            return finish.call(ecx, Err(ErrorKind::WouldBlock.into()));
         } else {
             self_ref.blocked_read_tid.borrow_mut().push(ecx.active_thread());
             // Blocking socketpair with writer and empty buffer.
             // Block the current thread; only keep a weak ref for this.
             let weak_self_ref = FileDescriptionRef::downgrade(&self_ref);
-            let dest = dest.clone();
             ecx.block_thread(
                 BlockReason::UnnamedSocket,
                 None,
                 callback!(
                     @capture<'tcx> {
                         weak_self_ref: WeakFileDescriptionRef<AnonSocket>,
-                        len: usize,
                         ptr: Pointer,
-                        dest: MPlaceTy<'tcx>,
+                        len: usize,
+                        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
                     }
                     |this, unblock: UnblockKind| {
                         assert_eq!(unblock, UnblockKind::Ready);
                         // If we got unblocked, then our peer successfully upgraded its weak
                         // ref to us. That means we can also upgrade our weak ref.
                         let self_ref = weak_self_ref.upgrade().unwrap();
-                        anonsocket_read(self_ref, len, ptr, &dest, this)
+                        anonsocket_read(self_ref, ptr, len, this, finish)
                     }
                 ),
             );
         }
     } else {
         // There's data to be read!
-        let mut bytes = vec![0; len];
         let mut readbuf = readbuf.borrow_mut();
         // Synchronize with all previous writes to this buffer.
         // FIXME: this over-synchronizes; a more precise approach would be to
@@ -270,7 +265,7 @@ fn anonsocket_read<'tcx>(
 
         // Do full read / partial read based on the space available.
         // Conveniently, `read` exists on `VecDeque` and has exactly the desired behavior.
-        let actual_read_size = readbuf.buf.read(&mut bytes[..]).unwrap();
+        let read_size = ecx.read_from_host(&mut readbuf.buf, len, ptr)?.unwrap();
 
         // Need to drop before others can access the readbuf again.
         drop(readbuf);
@@ -293,7 +288,7 @@ fn anonsocket_read<'tcx>(
             ecx.check_and_update_readiness(peer_fd)?;
         };
 
-        return ecx.return_read_success(ptr, &bytes, actual_read_size, dest);
+        return finish.call(ecx, Ok(read_size));
     }
     interp_ok(())
 }
@@ -362,7 +357,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         let domain = this.read_scalar(domain)?.to_i32()?;
         let mut flags = this.read_scalar(type_)?.to_i32()?;
         let protocol = this.read_scalar(protocol)?.to_i32()?;
-        let sv = this.deref_pointer(sv)?;
+        // This is really a pointer to `[i32; 2]` but we use a ptr-to-first-element representation.
+        let sv = this.deref_pointer_as(sv, this.machine.layouts.i32)?;
 
         let mut is_sock_nonblock = false;
 
diff --git a/src/tools/miri/src/shims/windows/env.rs b/src/tools/miri/src/shims/windows/env.rs
index 72c1fb58023a8..1b2ccd99ef9f4 100644
--- a/src/tools/miri/src/shims/windows/env.rs
+++ b/src/tools/miri/src/shims/windows/env.rs
@@ -218,7 +218,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
         let token = this.read_target_isize(token)?;
         let buf = this.read_pointer(buf)?;
-        let size = this.deref_pointer(size)?;
+        let size = this.deref_pointer_as(size, this.machine.layouts.u32)?;
 
         if token != -4 {
             throw_unsup_format!(
diff --git a/src/tools/miri/src/shims/windows/foreign_items.rs b/src/tools/miri/src/shims/windows/foreign_items.rs
index 4462d025beada..fae6170a9e72c 100644
--- a/src/tools/miri/src/shims/windows/foreign_items.rs
+++ b/src/tools/miri/src/shims/windows/foreign_items.rs
@@ -266,7 +266,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     Size::from_bytes(size),
                     Align::from_bytes(align).unwrap(),
                     MiriMemoryKind::WinHeap.into(),
-                    init
+                    init,
                 )?;
                 this.write_pointer(ptr, dest)?;
             }
@@ -299,7 +299,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     Size::from_bytes(size),
                     Align::from_bytes(align).unwrap(),
                     MiriMemoryKind::WinHeap.into(),
-                    AllocInit::Uninit
+                    AllocInit::Uninit,
                 )?;
                 this.write_pointer(new_ptr, dest)?;
             }
@@ -335,7 +335,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 // Initialize with `0`.
                 this.write_bytes_ptr(
                     system_info.ptr(),
-                    iter::repeat(0u8).take(system_info.layout.size.bytes_usize()),
+                    iter::repeat_n(0u8, system_info.layout.size.bytes_usize()),
                 )?;
                 // Set selected fields.
                 this.write_int_fields_named(
@@ -523,7 +523,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 let [handle, name_ptr] = this.check_shim(abi, sys_conv, link_name, args)?;
 
                 let handle = this.read_scalar(handle)?;
-                let name_ptr = this.deref_pointer(name_ptr)?; // the pointer where we should store the ptr to the name
+                let name_ptr = this.deref_pointer_as(name_ptr, this.machine.layouts.mut_raw_ptr)?; // the pointer where we should store the ptr to the name
 
                 let thread = match Handle::try_from_scalar(handle, this)? {
                     Ok(Handle::Thread(thread)) => Ok(thread),
@@ -725,7 +725,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             "GetConsoleMode" if this.frame_in_std() => {
                 let [console, mode] = this.check_shim(abi, sys_conv, link_name, args)?;
                 this.read_target_isize(console)?;
-                this.deref_pointer(mode)?;
+                this.deref_pointer_as(mode, this.machine.layouts.u32)?;
                 // Indicate an error.
                 this.write_null(dest)?;
             }
diff --git a/src/tools/miri/src/shims/windows/sync.rs b/src/tools/miri/src/shims/windows/sync.rs
index 4001201bf678a..8d5ea7db9e496 100644
--- a/src/tools/miri/src/shims/windows/sync.rs
+++ b/src/tools/miri/src/shims/windows/sync.rs
@@ -29,7 +29,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
     {
         let this = self.eval_context_mut();
 
-        let init_once = this.deref_pointer(init_once_ptr)?;
+        let init_once =
+            this.deref_pointer_as(init_once_ptr, this.windows_ty_layout("INIT_ONCE"))?;
         let init_offset = Size::ZERO;
 
         this.lazy_sync_get_data(
@@ -85,7 +86,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
         let id = this.init_once_get_data(init_once_op)?.id;
         let flags = this.read_scalar(flags_op)?.to_u32()?;
-        let pending_place = this.deref_pointer(pending_op)?;
+        // PBOOL is int*
+        let pending_place = this.deref_pointer_as(pending_op, this.machine.layouts.i32)?;
         let context = this.read_pointer(context_op)?;
 
         if flags != 0 {
@@ -210,14 +212,27 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 .futex
                 .clone();
 
+            let dest = dest.clone();
             this.futex_wait(
                 futex_ref,
                 u32::MAX, // bitset
                 timeout,
-                Scalar::from_i32(1), // retval_succ
-                Scalar::from_i32(0), // retval_timeout
-                dest.clone(),
-                IoError::WindowsError("ERROR_TIMEOUT"), // errno_timeout
+                callback!(
+                    @capture<'tcx> {
+                        dest: MPlaceTy<'tcx>
+                    }
+                    |this, unblock: UnblockKind| {
+                        match unblock {
+                            UnblockKind::Ready => {
+                                this.write_int(1, &dest)
+                            }
+                            UnblockKind::TimedOut => {
+                                this.set_last_error(IoError::WindowsError("ERROR_TIMEOUT"))?;
+                                this.write_int(0, &dest)
+                            }
+                        }
+                    }
+                ),
             );
         }
 
@@ -242,7 +257,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         };
         let futex_ref = futex_ref.futex.clone();
 
-        this.futex_wake(&futex_ref, u32::MAX)?;
+        this.futex_wake(&futex_ref, u32::MAX, 1)?;
 
         interp_ok(())
     }
@@ -262,7 +277,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         };
         let futex_ref = futex_ref.futex.clone();
 
-        while this.futex_wake(&futex_ref, u32::MAX)? {}
+        this.futex_wake(&futex_ref, u32::MAX, usize::MAX)?;
 
         interp_ok(())
     }
diff --git a/src/tools/miri/src/shims/windows/thread.rs b/src/tools/miri/src/shims/windows/thread.rs
index efc1c2286bcbc..5db554044227c 100644
--- a/src/tools/miri/src/shims/windows/thread.rs
+++ b/src/tools/miri/src/shims/windows/thread.rs
@@ -29,7 +29,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         let thread = if this.ptr_is_null(this.read_pointer(thread_op)?)? {
             None
         } else {
-            let thread_info_place = this.deref_pointer(thread_op)?;
+            let thread_info_place = this.deref_pointer_as(thread_op, this.machine.layouts.u32)?;
             Some(thread_info_place)
         };
 
diff --git a/src/tools/miri/test_dependencies/Cargo.lock b/src/tools/miri/test_dependencies/Cargo.lock
index 0a5e9f62dd9fb..af92f9d0dec49 100644
--- a/src/tools/miri/test_dependencies/Cargo.lock
+++ b/src/tools/miri/test_dependencies/Cargo.lock
@@ -105,6 +105,18 @@ dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "getrandom"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.13.3+wasi-0.2.2",
+ "windows-targets",
+]
+
 [[package]]
 name = "gimli"
 version = "0.29.0"
@@ -178,6 +190,7 @@ dependencies = [
  "cfg-if",
  "getrandom 0.1.16",
  "getrandom 0.2.15",
+ "getrandom 0.3.1",
  "libc",
  "num_cpus",
  "page_size",
@@ -359,6 +372,15 @@ version = "0.11.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
+[[package]]
+name = "wasi"
+version = "0.13.3+wasi-0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
+dependencies = [
+ "wit-bindgen-rt",
+]
+
 [[package]]
 name = "wasm-bindgen"
 version = "0.2.92"
@@ -507,3 +529,12 @@ name = "windows_x86_64_msvc"
 version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "wit-bindgen-rt"
+version = "0.33.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
+dependencies = [
+ "bitflags",
+]
diff --git a/src/tools/miri/test_dependencies/Cargo.toml b/src/tools/miri/test_dependencies/Cargo.toml
index e7eff46afca52..78dddaf11dff5 100644
--- a/src/tools/miri/test_dependencies/Cargo.toml
+++ b/src/tools/miri/test_dependencies/Cargo.toml
@@ -8,13 +8,14 @@ version = "0.1.0"
 edition = "2021"
 
 [dependencies]
-# all dependencies (and their transitive ones) listed here can be used in `tests/`.
+# all dependencies (and their transitive ones) listed here can be used in `tests/*-dep`.
 libc = "0.2"
 num_cpus = "1.10.1"
 cfg-if = "1"
 
 getrandom_01 = { package = "getrandom", version = "0.1" }
 getrandom_02 = { package = "getrandom", version = "0.2", features = ["js"] }
+getrandom_03 = { package = "getrandom", version = "0.3" }
 
 [target.'cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))'.dependencies]
 tempfile = "3"
diff --git a/src/tools/miri/tests/fail-dep/libc/fs/unix_open_missing_required_mode.rs b/src/tools/miri/tests/fail-dep/libc/fs/unix_open_missing_required_mode.rs
index 4b6f344a78e28..457f32e55446e 100644
--- a/src/tools/miri/tests/fail-dep/libc/fs/unix_open_missing_required_mode.rs
+++ b/src/tools/miri/tests/fail-dep/libc/fs/unix_open_missing_required_mode.rs
@@ -8,5 +8,5 @@ fn main() {
 fn test_file_open_missing_needed_mode() {
     let name = b"missing_arg.txt\0";
     let name_ptr = name.as_ptr().cast::<libc::c_char>();
-    let _fd = unsafe { libc::open(name_ptr, libc::O_CREAT) }; //~ ERROR: Undefined Behavior: incorrect number of arguments for `open(pathname, O_CREAT, ...)`: got 2, expected at least 3
+    let _fd = unsafe { libc::open(name_ptr, libc::O_CREAT) }; //~ ERROR: Undefined Behavior: not enough variadic arguments
 }
diff --git a/src/tools/miri/tests/fail-dep/libc/fs/unix_open_missing_required_mode.stderr b/src/tools/miri/tests/fail-dep/libc/fs/unix_open_missing_required_mode.stderr
index ca9e3c6c4be40..f48b75460d4f9 100644
--- a/src/tools/miri/tests/fail-dep/libc/fs/unix_open_missing_required_mode.stderr
+++ b/src/tools/miri/tests/fail-dep/libc/fs/unix_open_missing_required_mode.stderr
@@ -1,8 +1,8 @@
-error: Undefined Behavior: incorrect number of arguments for `open(pathname, O_CREAT, ...)`: got 2, expected at least 3
+error: Undefined Behavior: not enough variadic arguments for `open(pathname, O_CREAT, ...)`: got 0, expected at least 1
   --> tests/fail-dep/libc/fs/unix_open_missing_required_mode.rs:LL:CC
    |
-LL | ... { libc::open(name_ptr, libc::O_CREAT) };
-   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ incorrect number of arguments for `open(pathname, O_CREAT, ...)`: got 2, expected at least 3
+LL |     let _fd = unsafe { libc::open(name_ptr, libc::O_CREAT) };
+   |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not enough variadic arguments for `open(pathname, O_CREAT, ...)`: got 0, expected at least 1
    |
    = 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
diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.rs b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.rs
index a20539ee7c708..c557c35c9dea8 100644
--- a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.rs
+++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.rs
@@ -1,10 +1,13 @@
 extern "Rust" {
-    fn miri_get_backtrace(flags: u64) -> Box<[*mut ()]>;
+    fn miri_backtrace_size(flags: u64) -> usize;
+    fn miri_get_backtrace(flags: u64, buf: *mut *mut ());
     fn miri_resolve_frame(ptr: *mut (), flags: u64);
 }
 
 fn main() {
-    let frames = unsafe { miri_get_backtrace(0) };
+    let size = unsafe { miri_backtrace_size(0) };
+    let mut frames = vec![std::ptr::null_mut(); size];
+    unsafe { miri_get_backtrace(1, frames.as_mut_ptr()) };
     for frame in frames.iter() {
         unsafe {
             miri_resolve_frame(*frame, 0); //~ ERROR: Undefined Behavior: bad declaration of miri_resolve_frame - should return a struct with 5 fields
diff --git a/src/tools/miri/tests/fail/shims/non_vararg_signature_mismatch.rs b/src/tools/miri/tests/fail/shims/non_vararg_signature_mismatch.rs
new file mode 100644
index 0000000000000..b920e6795f90d
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/non_vararg_signature_mismatch.rs
@@ -0,0 +1,17 @@
+//@ignore-target: windows # File handling is not implemented yet
+//@compile-flags: -Zmiri-disable-isolation
+use std::ffi::{CString, OsStr, c_char, c_int};
+use std::os::unix::ffi::OsStrExt;
+
+// Declare a variadic function as non-variadic.
+extern "C" {
+    fn open(path: *const c_char, oflag: c_int) -> c_int;
+}
+
+fn main() {
+    let c_path = CString::new(OsStr::new("./text").as_bytes()).expect("CString::new failed");
+    let _fd = unsafe {
+        open(c_path.as_ptr(), /* value does not matter */ 0)
+        //~^ ERROR: calling a variadic function with a non-variadic caller-side signature
+    };
+}
diff --git a/src/tools/miri/tests/fail/shims/non_vararg_signature_mismatch.stderr b/src/tools/miri/tests/fail/shims/non_vararg_signature_mismatch.stderr
new file mode 100644
index 0000000000000..43813af9a3c61
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/non_vararg_signature_mismatch.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: calling a variadic function with a non-variadic caller-side signature
+  --> tests/fail/shims/non_vararg_signature_mismatch.rs:LL:CC
+   |
+LL |         open(c_path.as_ptr(), /* value does not matter */ 0)
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ calling a variadic function with a non-variadic caller-side signature
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at tests/fail/shims/non_vararg_signature_mismatch.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to 1 previous error
+
diff --git a/src/tools/miri/tests/fail/shims/wrong_fixed_arg_count.rs b/src/tools/miri/tests/fail/shims/wrong_fixed_arg_count.rs
new file mode 100644
index 0000000000000..e9cb69418d22f
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/wrong_fixed_arg_count.rs
@@ -0,0 +1,16 @@
+//@ignore-target: windows # File handling is not implemented yet
+//@compile-flags: -Zmiri-disable-isolation
+use std::ffi::{CString, OsStr, c_char, c_int};
+use std::os::unix::ffi::OsStrExt;
+
+extern "C" {
+    fn open(path: *const c_char, ...) -> c_int;
+}
+
+fn main() {
+    let c_path = CString::new(OsStr::new("./text").as_bytes()).expect("CString::new failed");
+    let _fd = unsafe {
+        open(c_path.as_ptr(), /* value does not matter */ 0)
+        //~^ ERROR: incorrect number of fixed arguments for variadic function
+    };
+}
diff --git a/src/tools/miri/tests/fail/shims/wrong_fixed_arg_count.stderr b/src/tools/miri/tests/fail/shims/wrong_fixed_arg_count.stderr
new file mode 100644
index 0000000000000..12e464813cfff
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/wrong_fixed_arg_count.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: incorrect number of fixed arguments for variadic function `open`: got 1, expected 2
+  --> tests/fail/shims/wrong_fixed_arg_count.rs:LL:CC
+   |
+LL |         open(c_path.as_ptr(), /* value does not matter */ 0)
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ incorrect number of fixed arguments for variadic function `open`: got 1, expected 2
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at tests/fail/shims/wrong_fixed_arg_count.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to 1 previous error
+
diff --git a/src/tools/miri/tests/pass-dep/concurrency/apple-futex.rs b/src/tools/miri/tests/pass-dep/concurrency/apple-futex.rs
new file mode 100644
index 0000000000000..becb90eb92307
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/concurrency/apple-futex.rs
@@ -0,0 +1,276 @@
+//@only-target: darwin
+//@compile-flags: -Zmiri-preemption-rate=0
+
+use std::time::{Duration, Instant};
+use std::{io, ptr, thread};
+
+fn wake_nobody() {
+    let futex = 0;
+
+    // Wake 1 waiter. Expect ENOENT as nobody is waiting.
+    unsafe {
+        assert_eq!(
+            libc::os_sync_wake_by_address_any(
+                ptr::from_ref(&futex).cast_mut().cast(),
+                size_of::<i32>(),
+                libc::OS_SYNC_WAKE_BY_ADDRESS_NONE
+            ),
+            -1
+        );
+        assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ENOENT);
+    }
+}
+
+fn wake_dangling() {
+    let futex = Box::new(0);
+    let ptr = ptr::from_ref(&futex).cast_mut().cast();
+    drop(futex);
+
+    // Expect error since this is now "unmapped" memory.
+    unsafe {
+        assert_eq!(
+            libc::os_sync_wake_by_address_any(
+                ptr,
+                size_of::<i32>(),
+                libc::OS_SYNC_WAKE_BY_ADDRESS_NONE
+            ),
+            -1
+        );
+        assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ENOENT);
+    }
+}
+
+fn wait_wrong_val() {
+    let futex: i32 = 123;
+
+    // Only wait if the futex value is 456.
+    unsafe {
+        assert_eq!(
+            libc::os_sync_wait_on_address(
+                ptr::from_ref(&futex).cast_mut().cast(),
+                456,
+                size_of::<i32>(),
+                libc::OS_SYNC_WAIT_ON_ADDRESS_NONE
+            ),
+            0,
+        );
+    }
+}
+
+fn wait_timeout() {
+    let start = Instant::now();
+
+    let futex: i32 = 123;
+
+    // Wait for 200ms, with nobody waking us up early.
+    unsafe {
+        assert_eq!(
+            libc::os_sync_wait_on_address_with_timeout(
+                ptr::from_ref(&futex).cast_mut().cast(),
+                123,
+                size_of::<i32>(),
+                libc::OS_SYNC_WAIT_ON_ADDRESS_NONE,
+                libc::OS_CLOCK_MACH_ABSOLUTE_TIME,
+                200_000_000,
+            ),
+            -1,
+        );
+        assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ETIMEDOUT);
+    }
+
+    assert!((200..1000).contains(&start.elapsed().as_millis()));
+}
+
+fn wait_absolute_timeout() {
+    let start = Instant::now();
+
+    // Get the current monotonic timestamp.
+    #[allow(deprecated)]
+    let mut deadline = unsafe { libc::mach_absolute_time() };
+
+    // Add 200ms.
+    // What we should be doing here is call `mach_timebase_info` to determine the
+    // unit used for `deadline`, but we know what Miri returns for that function:
+    // the unit is nanoseconds.
+    deadline += 200_000_000;
+
+    let futex: i32 = 123;
+
+    // Wait for 200ms from now, with nobody waking us up early.
+    unsafe {
+        assert_eq!(
+            libc::os_sync_wait_on_address_with_deadline(
+                ptr::from_ref(&futex).cast_mut().cast(),
+                123,
+                size_of::<i32>(),
+                libc::OS_SYNC_WAIT_ON_ADDRESS_NONE,
+                libc::OS_CLOCK_MACH_ABSOLUTE_TIME,
+                deadline,
+            ),
+            -1,
+        );
+        assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ETIMEDOUT);
+    }
+
+    assert!((200..1000).contains(&start.elapsed().as_millis()));
+}
+
+fn wait_wake() {
+    let start = Instant::now();
+
+    static mut FUTEX: i32 = 0;
+
+    let t = thread::spawn(move || {
+        thread::sleep(Duration::from_millis(200));
+        unsafe {
+            assert_eq!(
+                libc::os_sync_wake_by_address_any(
+                    (&raw const FUTEX).cast_mut().cast(),
+                    size_of::<i32>(),
+                    libc::OS_SYNC_WAKE_BY_ADDRESS_NONE,
+                ),
+                0,
+            );
+        }
+    });
+
+    unsafe {
+        assert_eq!(
+            libc::os_sync_wait_on_address(
+                (&raw const FUTEX).cast_mut().cast(),
+                0,
+                size_of::<i32>(),
+                libc::OS_SYNC_WAIT_ON_ADDRESS_NONE,
+            ),
+            0,
+        );
+    }
+
+    // When running this in stress-gc mode, things can take quite long.
+    // So the timeout is 3000 ms.
+    assert!((200..3000).contains(&start.elapsed().as_millis()));
+    t.join().unwrap();
+}
+
+fn wait_wake_multiple() {
+    let val = 0i32;
+    let futex = &val;
+
+    thread::scope(|s| {
+        // Spawn some threads and make them wait on the futex.
+        for i in 0..4 {
+            s.spawn(move || unsafe {
+                assert_eq!(
+                    libc::os_sync_wait_on_address(
+                        ptr::from_ref(futex).cast_mut().cast(),
+                        0,
+                        size_of::<i32>(),
+                        libc::OS_SYNC_WAIT_ON_ADDRESS_NONE,
+                    ),
+                    // The last two threads will be woken at the same time,
+                    // but for the first two threads the remaining number
+                    // of waiters should be strictly decreasing.
+                    if i < 2 { 3 - i } else { 0 },
+                );
+            });
+
+            thread::yield_now();
+        }
+
+        // Wake the threads up again.
+        unsafe {
+            assert_eq!(
+                libc::os_sync_wake_by_address_any(
+                    ptr::from_ref(futex).cast_mut().cast(),
+                    size_of::<i32>(),
+                    libc::OS_SYNC_WAKE_BY_ADDRESS_NONE,
+                ),
+                0
+            );
+
+            assert_eq!(
+                libc::os_sync_wake_by_address_any(
+                    ptr::from_ref(futex).cast_mut().cast(),
+                    size_of::<i32>(),
+                    libc::OS_SYNC_WAKE_BY_ADDRESS_NONE,
+                ),
+                0
+            );
+
+            // Wake both remaining threads at the same time.
+            assert_eq!(
+                libc::os_sync_wake_by_address_all(
+                    ptr::from_ref(futex).cast_mut().cast(),
+                    size_of::<i32>(),
+                    libc::OS_SYNC_WAKE_BY_ADDRESS_NONE,
+                ),
+                0
+            );
+        }
+    })
+}
+
+fn param_mismatch() {
+    let futex = 0;
+    thread::scope(|s| {
+        s.spawn(|| {
+            unsafe {
+                assert_eq!(
+                    libc::os_sync_wait_on_address_with_timeout(
+                        ptr::from_ref(&futex).cast_mut().cast(),
+                        0,
+                        size_of::<i32>(),
+                        libc::OS_SYNC_WAIT_ON_ADDRESS_NONE,
+                        libc::OS_CLOCK_MACH_ABSOLUTE_TIME,
+                        400_000_000,
+                    ),
+                    -1,
+                );
+                assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ETIMEDOUT);
+            }
+        });
+
+        s.spawn(|| {
+            thread::yield_now();
+            unsafe {
+                assert_eq!(
+                    libc::os_sync_wait_on_address(
+                        ptr::from_ref(&futex).cast_mut().cast(),
+                        0,
+                        size_of::<i32>(),
+                        libc::OS_SYNC_WAIT_ON_ADDRESS_SHARED,
+                    ),
+                    -1,
+                );
+                // This call fails because it uses the shared flag whereas the first waiter didn't.
+                assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::EINVAL);
+            }
+        });
+
+        thread::yield_now();
+
+        unsafe {
+            assert_eq!(
+                libc::os_sync_wake_by_address_any(
+                    ptr::from_ref(&futex).cast_mut().cast(),
+                    size_of::<i32>(),
+                    libc::OS_SYNC_WAIT_ON_ADDRESS_SHARED,
+                ),
+                -1,
+            );
+            // This call fails because it uses the shared flag whereas the waiter didn't.
+            assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::EINVAL);
+        }
+    });
+}
+
+fn main() {
+    wake_nobody();
+    wake_dangling();
+    wait_wrong_val();
+    wait_timeout();
+    wait_absolute_timeout();
+    wait_wake();
+    wait_wake_multiple();
+    param_mismatch();
+}
diff --git a/src/tools/miri/tests/pass-dep/getrandom.rs b/src/tools/miri/tests/pass-dep/getrandom.rs
index a5bc5ec7079be..d359730e7f971 100644
--- a/src/tools/miri/tests/pass-dep/getrandom.rs
+++ b/src/tools/miri/tests/pass-dep/getrandom.rs
@@ -3,7 +3,7 @@
 //@revisions: isolation no_isolation
 //@[no_isolation]compile-flags: -Zmiri-disable-isolation
 
-/// Test direct calls of getrandom 0.1 and 0.2.
+/// Test direct calls of getrandom 0.1, 0.2 and 0.3.
 fn main() {
     let mut data = vec![0; 16];
 
@@ -13,4 +13,6 @@ fn main() {
     getrandom_01::getrandom(&mut data).unwrap();
 
     getrandom_02::getrandom(&mut data).unwrap();
+
+    getrandom_03::fill(&mut data).unwrap();
 }
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 23e2122ee50fa..dc3ab2828faa2 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
@@ -230,10 +230,10 @@ fn test_two_same_fd_in_same_epoll_instance() {
     //Two notification should be received.
     let expected_event = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap();
     let expected_value = 5 as u64;
-    check_epoll_wait::<8>(epfd, &[
-        (expected_event, expected_value),
-        (expected_event, expected_value),
-    ]);
+    check_epoll_wait::<8>(
+        epfd,
+        &[(expected_event, expected_value), (expected_event, expected_value)],
+    );
 }
 
 fn test_epoll_eventfd() {
@@ -290,10 +290,10 @@ fn test_epoll_socketpair_both_sides() {
     let expected_value0 = fds[0] as u64;
     let expected_event1 = u32::try_from(libc::EPOLLOUT).unwrap();
     let expected_value1 = fds[1] as u64;
-    check_epoll_wait::<8>(epfd, &[
-        (expected_event0, expected_value0),
-        (expected_event1, expected_value1),
-    ]);
+    check_epoll_wait::<8>(
+        epfd,
+        &[(expected_event0, expected_value0), (expected_event1, expected_value1)],
+    );
 
     // Read from fds[0].
     let mut buf: [u8; 5] = [0; 5];
@@ -453,10 +453,10 @@ fn test_socketpair_read() {
     let expected_value0 = fds[0] as u64;
     let expected_event1 = u32::try_from(libc::EPOLLOUT).unwrap();
     let expected_value1 = fds[1] as u64;
-    check_epoll_wait::<8>(epfd, &[
-        (expected_event0, expected_value0),
-        (expected_event1, expected_value1),
-    ]);
+    check_epoll_wait::<8>(
+        epfd,
+        &[(expected_event0, expected_value0), (expected_event1, expected_value1)],
+    );
 
     // Read 3 bytes from fds[0].
     let mut buf: [u8; 3] = [0; 3];
diff --git a/src/tools/miri/tests/ui.rs b/src/tools/miri/tests/ui.rs
index 9b9542b88a962..3bc953c3a5fbf 100644
--- a/src/tools/miri/tests/ui.rs
+++ b/src/tools/miri/tests/ui.rs
@@ -13,7 +13,7 @@ use ui_test::custom_flags::edition::Edition;
 use ui_test::dependencies::DependencyBuilder;
 use ui_test::per_test_config::TestConfig;
 use ui_test::spanned::Spanned;
-use ui_test::{CommandBuilder, Config, Format, Match, OutputConflictHandling, status_emitter};
+use ui_test::{CommandBuilder, Config, Format, Match, ignore_output_conflict, status_emitter};
 
 #[derive(Copy, Clone, Debug)]
 enum Mode {
@@ -82,9 +82,18 @@ fn build_native_lib() -> PathBuf {
     native_lib_path
 }
 
+struct WithDependencies {
+    bless: bool,
+}
+
 /// Does *not* set any args or env vars, since it is shared between the test runner and
 /// run_dep_mode.
-fn miri_config(target: &str, path: &str, mode: Mode, with_dependencies: bool) -> Config {
+fn miri_config(
+    target: &str,
+    path: &str,
+    mode: Mode,
+    with_dependencies: Option<WithDependencies>,
+) -> Config {
     // Miri is rustc-like, so we create a default builder for rustc and modify it
     let mut program = CommandBuilder::rustc();
     program.program = miri_path();
@@ -119,22 +128,26 @@ fn miri_config(target: &str, path: &str, mode: Mode, with_dependencies: bool) ->
     // keep in sync with `./miri run`
     config.comment_defaults.base().add_custom("edition", Edition("2021".into()));
 
-    if with_dependencies {
-        config.comment_defaults.base().set_custom("dependencies", DependencyBuilder {
-            program: CommandBuilder {
-                // Set the `cargo-miri` binary, which we expect to be in the same folder as the `miri` binary.
-                // (It's a separate crate, so we don't get an env var from cargo.)
-                program: miri_path()
-                    .with_file_name(format!("cargo-miri{}", env::consts::EXE_SUFFIX)),
-                // There is no `cargo miri build` so we just use `cargo miri run`.
-                args: ["miri", "run"].into_iter().map(Into::into).collect(),
-                // Reset `RUSTFLAGS` to work around <https://github.com/rust-lang/rust/pull/119574#issuecomment-1876878344>.
-                envs: vec![("RUSTFLAGS".into(), None)],
-                ..CommandBuilder::cargo()
+    if let Some(WithDependencies { bless }) = with_dependencies {
+        config.comment_defaults.base().set_custom(
+            "dependencies",
+            DependencyBuilder {
+                program: CommandBuilder {
+                    // Set the `cargo-miri` binary, which we expect to be in the same folder as the `miri` binary.
+                    // (It's a separate crate, so we don't get an env var from cargo.)
+                    program: miri_path()
+                        .with_file_name(format!("cargo-miri{}", env::consts::EXE_SUFFIX)),
+                    // There is no `cargo miri build` so we just use `cargo miri run`.
+                    args: ["miri", "run"].into_iter().map(Into::into).collect(),
+                    // Reset `RUSTFLAGS` to work around <https://github.com/rust-lang/rust/pull/119574#issuecomment-1876878344>.
+                    envs: vec![("RUSTFLAGS".into(), None)],
+                    ..CommandBuilder::cargo()
+                },
+                crate_manifest_path: Path::new("test_dependencies").join("Cargo.toml"),
+                build_std: None,
+                bless_lockfile: bless,
             },
-            crate_manifest_path: Path::new("test_dependencies").join("Cargo.toml"),
-            build_std: None,
-        });
+        );
     }
     config
 }
@@ -146,7 +159,20 @@ fn run_tests(
     with_dependencies: bool,
     tmpdir: &Path,
 ) -> Result<()> {
+    // Handle command-line arguments.
+    let mut args = ui_test::Args::test()?;
+    args.bless |= env::var_os("RUSTC_BLESS").is_some_and(|v| v != "0");
+
+    let with_dependencies = with_dependencies.then_some(WithDependencies { bless: args.bless });
+
     let mut config = miri_config(target, path, mode, with_dependencies);
+    config.with_args(&args);
+    config.bless_command = Some("./miri test --bless".into());
+
+    if env::var_os("MIRI_SKIP_UI_CHECKS").is_some() {
+        assert!(!args.bless, "cannot use RUSTC_BLESS and MIRI_SKIP_UI_CHECKS at the same time");
+        config.output_conflict_handling = ignore_output_conflict;
+    }
 
     // Add a test env var to do environment communication tests.
     config.program.envs.push(("MIRI_ENV_VAR_TEST".into(), Some("0".into())));
@@ -182,16 +208,6 @@ fn run_tests(
         config.program.args.push(flag);
     }
 
-    // Handle command-line arguments.
-    let mut args = ui_test::Args::test()?;
-    args.bless |= env::var_os("RUSTC_BLESS").is_some_and(|v| v != "0");
-    config.with_args(&args);
-    config.bless_command = Some("./miri test --bless".into());
-
-    if env::var_os("MIRI_SKIP_UI_CHECKS").is_some() {
-        assert!(!args.bless, "cannot use RUSTC_BLESS and MIRI_SKIP_UI_CHECKS at the same time");
-        config.output_conflict_handling = OutputConflictHandling::Ignore;
-    }
     eprintln!("   Compiler: {}", config.program.display());
     ui_test::run_tests_generic(
         // Only run one test suite. In the future we can add all test suites to one `Vec` and run
@@ -327,7 +343,8 @@ fn main() -> Result<()> {
 }
 
 fn run_dep_mode(target: String, args: impl Iterator<Item = OsString>) -> Result<()> {
-    let mut config = miri_config(&target, "", Mode::RunDep, /* with dependencies */ true);
+    let mut config =
+        miri_config(&target, "", Mode::RunDep, Some(WithDependencies { bless: false }));
     config.comment_defaults.base().custom.remove("edition"); // `./miri` adds an `--edition` in `args`, so don't set it twice
     config.fill_host_and_target()?;
     config.program.args = args.collect();