about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-10-07 07:10:56 +0000
committerbors <bors@rust-lang.org>2023-10-07 07:10:56 +0000
commit0f3d72aa7a71b58f487b7e4ed663cf5b23b746fd (patch)
tree639f02cb465e8711b6a035d1b2120b1232ca561c
parentfc01a7432be662270467a4c3fa4a38811b621d9d (diff)
parenta04b7a374458b640960798dffe83e614f7907562 (diff)
downloadrust-0f3d72aa7a71b58f487b7e4ed663cf5b23b746fd.tar.gz
rust-0f3d72aa7a71b58f487b7e4ed663cf5b23b746fd.zip
Auto merge of #116508 - RalfJung:miri, r=RalfJung
Miri subtree update

r? `@ghost`
-rw-r--r--Cargo.lock52
-rw-r--r--src/tools/miri/CONTRIBUTING.md17
-rw-r--r--src/tools/miri/Cargo.lock302
-rw-r--r--src/tools/miri/README.md6
-rw-r--r--src/tools/miri/cargo-miri/Cargo.lock296
-rw-r--r--src/tools/miri/cargo-miri/Cargo.toml4
-rw-r--r--src/tools/miri/cargo-miri/src/setup.rs12
-rwxr-xr-xsrc/tools/miri/ci.sh2
-rw-r--r--src/tools/miri/miri-script/Cargo.lock216
-rw-r--r--src/tools/miri/miri-script/Cargo.toml4
-rw-r--r--src/tools/miri/miri-script/src/commands.rs5
-rw-r--r--src/tools/miri/rust-version2
-rw-r--r--src/tools/miri/src/borrow_tracker/mod.rs25
-rw-r--r--src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs3
-rw-r--r--src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs60
-rw-r--r--src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs21
-rw-r--r--src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs116
-rw-r--r--src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs187
-rw-r--r--src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs380
-rw-r--r--src/tools/miri/src/borrow_tracker/tree_borrows/tree/tests.rs25
-rw-r--r--src/tools/miri/src/borrow_tracker/tree_borrows/unimap.rs7
-rw-r--r--src/tools/miri/src/helpers.rs2
-rw-r--r--src/tools/miri/src/lib.rs4
-rw-r--r--src/tools/miri/src/machine.rs63
-rw-r--r--src/tools/miri/src/shims/dlsym.rs48
-rw-r--r--src/tools/miri/src/shims/foreign_items.rs426
-rw-r--r--src/tools/miri/src/shims/intrinsics/simd.rs57
-rw-r--r--src/tools/miri/src/shims/mod.rs4
-rw-r--r--src/tools/miri/src/shims/unix/android/dlsym.rs54
-rw-r--r--src/tools/miri/src/shims/unix/android/foreign_items.rs26
-rw-r--r--src/tools/miri/src/shims/unix/android/mod.rs1
-rw-r--r--src/tools/miri/src/shims/unix/dlsym.rs55
-rw-r--r--src/tools/miri/src/shims/unix/foreign_items.rs53
-rw-r--r--src/tools/miri/src/shims/unix/freebsd/dlsym.rs36
-rw-r--r--src/tools/miri/src/shims/unix/freebsd/foreign_items.rs15
-rw-r--r--src/tools/miri/src/shims/unix/freebsd/mod.rs1
-rw-r--r--src/tools/miri/src/shims/unix/fs.rs35
-rw-r--r--src/tools/miri/src/shims/unix/linux/dlsym.rs40
-rw-r--r--src/tools/miri/src/shims/unix/linux/foreign_items.rs18
-rw-r--r--src/tools/miri/src/shims/unix/linux/mod.rs1
-rw-r--r--src/tools/miri/src/shims/unix/macos/dlsym.rs52
-rw-r--r--src/tools/miri/src/shims/unix/macos/foreign_items.rs28
-rw-r--r--src/tools/miri/src/shims/unix/macos/mod.rs1
-rw-r--r--src/tools/miri/src/shims/unix/mod.rs1
-rw-r--r--src/tools/miri/src/shims/windows/dlsym.rs82
-rw-r--r--src/tools/miri/src/shims/windows/foreign_items.rs103
-rw-r--r--src/tools/miri/src/shims/windows/mod.rs1
-rw-r--r--src/tools/miri/src/shims/x86/mod.rs12
-rw-r--r--src/tools/miri/src/shims/x86/sse.rs12
-rw-r--r--src/tools/miri/src/shims/x86/sse2.rs12
-rw-r--r--src/tools/miri/src/shims/x86/sse3.rs8
-rw-r--r--src/tools/miri/src/shims/x86/ssse3.rs8
-rw-r--r--src/tools/miri/test_dependencies/Cargo.lock252
-rw-r--r--src/tools/miri/test_dependencies/Cargo.toml2
-rw-r--r--src/tools/miri/tests/compiletest.rs4
-rw-r--r--src/tools/miri/tests/fail/both_borrows/aliasing_mut1.tree.stderr6
-rw-r--r--src/tools/miri/tests/fail/both_borrows/aliasing_mut2.tree.stderr6
-rw-r--r--src/tools/miri/tests/fail/both_borrows/aliasing_mut3.tree.stderr6
-rw-r--r--src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.tree.stderr6
-rw-r--r--src/tools/miri/tests/fail/both_borrows/newtype_retagging.tree.stderr6
-rw-r--r--src/tools/miri/tests/fail/both_borrows/retag_data_race_protected_read.rs29
-rw-r--r--src/tools/miri/tests/fail/both_borrows/retag_data_race_protected_read.tree.stderr25
-rw-r--r--src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.tree.stderr8
-rw-r--r--src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.tree.stderr8
-rw-r--r--src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.tree.stderr8
-rw-r--r--src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.stderr8
-rw-r--r--src/tools/miri/tests/fail/function_calls/simd_feature_flag_difference.rs30
-rw-r--r--src/tools/miri/tests/fail/function_calls/simd_feature_flag_difference.stderr20
-rw-r--r--src/tools/miri/tests/fail/stacked_borrows/retag_data_race_protected_read.rs30
-rw-r--r--src/tools/miri/tests/fail/stacked_borrows/retag_data_race_protected_read.stderr (renamed from src/tools/miri/tests/fail/both_borrows/retag_data_race_protected_read.stack.stderr)0
-rw-r--r--src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.stderr14
-rw-r--r--src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.stderr14
-rw-r--r--src/tools/miri/tests/fail/tree_borrows/spurious_read.rs117
-rw-r--r--src/tools/miri/tests/fail/tree_borrows/spurious_read.stderr44
-rw-r--r--src/tools/miri/tests/pass-dep/shims/libc-misc.rs14
-rw-r--r--src/tools/miri/tests/pass/tree_borrows/cell-alternate-writes.stderr12
-rw-r--r--src/tools/miri/tests/pass/tree_borrows/end-of-protector.stderr48
-rw-r--r--src/tools/miri/tests/pass/tree_borrows/formatting.stderr50
-rw-r--r--src/tools/miri/tests/pass/tree_borrows/read_retag_no_race.rs114
-rw-r--r--src/tools/miri/tests/pass/tree_borrows/read_retag_no_race.stderr8
-rw-r--r--src/tools/miri/tests/pass/tree_borrows/reborrow-is-read.stderr18
-rw-r--r--src/tools/miri/tests/pass/tree_borrows/reserved.stderr68
-rw-r--r--src/tools/miri/tests/pass/tree_borrows/spurious_read.rs115
-rw-r--r--src/tools/miri/tests/pass/tree_borrows/spurious_read.stderr16
-rw-r--r--src/tools/miri/tests/pass/tree_borrows/tree-borrows.rs19
-rw-r--r--src/tools/miri/tests/pass/tree_borrows/unique.default.stderr24
-rw-r--r--src/tools/miri/tests/pass/tree_borrows/unique.uniq.stderr30
-rw-r--r--src/tools/miri/tests/pass/tree_borrows/vec_unique.default.stderr6
-rw-r--r--src/tools/miri/tests/pass/tree_borrows/vec_unique.uniq.stderr10
-rw-r--r--src/tools/tidy/src/deps.rs1
90 files changed, 2384 insertions, 1813 deletions
diff --git a/Cargo.lock b/Cargo.lock
index db316e998c1..3ad9d61e487 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -391,7 +391,7 @@ dependencies = [
 name = "cargo-miri"
 version = "0.1.0"
 dependencies = [
- "cargo_metadata",
+ "cargo_metadata 0.18.0",
  "directories",
  "rustc-build-sysroot",
  "rustc_tools_util",
@@ -424,6 +424,20 @@ dependencies = [
 ]
 
 [[package]]
+name = "cargo_metadata"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb9ac64500cc83ce4b9f8dafa78186aa008c8dea77a09b94cd307fd0cd5022a8"
+dependencies = [
+ "camino",
+ "cargo-platform",
+ "semver",
+ "serde",
+ "serde_json",
+ "thiserror",
+]
+
+[[package]]
 name = "cargotest2"
 version = "0.1.0"
 
@@ -551,7 +565,7 @@ name = "clippy_lints"
 version = "0.1.75"
 dependencies = [
  "arrayvec",
- "cargo_metadata",
+ "cargo_metadata 0.15.4",
  "clippy_utils",
  "declare_clippy_lint",
  "if_chain",
@@ -1038,11 +1052,11 @@ dependencies = [
 
 [[package]]
 name = "directories"
-version = "4.0.1"
+version = "5.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210"
+checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35"
 dependencies = [
- "dirs-sys",
+ "dirs-sys 0.4.1",
 ]
 
 [[package]]
@@ -1051,7 +1065,7 @@ version = "4.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
 dependencies = [
- "dirs-sys",
+ "dirs-sys 0.3.7",
 ]
 
 [[package]]
@@ -1076,6 +1090,18 @@ dependencies = [
 ]
 
 [[package]]
+name = "dirs-sys"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
+dependencies = [
+ "libc",
+ "option-ext",
+ "redox_users",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
 name = "dirs-sys-next"
 version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2665,6 +2691,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "option-ext"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+
+[[package]]
 name = "overload"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4686,7 +4718,7 @@ dependencies = [
  "annotate-snippets",
  "anyhow",
  "bytecount",
- "cargo_metadata",
+ "cargo_metadata 0.15.4",
  "clap",
  "diff",
  "dirs",
@@ -5358,7 +5390,7 @@ name = "tidy"
 version = "0.1.0"
 dependencies = [
  "cargo-platform",
- "cargo_metadata",
+ "cargo_metadata 0.15.4",
  "ignore",
  "lazy_static",
  "miropt-test-tools",
@@ -5663,7 +5695,7 @@ dependencies = [
  "anyhow",
  "bstr",
  "cargo-platform",
- "cargo_metadata",
+ "cargo_metadata 0.15.4",
  "color-eyre",
  "colored",
  "comma",
@@ -5690,7 +5722,7 @@ dependencies = [
  "anyhow",
  "bstr",
  "cargo-platform",
- "cargo_metadata",
+ "cargo_metadata 0.15.4",
  "color-eyre",
  "colored",
  "comma",
diff --git a/src/tools/miri/CONTRIBUTING.md b/src/tools/miri/CONTRIBUTING.md
index 4e50b8fae81..40a4332cdb9 100644
--- a/src/tools/miri/CONTRIBUTING.md
+++ b/src/tools/miri/CONTRIBUTING.md
@@ -71,19 +71,12 @@ and you can (cross-)run the entire test suite using:
 MIRI_TEST_TARGET=i686-unknown-linux-gnu ./miri test
 ```
 
-If your target doesn't support libstd, you can run miri with
+If your target doesn't support libstd that should usually just work. However, if you are using a
+custom target file, you might have to set `MIRI_NO_STD=1`.
 
-```
-MIRI_NO_STD=1 MIRI_TEST_TARGET=thumbv7em-none-eabihf ./miri test tests/fail/alloc/no_global_allocator.rs
-MIRI_NO_STD=1 ./miri run tests/pass/no_std.rs --target thumbv7em-none-eabihf
-```
-
-to avoid attempting (and failing) to build libstd. Note that almost no tests will pass
-this way, but you can run individual tests.
-
-`./miri test FILTER` only runs those tests that contain `FILTER` in their
-filename (including the base directory, e.g. `./miri test fail` will run all
-compile-fail tests).
+`./miri test FILTER` only runs those tests that contain `FILTER` in their filename (including the
+base directory, e.g. `./miri test fail` will run all compile-fail tests). These filters are passed
+to `cargo test`, so for multiple filers you need to use `./miri test -- FILTER1 FILTER2`.
 
 You can get a trace of which MIR statements are being executed by setting the
 `MIRI_LOG` environment variable.  For example:
diff --git a/src/tools/miri/Cargo.lock b/src/tools/miri/Cargo.lock
index 3f11e4db327..e654932255a 100644
--- a/src/tools/miri/Cargo.lock
+++ b/src/tools/miri/Cargo.lock
@@ -4,9 +4,9 @@ version = 3
 
 [[package]]
 name = "addr2line"
-version = "0.19.0"
+version = "0.21.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97"
+checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
 dependencies = [
  "gimli",
 ]
@@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
 
 [[package]]
 name = "aho-corasick"
-version = "1.0.1"
+version = "1.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04"
+checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab"
 dependencies = [
  "memchr",
 ]
@@ -47,9 +47,9 @@ dependencies = [
 
 [[package]]
 name = "anyhow"
-version = "1.0.71"
+version = "1.0.75"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
+checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
 
 [[package]]
 name = "autocfg"
@@ -59,9 +59,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
 [[package]]
 name = "backtrace"
-version = "0.3.67"
+version = "0.3.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca"
+checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
 dependencies = [
  "addr2line",
  "cc",
@@ -86,30 +86,29 @@ checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
 
 [[package]]
 name = "bstr"
-version = "1.4.0"
+version = "1.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09"
+checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a"
 dependencies = [
  "memchr",
- "once_cell",
  "regex-automata",
  "serde",
 ]
 
 [[package]]
 name = "camino"
-version = "1.1.4"
+version = "1.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c530edf18f37068ac2d977409ed5cd50d53d73bc653c7647b48eb78976ac9ae2"
+checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c"
 dependencies = [
  "serde",
 ]
 
 [[package]]
 name = "cargo-platform"
-version = "0.1.2"
+version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27"
+checksum = "2cfa25e60aea747ec7e1124f238816749faa93759c6ff5b31f1ccdda137f4479"
 dependencies = [
  "serde",
 ]
@@ -130,9 +129,12 @@ dependencies = [
 
 [[package]]
 name = "cc"
-version = "1.0.79"
+version = "1.0.83"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "libc",
+]
 
 [[package]]
 name = "cfg-if"
@@ -209,21 +211,21 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-utils"
-version = "0.8.15"
+version = "0.8.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
+checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
 dependencies = [
  "cfg-if",
 ]
 
 [[package]]
 name = "ctrlc"
-version = "3.2.5"
+version = "3.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbcf33c2a618cbe41ee43ae6e9f2e48368cd9f9db2896f10167d8d762679f639"
+checksum = "82e95fbd621905b854affdc67943b043a0fbb6ed7385fd5a25650d19a8a6cfdf"
 dependencies = [
  "nix",
- "windows-sys 0.45.0",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -247,9 +249,9 @@ dependencies = [
 
 [[package]]
 name = "errno"
-version = "0.3.1"
+version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
+checksum = "add4f07d43996f76ef320709726a556a9d4f965d9410d8d0271132d2f8293480"
 dependencies = [
  "errno-dragonfly",
  "libc",
@@ -278,18 +280,15 @@ dependencies = [
 
 [[package]]
 name = "fastrand"
-version = "1.9.0"
+version = "2.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
-dependencies = [
- "instant",
-]
+checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
 
 [[package]]
 name = "getrandom"
-version = "0.2.9"
+version = "0.2.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
+checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
 dependencies = [
  "cfg-if",
  "libc",
@@ -298,15 +297,15 @@ dependencies = [
 
 [[package]]
 name = "gimli"
-version = "0.27.2"
+version = "0.28.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4"
+checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
 
 [[package]]
 name = "hermit-abi"
-version = "0.3.1"
+version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
+checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
 
 [[package]]
 name = "humantime"
@@ -343,32 +342,21 @@ dependencies = [
 ]
 
 [[package]]
-name = "io-lifetimes"
-version = "1.0.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
-dependencies = [
- "hermit-abi",
- "libc",
- "windows-sys 0.48.0",
-]
-
-[[package]]
 name = "is-terminal"
 version = "0.4.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
 dependencies = [
  "hermit-abi",
- "rustix 0.38.14",
+ "rustix",
  "windows-sys 0.48.0",
 ]
 
 [[package]]
 name = "itoa"
-version = "1.0.6"
+version = "1.0.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
+checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
 
 [[package]]
 name = "lazy_static"
@@ -409,9 +397,9 @@ dependencies = [
 
 [[package]]
 name = "libloading"
-version = "0.8.0"
+version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb"
+checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"
 dependencies = [
  "cfg-if",
  "windows-sys 0.48.0",
@@ -419,21 +407,15 @@ dependencies = [
 
 [[package]]
 name = "linux-raw-sys"
-version = "0.3.7"
+version = "0.4.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f"
-
-[[package]]
-name = "linux-raw-sys"
-version = "0.4.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128"
+checksum = "3852614a3bd9ca9804678ba6be5e3b8ce76dfc902cae004e3e0c44051b6e88db"
 
 [[package]]
 name = "lock_api"
-version = "0.4.9"
+version = "0.4.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
+checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16"
 dependencies = [
  "autocfg",
  "scopeguard",
@@ -441,12 +423,9 @@ dependencies = [
 
 [[package]]
 name = "log"
-version = "0.4.17"
+version = "0.4.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
-dependencies = [
- "cfg-if",
-]
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
 
 [[package]]
 name = "measureme"
@@ -464,9 +443,9 @@ dependencies = [
 
 [[package]]
 name = "memchr"
-version = "2.5.0"
+version = "2.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
 
 [[package]]
 name = "memmap2"
@@ -479,9 +458,9 @@ dependencies = [
 
 [[package]]
 name = "miniz_oxide"
-version = "0.6.2"
+version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
 dependencies = [
  "adler",
 ]
@@ -510,14 +489,13 @@ dependencies = [
 
 [[package]]
 name = "nix"
-version = "0.26.2"
+version = "0.27.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a"
+checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
 dependencies = [
- "bitflags 1.3.2",
+ "bitflags 2.4.0",
  "cfg-if",
  "libc",
- "static_assertions",
 ]
 
 [[package]]
@@ -528,18 +506,18 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
 
 [[package]]
 name = "object"
-version = "0.30.3"
+version = "0.32.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439"
+checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
 dependencies = [
  "memchr",
 ]
 
 [[package]]
 name = "once_cell"
-version = "1.17.1"
+version = "1.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
+checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
 
 [[package]]
 name = "owo-colors"
@@ -592,9 +570,9 @@ dependencies = [
 
 [[package]]
 name = "pin-project-lite"
-version = "0.2.9"
+version = "0.2.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
+checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
 
 [[package]]
 name = "portable-atomic"
@@ -620,9 +598,9 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.66"
+version = "1.0.67"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
+checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
 dependencies = [
  "unicode-ident",
 ]
@@ -686,26 +664,32 @@ dependencies = [
 
 [[package]]
 name = "regex"
-version = "1.8.1"
+version = "1.9.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
+checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff"
 dependencies = [
  "aho-corasick",
  "memchr",
+ "regex-automata",
  "regex-syntax",
 ]
 
 [[package]]
 name = "regex-automata"
-version = "0.1.10"
+version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
 
 [[package]]
 name = "regex-syntax"
-version = "0.7.1"
+version = "0.7.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
+checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
 
 [[package]]
 name = "rustc-demangle"
@@ -742,66 +726,52 @@ dependencies = [
 
 [[package]]
 name = "rustix"
-version = "0.37.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d"
-dependencies = [
- "bitflags 1.3.2",
- "errno",
- "io-lifetimes",
- "libc",
- "linux-raw-sys 0.3.7",
- "windows-sys 0.48.0",
-]
-
-[[package]]
-name = "rustix"
-version = "0.38.14"
+version = "0.38.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f"
+checksum = "f25469e9ae0f3d0047ca8b93fc56843f38e6774f0914a107ff8b41be8be8e0b7"
 dependencies = [
  "bitflags 2.4.0",
  "errno",
  "libc",
- "linux-raw-sys 0.4.7",
+ "linux-raw-sys",
  "windows-sys 0.48.0",
 ]
 
 [[package]]
 name = "ryu"
-version = "1.0.13"
+version = "1.0.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
+checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
 
 [[package]]
 name = "scopeguard"
-version = "1.1.0"
+version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
 [[package]]
 name = "semver"
-version = "1.0.17"
+version = "1.0.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
+checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0"
 dependencies = [
  "serde",
 ]
 
 [[package]]
 name = "serde"
-version = "1.0.185"
+version = "1.0.188"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be9b6f69f1dfd54c3b568ffa45c310d6973a5e5148fd40cf515acaf38cf5bc31"
+checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.185"
+version = "1.0.188"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc59dfdcbad1437773485e0367fea4b090a2e0a16d9ffc46af47764536a298ec"
+checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -810,9 +780,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.96"
+version = "1.0.107"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
+checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
 dependencies = [
  "itoa",
  "ryu",
@@ -821,30 +791,24 @@ dependencies = [
 
 [[package]]
 name = "sharded-slab"
-version = "0.1.4"
+version = "0.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
 dependencies = [
  "lazy_static",
 ]
 
 [[package]]
 name = "smallvec"
-version = "1.10.0"
+version = "1.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
-
-[[package]]
-name = "static_assertions"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"
 
 [[package]]
 name = "syn"
-version = "2.0.29"
+version = "2.0.37"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
+checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -853,40 +817,40 @@ dependencies = [
 
 [[package]]
 name = "tempfile"
-version = "3.5.0"
+version = "3.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998"
+checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
 dependencies = [
  "cfg-if",
  "fastrand",
  "redox_syscall 0.3.5",
- "rustix 0.37.19",
- "windows-sys 0.45.0",
+ "rustix",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
 name = "termcolor"
-version = "1.2.0"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
+checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64"
 dependencies = [
  "winapi-util",
 ]
 
 [[package]]
 name = "thiserror"
-version = "1.0.40"
+version = "1.0.49"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
+checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.40"
+version = "1.0.49"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
+checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -916,9 +880,9 @@ dependencies = [
 
 [[package]]
 name = "tracing-core"
-version = "0.1.30"
+version = "0.1.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
+checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
 dependencies = [
  "once_cell",
  "valuable",
@@ -974,9 +938,9 @@ dependencies = [
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.8"
+version = "1.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
 
 [[package]]
 name = "unicode-width"
@@ -1014,9 +978,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
 
 [[package]]
 name = "winapi-util"
-version = "0.1.5"
+version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
 dependencies = [
  "winapi",
 ]
@@ -1042,7 +1006,7 @@ version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
 dependencies = [
- "windows-targets 0.48.0",
+ "windows-targets 0.48.5",
 ]
 
 [[package]]
@@ -1062,17 +1026,17 @@ dependencies = [
 
 [[package]]
 name = "windows-targets"
-version = "0.48.0"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
 dependencies = [
- "windows_aarch64_gnullvm 0.48.0",
- "windows_aarch64_msvc 0.48.0",
- "windows_i686_gnu 0.48.0",
- "windows_i686_msvc 0.48.0",
- "windows_x86_64_gnu 0.48.0",
- "windows_x86_64_gnullvm 0.48.0",
- "windows_x86_64_msvc 0.48.0",
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
 ]
 
 [[package]]
@@ -1083,9 +1047,9 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
 
 [[package]]
 name = "windows_aarch64_gnullvm"
-version = "0.48.0"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
 
 [[package]]
 name = "windows_aarch64_msvc"
@@ -1095,9 +1059,9 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
 
 [[package]]
 name = "windows_aarch64_msvc"
-version = "0.48.0"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
 
 [[package]]
 name = "windows_i686_gnu"
@@ -1107,9 +1071,9 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
 
 [[package]]
 name = "windows_i686_gnu"
-version = "0.48.0"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
 
 [[package]]
 name = "windows_i686_msvc"
@@ -1119,9 +1083,9 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
 
 [[package]]
 name = "windows_i686_msvc"
-version = "0.48.0"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
 
 [[package]]
 name = "windows_x86_64_gnu"
@@ -1131,9 +1095,9 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
 
 [[package]]
 name = "windows_x86_64_gnu"
-version = "0.48.0"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
@@ -1143,9 +1107,9 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
-version = "0.48.0"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
 
 [[package]]
 name = "windows_x86_64_msvc"
@@ -1155,9 +1119,9 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
 
 [[package]]
 name = "windows_x86_64_msvc"
-version = "0.48.0"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
 
 [[package]]
 name = "yansi-term"
diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md
index 6127eada222..ca2bb80bd26 100644
--- a/src/tools/miri/README.md
+++ b/src/tools/miri/README.md
@@ -480,8 +480,10 @@ Moreover, Miri recognizes some environment variables:
   purpose.
 * `MIRI_TEST_THREADS` (recognized by the test suite): set the number of threads to use for running tests.
   By default the number of cores is used.
-* `MIRI_NO_STD` (recognized by `cargo miri` and the test suite) makes sure that the target's
-  sysroot is built without libstd. This allows testing and running no_std programs.
+* `MIRI_NO_STD` (recognized by `cargo miri`) makes sure that the target's sysroot is built without
+  libstd. This allows testing and running no_std programs.
+  (Miri has a heuristic to detect no-std targets based on the target name; this environment variable
+  is only needed when that heuristic fails.)
 * `RUSTC_BLESS` (recognized by the test suite and `cargo-miri-test/run-test.py`): overwrite all
   `stderr` and `stdout` files instead of checking whether the output matches.
 * `MIRI_SKIP_UI_CHECKS` (recognized by the test suite): don't check whether the
diff --git a/src/tools/miri/cargo-miri/Cargo.lock b/src/tools/miri/cargo-miri/Cargo.lock
index 1f3a33270e1..c37a5ca8875 100644
--- a/src/tools/miri/cargo-miri/Cargo.lock
+++ b/src/tools/miri/cargo-miri/Cargo.lock
@@ -4,9 +4,9 @@ version = 3
 
 [[package]]
 name = "anyhow"
-version = "1.0.71"
+version = "1.0.75"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
+checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
 
 [[package]]
 name = "bitflags"
@@ -15,10 +15,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
 [[package]]
+name = "bitflags"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
+
+[[package]]
 name = "camino"
-version = "1.1.4"
+version = "1.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c530edf18f37068ac2d977409ed5cd50d53d73bc653c7647b48eb78976ac9ae2"
+checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c"
 dependencies = [
  "serde",
 ]
@@ -38,18 +44,18 @@ dependencies = [
 
 [[package]]
 name = "cargo-platform"
-version = "0.1.2"
+version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27"
+checksum = "2cfa25e60aea747ec7e1124f238816749faa93759c6ff5b31f1ccdda137f4479"
 dependencies = [
  "serde",
 ]
 
 [[package]]
 name = "cargo_metadata"
-version = "0.15.4"
+version = "0.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a"
+checksum = "fb9ac64500cc83ce4b9f8dafa78186aa008c8dea77a09b94cd307fd0cd5022a8"
 dependencies = [
  "camino",
  "cargo-platform",
@@ -61,9 +67,12 @@ dependencies = [
 
 [[package]]
 name = "cc"
-version = "1.0.79"
+version = "1.0.83"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "libc",
+]
 
 [[package]]
 name = "cfg-if"
@@ -73,33 +82,34 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
 name = "directories"
-version = "4.0.1"
+version = "5.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210"
+checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35"
 dependencies = [
  "dirs-sys",
 ]
 
 [[package]]
 name = "dirs-sys"
-version = "0.3.7"
+version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
+checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
 dependencies = [
  "libc",
+ "option-ext",
  "redox_users",
- "winapi",
+ "windows-sys",
 ]
 
 [[package]]
 name = "errno"
-version = "0.3.1"
+version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
+checksum = "add4f07d43996f76ef320709726a556a9d4f965d9410d8d0271132d2f8293480"
 dependencies = [
  "errno-dragonfly",
  "libc",
- "windows-sys 0.48.0",
+ "windows-sys",
 ]
 
 [[package]]
@@ -114,18 +124,15 @@ dependencies = [
 
 [[package]]
 name = "fastrand"
-version = "1.9.0"
+version = "2.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
-dependencies = [
- "instant",
-]
+checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
 
 [[package]]
 name = "getrandom"
-version = "0.2.9"
+version = "0.2.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
+checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
 dependencies = [
  "cfg-if",
  "libc",
@@ -133,54 +140,34 @@ dependencies = [
 ]
 
 [[package]]
-name = "hermit-abi"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
-
-[[package]]
-name = "instant"
-version = "0.1.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
-dependencies = [
- "cfg-if",
-]
-
-[[package]]
-name = "io-lifetimes"
-version = "1.0.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
-dependencies = [
- "hermit-abi",
- "libc",
- "windows-sys 0.48.0",
-]
-
-[[package]]
 name = "itoa"
-version = "1.0.6"
+version = "1.0.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
+checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
 
 [[package]]
 name = "libc"
-version = "0.2.142"
+version = "0.2.148"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
+checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
 
 [[package]]
 name = "linux-raw-sys"
-version = "0.3.7"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3852614a3bd9ca9804678ba6be5e3b8ce76dfc902cae004e3e0c44051b6e88db"
+
+[[package]]
+name = "option-ext"
+version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f"
+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.66"
+version = "1.0.67"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
+checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
 dependencies = [
  "unicode-ident",
 ]
@@ -200,7 +187,7 @@ version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
 dependencies = [
- "bitflags",
+ "bitflags 1.3.2",
 ]
 
 [[package]]
@@ -209,7 +196,7 @@ version = "0.3.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
 dependencies = [
- "bitflags",
+ "bitflags 1.3.2",
 ]
 
 [[package]]
@@ -251,47 +238,46 @@ dependencies = [
 
 [[package]]
 name = "rustix"
-version = "0.37.19"
+version = "0.38.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d"
+checksum = "f25469e9ae0f3d0047ca8b93fc56843f38e6774f0914a107ff8b41be8be8e0b7"
 dependencies = [
- "bitflags",
+ "bitflags 2.4.0",
  "errno",
- "io-lifetimes",
  "libc",
  "linux-raw-sys",
- "windows-sys 0.48.0",
+ "windows-sys",
 ]
 
 [[package]]
 name = "ryu"
-version = "1.0.13"
+version = "1.0.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
+checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
 
 [[package]]
 name = "semver"
-version = "1.0.17"
+version = "1.0.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
+checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0"
 dependencies = [
  "serde",
 ]
 
 [[package]]
 name = "serde"
-version = "1.0.185"
+version = "1.0.188"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be9b6f69f1dfd54c3b568ffa45c310d6973a5e5148fd40cf515acaf38cf5bc31"
+checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.185"
+version = "1.0.188"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc59dfdcbad1437773485e0367fea4b090a2e0a16d9ffc46af47764536a298ec"
+checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -300,9 +286,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.96"
+version = "1.0.107"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
+checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
 dependencies = [
  "itoa",
  "ryu",
@@ -311,9 +297,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.29"
+version = "2.0.37"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
+checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -322,31 +308,31 @@ dependencies = [
 
 [[package]]
 name = "tempfile"
-version = "3.5.0"
+version = "3.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998"
+checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
 dependencies = [
  "cfg-if",
  "fastrand",
  "redox_syscall 0.3.5",
  "rustix",
- "windows-sys 0.45.0",
+ "windows-sys",
 ]
 
 [[package]]
 name = "thiserror"
-version = "1.0.40"
+version = "1.0.49"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
+checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.40"
+version = "1.0.49"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
+checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -355,9 +341,9 @@ dependencies = [
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.8"
+version = "1.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
 
 [[package]]
 name = "wasi"
@@ -366,155 +352,67 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
-name = "winapi"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
-dependencies = [
- "winapi-i686-pc-windows-gnu",
- "winapi-x86_64-pc-windows-gnu",
-]
-
-[[package]]
-name = "winapi-i686-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
-
-[[package]]
-name = "winapi-x86_64-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
-
-[[package]]
-name = "windows-sys"
-version = "0.45.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
-dependencies = [
- "windows-targets 0.42.2",
-]
-
-[[package]]
 name = "windows-sys"
 version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
 dependencies = [
- "windows-targets 0.48.0",
+ "windows-targets",
 ]
 
 [[package]]
 name = "windows-targets"
-version = "0.42.2"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
 dependencies = [
- "windows_aarch64_gnullvm 0.42.2",
- "windows_aarch64_msvc 0.42.2",
- "windows_i686_gnu 0.42.2",
- "windows_i686_msvc 0.42.2",
- "windows_x86_64_gnu 0.42.2",
- "windows_x86_64_gnullvm 0.42.2",
- "windows_x86_64_msvc 0.42.2",
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
 ]
 
 [[package]]
-name = "windows-targets"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
-dependencies = [
- "windows_aarch64_gnullvm 0.48.0",
- "windows_aarch64_msvc 0.48.0",
- "windows_i686_gnu 0.48.0",
- "windows_i686_msvc 0.48.0",
- "windows_x86_64_gnu 0.48.0",
- "windows_x86_64_gnullvm 0.48.0",
- "windows_x86_64_msvc 0.48.0",
-]
-
-[[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.42.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
-
-[[package]]
 name = "windows_aarch64_gnullvm"
-version = "0.48.0"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
 
 [[package]]
 name = "windows_aarch64_msvc"
-version = "0.42.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
-
-[[package]]
-name = "windows_aarch64_msvc"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
-
-[[package]]
-name = "windows_i686_gnu"
-version = "0.42.2"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
 
 [[package]]
 name = "windows_i686_gnu"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
-
-[[package]]
-name = "windows_i686_msvc"
-version = "0.42.2"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
 
 [[package]]
 name = "windows_i686_msvc"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
-
-[[package]]
-name = "windows_x86_64_gnu"
-version = "0.42.2"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
 
 [[package]]
 name = "windows_x86_64_gnu"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
-
-[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.42.2"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
-version = "0.48.0"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
 
 [[package]]
 name = "windows_x86_64_msvc"
-version = "0.42.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
-
-[[package]]
-name = "windows_x86_64_msvc"
-version = "0.48.0"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
diff --git a/src/tools/miri/cargo-miri/Cargo.toml b/src/tools/miri/cargo-miri/Cargo.toml
index e118d12897a..55f6b5ac7ef 100644
--- a/src/tools/miri/cargo-miri/Cargo.toml
+++ b/src/tools/miri/cargo-miri/Cargo.toml
@@ -14,10 +14,10 @@ test = false # we have no unit tests
 doctest = false # and no doc tests
 
 [dependencies]
-directories = "4"
+directories = "5"
 rustc_version = "0.4"
 serde_json = "1.0.40"
-cargo_metadata = "0.15.0"
+cargo_metadata = "0.18.0"
 rustc-build-sysroot = "0.4.1"
 
 # Enable some feature flags that dev-dependencies need but dependencies
diff --git a/src/tools/miri/cargo-miri/src/setup.rs b/src/tools/miri/cargo-miri/src/setup.rs
index 77cecddcb8b..d921741d5de 100644
--- a/src/tools/miri/cargo-miri/src/setup.rs
+++ b/src/tools/miri/cargo-miri/src/setup.rs
@@ -74,7 +74,17 @@ pub fn setup(
         }
     };
     // Sysroot configuration and build details.
-    let sysroot_config = if std::env::var_os("MIRI_NO_STD").is_some() {
+    let no_std = match std::env::var_os("MIRI_NO_STD") {
+        None =>
+        // No-std heuristic taken from rust/src/bootstrap/config.rs
+        // (https://github.com/rust-lang/rust/blob/25b5af1b3a0b9e2c0c57b223b2d0e3e203869b2c/src/bootstrap/config.rs#L549-L555).
+            target.contains("-none")
+                || target.contains("nvptx")
+                || target.contains("switch")
+                || target.contains("-uefi"),
+        Some(val) => val != "0",
+    };
+    let sysroot_config = if no_std {
         SysrootConfig::NoStd
     } else {
         SysrootConfig::WithStd {
diff --git a/src/tools/miri/ci.sh b/src/tools/miri/ci.sh
index 9e7779e3513..1b3ed796c66 100755
--- a/src/tools/miri/ci.sh
+++ b/src/tools/miri/ci.sh
@@ -112,7 +112,7 @@ case $HOST_TARGET in
     MIRI_TEST_TARGET=aarch64-linux-android run_tests_minimal hello integer vec panic/panic
     MIRI_TEST_TARGET=wasm32-wasi run_tests_minimal no_std integer strings
     MIRI_TEST_TARGET=wasm32-unknown-unknown run_tests_minimal no_std integer strings
-    MIRI_TEST_TARGET=thumbv7em-none-eabihf MIRI_NO_STD=1 run_tests_minimal no_std # no_std embedded architecture
+    MIRI_TEST_TARGET=thumbv7em-none-eabihf run_tests_minimal no_std # no_std embedded architecture
     MIRI_TEST_TARGET=tests/avr.json MIRI_NO_STD=1 run_tests_minimal no_std # JSON target file
     ;;
   x86_64-apple-darwin)
diff --git a/src/tools/miri/miri-script/Cargo.lock b/src/tools/miri/miri-script/Cargo.lock
index 1a22596b774..6f8dd973fdd 100644
--- a/src/tools/miri/miri-script/Cargo.lock
+++ b/src/tools/miri/miri-script/Cargo.lock
@@ -4,9 +4,9 @@ version = 3
 
 [[package]]
 name = "anyhow"
-version = "1.0.71"
+version = "1.0.75"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
+checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
 
 [[package]]
 name = "bitflags"
@@ -15,6 +15,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
 [[package]]
+name = "bitflags"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
+
+[[package]]
+name = "cc"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
 name = "cfg-if"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -22,22 +37,23 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
 name = "directories"
-version = "4.0.1"
+version = "5.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210"
+checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35"
 dependencies = [
  "dirs-sys",
 ]
 
 [[package]]
 name = "dirs-sys"
-version = "0.3.7"
+version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
+checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
 dependencies = [
  "libc",
+ "option-ext",
  "redox_users",
- "winapi",
+ "windows-sys",
 ]
 
 [[package]]
@@ -48,9 +64,30 @@ checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
 
 [[package]]
 name = "either"
-version = "1.8.1"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
+
+[[package]]
+name = "errno"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "add4f07d43996f76ef320709726a556a9d4f965d9410d8d0271132d2f8293480"
+dependencies = [
+ "errno-dragonfly",
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "errno-dragonfly"
+version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
+dependencies = [
+ "cc",
+ "libc",
+]
 
 [[package]]
 name = "getrandom"
@@ -64,19 +101,34 @@ dependencies = [
 ]
 
 [[package]]
+name = "home"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
 name = "itertools"
-version = "0.10.5"
+version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
 dependencies = [
  "either",
 ]
 
 [[package]]
 name = "libc"
-version = "0.2.144"
+version = "0.2.148"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
+checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3852614a3bd9ca9804678ba6be5e3b8ce76dfc902cae004e3e0c44051b6e88db"
 
 [[package]]
 name = "miri-script"
@@ -96,9 +148,15 @@ dependencies = [
 
 [[package]]
 name = "once_cell"
-version = "1.17.1"
+version = "1.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
+checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+
+[[package]]
+name = "option-ext"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
 
 [[package]]
 name = "path_macro"
@@ -108,9 +166,9 @@ checksum = "a6e819bbd49d5939f682638fa54826bf1650abddcd65d000923de8ad63cc7d15"
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.66"
+version = "1.0.67"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
+checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
 dependencies = [
  "unicode-ident",
 ]
@@ -130,7 +188,7 @@ version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
 dependencies = [
- "bitflags",
+ "bitflags 1.3.2",
 ]
 
 [[package]]
@@ -154,6 +212,19 @@ dependencies = [
 ]
 
 [[package]]
+name = "rustix"
+version = "0.38.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f25469e9ae0f3d0047ca8b93fc56843f38e6774f0914a107ff8b41be8be8e0b7"
+dependencies = [
+ "bitflags 2.4.0",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
+[[package]]
 name = "same-file"
 version = "1.0.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -164,9 +235,9 @@ dependencies = [
 
 [[package]]
 name = "semver"
-version = "1.0.17"
+version = "1.0.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
+checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0"
 
 [[package]]
 name = "shell-words"
@@ -176,9 +247,9 @@ checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
 
 [[package]]
 name = "syn"
-version = "2.0.29"
+version = "2.0.37"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
+checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -187,18 +258,18 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "1.0.47"
+version = "1.0.49"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f"
+checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.47"
+version = "1.0.49"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b"
+checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -207,15 +278,15 @@ dependencies = [
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.11"
+version = "1.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
 
 [[package]]
 name = "walkdir"
-version = "2.3.3"
+version = "2.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
+checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
 dependencies = [
  "same-file",
  "winapi-util",
@@ -229,13 +300,14 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
 name = "which"
-version = "4.4.0"
+version = "4.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
+checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
 dependencies = [
  "either",
- "libc",
+ "home",
  "once_cell",
+ "rustix",
 ]
 
 [[package]]
@@ -256,9 +328,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
 
 [[package]]
 name = "winapi-util"
-version = "0.1.5"
+version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
 dependencies = [
  "winapi",
 ]
@@ -270,16 +342,82 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
 [[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
 name = "xshell"
-version = "0.2.3"
+version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "962c039b3a7b16cf4e9a4248397c6585c07547412e7d6a6e035389a802dcfe90"
+checksum = "ce2107fe03e558353b4c71ad7626d58ed82efaf56c54134228608893c77023ad"
 dependencies = [
  "xshell-macros",
 ]
 
 [[package]]
 name = "xshell-macros"
-version = "0.2.3"
+version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1dbabb1cbd15a1d6d12d9ed6b35cc6777d4af87ab3ba155ea37215f20beab80c"
+checksum = "7e2c411759b501fb9501aac2b1b2d287a6e93e5bdcf13c25306b23e1b716dd0e"
diff --git a/src/tools/miri/miri-script/Cargo.toml b/src/tools/miri/miri-script/Cargo.toml
index d805a94c8f5..aaa788d5846 100644
--- a/src/tools/miri/miri-script/Cargo.toml
+++ b/src/tools/miri/miri-script/Cargo.toml
@@ -13,11 +13,11 @@ edition = "2021"
 [dependencies]
 which = "4.4"
 walkdir = "2.3"
-itertools = "0.10"
+itertools = "0.11"
 path_macro = "1.0"
 shell-words = "1.1"
 anyhow = "1.0"
 xshell = "0.2"
 rustc_version = "0.4"
 dunce = "1.0.4"
-directories = "4"
+directories = "5"
diff --git a/src/tools/miri/miri-script/src/commands.rs b/src/tools/miri/miri-script/src/commands.rs
index de80777a689..c24035ae086 100644
--- a/src/tools/miri/miri-script/src/commands.rs
+++ b/src/tools/miri/miri-script/src/commands.rs
@@ -37,7 +37,10 @@ impl MiriEnv {
             Err(_) => vec![],
         };
         if !quiet {
-            eprintln!("$ (building Miri sysroot)");
+            match self.sh.var("MIRI_TEST_TARGET") {
+                Ok(target) => eprintln!("$ (building Miri sysroot for {target})"),
+                Err(_) => eprintln!("$ (building Miri sysroot)"),
+            }
         }
         let output = cmd!(self.sh,
             "cargo +{toolchain} --quiet run {cargo_extra_flags...} --manifest-path {manifest_path} --
diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version
index b60de8344d9..8f0a0a045ab 100644
--- a/src/tools/miri/rust-version
+++ b/src/tools/miri/rust-version
@@ -1 +1 @@
-bb6c66be3793ac5c738eeac91ecdc4b99388d0b4
+4ea5190026dbc1302b644d938e68bc6843cb8b24
diff --git a/src/tools/miri/src/borrow_tracker/mod.rs b/src/tools/miri/src/borrow_tracker/mod.rs
index b6dfd9944ee..a95571572d6 100644
--- a/src/tools/miri/src/borrow_tracker/mod.rs
+++ b/src/tools/miri/src/borrow_tracker/mod.rs
@@ -66,10 +66,13 @@ pub struct FrameState {
     /// `stacked_borrows::GlobalState` upon function return, and if we attempt to pop a protected
     /// tag, to identify which call is responsible for protecting the tag.
     /// See `Stack::item_popped` for more explanation.
+    /// Tree Borrows also needs to know which allocation these tags
+    /// belong to so that it can perform a read through them immediately before
+    /// the frame gets popped.
     ///
     /// This will contain one tag per reference passed to the function, so
     /// a size of 2 is enough for the vast majority of functions.
-    pub protected_tags: SmallVec<[BorTag; 2]>,
+    pub protected_tags: SmallVec<[(AllocId, BorTag); 2]>,
 }
 
 impl VisitTags for FrameState {
@@ -208,7 +211,7 @@ impl GlobalStateInner {
     }
 
     pub fn end_call(&mut self, frame: &machine::FrameExtra<'_>) {
-        for tag in &frame
+        for (_, tag) in &frame
             .borrow_tracker
             .as_ref()
             .expect("we should have borrow tracking data")
@@ -305,7 +308,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
         }
     }
 
-    fn protect_place(&mut self, place: &MPlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
+    fn protect_place(
+        &mut self,
+        place: &MPlaceTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, MPlaceTy<'tcx, Provenance>> {
         let this = self.eval_context_mut();
         let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
         match method {
@@ -450,6 +456,19 @@ impl AllocState {
             AllocState::TreeBorrows(tb) => tb.borrow_mut().remove_unreachable_tags(tags),
         }
     }
+
+    /// Tree Borrows needs to be told when a tag stops being protected.
+    pub fn release_protector<'tcx>(
+        &self,
+        machine: &MiriMachine<'_, 'tcx>,
+        global: &GlobalState,
+        tag: BorTag,
+    ) -> InterpResult<'tcx> {
+        match self {
+            AllocState::StackedBorrows(_sb) => Ok(()),
+            AllocState::TreeBorrows(tb) => tb.borrow_mut().release_protector(machine, global, tag),
+        }
+    }
 }
 
 impl VisitTags for AllocState {
diff --git a/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs b/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs
index 1ef30cb9fad..1a11879fa90 100644
--- a/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs
+++ b/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs
@@ -436,6 +436,7 @@ impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
             ProtectorKind::WeakProtector => "weakly protected",
             ProtectorKind::StrongProtector => "strongly protected",
         };
+        let item_tag = item.tag();
         let call_id = self
             .machine
             .threads
@@ -444,7 +445,7 @@ impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
             .map(|frame| {
                 frame.extra.borrow_tracker.as_ref().expect("we should have borrow tracking data")
             })
-            .find(|frame| frame.protected_tags.contains(&item.tag()))
+            .find(|frame| frame.protected_tags.iter().any(|(_, tag)| tag == &item_tag))
             .map(|frame| frame.call_id)
             .unwrap(); // FIXME: Surely we should find something, but a panic seems wrong here?
         match self.operation {
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 e670dcef330..66b729fb166 100644
--- a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs
+++ b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs
@@ -719,7 +719,13 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
 
         if let Some(protect) = new_perm.protector() {
             // See comment in `Stack::item_invalidated` for why we store the tag twice.
-            this.frame_mut().extra.borrow_tracker.as_mut().unwrap().protected_tags.push(new_tag);
+            this.frame_mut()
+                .extra
+                .borrow_tracker
+                .as_mut()
+                .unwrap()
+                .protected_tags
+                .push((alloc_id, new_tag));
             this.machine
                 .borrow_tracker
                 .as_mut()
@@ -810,36 +816,43 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
         Ok(Some(Provenance::Concrete { alloc_id, tag: new_tag }))
     }
 
-    /// Retags an individual pointer, returning the retagged version.
-    /// `kind` indicates what kind of reference is being created.
-    fn sb_retag_reference(
+    fn sb_retag_place(
         &mut self,
-        val: &ImmTy<'tcx, Provenance>,
+        place: &MPlaceTy<'tcx, Provenance>,
         new_perm: NewPermission,
         info: RetagInfo, // diagnostics info about this retag
-    ) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> {
+    ) -> InterpResult<'tcx, MPlaceTy<'tcx, Provenance>> {
         let this = self.eval_context_mut();
-        // We want a place for where the ptr *points to*, so we get one.
-        let place = this.ref_to_mplace(val)?;
-        let size = this.size_and_align_of_mplace(&place)?.map(|(size, _)| size);
+        let size = this.size_and_align_of_mplace(place)?.map(|(size, _)| size);
         // FIXME: If we cannot determine the size (because the unsized tail is an `extern type`),
         // bail out -- we cannot reasonably figure out which memory range to reborrow.
         // See https://github.com/rust-lang/unsafe-code-guidelines/issues/276.
         let size = match size {
             Some(size) => size,
-            None => return Ok(val.clone()),
+            None => return Ok(place.clone()),
         };
 
         // Compute new borrow.
         let new_tag = this.machine.borrow_tracker.as_mut().unwrap().get_mut().new_ptr();
 
         // Reborrow.
-        let new_prov = this.sb_reborrow(&place, size, new_perm, new_tag, info)?;
+        let new_prov = this.sb_reborrow(place, size, new_perm, new_tag, info)?;
 
-        // Adjust pointer.
-        let new_place = place.map_provenance(|_| new_prov);
+        // Adjust place.
+        Ok(place.clone().map_provenance(|_| new_prov))
+    }
 
-        // Return new pointer.
+    /// Retags an individual pointer, returning the retagged version.
+    /// `kind` indicates what kind of reference is being created.
+    fn sb_retag_reference(
+        &mut self,
+        val: &ImmTy<'tcx, Provenance>,
+        new_perm: NewPermission,
+        info: RetagInfo, // diagnostics info about this retag
+    ) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> {
+        let this = self.eval_context_mut();
+        let place = this.ref_to_mplace(val)?;
+        let new_place = this.sb_retag_place(&place, new_perm, info)?;
         Ok(ImmTy::from_immediate(new_place.to_ref(this), val.layout))
     }
 }
@@ -972,26 +985,23 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
     /// call.
     ///
     /// This is used to ensure soundness of in-place function argument/return passing.
-    fn sb_protect_place(&mut self, place: &MPlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
+    fn sb_protect_place(
+        &mut self,
+        place: &MPlaceTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, MPlaceTy<'tcx, Provenance>> {
         let this = self.eval_context_mut();
 
-        // We have to turn the place into a pointer to use the usual retagging logic.
-        // (The pointer type does not matter, so we use a raw pointer.)
-        let ptr = this.mplace_to_ref(place)?;
-        // Reborrow it. With protection! That is the entire point.
+        // Retag it. With protection! That is the entire point.
         let new_perm = NewPermission::Uniform {
             perm: Permission::Unique,
             access: Some(AccessKind::Write),
             protector: Some(ProtectorKind::StrongProtector),
         };
-        let _new_ptr = this.sb_retag_reference(
-            &ptr,
+        this.sb_retag_place(
+            place,
             new_perm,
             RetagInfo { cause: RetagCause::InPlaceFnPassing, in_field: false },
-        )?;
-        // We just throw away `new_ptr`, so nobody can access this memory while it is protected.
-
-        Ok(())
+        )
     }
 
     /// 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/diagnostics.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs
index fd45671ba29..b3001b5b88c 100644
--- a/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs
+++ b/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs
@@ -19,6 +19,7 @@ pub enum AccessCause {
     Explicit(AccessKind),
     Reborrow,
     Dealloc,
+    FnExit,
 }
 
 impl fmt::Display for AccessCause {
@@ -27,6 +28,7 @@ impl fmt::Display for AccessCause {
             Self::Explicit(kind) => write!(f, "{kind}"),
             Self::Reborrow => write!(f, "reborrow"),
             Self::Dealloc => write!(f, "deallocation"),
+            Self::FnExit => write!(f, "protector release"),
         }
     }
 }
@@ -38,6 +40,7 @@ impl AccessCause {
             Self::Explicit(kind) => format!("{rel} {kind}"),
             Self::Reborrow => format!("reborrow (acting as a {rel} read access)"),
             Self::Dealloc => format!("deallocation (acting as a {rel} write access)"),
+            Self::FnExit => format!("protector release (acting as a {rel} read access)"),
         }
     }
 }
@@ -52,7 +55,9 @@ pub struct Event {
     /// Relative position of the tag to the one used for the access.
     pub is_foreign: bool,
     /// User-visible range of the access.
-    pub access_range: AllocRange,
+    /// `None` means that this is an implicit access to the entire allocation
+    /// (used for the implicit read on protector release).
+    pub access_range: Option<AllocRange>,
     /// The transition recorded by this event only occured on a subrange of
     /// `access_range`: a single access on `access_range` triggers several events,
     /// each with their own mutually disjoint `transition_range`. No-op transitions
@@ -123,7 +128,17 @@ impl HistoryData {
             // NOTE: `transition_range` is explicitly absent from the error message, it has no significance
             // to the user. The meaningful one is `access_range`.
             let access = access_cause.print_as_access(is_foreign);
-            self.events.push((Some(span.data()), format!("{this} later transitioned to {endpoint} due to a {access} at offsets {access_range:?}", endpoint = transition.endpoint())));
+            let access_range_text = match access_range {
+                Some(r) => format!("at offsets {r:?}"),
+                None => format!("on every location previously accessed by this tag"),
+            };
+            self.events.push((
+                Some(span.data()),
+                format!(
+                    "{this} later transitioned to {endpoint} due to a {access} {access_range_text}",
+                    endpoint = transition.endpoint()
+                ),
+            ));
             self.events
                 .push((None, format!("this transition corresponds to {}", transition.summary())));
         }
@@ -745,7 +760,7 @@ const DEFAULT_FORMATTER: DisplayFmt = DisplayFmt {
         bot: '─',
         warning_text: "Warning: this tree is indicative only. Some tags may have been hidden.",
     },
-    perm: DisplayFmtPermission { open: "|", sep: "|", close: "|", uninit: "---", range_sep: ".." },
+    perm: DisplayFmtPermission { open: "|", sep: "|", close: "|", uninit: "----", range_sep: ".." },
     padding: DisplayFmtPadding {
         join_middle: "├",
         join_last: "└",
diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs
index 924e0de38c9..6d4c573a35c 100644
--- a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs
+++ b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs
@@ -2,10 +2,16 @@ use log::trace;
 
 use rustc_target::abi::{Abi, Align, Size};
 
-use crate::borrow_tracker::{AccessKind, GlobalStateInner, ProtectorKind, RetagFields};
+use crate::borrow_tracker::{
+    AccessKind, GlobalState, GlobalStateInner, ProtectorKind, RetagFields,
+};
 use rustc_middle::{
     mir::{Mutability, RetagKind},
-    ty::{self, layout::HasParamEnv, Ty},
+    ty::{
+        self,
+        layout::{HasParamEnv, HasTyCtxt},
+        Ty,
+    },
 };
 use rustc_span::def_id::DefId;
 
@@ -66,7 +72,7 @@ impl<'tcx> Tree {
         self.perform_access(
             access_kind,
             tag,
-            range,
+            Some(range),
             global,
             span,
             diagnostics::AccessCause::Explicit(access_kind),
@@ -95,6 +101,29 @@ impl<'tcx> Tree {
     pub fn expose_tag(&mut self, _tag: BorTag) {
         // TODO
     }
+
+    /// A tag just lost its protector.
+    ///
+    /// This emits a special kind of access that is only applied
+    /// to initialized locations, as a protection against other
+    /// tags not having been made aware of the existence of this
+    /// protector.
+    pub fn release_protector(
+        &mut self,
+        machine: &MiriMachine<'_, 'tcx>,
+        global: &GlobalState,
+        tag: BorTag,
+    ) -> InterpResult<'tcx> {
+        let span = machine.current_span();
+        self.perform_access(
+            AccessKind::Read,
+            tag,
+            None, // no specified range because it occurs on the entire allocation
+            global,
+            span,
+            diagnostics::AccessCause::FnExit,
+        )
+    }
 }
 
 /// Policy for a new borrow.
@@ -174,6 +203,8 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
         new_tag: BorTag,
     ) -> InterpResult<'tcx, Option<Provenance>> {
         let this = self.eval_context_mut();
+        // Make sure the new permission makes sense as the initial permission of a fresh tag.
+        assert!(new_perm.initial_state.is_initial());
         // Ensure we bail out if the pointer goes out-of-bounds (see miri#1050).
         this.check_ptr_access_align(
             place.ptr(),
@@ -242,7 +273,13 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
             // We register the protection in two different places.
             // This makes creating a protector slower, but checking whether a tag
             // is protected faster.
-            this.frame_mut().extra.borrow_tracker.as_mut().unwrap().protected_tags.push(new_tag);
+            this.frame_mut()
+                .extra
+                .borrow_tracker
+                .as_mut()
+                .unwrap()
+                .protected_tags
+                .push((alloc_id, new_tag));
             this.machine
                 .borrow_tracker
                 .as_mut()
@@ -269,52 +306,31 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
         tree_borrows.perform_access(
             AccessKind::Read,
             orig_tag,
-            range,
+            Some(range),
             this.machine.borrow_tracker.as_ref().unwrap(),
             this.machine.current_span(),
             diagnostics::AccessCause::Reborrow,
         )?;
         // Record the parent-child pair in the tree.
-        // FIXME: We should eventually ensure that the following `assert` holds, because
-        // some "exhaustive" tests consider only the initial configurations that satisfy it.
-        // The culprit is `Permission::new_active` in `tb_protect_place`.
-        //assert!(new_perm.initial_state.is_initial());
         tree_borrows.new_child(orig_tag, new_tag, new_perm.initial_state, range, span)?;
         drop(tree_borrows);
 
         // Also inform the data race model (but only if any bytes are actually affected).
         if range.size.bytes() > 0 {
             if let Some(data_race) = alloc_extra.data_race.as_ref() {
-                // We sometimes need to make it a write, since not all retags commute with reads!
-                // FIXME: Is that truly the semantics we want? Some optimizations are likely to be
-                // very unhappy without this. We'd tsill ge some UB just by picking a suitable
-                // interleaving, but wether UB happens can depend on whether a write occurs in the
-                // future...
-                let is_write = new_perm.initial_state.is_active()
-                    || (new_perm.initial_state.is_reserved(None) && new_perm.protector.is_some());
-                if is_write {
-                    // Need to get mutable access to alloc_extra.
-                    // (Cannot always do this as we can do read-only reborrowing on read-only allocations.)
-                    let (alloc_extra, machine) = this.get_alloc_extra_mut(alloc_id)?;
-                    alloc_extra.data_race.as_mut().unwrap().write(alloc_id, range, machine)?;
-                } else {
-                    data_race.read(alloc_id, range, &this.machine)?;
-                }
+                data_race.read(alloc_id, range, &this.machine)?;
             }
         }
 
         Ok(Some(Provenance::Concrete { alloc_id, tag: new_tag }))
     }
 
-    /// Retags an individual pointer, returning the retagged version.
-    fn tb_retag_reference(
+    fn tb_retag_place(
         &mut self,
-        val: &ImmTy<'tcx, Provenance>,
+        place: &MPlaceTy<'tcx, Provenance>,
         new_perm: NewPermission,
-    ) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> {
+    ) -> InterpResult<'tcx, MPlaceTy<'tcx, Provenance>> {
         let this = self.eval_context_mut();
-        // We want a place for where the ptr *points to*, so we get one.
-        let place = this.ref_to_mplace(val)?;
 
         // Determine the size of the reborrow.
         // For most types this is the entire size of the place, however
@@ -323,7 +339,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
         // then we override the size to do a zero-length reborrow.
         let reborrow_size = match new_perm {
             NewPermission { zero_size: false, .. } =>
-                this.size_and_align_of_mplace(&place)?
+                this.size_and_align_of_mplace(place)?
                     .map(|(size, _)| size)
                     .unwrap_or(place.layout.size),
             _ => Size::from_bytes(0),
@@ -339,12 +355,21 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
         let new_tag = this.machine.borrow_tracker.as_mut().unwrap().get_mut().new_ptr();
 
         // Compute the actual reborrow.
-        let new_prov = this.tb_reborrow(&place, reborrow_size, new_perm, new_tag)?;
+        let new_prov = this.tb_reborrow(place, reborrow_size, new_perm, new_tag)?;
 
-        // Adjust pointer.
-        let new_place = place.map_provenance(|_| new_prov);
+        // Adjust place.
+        Ok(place.clone().map_provenance(|_| new_prov))
+    }
 
-        // Return new pointer.
+    /// Retags an individual pointer, returning the retagged version.
+    fn tb_retag_reference(
+        &mut self,
+        val: &ImmTy<'tcx, Provenance>,
+        new_perm: NewPermission,
+    ) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> {
+        let this = self.eval_context_mut();
+        let place = this.ref_to_mplace(val)?;
+        let new_place = this.tb_retag_place(&place, new_perm)?;
         Ok(ImmTy::from_immediate(new_place.to_ref(this), val.layout))
     }
 }
@@ -493,22 +518,21 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
     /// call.
     ///
     /// This is used to ensure soundness of in-place function argument/return passing.
-    fn tb_protect_place(&mut self, place: &MPlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
+    fn tb_protect_place(
+        &mut self,
+        place: &MPlaceTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, MPlaceTy<'tcx, Provenance>> {
         let this = self.eval_context_mut();
 
-        // We have to turn the place into a pointer to use the usual retagging logic.
-        // (The pointer type does not matter, so we use a raw pointer.)
-        let ptr = this.mplace_to_ref(place)?;
-        // Reborrow it. With protection! That is the entire point.
+        // Retag it. With protection! That is the entire point.
         let new_perm = NewPermission {
-            initial_state: Permission::new_active(),
+            initial_state: Permission::new_reserved(
+                place.layout.ty.is_freeze(this.tcx(), this.param_env()),
+            ),
             zero_size: false,
             protector: Some(ProtectorKind::StrongProtector),
         };
-        let _new_ptr = this.tb_retag_reference(&ptr, new_perm)?;
-        // We just throw away `new_ptr`, so nobody can access this memory while it is protected.
-
-        Ok(())
+        this.tb_retag_place(place, new_perm)
     }
 
     /// Mark the given tag as exposed. It was found on a pointer with the given AllocId.
@@ -525,7 +549,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                 // if converting this alloc_id from a global to a local one
                 // uncovers a non-supported `extern static`.
                 let alloc_extra = this.get_alloc_extra(alloc_id)?;
-                trace!("Stacked Borrows tag {tag:?} exposed in {alloc_id:?}");
+                trace!("Tree Borrows tag {tag:?} exposed in {alloc_id:?}");
                 alloc_extra.borrow_tracker_tb().borrow_mut().expose_tag(tag);
             }
             AllocKind::Function | AllocKind::VTable | AllocKind::Dead => {
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 16bad13e28f..bf72e902993 100644
--- a/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs
+++ b/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs
@@ -10,9 +10,18 @@ use crate::borrow_tracker::AccessKind;
 enum PermissionPriv {
     /// represents: a local reference that has not yet been written to;
     /// allows: child reads, foreign reads, foreign writes if type is freeze;
-    /// rejects: child writes (Active), foreign writes (Disabled, except if type is not freeze).
-    /// special case: behaves differently when protected to adhere more closely to noalias
-    Reserved { ty_is_freeze: bool },
+    /// affected by: child writes (becomes Active),
+    /// rejects: foreign writes (Disabled, except if type is not freeze).
+    ///
+    /// special case: behaves differently when protected, which is where `conflicted`
+    /// is relevant
+    /// - `conflicted` is set on foreign reads,
+    /// - `conflicted` must not be set on child writes (there is UB otherwise).
+    /// This is so that the behavior of `Reserved` adheres to the rules of `noalias`:
+    /// - foreign-read then child-write is UB due to `conflicted`,
+    /// - child-write then foreign-read is UB since child-write will activate and then
+    ///   foreign-read disables a protected `Active`, which is UB.
+    Reserved { ty_is_freeze: bool, conflicted: bool },
     /// represents: a unique pointer;
     /// allows: child reads, child writes;
     /// rejects: foreign reads (Frozen), foreign writes (Disabled).
@@ -29,10 +38,11 @@ enum PermissionPriv {
 use PermissionPriv::*;
 
 impl PartialOrd for PermissionPriv {
-    /// PermissionPriv is ordered as follows:
-    /// - Reserved(_) < Active < Frozen < Disabled;
-    /// - different kinds of `Reserved` (with or without interior mutability)
-    /// are incomparable to each other.
+    /// PermissionPriv is ordered by the reflexive transitive closure of
+    /// `Reserved(conflicted=false) < Reserved(conflicted=true) < Active < Frozen < Disabled`.
+    /// `Reserved` that have incompatible `ty_is_freeze` are incomparable to each other.
+    /// This ordering matches the reachability by transitions, as asserted by the exhaustive test
+    /// `permissionpriv_partialord_is_reachability`.
     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
         use Ordering::*;
         Some(match (self, other) {
@@ -43,7 +53,17 @@ impl PartialOrd for PermissionPriv {
             (_, Frozen) => Less,
             (Active, _) => Greater,
             (_, Active) => Less,
-            (Reserved { .. }, Reserved { .. }) => return None,
+            (
+                Reserved { ty_is_freeze: f1, conflicted: c1 },
+                Reserved { ty_is_freeze: f2, conflicted: c2 },
+            ) => {
+                // No transition ever changes `ty_is_freeze`.
+                if f1 != f2 {
+                    return None;
+                }
+                // `bool` is ordered such that `false <= true`, so this works as intended.
+                c1.cmp(c2)
+            }
         })
     }
 }
@@ -51,7 +71,7 @@ impl PartialOrd for PermissionPriv {
 impl PermissionPriv {
     /// Check if `self` can be the initial state of a pointer.
     fn is_initial(&self) -> bool {
-        matches!(self, Reserved { ty_is_freeze: _ } | Frozen)
+        matches!(self, Reserved { conflicted: false, .. } | Frozen)
     }
 }
 
@@ -66,12 +86,13 @@ mod transition {
         Some(match state {
             Disabled => return None,
             // The inner data `ty_is_freeze` of `Reserved` is always irrelevant for Read
-            // accesses, since the data is not being mutated. Hence the `{ .. }`
+            // accesses, since the data is not being mutated. Hence the `{ .. }`.
             readable @ (Reserved { .. } | Active | Frozen) => readable,
         })
     }
 
-    /// A non-child node was read-accessed: noop on non-protected Reserved, advance to Frozen otherwise.
+    /// A non-child node was read-accessed: keep `Reserved` but mark it as `conflicted` if it
+    /// is protected; invalidate `Active`.
     fn foreign_read(state: PermissionPriv, protected: bool) -> Option<PermissionPriv> {
         Some(match state {
             // Non-writeable states just ignore foreign reads.
@@ -79,16 +100,14 @@ mod transition {
             // Writeable states are more tricky, and depend on whether things are protected.
             // The inner data `ty_is_freeze` of `Reserved` is always irrelevant for Read
             // accesses, since the data is not being mutated. Hence the `{ .. }`
-            res @ Reserved { .. } =>
-                if protected {
-                    // Someone else read, make sure we won't write.
-                    // We could make this `Disabled` but it doesn't look like we get anything out of that extra UB.
-                    Frozen
-                } else {
-                    // Before activation and without protectors, foreign reads are fine.
-                    // That's the entire point of 2-phase borrows.
-                    res
-                },
+
+            // Someone else read. To make sure we won't write before function exit,
+            // we set the "conflicted" flag, which will disallow writes while we are protected.
+            Reserved { ty_is_freeze, .. } if protected =>
+                Reserved { ty_is_freeze, conflicted: true },
+            // Before activation and without protectors, foreign reads are fine.
+            // That's the entire point of 2-phase borrows.
+            res @ Reserved { .. } => res,
             Active =>
                 if protected {
                     // We wrote, someone else reads -- that's bad.
@@ -104,8 +123,12 @@ mod transition {
 
     /// A child node was write-accessed: `Reserved` must become `Active` to obtain
     /// write permissions, `Frozen` and `Disabled` cannot obtain such permissions and produce UB.
-    fn child_write(state: PermissionPriv, _protected: bool) -> Option<PermissionPriv> {
+    fn child_write(state: PermissionPriv, protected: bool) -> Option<PermissionPriv> {
         Some(match state {
+            // If the `conflicted` flag is set, then there was a foreign read during
+            // the function call that is still ongoing (still `protected`),
+            // this is UB (`noalias` violation).
+            Reserved { conflicted: true, .. } if protected => return None,
             // A write always activates the 2-phase borrow, even with interior
             // mutability
             Reserved { .. } | Active => Active,
@@ -117,7 +140,8 @@ mod transition {
     /// non-protected interior mutable `Reserved` which stay the same.
     fn foreign_write(state: PermissionPriv, protected: bool) -> Option<PermissionPriv> {
         Some(match state {
-            cell @ Reserved { ty_is_freeze: false } if !protected => cell,
+            Reserved { .. } if protected => Disabled,
+            res @ Reserved { ty_is_freeze: false, .. } => res,
             _ => Disabled,
         })
     }
@@ -159,13 +183,14 @@ impl Permission {
     }
 
     /// Default initial permission of the root of a new tree.
+    /// Must *only* be used for the root, this is not in general an "initial" permission!
     pub fn new_active() -> Self {
         Self { inner: Active }
     }
 
     /// Default initial permission of a reborrowed mutable reference.
     pub fn new_reserved(ty_is_freeze: bool) -> Self {
-        Self { inner: Reserved { ty_is_freeze } }
+        Self { inner: Reserved { ty_is_freeze, conflicted: false } }
     }
 
     /// Default initial permission of a reborrowed shared reference
@@ -173,28 +198,6 @@ impl Permission {
         Self { inner: Frozen }
     }
 
-    pub fn is_active(self) -> bool {
-        matches!(self.inner, Active)
-    }
-
-    // Leave `interior_mut` as `None` if interior mutability
-    // is irrelevant.
-    pub fn is_reserved(self, interior_mut: Option<bool>) -> bool {
-        match (interior_mut, self.inner) {
-            (None, Reserved { .. }) => true,
-            (Some(b1), Reserved { ty_is_freeze: b2 }) => b1 == b2,
-            _ => false,
-        }
-    }
-
-    pub fn is_frozen(self) -> bool {
-        matches!(self.inner, Frozen)
-    }
-
-    pub fn is_disabled(self) -> bool {
-        matches!(self.inner, Disabled)
-    }
-
     /// Apply the transition to the inner PermissionPriv.
     pub fn perform_access(
         kind: AccessKind,
@@ -250,8 +253,12 @@ pub mod diagnostics {
                 f,
                 "{}",
                 match self {
-                    Reserved { ty_is_freeze: true } => "Reserved",
-                    Reserved { ty_is_freeze: false } => "Reserved (interior mutable)",
+                    Reserved { ty_is_freeze: true, conflicted: false } => "Reserved",
+                    Reserved { ty_is_freeze: true, conflicted: true } => "Reserved (conflicted)",
+                    Reserved { ty_is_freeze: false, conflicted: false } =>
+                        "Reserved (interior mutable)",
+                    Reserved { ty_is_freeze: false, conflicted: true } =>
+                        "Reserved (interior mutable, conflicted)",
                     Active => "Active",
                     Frozen => "Frozen",
                     Disabled => "Disabled",
@@ -279,11 +286,13 @@ pub mod diagnostics {
             // and also as `diagnostics::DisplayFmtPermission.uninit` otherwise
             // alignment will be incorrect.
             match self.inner {
-                Reserved { ty_is_freeze: true } => "Res",
-                Reserved { ty_is_freeze: false } => "Re*",
-                Active => "Act",
-                Frozen => "Frz",
-                Disabled => "Dis",
+                Reserved { ty_is_freeze: true, conflicted: false } => "Rs  ",
+                Reserved { ty_is_freeze: true, conflicted: true } => "RsC ",
+                Reserved { ty_is_freeze: false, conflicted: false } => "RsM ",
+                Reserved { ty_is_freeze: false, conflicted: true } => "RsCM",
+                Active => "Act ",
+                Frozen => "Frz ",
+                Disabled => "Dis ",
             }
         }
     }
@@ -291,15 +300,13 @@ pub mod diagnostics {
     impl PermTransition {
         /// Readable explanation of the consequences of an event.
         /// Fits in the sentence "This accessed caused {trans.summary()}".
-        ///
-        /// Important: for the purposes of this explanation, `Reserved` is considered
-        /// to have write permissions, because that's what the diagnostics care about
-        /// (otherwise `Reserved -> Frozen` would be considered a noop).
         pub fn summary(&self) -> &'static str {
             assert!(self.is_possible());
             match (self.from, self.to) {
                 (_, Active) => "the first write to a 2-phase borrowed mutable reference",
                 (_, Frozen) => "a loss of write permissions",
+                (Reserved { conflicted: false, .. }, Reserved { conflicted: true, .. }) =>
+                    "a temporary loss of write permissions until function exit",
                 (Frozen, Disabled) => "a loss of read permissions",
                 (_, Disabled) => "a loss of read and write permissions",
                 (old, new) =>
@@ -314,7 +321,7 @@ pub mod diagnostics {
         /// Irrelevant events:
         /// - modifications of write permissions when the error is related to read permissions
         ///   (on failed reads and protected `Frozen -> Disabled`, ignore `Reserved -> Active`,
-        ///   `Reserved -> Frozen`, and `Active -> Frozen`)
+        ///   `Reserved(conflicted=false) -> Reserved(conflicted=true)`, and `Active -> Frozen`)
         /// - all transitions for attempts to deallocate strongly protected tags
         ///
         /// # Panics
@@ -325,8 +332,9 @@ pub mod diagnostics {
         /// (Reserved < Active < Frozen < Disabled);
         /// - between `self` and `err` the permission should also be increasing,
         /// so all permissions inside `err` should be greater than `self.1`;
-        /// - `Active` and `Reserved` cannot cause an error due to insufficient permissions,
-        /// so `err` cannot be a `ChildAccessForbidden(_)` of either of them;
+        /// - `Active` and `Reserved(conflicted=false)` cannot cause an error
+        /// due to insufficient permissions, so `err` cannot be a `ChildAccessForbidden(_)`
+        /// of either of them;
         /// - `err` should not be `ProtectedDisabled(Disabled)`, because the protected
         /// tag should not have been `Disabled` in the first place (if this occurs it means
         /// we have unprotected tags that become protected)
@@ -346,23 +354,28 @@ pub mod diagnostics {
                         (Frozen, Frozen) => true,
                         (Active, Frozen) => true,
                         (Disabled, Disabled) => true,
+                        (Reserved { conflicted: true, .. }, Reserved { conflicted: true, .. }) =>
+                            true,
                         // A pointer being `Disabled` is a strictly stronger source of
                         // errors than it being `Frozen`. If we try to access a `Disabled`,
-                        // then where it became `Frozen` (or `Active`) is the least of our concerns for now.
-                        (Active | Frozen, Disabled) => false,
+                        // then where it became `Frozen` (or `Active` or `Reserved`) is the least
+                        // of our concerns for now.
+                        (Reserved { conflicted: true, .. } | Active | Frozen, Disabled) => false,
+                        (Reserved { conflicted: true, .. }, Frozen) => false,
 
                         // `Active` and `Reserved` have all permissions, so a
                         // `ChildAccessForbidden(Reserved | Active)` can never exist.
-                        (_, Active) | (_, Reserved { .. }) =>
+                        (_, Active) | (_, Reserved { conflicted: false, .. }) =>
                             unreachable!("this permission cannot cause an error"),
-                        // No transition has `Reserved` as its `.to` unless it's a noop.
-                        (Reserved { .. }, _) => unreachable!("self is a noop transition"),
+                        // No transition has `Reserved(conflicted=false)` as its `.to` unless it's a noop.
+                        (Reserved { conflicted: false, .. }, _) =>
+                            unreachable!("self is a noop transition"),
                         // All transitions produced in normal executions (using `apply_access`)
                         // change permissions in the order `Reserved -> Active -> Frozen -> Disabled`.
                         // We assume that the error was triggered on the same location that
                         // the transition `self` applies to, so permissions found must be increasing
                         // in the order `self.from < self.to <= insufficient.inner`
-                        (Disabled, Frozen) =>
+                        (Active | Frozen | Disabled, Reserved { .. }) | (Disabled, Frozen) =>
                             unreachable!("permissions between self and err must be increasing"),
                     }
                 }
@@ -372,25 +385,34 @@ pub mod diagnostics {
                     // This eliminates transitions like `Reserved -> Active`
                     // when the error is a `Frozen -> Disabled`.
                     match (self.to, before_disabled.inner) {
-                        // We absolutely want to know where it was activated.
+                        // We absolutely want to know where it was activated/frozen/marked
+                        // conflicted.
                         (Active, Active) => true,
-                        // And knowing where it became Frozen is also important.
                         (Frozen, Frozen) => true,
+                        (Reserved { conflicted: true, .. }, Reserved { conflicted: true, .. }) =>
+                            true,
                         // If the error is a transition `Frozen -> Disabled`, then we don't really
                         // care whether before that was `Reserved -> Active -> Frozen` or
-                        // `Reserved -> Frozen` or even `Frozen` directly.
+                        // `Frozen` directly.
                         // The error will only show either
-                        // - created as Frozen, then Frozen -> Disabled is forbidden
-                        // - created as Reserved, later became Frozen, then Frozen -> Disabled is forbidden
-                        // In both cases the `Reserved -> Active` part is inexistant or irrelevant.
+                        // - created as Reserved { conflicted: false },
+                        //   then Reserved { .. } -> Disabled is forbidden
+                        // - created as Reserved { conflicted: false },
+                        //   then Active -> Disabled is forbidden
+                        // A potential `Reserved { conflicted: false }
+                        //   -> Reserved { conflicted: true }` is inexistant or irrelevant,
+                        // and so is the `Reserved { conflicted: false } -> Active`
                         (Active, Frozen) => false,
+                        (Reserved { conflicted: true, .. }, _) => false,
 
                         (_, Disabled) =>
                             unreachable!(
                                 "permission that results in Disabled should not itself be Disabled in the first place"
                             ),
-                        // No transition has `Reserved` as its `.to` unless it's a noop.
-                        (Reserved { .. }, _) => unreachable!("self is a noop transition"),
+                        // No transition has `Reserved { conflicted: false }` as its `.to`
+                        // unless it's a noop.
+                        (Reserved { conflicted: false, .. }, _) =>
+                            unreachable!("self is a noop transition"),
 
                         // Permissions only evolve in the order `Reserved -> Active -> Frozen -> Disabled`,
                         // so permissions found must be increasing in the order
@@ -417,6 +439,16 @@ pub mod diagnostics {
 }
 
 #[cfg(test)]
+impl Permission {
+    pub fn is_reserved_with_conflicted(&self, expected_conflicted: bool) -> bool {
+        match self.inner {
+            Reserved { conflicted, .. } => conflicted == expected_conflicted,
+            _ => false,
+        }
+    }
+}
+
+#[cfg(test)]
 mod propagation_optimization_checks {
     pub use super::*;
     use crate::borrow_tracker::tree_borrows::exhaustive::{precondition, Exhaustive};
@@ -424,9 +456,10 @@ mod propagation_optimization_checks {
     impl Exhaustive for PermissionPriv {
         fn exhaustive() -> Box<dyn Iterator<Item = Self>> {
             Box::new(
-                vec![Active, Frozen, Disabled]
-                    .into_iter()
-                    .chain(bool::exhaustive().map(|ty_is_freeze| Reserved { ty_is_freeze })),
+                vec![Active, Frozen, Disabled].into_iter().chain(
+                    <[bool; 2]>::exhaustive()
+                        .map(|[ty_is_freeze, conflicted]| Reserved { ty_is_freeze, conflicted }),
+                ),
             )
         }
     }
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 eb466f89a84..b63b0bdff12 100644
--- a/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs
+++ b/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs
@@ -282,6 +282,113 @@ enum ContinueTraversal {
     SkipChildren,
 }
 
+/// Stack of nodes left to explore in a tree traversal.
+struct TreeVisitorStack<NodeApp, ErrHandler> {
+    /// Identifier of the original access.
+    initial: UniIndex,
+    /// Function to apply to each tag.
+    f_propagate: NodeApp,
+    /// Handler to add the required context to diagnostics.
+    err_builder: ErrHandler,
+    /// Mutable state of the visit: the tags left to handle.
+    /// Every tag pushed should eventually be handled,
+    /// and the precise order is relevant for diagnostics.
+    stack: Vec<(UniIndex, AccessRelatedness)>,
+}
+
+impl<NodeApp, InnErr, OutErr, ErrHandler> TreeVisitorStack<NodeApp, ErrHandler>
+where
+    NodeApp: Fn(NodeAppArgs<'_>) -> Result<ContinueTraversal, InnErr>,
+    ErrHandler: Fn(ErrHandlerArgs<'_, InnErr>) -> OutErr,
+{
+    /// Apply the function to the current `tag`, and push its children
+    /// to the stack of future tags to visit.
+    fn exec_and_visit(
+        &mut self,
+        this: &mut TreeVisitor<'_>,
+        idx: UniIndex,
+        exclude: Option<UniIndex>,
+        rel_pos: AccessRelatedness,
+    ) -> Result<(), OutErr> {
+        // 1. apply the propagation function
+        let node = this.nodes.get_mut(idx).unwrap();
+        let recurse =
+            (self.f_propagate)(NodeAppArgs { node, perm: this.perms.entry(idx), rel_pos })
+                .map_err(|error_kind| {
+                    (self.err_builder)(ErrHandlerArgs {
+                        error_kind,
+                        conflicting_info: &this.nodes.get(idx).unwrap().debug_info,
+                        accessed_info: &this.nodes.get(self.initial).unwrap().debug_info,
+                    })
+                })?;
+        let node = this.nodes.get(idx).unwrap();
+        // 2. add the children to the stack for future traversal
+        if matches!(recurse, ContinueTraversal::Recurse) {
+            let general_child_rel = rel_pos.for_child();
+            for &child in node.children.iter() {
+                // Some child might be excluded from here and handled separately,
+                // e.g. the initially accessed tag.
+                if Some(child) != exclude {
+                    // We should still ensure that if we don't skip the initially accessed
+                    // it will receive the proper `AccessRelatedness`.
+                    let this_child_rel = if child == self.initial {
+                        AccessRelatedness::This
+                    } else {
+                        general_child_rel
+                    };
+                    self.stack.push((child, this_child_rel));
+                }
+            }
+        }
+        Ok(())
+    }
+
+    fn new(initial: UniIndex, f_propagate: NodeApp, err_builder: ErrHandler) -> Self {
+        Self { initial, f_propagate, err_builder, stack: Vec::new() }
+    }
+
+    /// Finish the exploration by applying `exec_and_visit` until
+    /// the stack is empty.
+    fn finish(&mut self, visitor: &mut TreeVisitor<'_>) -> Result<(), OutErr> {
+        while let Some((idx, rel_pos)) = self.stack.pop() {
+            self.exec_and_visit(visitor, idx, /* no children to exclude */ None, rel_pos)?;
+        }
+        Ok(())
+    }
+
+    /// Push all ancestors to the exploration stack in order of nearest ancestor
+    /// towards the top.
+    fn push_and_visit_strict_ancestors(
+        &mut self,
+        visitor: &mut TreeVisitor<'_>,
+    ) -> Result<(), OutErr> {
+        let mut path_ascend = Vec::new();
+        // First climb to the root while recording the path
+        let mut curr = self.initial;
+        while let Some(ancestor) = visitor.nodes.get(curr).unwrap().parent {
+            path_ascend.push((ancestor, curr));
+            curr = ancestor;
+        }
+        // Then descend:
+        // - execute f_propagate on each node
+        // - record children in visit
+        while let Some((ancestor, next_in_path)) = path_ascend.pop() {
+            // Explore ancestors in descending order.
+            // `next_in_path` is excluded from the recursion because it
+            // will be the `ancestor` of the next iteration.
+            // It also needs a different `AccessRelatedness` than the other
+            // children of `ancestor`.
+            self.exec_and_visit(
+                visitor,
+                ancestor,
+                Some(next_in_path),
+                AccessRelatedness::StrictChildAccess,
+            )?;
+        }
+        Ok(())
+    }
+}
+
 impl<'tree> TreeVisitor<'tree> {
     // Applies `f_propagate` to every vertex of the tree top-down in the following order: first
     // all ancestors of `start`, then `start` itself, then children of `start`, then the rest.
@@ -298,107 +405,40 @@ impl<'tree> TreeVisitor<'tree> {
         start: BorTag,
         f_propagate: impl Fn(NodeAppArgs<'_>) -> Result<ContinueTraversal, InnErr>,
         err_builder: impl Fn(ErrHandlerArgs<'_, InnErr>) -> OutErr,
-    ) -> Result<(), OutErr>
-where {
-        struct TreeVisitAux<NodeApp, ErrHandler> {
-            accessed_tag: UniIndex,
-            f_propagate: NodeApp,
-            err_builder: ErrHandler,
-            stack: Vec<(UniIndex, AccessRelatedness)>,
-        }
-        impl<NodeApp, InnErr, OutErr, ErrHandler> TreeVisitAux<NodeApp, ErrHandler>
-        where
-            NodeApp: Fn(NodeAppArgs<'_>) -> Result<ContinueTraversal, InnErr>,
-            ErrHandler: Fn(ErrHandlerArgs<'_, InnErr>) -> OutErr,
-        {
-            fn pop(&mut self) -> Option<(UniIndex, AccessRelatedness)> {
-                self.stack.pop()
-            }
-
-            /// Apply the function to the current `tag`, and push its children
-            /// to the stack of future tags to visit.
-            fn exec_and_visit(
-                &mut self,
-                this: &mut TreeVisitor<'_>,
-                tag: UniIndex,
-                exclude: Option<UniIndex>,
-                rel_pos: AccessRelatedness,
-            ) -> Result<(), OutErr> {
-                // 1. apply the propagation function
-                let node = this.nodes.get_mut(tag).unwrap();
-                let recurse =
-                    (self.f_propagate)(NodeAppArgs { node, perm: this.perms.entry(tag), rel_pos })
-                        .map_err(|error_kind| {
-                            (self.err_builder)(ErrHandlerArgs {
-                                error_kind,
-                                conflicting_info: &this.nodes.get(tag).unwrap().debug_info,
-                                accessed_info: &this
-                                    .nodes
-                                    .get(self.accessed_tag)
-                                    .unwrap()
-                                    .debug_info,
-                            })
-                        })?;
-                let node = this.nodes.get(tag).unwrap();
-                // 2. add the children to the stack for future traversal
-                if matches!(recurse, ContinueTraversal::Recurse) {
-                    let child_rel = rel_pos.for_child();
-                    for &child in node.children.iter() {
-                        // some child might be excluded from here and handled separately
-                        if Some(child) != exclude {
-                            self.stack.push((child, child_rel));
-                        }
-                    }
-                }
-                Ok(())
-            }
-        }
+    ) -> Result<(), OutErr> {
+        let start_idx = self.tag_mapping.get(&start).unwrap();
+        let mut stack = TreeVisitorStack::new(start_idx, f_propagate, err_builder);
+        stack.push_and_visit_strict_ancestors(&mut self)?;
+        // All (potentially zero) ancestors have been explored,
+        // it's time to explore the `start` tag.
+        stack.exec_and_visit(
+            &mut self,
+            start_idx,
+            /* no children to exclude */ None,
+            AccessRelatedness::This,
+        )?;
+        // Then finish with a normal DFS.
+        stack.finish(&mut self)
+    }
 
+    // Applies `f_propagate` to every non-child vertex of the tree (ancestors first).
+    //
+    // `f_propagate` should follow the following format: for a given `Node` it updates its
+    // `Permission` depending on the position relative to `start` (given by an
+    // `AccessRelatedness`).
+    // It outputs whether the tree traversal for this subree should continue or not.
+    fn traverse_nonchildren<InnErr, OutErr>(
+        mut self,
+        start: BorTag,
+        f_propagate: impl Fn(NodeAppArgs<'_>) -> Result<ContinueTraversal, InnErr>,
+        err_builder: impl Fn(ErrHandlerArgs<'_, InnErr>) -> OutErr,
+    ) -> Result<(), OutErr> {
         let start_idx = self.tag_mapping.get(&start).unwrap();
-        let mut stack =
-            TreeVisitAux { accessed_tag: start_idx, f_propagate, err_builder, stack: Vec::new() };
-        {
-            let mut path_ascend = Vec::new();
-            // First climb to the root while recording the path
-            let mut curr = start_idx;
-            while let Some(ancestor) = self.nodes.get(curr).unwrap().parent {
-                path_ascend.push((ancestor, curr));
-                curr = ancestor;
-            }
-            // Then descend:
-            // - execute f_propagate on each node
-            // - record children in visit
-            while let Some((ancestor, next_in_path)) = path_ascend.pop() {
-                // Explore ancestors in descending order.
-                // `next_in_path` is excluded from the recursion because it
-                // will be the `ancestor` of the next iteration.
-                // It also needs a different `AccessRelatedness` than the other
-                // children of `ancestor`.
-                stack.exec_and_visit(
-                    &mut self,
-                    ancestor,
-                    Some(next_in_path),
-                    AccessRelatedness::StrictChildAccess,
-                )?;
-            }
-        };
-        // All (potentially zero) ancestors have been explored, call f_propagate on start
-        stack.exec_and_visit(&mut self, start_idx, None, AccessRelatedness::This)?;
-        // up to this point we have never popped from `stack`, hence if the
-        // path to the root is `root = p(n) <- p(n-1)... <- p(1) <- p(0) = start`
-        // then now `stack` contains
-        // `[<children(p(n)) except p(n-1)> ... <children(p(1)) except p(0)> <children(p(0))>]`,
-        // all of which are for now unexplored.
-        // This is the starting point of a standard DFS which will thus
-        // explore all non-ancestors of `start` in the following order:
-        // - all descendants of `start`;
-        // - then the unexplored descendants of `parent(start)`;
-        // ...
-        // - until finally the unexplored descendants of `root`.
-        while let Some((tag, rel_pos)) = stack.pop() {
-            stack.exec_and_visit(&mut self, tag, None, rel_pos)?;
-        }
-        Ok(())
+        let mut stack = TreeVisitorStack::new(start_idx, f_propagate, err_builder);
+        stack.push_and_visit_strict_ancestors(&mut self)?;
+        // We *don't* visit the `start` tag, and we don't push its children.
+        // Only finish the DFS with the cousins.
+        stack.finish(&mut self)
     }
 }
 
@@ -482,7 +522,7 @@ impl<'tcx> Tree {
         self.perform_access(
             AccessKind::Write,
             tag,
-            access_range,
+            Some(access_range),
             global,
             span,
             diagnostics::AccessCause::Dealloc,
@@ -520,6 +560,11 @@ impl<'tcx> Tree {
     /// Map the per-node and per-location `LocationState::perform_access`
     /// to each location of `access_range`, on every tag of the allocation.
     ///
+    /// If `access_range` is `None`, this is interpreted as the special
+    /// access that is applied on protector release:
+    /// - the access will be applied only to initialized locations of the allocation,
+    /// - and it will not be visible to children.
+    ///
     /// `LocationState::perform_access` will take care of raising transition
     /// errors and updating the `initialized` status of each location,
     /// this traversal adds to that:
@@ -530,56 +575,105 @@ impl<'tcx> Tree {
         &mut self,
         access_kind: AccessKind,
         tag: BorTag,
-        access_range: AllocRange,
+        access_range: Option<AllocRange>,
         global: &GlobalState,
         span: Span,                             // diagnostics
         access_cause: diagnostics::AccessCause, // diagnostics
     ) -> InterpResult<'tcx> {
-        for (perms_range, perms) in self.rperms.iter_mut(access_range.start, access_range.size) {
-            TreeVisitor { nodes: &mut self.nodes, tag_mapping: &self.tag_mapping, perms }
-                .traverse_parents_this_children_others(
-                    tag,
-                    |args: NodeAppArgs<'_>| -> Result<ContinueTraversal, TransitionError> {
-                        let NodeAppArgs { node, mut perm, rel_pos } = args;
+        use std::ops::Range;
+        // Performs the per-node work:
+        // - insert the permission if it does not exist
+        // - perform the access
+        // - record the transition
+        // to which some optimizations are added:
+        // - skip the traversal of the children in some cases
+        // - do not record noop transitions
+        //
+        // `perms_range` is only for diagnostics (it is the range of
+        // the `RangeMap` on which we are currently working).
+        let node_app = |perms_range: Range<u64>,
+                        args: NodeAppArgs<'_>|
+         -> Result<ContinueTraversal, TransitionError> {
+            let NodeAppArgs { node, mut perm, rel_pos } = args;
+
+            let old_state = perm.or_insert(LocationState::new(node.default_initial_perm));
+
+            match old_state.skip_if_known_noop(access_kind, rel_pos) {
+                ContinueTraversal::SkipChildren => return Ok(ContinueTraversal::SkipChildren),
+                _ => {}
+            }
 
-                        let old_state =
-                            perm.or_insert_with(|| LocationState::new(node.default_initial_perm));
+            let protected = global.borrow().protected_tags.contains_key(&node.tag);
+            let transition = old_state.perform_access(access_kind, rel_pos, protected)?;
+
+            // Record the event as part of the history
+            if !transition.is_noop() {
+                node.debug_info.history.push(diagnostics::Event {
+                    transition,
+                    is_foreign: rel_pos.is_foreign(),
+                    access_cause,
+                    access_range,
+                    transition_range: perms_range.clone(),
+                    span,
+                });
+            }
+            Ok(ContinueTraversal::Recurse)
+        };
 
-                        match old_state.skip_if_known_noop(access_kind, rel_pos) {
-                            ContinueTraversal::SkipChildren =>
-                                return Ok(ContinueTraversal::SkipChildren),
-                            _ => {}
-                        }
+        // Error handler in case `node_app` goes wrong.
+        // Wraps the faulty transition in more context for diagnostics.
+        let err_handler = |perms_range: Range<u64>,
+                           args: ErrHandlerArgs<'_, TransitionError>|
+         -> InterpError<'tcx> {
+            let ErrHandlerArgs { error_kind, conflicting_info, accessed_info } = args;
+            TbError {
+                conflicting_info,
+                access_cause,
+                error_offset: perms_range.start,
+                error_kind,
+                accessed_info,
+            }
+            .build()
+        };
 
-                        let protected = global.borrow().protected_tags.contains_key(&node.tag);
-                        let transition =
-                            old_state.perform_access(access_kind, rel_pos, protected)?;
-
-                        // Record the event as part of the history
-                        if !transition.is_noop() {
-                            node.debug_info.history.push(diagnostics::Event {
-                                transition,
-                                is_foreign: rel_pos.is_foreign(),
-                                access_cause,
-                                access_range,
-                                transition_range: perms_range.clone(),
-                                span,
-                            });
-                        }
-                        Ok(ContinueTraversal::Recurse)
-                    },
-                    |args: ErrHandlerArgs<'_, TransitionError>| -> InterpError<'tcx> {
-                        let ErrHandlerArgs { error_kind, conflicting_info, accessed_info } = args;
-                        TbError {
-                            conflicting_info,
-                            access_cause,
-                            error_offset: perms_range.start,
-                            error_kind,
-                            accessed_info,
-                        }
-                        .build()
-                    },
-                )?;
+        if let Some(access_range) = access_range {
+            // Default branch: this is a "normal" access through a known range.
+            // We iterate over affected locations and traverse the tree for each of them.
+            for (perms_range, perms) in self.rperms.iter_mut(access_range.start, access_range.size)
+            {
+                TreeVisitor { nodes: &mut self.nodes, tag_mapping: &self.tag_mapping, perms }
+                    .traverse_parents_this_children_others(
+                        tag,
+                        |args| node_app(perms_range.clone(), args),
+                        |args| err_handler(perms_range.clone(), args),
+                    )?;
+            }
+        } else {
+            // This is a special access through the entire allocation.
+            // It actually only affects `initialized` locations, so we need
+            // to filter on those before initiating the traversal.
+            //
+            // In addition this implicit access should not be visible to children,
+            // thus the use of `traverse_nonchildren`.
+            // See the test case `returned_mut_is_usable` from
+            // `tests/pass/tree_borrows/tree-borrows.rs` for an example of
+            // why this is important.
+            for (perms_range, perms) in self.rperms.iter_mut_all() {
+                let idx = self.tag_mapping.get(&tag).unwrap();
+                // Only visit initialized permissions
+                if let Some(p) = perms.get(idx) && p.initialized {
+                    TreeVisitor {
+                        nodes: &mut self.nodes,
+                        tag_mapping: &self.tag_mapping,
+                        perms,
+                    }
+                    .traverse_nonchildren(
+                        tag,
+                        |args| node_app(perms_range.clone(), args),
+                        |args| err_handler(perms_range.clone(), args),
+                    )?;
+                }
+            }
         }
         Ok(())
     }
diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/tree/tests.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/tree/tests.rs
index 11c06d0b763..35f3b53afdb 100644
--- a/src/tools/miri/src/borrow_tracker/tree_borrows/tree/tests.rs
+++ b/src/tools/miri/src/borrow_tracker/tree_borrows/tree/tests.rs
@@ -302,6 +302,9 @@ mod spurious_read {
             Ok(Self { state, prot: self.prot })
         }
 
+        /// Remove the protector.
+        /// This does not perform the implicit read on function exit because
+        /// `LocStateProt` does not have enough context to apply its effect.
         fn end_protector(&self) -> Self {
             Self { prot: false, state: self.state }
         }
@@ -351,7 +354,8 @@ mod spurious_read {
         }
 
         /// Perform a read on the given pointer if its state is `initialized`.
-        /// Must be called just after reborrowing a pointer.
+        /// Must be called just after reborrowing a pointer, and just after
+        /// removing a protector.
         fn read_if_initialized(self, ptr: PtrSelector) -> Result<Self, ()> {
             let initialized = match ptr {
                 PtrSelector::X => self.x.state.initialized,
@@ -368,14 +372,16 @@ mod spurious_read {
             }
         }
 
+        /// Remove the protector of `x`, including the implicit read on function exit.
         fn end_protector_x(self) -> Result<Self, ()> {
             let x = self.x.end_protector();
-            Ok(Self { x, ..self })
+            Self { x, ..self }.read_if_initialized(PtrSelector::X)
         }
 
+        /// Remove the protector of `y`, including the implicit read on function exit.
         fn end_protector_y(self) -> Result<Self, ()> {
             let y = self.y.end_protector();
-            Ok(Self { y, ..self })
+            Self { y, ..self }.read_if_initialized(PtrSelector::Y)
         }
 
         fn retag_y(self, new_y: LocStateProt) -> Result<Self, ()> {
@@ -504,11 +510,9 @@ mod spurious_read {
     }
 
     #[test]
-    #[should_panic]
-    // This is why `Reserved -> Frozen` on foreign read for protected references
-    // prevents the insertion of spurious reads: the transition can cause UB in the target
-    // later down the line.
-    fn reserved_frozen_protected_distinguishable() {
+    // `Reserved(aliased=false)` and `Reserved(aliased=true)` are properly indistinguishable
+    // under the conditions where we want to insert a spurious read.
+    fn reserved_aliased_protected_indistinguishable() {
         let source = LocStateProtPair {
             xy_rel: RelPosXY::MutuallyForeign,
             x: LocStateProt {
@@ -522,8 +526,8 @@ mod spurious_read {
         };
         let acc = TestAccess { ptr: PtrSelector::X, kind: AccessKind::Read };
         let target = source.clone().perform_test_access(&acc).unwrap();
-        assert!(source.y.state.permission.is_reserved(None));
-        assert!(target.y.state.permission.is_frozen());
+        assert!(source.y.state.permission.is_reserved_with_conflicted(false));
+        assert!(target.y.state.permission.is_reserved_with_conflicted(true));
         assert!(!source.distinguishable::<(), ()>(&target))
     }
 
@@ -599,7 +603,6 @@ mod spurious_read {
     }
 
     #[test]
-    #[should_panic]
     /// For each of the patterns described above, execute it once
     /// as-is, and once with a spurious read inserted. Report any UB
     /// in the target but not in the source.
diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/unimap.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/unimap.rs
index 58af32385c5..d9cad9c8e0b 100644
--- a/src/tools/miri/src/borrow_tracker/tree_borrows/unimap.rs
+++ b/src/tools/miri/src/borrow_tracker/tree_borrows/unimap.rs
@@ -212,12 +212,9 @@ impl<'a, V> UniValMap<V> {
 
 impl<'a, V> UniEntry<'a, V> {
     /// Insert in the map and get the value.
-    pub fn or_insert_with<F>(&mut self, default: F) -> &mut V
-    where
-        F: FnOnce() -> V,
-    {
+    pub fn or_insert(&mut self, default: V) -> &mut V {
         if self.inner.is_none() {
-            *self.inner = Some(default());
+            *self.inner = Some(default);
         }
         self.inner.as_mut().unwrap()
     }
diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs
index fd9d57c487c..4146a9b41ae 100644
--- a/src/tools/miri/src/helpers.rs
+++ b/src/tools/miri/src/helpers.rs
@@ -960,7 +960,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
         self.check_abi(abi, exp_abi)?;
         if let Some((body, instance)) = self.eval_context_mut().lookup_exported_symbol(link_name)? {
             // If compiler-builtins is providing the symbol, then don't treat it as a clash.
-            // We'll use our built-in implementation in `emulate_foreign_item_by_name` for increased
+            // We'll use our built-in implementation in `emulate_foreign_item_inner` for increased
             // performance. Note that this means we won't catch any undefined behavior in
             // compiler-builtins when running other crates, but Miri can still be run on
             // compiler-builtins itself (or any crate that uses it as a normal dependency)
diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs
index 816055cc4fe..f1d8ce01bc2 100644
--- a/src/tools/miri/src/lib.rs
+++ b/src/tools/miri/src/lib.rs
@@ -8,6 +8,7 @@
 #![feature(yeet_expr)]
 #![feature(nonzero_ops)]
 #![feature(round_ties_even)]
+#![feature(let_chains)]
 #![feature(lint_reasons)]
 #![feature(trait_upcasting)]
 // Configure clippy and other lints
@@ -86,9 +87,8 @@ pub use rustc_const_eval::interpret::*;
 // Resolve ambiguity.
 pub use rustc_const_eval::interpret::{self, AllocMap, PlaceTy, Provenance as _};
 
-pub use crate::shims::dlsym::{Dlsym, EvalContextExt as _};
 pub use crate::shims::env::{EnvVars, EvalContextExt as _};
-pub use crate::shims::foreign_items::EvalContextExt as _;
+pub use crate::shims::foreign_items::{DynSym, EvalContextExt as _};
 pub use crate::shims::intrinsics::EvalContextExt as _;
 pub use crate::shims::os_str::EvalContextExt as _;
 pub use crate::shims::panic::{CatchUnwindData, EvalContextExt as _};
diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs
index f1c50794ca8..930fa053d20 100644
--- a/src/tools/miri/src/machine.rs
+++ b/src/tools/miri/src/machine.rs
@@ -707,11 +707,10 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
                 );
             }
             "android" => {
-                // "signal"
+                // "signal" -- just needs a non-zero pointer value (function does not even get called),
+                // but we arrange for this to be callable anyway (it will then do nothing).
                 let layout = this.machine.layouts.const_raw_ptr;
-                let dlsym = Dlsym::from_str("signal".as_bytes(), &this.tcx.sess.target.os)?
-                    .expect("`signal` must be an actual dlsym on android");
-                let ptr = this.fn_ptr(FnVal::Other(dlsym));
+                let ptr = this.fn_ptr(FnVal::Other(DynSym::from_str("signal")));
                 let val = ImmTy::from_scalar(Scalar::from_pointer(ptr, this), layout);
                 Self::alloc_extern_static(this, "signal", val)?;
                 // A couple zero-initialized pointer-sized extern statics.
@@ -867,7 +866,7 @@ impl<'mir, 'tcx> MiriInterpCxExt<'mir, 'tcx> for MiriInterpCx<'mir, 'tcx> {
 /// Machine hook implementations.
 impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
     type MemoryKind = MiriMemoryKind;
-    type ExtraFnVal = Dlsym;
+    type ExtraFnVal = DynSym;
 
     type FrameExtra = FrameExtra<'tcx>;
     type AllocExtra = AllocExtra<'tcx>;
@@ -939,15 +938,15 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
     #[inline(always)]
     fn call_extra_fn(
         ecx: &mut MiriInterpCx<'mir, 'tcx>,
-        fn_val: Dlsym,
+        fn_val: DynSym,
         abi: Abi,
         args: &[FnArg<'tcx, Provenance>],
         dest: &PlaceTy<'tcx, Provenance>,
         ret: Option<mir::BasicBlock>,
-        _unwind: mir::UnwindAction,
+        unwind: mir::UnwindAction,
     ) -> InterpResult<'tcx> {
         let args = ecx.copy_fn_args(args)?; // FIXME: Should `InPlace` arguments be reset to uninit?
-        ecx.call_dlsym(fn_val, abi, &args, dest, ret)
+        ecx.emulate_dyn_sym(fn_val, abi, &args, dest, ret, unwind)
     }
 
     #[inline(always)]
@@ -1275,19 +1274,25 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
         ecx: &mut InterpCx<'mir, 'tcx, Self>,
         place: &PlaceTy<'tcx, Provenance>,
     ) -> InterpResult<'tcx> {
-        // We do need to write `uninit` so that even after the call ends, the former contents of
-        // this place cannot be observed any more.
-        ecx.write_uninit(place)?;
         // If we have a borrow tracker, we also have it set up protection so that all reads *and
         // writes* during this call are insta-UB.
-        if ecx.machine.borrow_tracker.is_some() {
+        let protected_place = if ecx.machine.borrow_tracker.is_some() {
             // Have to do `to_op` first because a `Place::Local` doesn't imply the local doesn't have an address.
             if let Either::Left(place) = ecx.place_to_op(place)?.as_mplace_or_imm() {
-                ecx.protect_place(&place)?;
+                ecx.protect_place(&place)?.into()
             } else {
                 // Locals that don't have their address taken are as protected as they can ever be.
+                place.clone()
             }
-        }
+        } else {
+            // No borrow tracker.
+            place.clone()
+        };
+        // We do need to write `uninit` so that even after the call ends, the former contents of
+        // this place cannot be observed any more. We do the write after retagging so that for
+        // Tree Borrows, this is considered to activate the new tag.
+        ecx.write_uninit(&protected_place)?;
+        // Now we throw away the protected place, ensuring its tag is never used again.
         Ok(())
     }
 
@@ -1382,8 +1387,34 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
     ) -> InterpResult<'tcx> {
         // We want this *before* the return value copy, because the return place itself is protected
         // until we do `end_call` here.
-        if let Some(borrow_tracker) = &ecx.machine.borrow_tracker {
-            borrow_tracker.borrow_mut().end_call(&frame.extra);
+        if let Some(global_borrow_tracker) = &ecx.machine.borrow_tracker {
+            // The body of this loop needs `global_borrow_tracker` immutably
+            // so we can't move this code inside the following `end_call`.
+            for (alloc_id, tag) in &frame
+                .extra
+                .borrow_tracker
+                .as_ref()
+                .expect("we should have borrow tracking data")
+                .protected_tags
+            {
+                // Just because the tag is protected doesn't guarantee that
+                // the allocation still exists (weak protectors allow deallocations)
+                // so we must check that the allocation exists.
+                // If it does exist, then we have the guarantee that the
+                // pointer is readable, and the implicit read access inserted
+                // will never cause UB on the pointer itself.
+                let (_, _, kind) = ecx.get_alloc_info(*alloc_id);
+                if matches!(kind, AllocKind::LiveData) {
+                    let alloc_extra = ecx.get_alloc_extra(*alloc_id).unwrap();
+                    let alloc_borrow_tracker = &alloc_extra.borrow_tracker.as_ref().unwrap();
+                    alloc_borrow_tracker.release_protector(
+                        &ecx.machine,
+                        global_borrow_tracker,
+                        *tag,
+                    )?;
+                }
+            }
+            global_borrow_tracker.borrow_mut().end_call(&frame.extra);
         }
         Ok(())
     }
diff --git a/src/tools/miri/src/shims/dlsym.rs b/src/tools/miri/src/shims/dlsym.rs
deleted file mode 100644
index 8bf6d24f85f..00000000000
--- a/src/tools/miri/src/shims/dlsym.rs
+++ /dev/null
@@ -1,48 +0,0 @@
-use rustc_middle::mir;
-use rustc_target::spec::abi::Abi;
-
-use crate::helpers::target_os_is_unix;
-use crate::*;
-use shims::unix::dlsym as unix;
-use shims::windows::dlsym as windows;
-
-#[derive(Debug, Copy, Clone)]
-#[allow(non_camel_case_types)]
-pub enum Dlsym {
-    Posix(unix::Dlsym),
-    Windows(windows::Dlsym),
-}
-
-impl Dlsym {
-    // Returns an error for unsupported symbols, and None if this symbol
-    // should become a NULL pointer (pretend it does not exist).
-    pub fn from_str<'tcx>(name: &[u8], target_os: &str) -> InterpResult<'tcx, Option<Dlsym>> {
-        let name = &*String::from_utf8_lossy(name);
-        Ok(match target_os {
-            target if target_os_is_unix(target) =>
-                unix::Dlsym::from_str(name, target)?.map(Dlsym::Posix),
-            "windows" => windows::Dlsym::from_str(name)?.map(Dlsym::Windows),
-            os => bug!("dlsym not implemented for target_os {}", os),
-        })
-    }
-}
-
-impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
-pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
-    fn call_dlsym(
-        &mut self,
-        dlsym: Dlsym,
-        abi: Abi,
-        args: &[OpTy<'tcx, Provenance>],
-        dest: &PlaceTy<'tcx, Provenance>,
-        ret: Option<mir::BasicBlock>,
-    ) -> InterpResult<'tcx> {
-        let this = self.eval_context_mut();
-        match dlsym {
-            Dlsym::Posix(dlsym) =>
-                unix::EvalContextExt::call_dlsym(this, dlsym, abi, args, dest, ret),
-            Dlsym::Windows(dlsym) =>
-                windows::EvalContextExt::call_dlsym(this, dlsym, abi, args, dest, ret),
-        }
-    }
-}
diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs
index 0c92ede40fd..3462f03c30f 100644
--- a/src/tools/miri/src/shims/foreign_items.rs
+++ b/src/tools/miri/src/shims/foreign_items.rs
@@ -6,7 +6,7 @@ use rustc_apfloat::Float;
 use rustc_ast::expand::allocator::AllocatorKind;
 use rustc_hir::{
     def::DefKind,
-    def_id::{CrateNum, DefId, LOCAL_CRATE},
+    def_id::{CrateNum, LOCAL_CRATE},
 };
 use rustc_middle::middle::{
     codegen_fn_attrs::CodegenFnAttrFlags, dependency_format::Linkage,
@@ -25,120 +25,141 @@ use super::backtrace::EvalContextExt as _;
 use crate::helpers::target_os_is_unix;
 use crate::*;
 
-/// Returned by `emulate_foreign_item_by_name`.
-pub enum EmulateByNameResult<'mir, 'tcx> {
+/// Type of dynamic symbols (for `dlsym` et al)
+#[derive(Debug, Copy, Clone)]
+pub struct DynSym(Symbol);
+
+#[allow(clippy::should_implement_trait)]
+impl DynSym {
+    pub fn from_str(name: &str) -> Self {
+        DynSym(Symbol::intern(name))
+    }
+}
+
+/// Returned by `emulate_foreign_item_inner`.
+pub enum EmulateForeignItemResult {
     /// The caller is expected to jump to the return block.
     NeedsJumping,
     /// Jumping has already been taken care of.
     AlreadyJumped,
-    /// A MIR body has been found for the function.
-    MirBody(&'mir mir::Body<'tcx>, ty::Instance<'tcx>),
     /// The item is not supported.
     NotSupported,
 }
 
 impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
 pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
-    /// Returns the minimum alignment for the target architecture for allocations of the given size.
-    fn min_align(&self, size: u64, kind: MiriMemoryKind) -> Align {
-        let this = self.eval_context_ref();
-        // List taken from `library/std/src/sys/common/alloc.rs`.
-        // This list should be kept in sync with the one from libstd.
-        let min_align = match this.tcx.sess.target.arch.as_ref() {
-            "x86" | "arm" | "mips" | "mips32r6" | "powerpc" | "powerpc64" | "asmjs" | "wasm32" => 8,
-            "x86_64" | "aarch64" | "mips64" | "mips64r6" | "s390x" | "sparc64" | "loongarch64" =>
-                16,
-            arch => bug!("unsupported target architecture for malloc: `{}`", arch),
+    /// Emulates calling a foreign item, failing if the item is not supported.
+    /// This function will handle `goto_block` if needed.
+    /// Returns Ok(None) if the foreign item was completely handled
+    /// by this function.
+    /// Returns Ok(Some(body)) if processing the foreign item
+    /// is delegated to another function.
+    fn emulate_foreign_item(
+        &mut self,
+        link_name: Symbol,
+        abi: Abi,
+        args: &[OpTy<'tcx, Provenance>],
+        dest: &PlaceTy<'tcx, Provenance>,
+        ret: Option<mir::BasicBlock>,
+        unwind: mir::UnwindAction,
+    ) -> InterpResult<'tcx, Option<(&'mir mir::Body<'tcx>, ty::Instance<'tcx>)>> {
+        let this = self.eval_context_mut();
+        let tcx = this.tcx.tcx;
+
+        // First: functions that diverge.
+        let ret = match ret {
+            None =>
+                match link_name.as_str() {
+                    "miri_start_panic" => {
+                        // `check_shim` happens inside `handle_miri_start_panic`.
+                        this.handle_miri_start_panic(abi, link_name, args, unwind)?;
+                        return Ok(None);
+                    }
+                    // This matches calls to the foreign item `panic_impl`.
+                    // The implementation is provided by the function with the `#[panic_handler]` attribute.
+                    "panic_impl" => {
+                        // We don't use `check_shim` here because we are just forwarding to the lang
+                        // item. Argument count checking will be performed when the returned `Body` is
+                        // called.
+                        this.check_abi_and_shim_symbol_clash(abi, Abi::Rust, link_name)?;
+                        let panic_impl_id = tcx.lang_items().panic_impl().unwrap();
+                        let panic_impl_instance = ty::Instance::mono(tcx, panic_impl_id);
+                        return Ok(Some((
+                            this.load_mir(panic_impl_instance.def, None)?,
+                            panic_impl_instance,
+                        )));
+                    }
+                    #[rustfmt::skip]
+                    | "exit"
+                    | "ExitProcess"
+                    => {
+                        let exp_abi = if link_name.as_str() == "exit" {
+                            Abi::C { unwind: false }
+                        } else {
+                            Abi::System { unwind: false }
+                        };
+                        let [code] = this.check_shim(abi, exp_abi, link_name, args)?;
+                        // it's really u32 for ExitProcess, but we have to put it into the `Exit` variant anyway
+                        let code = this.read_scalar(code)?.to_i32()?;
+                        throw_machine_stop!(TerminationInfo::Exit { code: code.into(), leak_check: false });
+                    }
+                    "abort" => {
+                        let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                        throw_machine_stop!(TerminationInfo::Abort(
+                            "the program aborted execution".to_owned()
+                        ))
+                    }
+                    _ => {
+                        if let Some(body) = this.lookup_exported_symbol(link_name)? {
+                            return Ok(Some(body));
+                        }
+                        this.handle_unsupported(format!(
+                            "can't call (diverging) foreign function: {link_name}"
+                        ))?;
+                        return Ok(None);
+                    }
+                },
+            Some(p) => p,
         };
-        // Windows always aligns, even small allocations.
-        // Source: <https://support.microsoft.com/en-us/help/286470/how-to-use-pageheap-exe-in-windows-xp-windows-2000-and-windows-server>
-        // But jemalloc does not, so for the C heap we only align if the allocation is sufficiently big.
-        if kind == MiriMemoryKind::WinHeap || size >= min_align {
-            return Align::from_bytes(min_align).unwrap();
-        }
-        // We have `size < min_align`. Round `size` *down* to the next power of two and use that.
-        fn prev_power_of_two(x: u64) -> u64 {
-            let next_pow2 = x.next_power_of_two();
-            if next_pow2 == x {
-                // x *is* a power of two, just use that.
-                x
-            } else {
-                // x is between two powers, so next = 2*prev.
-                next_pow2 / 2
+
+        // Second: functions that return immediately.
+        match this.emulate_foreign_item_inner(link_name, abi, args, dest)? {
+            EmulateForeignItemResult::NeedsJumping => {
+                trace!("{:?}", this.dump_place(dest));
+                this.go_to_block(ret);
             }
-        }
-        Align::from_bytes(prev_power_of_two(size)).unwrap()
-    }
+            EmulateForeignItemResult::AlreadyJumped => (),
+            EmulateForeignItemResult::NotSupported => {
+                if let Some(body) = this.lookup_exported_symbol(link_name)? {
+                    return Ok(Some(body));
+                }
 
-    fn malloc(
-        &mut self,
-        size: u64,
-        zero_init: bool,
-        kind: MiriMemoryKind,
-    ) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
-        let this = self.eval_context_mut();
-        if size == 0 {
-            Ok(Pointer::null())
-        } else {
-            let align = this.min_align(size, kind);
-            let ptr = this.allocate_ptr(Size::from_bytes(size), align, kind.into())?;
-            if zero_init {
-                // We just allocated this, the access is definitely in-bounds and fits into our address space.
-                this.write_bytes_ptr(
-                    ptr.into(),
-                    iter::repeat(0u8).take(usize::try_from(size).unwrap()),
-                )
-                .unwrap();
+                this.handle_unsupported(format!(
+                    "can't call foreign function `{link_name}` on OS `{os}`",
+                    os = this.tcx.sess.target.os,
+                ))?;
+                return Ok(None);
             }
-            Ok(ptr.into())
         }
+
+        Ok(None)
     }
 
-    fn free(
+    /// Emulates a call to a `DynSym`.
+    fn emulate_dyn_sym(
         &mut self,
-        ptr: Pointer<Option<Provenance>>,
-        kind: MiriMemoryKind,
+        sym: DynSym,
+        abi: Abi,
+        args: &[OpTy<'tcx, Provenance>],
+        dest: &PlaceTy<'tcx, Provenance>,
+        ret: Option<mir::BasicBlock>,
+        unwind: mir::UnwindAction,
     ) -> InterpResult<'tcx> {
-        let this = self.eval_context_mut();
-        if !this.ptr_is_null(ptr)? {
-            this.deallocate_ptr(ptr, None, kind.into())?;
-        }
+        let res = self.emulate_foreign_item(sym.0, abi, args, dest, ret, unwind)?;
+        assert!(res.is_none(), "DynSyms that delegate are not supported");
         Ok(())
     }
 
-    fn realloc(
-        &mut self,
-        old_ptr: Pointer<Option<Provenance>>,
-        new_size: u64,
-        kind: MiriMemoryKind,
-    ) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
-        let this = self.eval_context_mut();
-        let new_align = this.min_align(new_size, kind);
-        if this.ptr_is_null(old_ptr)? {
-            if new_size == 0 {
-                Ok(Pointer::null())
-            } else {
-                let new_ptr =
-                    this.allocate_ptr(Size::from_bytes(new_size), new_align, kind.into())?;
-                Ok(new_ptr.into())
-            }
-        } else {
-            if new_size == 0 {
-                this.deallocate_ptr(old_ptr, None, kind.into())?;
-                Ok(Pointer::null())
-            } else {
-                let new_ptr = this.reallocate_ptr(
-                    old_ptr,
-                    None,
-                    Size::from_bytes(new_size),
-                    new_align,
-                    kind.into(),
-                )?;
-                Ok(new_ptr.into())
-            }
-        }
-    }
-
     /// Lookup the body of a function that has `link_name` as the symbol name.
     fn lookup_exported_symbol(
         &mut self,
@@ -233,6 +254,78 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
         }
     }
 
+    fn malloc(
+        &mut self,
+        size: u64,
+        zero_init: bool,
+        kind: MiriMemoryKind,
+    ) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
+        let this = self.eval_context_mut();
+        if size == 0 {
+            Ok(Pointer::null())
+        } else {
+            let align = this.min_align(size, kind);
+            let ptr = this.allocate_ptr(Size::from_bytes(size), align, kind.into())?;
+            if zero_init {
+                // We just allocated this, the access is definitely in-bounds and fits into our address space.
+                this.write_bytes_ptr(
+                    ptr.into(),
+                    iter::repeat(0u8).take(usize::try_from(size).unwrap()),
+                )
+                .unwrap();
+            }
+            Ok(ptr.into())
+        }
+    }
+
+    fn free(
+        &mut self,
+        ptr: Pointer<Option<Provenance>>,
+        kind: MiriMemoryKind,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        if !this.ptr_is_null(ptr)? {
+            this.deallocate_ptr(ptr, None, kind.into())?;
+        }
+        Ok(())
+    }
+
+    fn realloc(
+        &mut self,
+        old_ptr: Pointer<Option<Provenance>>,
+        new_size: u64,
+        kind: MiriMemoryKind,
+    ) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
+        let this = self.eval_context_mut();
+        let new_align = this.min_align(new_size, kind);
+        if this.ptr_is_null(old_ptr)? {
+            if new_size == 0 {
+                Ok(Pointer::null())
+            } else {
+                let new_ptr =
+                    this.allocate_ptr(Size::from_bytes(new_size), new_align, kind.into())?;
+                Ok(new_ptr.into())
+            }
+        } else {
+            if new_size == 0 {
+                this.deallocate_ptr(old_ptr, None, kind.into())?;
+                Ok(Pointer::null())
+            } else {
+                let new_ptr = this.reallocate_ptr(
+                    old_ptr,
+                    None,
+                    Size::from_bytes(new_size),
+                    new_align,
+                    kind.into(),
+                )?;
+                Ok(new_ptr.into())
+            }
+        }
+    }
+}
+
+impl<'mir, 'tcx: 'mir> EvalContextExtPriv<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
     /// Read bytes from a `(ptr, len)` argument
     fn read_byte_slice<'i>(&'i self, bytes: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, &'i [u8]>
     where
@@ -246,115 +339,47 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
         Ok(bytes)
     }
 
-    /// Emulates calling a foreign item, failing if the item is not supported.
-    /// This function will handle `goto_block` if needed.
-    /// Returns Ok(None) if the foreign item was completely handled
-    /// by this function.
-    /// Returns Ok(Some(body)) if processing the foreign item
-    /// is delegated to another function.
-    fn emulate_foreign_item(
-        &mut self,
-        def_id: DefId,
-        abi: Abi,
-        args: &[OpTy<'tcx, Provenance>],
-        dest: &PlaceTy<'tcx, Provenance>,
-        ret: Option<mir::BasicBlock>,
-        unwind: mir::UnwindAction,
-    ) -> InterpResult<'tcx, Option<(&'mir mir::Body<'tcx>, ty::Instance<'tcx>)>> {
-        let this = self.eval_context_mut();
-        let link_name = this.item_link_name(def_id);
-        let tcx = this.tcx.tcx;
-
-        // First: functions that diverge.
-        let ret = match ret {
-            None =>
-                match link_name.as_str() {
-                    "miri_start_panic" => {
-                        // `check_shim` happens inside `handle_miri_start_panic`.
-                        this.handle_miri_start_panic(abi, link_name, args, unwind)?;
-                        return Ok(None);
-                    }
-                    // This matches calls to the foreign item `panic_impl`.
-                    // The implementation is provided by the function with the `#[panic_handler]` attribute.
-                    "panic_impl" => {
-                        // We don't use `check_shim` here because we are just forwarding to the lang
-                        // item. Argument count checking will be performed when the returned `Body` is
-                        // called.
-                        this.check_abi_and_shim_symbol_clash(abi, Abi::Rust, link_name)?;
-                        let panic_impl_id = tcx.lang_items().panic_impl().unwrap();
-                        let panic_impl_instance = ty::Instance::mono(tcx, panic_impl_id);
-                        return Ok(Some((
-                            this.load_mir(panic_impl_instance.def, None)?,
-                            panic_impl_instance,
-                        )));
-                    }
-                    #[rustfmt::skip]
-                    | "exit"
-                    | "ExitProcess"
-                    => {
-                        let exp_abi = if link_name.as_str() == "exit" {
-                            Abi::C { unwind: false }
-                        } else {
-                            Abi::System { unwind: false }
-                        };
-                        let [code] = this.check_shim(abi, exp_abi, link_name, args)?;
-                        // it's really u32 for ExitProcess, but we have to put it into the `Exit` variant anyway
-                        let code = this.read_scalar(code)?.to_i32()?;
-                        throw_machine_stop!(TerminationInfo::Exit { code: code.into(), leak_check: false });
-                    }
-                    "abort" => {
-                        let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
-                        throw_machine_stop!(TerminationInfo::Abort(
-                            "the program aborted execution".to_owned()
-                        ))
-                    }
-                    _ => {
-                        if let Some(body) = this.lookup_exported_symbol(link_name)? {
-                            return Ok(Some(body));
-                        }
-                        this.handle_unsupported(format!(
-                            "can't call (diverging) foreign function: {link_name}"
-                        ))?;
-                        return Ok(None);
-                    }
-                },
-            Some(p) => p,
+    /// Returns the minimum alignment for the target architecture for allocations of the given size.
+    fn min_align(&self, size: u64, kind: MiriMemoryKind) -> Align {
+        let this = self.eval_context_ref();
+        // List taken from `library/std/src/sys/common/alloc.rs`.
+        // This list should be kept in sync with the one from libstd.
+        let min_align = match this.tcx.sess.target.arch.as_ref() {
+            "x86" | "arm" | "mips" | "mips32r6" | "powerpc" | "powerpc64" | "asmjs" | "wasm32" => 8,
+            "x86_64" | "aarch64" | "mips64" | "mips64r6" | "s390x" | "sparc64" | "loongarch64" =>
+                16,
+            arch => bug!("unsupported target architecture for malloc: `{}`", arch),
         };
-
-        // Second: functions that return immediately.
-        match this.emulate_foreign_item_by_name(link_name, abi, args, dest)? {
-            EmulateByNameResult::NeedsJumping => {
-                trace!("{:?}", this.dump_place(dest));
-                this.go_to_block(ret);
-            }
-            EmulateByNameResult::AlreadyJumped => (),
-            EmulateByNameResult::MirBody(mir, instance) => return Ok(Some((mir, instance))),
-            EmulateByNameResult::NotSupported => {
-                if let Some(body) = this.lookup_exported_symbol(link_name)? {
-                    return Ok(Some(body));
-                }
-
-                this.handle_unsupported(format!(
-                    "can't call foreign function `{link_name}` on OS `{os}`",
-                    os = this.tcx.sess.target.os,
-                ))?;
-                return Ok(None);
+        // Windows always aligns, even small allocations.
+        // Source: <https://support.microsoft.com/en-us/help/286470/how-to-use-pageheap-exe-in-windows-xp-windows-2000-and-windows-server>
+        // But jemalloc does not, so for the C heap we only align if the allocation is sufficiently big.
+        if kind == MiriMemoryKind::WinHeap || size >= min_align {
+            return Align::from_bytes(min_align).unwrap();
+        }
+        // We have `size < min_align`. Round `size` *down* to the next power of two and use that.
+        fn prev_power_of_two(x: u64) -> u64 {
+            let next_pow2 = x.next_power_of_two();
+            if next_pow2 == x {
+                // x *is* a power of two, just use that.
+                x
+            } else {
+                // x is between two powers, so next = 2*prev.
+                next_pow2 / 2
             }
         }
-
-        Ok(None)
+        Align::from_bytes(prev_power_of_two(size)).unwrap()
     }
 
     /// Emulates calling the internal __rust_* allocator functions
     fn emulate_allocator(
         &mut self,
         default: impl FnOnce(&mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx>,
-    ) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
+    ) -> InterpResult<'tcx, EmulateForeignItemResult> {
         let this = self.eval_context_mut();
 
         let Some(allocator_kind) = this.tcx.allocator_kind(()) else {
             // in real code, this symbol does not exist without an allocator
-            return Ok(EmulateByNameResult::NotSupported);
+            return Ok(EmulateForeignItemResult::NotSupported);
         };
 
         match allocator_kind {
@@ -364,23 +389,22 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                 // and not execute any Miri shim. Somewhat unintuitively doing so is done
                 // by returning `NotSupported`, which triggers the `lookup_exported_symbol`
                 // fallback case in `emulate_foreign_item`.
-                return Ok(EmulateByNameResult::NotSupported);
+                return Ok(EmulateForeignItemResult::NotSupported);
             }
             AllocatorKind::Default => {
                 default(this)?;
-                Ok(EmulateByNameResult::NeedsJumping)
+                Ok(EmulateForeignItemResult::NeedsJumping)
             }
         }
     }
 
-    /// Emulates calling a foreign item using its name.
-    fn emulate_foreign_item_by_name(
+    fn emulate_foreign_item_inner(
         &mut self,
         link_name: Symbol,
         abi: Abi,
         args: &[OpTy<'tcx, Provenance>],
         dest: &PlaceTy<'tcx, Provenance>,
-    ) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
+    ) -> InterpResult<'tcx, EmulateForeignItemResult> {
         let this = self.eval_context_mut();
 
         // First deal with any external C functions in linked .so file.
@@ -391,7 +415,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
             // by the specified `.so` file; we should continue and check if it corresponds to
             // a provided shim.
             if this.call_external_c_fct(link_name, dest, args)? {
-                return Ok(EmulateByNameResult::NeedsJumping);
+                return Ok(EmulateForeignItemResult::NeedsJumping);
             }
         }
 
@@ -591,7 +615,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                     "__rust_alloc" => return this.emulate_allocator(default),
                     "miri_alloc" => {
                         default(this)?;
-                        return Ok(EmulateByNameResult::NeedsJumping);
+                        return Ok(EmulateForeignItemResult::NeedsJumping);
                     }
                     _ => unreachable!(),
                 }
@@ -651,7 +675,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                     }
                     "miri_dealloc" => {
                         default(this)?;
-                        return Ok(EmulateByNameResult::NeedsJumping);
+                        return Ok(EmulateForeignItemResult::NeedsJumping);
                     }
                     _ => unreachable!(),
                 }
@@ -1045,19 +1069,19 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
             _ =>
                 return match this.tcx.sess.target.os.as_ref() {
                     target_os if target_os_is_unix(target_os) =>
-                        shims::unix::foreign_items::EvalContextExt::emulate_foreign_item_by_name(
+                        shims::unix::foreign_items::EvalContextExt::emulate_foreign_item_inner(
                             this, link_name, abi, args, dest,
                         ),
                     "windows" =>
-                        shims::windows::foreign_items::EvalContextExt::emulate_foreign_item_by_name(
+                        shims::windows::foreign_items::EvalContextExt::emulate_foreign_item_inner(
                             this, link_name, abi, args, dest,
                         ),
-                    _ => Ok(EmulateByNameResult::NotSupported),
+                    _ => Ok(EmulateForeignItemResult::NotSupported),
                 },
         };
         // We only fall through to here if we did *not* hit the `_` arm above,
         // i.e., if we actually emulated the function with one of the shims.
-        Ok(EmulateByNameResult::NeedsJumping)
+        Ok(EmulateForeignItemResult::NeedsJumping)
     }
 
     /// Check some basic requirements for this allocation request:
diff --git a/src/tools/miri/src/shims/intrinsics/simd.rs b/src/tools/miri/src/shims/intrinsics/simd.rs
index 200f37efa27..70f90aac2c2 100644
--- a/src/tools/miri/src/shims/intrinsics/simd.rs
+++ b/src/tools/miri/src/shims/intrinsics/simd.rs
@@ -33,27 +33,20 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                 assert_eq!(dest_len, op_len);
 
                 #[derive(Copy, Clone)]
-                enum HostFloatOp {
-                    Ceil,
-                    Floor,
-                    Round,
-                    Trunc,
-                    Sqrt,
-                }
-                #[derive(Copy, Clone)]
                 enum Op {
                     MirOp(mir::UnOp),
                     Abs,
-                    HostOp(HostFloatOp),
+                    Sqrt,
+                    Round(rustc_apfloat::Round),
                 }
                 let which = match intrinsic_name {
                     "neg" => Op::MirOp(mir::UnOp::Neg),
                     "fabs" => Op::Abs,
-                    "ceil" => Op::HostOp(HostFloatOp::Ceil),
-                    "floor" => Op::HostOp(HostFloatOp::Floor),
-                    "round" => Op::HostOp(HostFloatOp::Round),
-                    "trunc" => Op::HostOp(HostFloatOp::Trunc),
-                    "fsqrt" => Op::HostOp(HostFloatOp::Sqrt),
+                    "fsqrt" => Op::Sqrt,
+                    "ceil" => Op::Round(rustc_apfloat::Round::TowardPositive),
+                    "floor" => Op::Round(rustc_apfloat::Round::TowardNegative),
+                    "round" => Op::Round(rustc_apfloat::Round::NearestTiesToAway),
+                    "trunc" => Op::Round(rustc_apfloat::Round::TowardZero),
                     _ => unreachable!(),
                 };
 
@@ -73,7 +66,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                                 FloatTy::F64 => Scalar::from_f64(op.to_f64()?.abs()),
                             }
                         }
-                        Op::HostOp(host_op) => {
+                        Op::Sqrt => {
                             let ty::Float(float_ty) = op.layout.ty.kind() else {
                                 span_bug!(this.cur_span(), "{} operand is not a float", intrinsic_name)
                             };
@@ -81,28 +74,32 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                             match float_ty {
                                 FloatTy::F32 => {
                                     let f = f32::from_bits(op.to_scalar().to_u32()?);
-                                    let res = match host_op {
-                                        HostFloatOp::Ceil => f.ceil(),
-                                        HostFloatOp::Floor => f.floor(),
-                                        HostFloatOp::Round => f.round(),
-                                        HostFloatOp::Trunc => f.trunc(),
-                                        HostFloatOp::Sqrt => f.sqrt(),
-                                    };
+                                    let res = f.sqrt();
                                     Scalar::from_u32(res.to_bits())
                                 }
                                 FloatTy::F64 => {
                                     let f = f64::from_bits(op.to_scalar().to_u64()?);
-                                    let res = match host_op {
-                                        HostFloatOp::Ceil => f.ceil(),
-                                        HostFloatOp::Floor => f.floor(),
-                                        HostFloatOp::Round => f.round(),
-                                        HostFloatOp::Trunc => f.trunc(),
-                                        HostFloatOp::Sqrt => f.sqrt(),
-                                    };
+                                    let res = f.sqrt();
                                     Scalar::from_u64(res.to_bits())
                                 }
                             }
-
+                        }
+                        Op::Round(rounding) => {
+                            let ty::Float(float_ty) = op.layout.ty.kind() else {
+                                span_bug!(this.cur_span(), "{} operand is not a float", intrinsic_name)
+                            };
+                            match float_ty {
+                                FloatTy::F32 => {
+                                    let f = op.to_scalar().to_f32()?;
+                                    let res = f.round_to_integral(rounding).value;
+                                    Scalar::from_f32(res)
+                                }
+                                FloatTy::F64 => {
+                                    let f = op.to_scalar().to_f64()?;
+                                    let res = f.round_to_integral(rounding).value;
+                                    Scalar::from_f64(res)
+                                }
+                            }
                         }
                     };
                     this.write_scalar(val, &dest)?;
diff --git a/src/tools/miri/src/shims/mod.rs b/src/tools/miri/src/shims/mod.rs
index 5a9574766f3..a031a2a25c9 100644
--- a/src/tools/miri/src/shims/mod.rs
+++ b/src/tools/miri/src/shims/mod.rs
@@ -9,7 +9,6 @@ pub mod unix;
 pub mod windows;
 mod x86;
 
-pub mod dlsym;
 pub mod env;
 pub mod os_str;
 pub mod panic;
@@ -58,7 +57,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
             // foreign function
             // Any needed call to `goto_block` will be performed by `emulate_foreign_item`.
             let args = this.copy_fn_args(args)?; // FIXME: Should `InPlace` arguments be reset to uninit?
-            return this.emulate_foreign_item(instance.def_id(), abi, &args, dest, ret, unwind);
+            let link_name = this.item_link_name(instance.def_id());
+            return this.emulate_foreign_item(link_name, abi, &args, dest, ret, unwind);
         }
 
         // Otherwise, load the MIR.
diff --git a/src/tools/miri/src/shims/unix/android/dlsym.rs b/src/tools/miri/src/shims/unix/android/dlsym.rs
deleted file mode 100644
index 451bc0bd5e1..00000000000
--- a/src/tools/miri/src/shims/unix/android/dlsym.rs
+++ /dev/null
@@ -1,54 +0,0 @@
-use rustc_middle::mir;
-
-use crate::helpers::check_arg_count;
-use crate::*;
-
-#[derive(Debug, Copy, Clone)]
-#[allow(non_camel_case_types)]
-pub enum Dlsym {
-    signal,
-}
-
-impl Dlsym {
-    // Returns an error for unsupported symbols, and None if this symbol
-    // should become a NULL pointer (pretend it does not exist).
-    pub fn from_str<'tcx>(name: &str) -> InterpResult<'tcx, Option<Dlsym>> {
-        Ok(match name {
-            "signal" => Some(Dlsym::signal),
-            "android_set_abort_message" => None,
-            _ => throw_unsup_format!("unsupported Android dlsym: {}", name),
-        })
-    }
-}
-
-impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
-pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
-    fn call_dlsym(
-        &mut self,
-        dlsym: Dlsym,
-        args: &[OpTy<'tcx, Provenance>],
-        dest: &PlaceTy<'tcx, Provenance>,
-        ret: Option<mir::BasicBlock>,
-    ) -> InterpResult<'tcx> {
-        let this = self.eval_context_mut();
-        let ret = ret.expect("we don't support any diverging dlsym");
-        assert!(this.tcx.sess.target.os == "android");
-
-        match dlsym {
-            Dlsym::signal => {
-                if !this.frame_in_std() {
-                    throw_unsup_format!(
-                        "`signal` support is crude and just enough for libstd to work"
-                    );
-                }
-
-                let [_sig, _func] = check_arg_count(args)?;
-                this.write_null(dest)?;
-            }
-        }
-
-        log::trace!("{:?}", this.dump_place(dest));
-        this.go_to_block(ret);
-        Ok(())
-    }
-}
diff --git a/src/tools/miri/src/shims/unix/android/foreign_items.rs b/src/tools/miri/src/shims/unix/android/foreign_items.rs
index 756aed369f1..f61ebd5a3a8 100644
--- a/src/tools/miri/src/shims/unix/android/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/android/foreign_items.rs
@@ -2,25 +2,29 @@ use rustc_span::Symbol;
 use rustc_target::spec::abi::Abi;
 
 use crate::*;
-use shims::foreign_items::EmulateByNameResult;
+use shims::foreign_items::EmulateForeignItemResult;
 
 impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
 
+pub fn is_dyn_sym(_name: &str) -> bool {
+    false
+}
+
 pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
-    fn emulate_foreign_item_by_name(
+    #[allow(unused, clippy::match_single_binding)] // there isn't anything here yet
+    fn emulate_foreign_item_inner(
         &mut self,
         link_name: Symbol,
-        _abi: Abi,
-        _args: &[OpTy<'tcx, Provenance>],
-        _dest: &PlaceTy<'tcx, Provenance>,
-    ) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
-        let _this = self.eval_context_mut();
-        #[allow(clippy::match_single_binding)]
+        abi: Abi,
+        args: &[OpTy<'tcx, Provenance>],
+        dest: &PlaceTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, EmulateForeignItemResult> {
+        let this = self.eval_context_mut();
+
         match link_name.as_str() {
-            _ => return Ok(EmulateByNameResult::NotSupported),
+            _ => return Ok(EmulateForeignItemResult::NotSupported),
         }
 
-        #[allow(unreachable_code)]
-        Ok(EmulateByNameResult::NeedsJumping)
+        Ok(EmulateForeignItemResult::NeedsJumping)
     }
 }
diff --git a/src/tools/miri/src/shims/unix/android/mod.rs b/src/tools/miri/src/shims/unix/android/mod.rs
index 434f5f30b5a..09c6507b24f 100644
--- a/src/tools/miri/src/shims/unix/android/mod.rs
+++ b/src/tools/miri/src/shims/unix/android/mod.rs
@@ -1,2 +1 @@
-pub mod dlsym;
 pub mod foreign_items;
diff --git a/src/tools/miri/src/shims/unix/dlsym.rs b/src/tools/miri/src/shims/unix/dlsym.rs
deleted file mode 100644
index 8bc19d18f2b..00000000000
--- a/src/tools/miri/src/shims/unix/dlsym.rs
+++ /dev/null
@@ -1,55 +0,0 @@
-use rustc_middle::mir;
-use rustc_target::spec::abi::Abi;
-
-use crate::*;
-use shims::unix::android::dlsym as android;
-use shims::unix::freebsd::dlsym as freebsd;
-use shims::unix::linux::dlsym as linux;
-use shims::unix::macos::dlsym as macos;
-
-#[derive(Debug, Copy, Clone)]
-pub enum Dlsym {
-    Android(android::Dlsym),
-    FreeBsd(freebsd::Dlsym),
-    Linux(linux::Dlsym),
-    MacOs(macos::Dlsym),
-}
-
-impl Dlsym {
-    // Returns an error for unsupported symbols, and None if this symbol
-    // should become a NULL pointer (pretend it does not exist).
-    pub fn from_str<'tcx>(name: &str, target_os: &str) -> InterpResult<'tcx, Option<Dlsym>> {
-        Ok(match target_os {
-            "android" => android::Dlsym::from_str(name)?.map(Dlsym::Android),
-            "freebsd" => freebsd::Dlsym::from_str(name)?.map(Dlsym::FreeBsd),
-            "linux" => linux::Dlsym::from_str(name)?.map(Dlsym::Linux),
-            "macos" => macos::Dlsym::from_str(name)?.map(Dlsym::MacOs),
-            _ => panic!("unsupported Unix OS {target_os}"),
-        })
-    }
-}
-
-impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
-pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
-    fn call_dlsym(
-        &mut self,
-        dlsym: Dlsym,
-        abi: Abi,
-        args: &[OpTy<'tcx, Provenance>],
-        dest: &PlaceTy<'tcx, Provenance>,
-        ret: Option<mir::BasicBlock>,
-    ) -> InterpResult<'tcx> {
-        let this = self.eval_context_mut();
-
-        this.check_abi(abi, Abi::C { unwind: false })?;
-
-        match dlsym {
-            Dlsym::Android(dlsym) =>
-                android::EvalContextExt::call_dlsym(this, dlsym, args, dest, ret),
-            Dlsym::FreeBsd(dlsym) =>
-                freebsd::EvalContextExt::call_dlsym(this, dlsym, args, dest, ret),
-            Dlsym::Linux(dlsym) => linux::EvalContextExt::call_dlsym(this, dlsym, args, dest, ret),
-            Dlsym::MacOs(dlsym) => macos::EvalContextExt::call_dlsym(this, dlsym, args, dest, ret),
-        }
-    }
-}
diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs
index 4bcca5076ca..c013d275029 100644
--- a/src/tools/miri/src/shims/unix/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/foreign_items.rs
@@ -1,4 +1,5 @@
 use std::ffi::OsStr;
+use std::str;
 
 use log::trace;
 
@@ -8,24 +9,48 @@ use rustc_target::abi::{Align, Size};
 use rustc_target::spec::abi::Abi;
 
 use crate::*;
-use shims::foreign_items::EmulateByNameResult;
+use shims::foreign_items::EmulateForeignItemResult;
 use shims::unix::fs::EvalContextExt as _;
 use shims::unix::mem::EvalContextExt as _;
 use shims::unix::sync::EvalContextExt as _;
 use shims::unix::thread::EvalContextExt as _;
 
+use shims::unix::android::foreign_items as android;
+use shims::unix::freebsd::foreign_items as freebsd;
+use shims::unix::linux::foreign_items as linux;
+use shims::unix::macos::foreign_items as macos;
+
+fn is_dyn_sym(name: &str, target_os: &str) -> bool {
+    match name {
+        // Used for tests.
+        "isatty" => true,
+        // `signal` is set up as a weak symbol in `init_extern_statics` (on Android) so we might as
+        // well allow it in `dlsym`.
+        "signal" => true,
+        // Give specific OSes a chance to allow their symbols.
+        _ =>
+            match target_os {
+                "android" => android::is_dyn_sym(name),
+                "freebsd" => freebsd::is_dyn_sym(name),
+                "linux" => linux::is_dyn_sym(name),
+                "macos" => macos::is_dyn_sym(name),
+                target_os => panic!("unsupported Unix OS {target_os}"),
+            },
+    }
+}
+
 impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
 pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
-    fn emulate_foreign_item_by_name(
+    fn emulate_foreign_item_inner(
         &mut self,
         link_name: Symbol,
         abi: Abi,
         args: &[OpTy<'tcx, Provenance>],
         dest: &PlaceTy<'tcx, Provenance>,
-    ) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
+    ) -> InterpResult<'tcx, EmulateForeignItemResult> {
         let this = self.eval_context_mut();
 
-        // See `fn emulate_foreign_item_by_name` in `shims/foreign_items.rs` for the general pattern.
+        // See `fn emulate_foreign_item_inner` in `shims/foreign_items.rs` for the general pattern.
         #[rustfmt::skip]
         match link_name.as_str() {
             // Environment related shims
@@ -230,9 +255,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                 let [handle, symbol] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
                 this.read_target_usize(handle)?;
                 let symbol = this.read_pointer(symbol)?;
-                let symbol_name = this.read_c_str(symbol)?;
-                if let Some(dlsym) = Dlsym::from_str(symbol_name, &this.tcx.sess.target.os)? {
-                    let ptr = this.fn_ptr(FnVal::Other(dlsym));
+                let name = this.read_c_str(symbol)?;
+                if let Ok(name) = str::from_utf8(name) && is_dyn_sym(name, &this.tcx.sess.target.os) {
+                    let ptr = this.fn_ptr(FnVal::Other(DynSym::from_str(name)));
                     this.write_pointer(ptr, dest)?;
                 } else {
                     this.write_null(dest)?;
@@ -565,7 +590,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
             "getuid"
             if this.frame_in_std() => {
                 let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
-                // FOr now, just pretend we always have this fixed UID.
+                // For now, just pretend we always have this fixed UID.
                 this.write_int(super::UID, dest)?;
             }
 
@@ -609,15 +634,15 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
             _ => {
                 let target_os = &*this.tcx.sess.target.os;
                 return match target_os {
-                    "android" => shims::unix::android::foreign_items::EvalContextExt::emulate_foreign_item_by_name(this, link_name, abi, args, dest),
-                    "freebsd" => shims::unix::freebsd::foreign_items::EvalContextExt::emulate_foreign_item_by_name(this, link_name, abi, args, dest),
-                    "linux" => shims::unix::linux::foreign_items::EvalContextExt::emulate_foreign_item_by_name(this, link_name, abi, args, dest),
-                    "macos" => shims::unix::macos::foreign_items::EvalContextExt::emulate_foreign_item_by_name(this, link_name, abi, args, dest),
-                    _ => Ok(EmulateByNameResult::NotSupported),
+                    "android" => android::EvalContextExt::emulate_foreign_item_inner(this, link_name, abi, args, dest),
+                    "freebsd" => freebsd::EvalContextExt::emulate_foreign_item_inner(this, link_name, abi, args, dest),
+                    "linux" => linux::EvalContextExt::emulate_foreign_item_inner(this, link_name, abi, args, dest),
+                    "macos" => macos::EvalContextExt::emulate_foreign_item_inner(this, link_name, abi, args, dest),
+                    _ => Ok(EmulateForeignItemResult::NotSupported),
                 };
             }
         };
 
-        Ok(EmulateByNameResult::NeedsJumping)
+        Ok(EmulateForeignItemResult::NeedsJumping)
     }
 }
diff --git a/src/tools/miri/src/shims/unix/freebsd/dlsym.rs b/src/tools/miri/src/shims/unix/freebsd/dlsym.rs
deleted file mode 100644
index d759ffb8994..00000000000
--- a/src/tools/miri/src/shims/unix/freebsd/dlsym.rs
+++ /dev/null
@@ -1,36 +0,0 @@
-use rustc_middle::mir;
-
-use crate::*;
-
-#[derive(Debug, Copy, Clone)]
-#[allow(non_camel_case_types)]
-pub enum Dlsym {}
-
-impl Dlsym {
-    // Returns an error for unsupported symbols, and None if this symbol
-    // should become a NULL pointer (pretend it does not exist).
-    pub fn from_str<'tcx>(name: &str) -> InterpResult<'tcx, Option<Dlsym>> {
-        throw_unsup_format!("unsupported FreeBSD dlsym: {}", name)
-    }
-}
-
-impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
-pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
-    fn call_dlsym(
-        &mut self,
-        dlsym: Dlsym,
-        _args: &[OpTy<'tcx, Provenance>],
-        _dest: &PlaceTy<'tcx, Provenance>,
-        ret: Option<mir::BasicBlock>,
-    ) -> InterpResult<'tcx> {
-        let this = self.eval_context_mut();
-        let _ret = ret.expect("we don't support any diverging dlsym");
-        assert!(this.tcx.sess.target.os == "freebsd");
-
-        match dlsym {}
-
-        //trace!("{:?}", this.dump_place(**dest));
-        //this.go_to_block(ret);
-        //Ok(())
-    }
-}
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 d755e5f10ba..869434e8876 100644
--- a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs
@@ -2,19 +2,22 @@ use rustc_span::Symbol;
 use rustc_target::spec::abi::Abi;
 
 use crate::*;
-use shims::foreign_items::EmulateByNameResult;
+use shims::foreign_items::EmulateForeignItemResult;
 use shims::unix::thread::EvalContextExt as _;
 
-impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+pub fn is_dyn_sym(_name: &str) -> bool {
+    false
+}
 
+impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
 pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
-    fn emulate_foreign_item_by_name(
+    fn emulate_foreign_item_inner(
         &mut self,
         link_name: Symbol,
         abi: Abi,
         args: &[OpTy<'tcx, Provenance>],
         dest: &PlaceTy<'tcx, Provenance>,
-    ) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
+    ) -> InterpResult<'tcx, EmulateForeignItemResult> {
         let this = self.eval_context_mut();
         match link_name.as_str() {
             // Threading
@@ -42,8 +45,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                 this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?;
             }
 
-            _ => return Ok(EmulateByNameResult::NotSupported),
+            _ => return Ok(EmulateForeignItemResult::NotSupported),
         }
-        Ok(EmulateByNameResult::NeedsJumping)
+        Ok(EmulateForeignItemResult::NeedsJumping)
     }
 }
diff --git a/src/tools/miri/src/shims/unix/freebsd/mod.rs b/src/tools/miri/src/shims/unix/freebsd/mod.rs
index 434f5f30b5a..09c6507b24f 100644
--- a/src/tools/miri/src/shims/unix/freebsd/mod.rs
+++ b/src/tools/miri/src/shims/unix/freebsd/mod.rs
@@ -1,2 +1 @@
-pub mod dlsym;
 pub mod foreign_items;
diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs
index 8963dfb97a6..1014a61b75e 100644
--- a/src/tools/miri/src/shims/unix/fs.rs
+++ b/src/tools/miri/src/shims/unix/fs.rs
@@ -63,7 +63,7 @@ pub trait FileDescriptor: std::fmt::Debug + Any {
 
     fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>>;
 
-    fn is_tty(&self) -> bool {
+    fn is_tty(&self, _communicate_allowed: bool) -> bool {
         false
     }
 
@@ -156,8 +156,8 @@ impl FileDescriptor for FileHandle {
         Some(self.file.as_raw_fd())
     }
 
-    fn is_tty(&self) -> bool {
-        self.file.is_terminal()
+    fn is_tty(&self, communicate_allowed: bool) -> bool {
+        communicate_allowed && self.file.is_terminal()
     }
 }
 
@@ -188,8 +188,8 @@ impl FileDescriptor for io::Stdin {
         Some(libc::STDIN_FILENO)
     }
 
-    fn is_tty(&self) -> bool {
-        self.is_terminal()
+    fn is_tty(&self, communicate_allowed: bool) -> bool {
+        communicate_allowed && self.is_terminal()
     }
 }
 
@@ -225,8 +225,8 @@ impl FileDescriptor for io::Stdout {
         Some(libc::STDOUT_FILENO)
     }
 
-    fn is_tty(&self) -> bool {
-        self.is_terminal()
+    fn is_tty(&self, communicate_allowed: bool) -> bool {
+        communicate_allowed && self.is_terminal()
     }
 }
 
@@ -255,8 +255,8 @@ impl FileDescriptor for io::Stderr {
         Some(libc::STDERR_FILENO)
     }
 
-    fn is_tty(&self) -> bool {
-        self.is_terminal()
+    fn is_tty(&self, communicate_allowed: bool) -> bool {
+        communicate_allowed && self.is_terminal()
     }
 }
 
@@ -1721,15 +1721,18 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
         let this = self.eval_context_mut();
         // "returns 1 if fd is an open file descriptor referring to a terminal;
         // otherwise 0 is returned, and errno is set to indicate the error"
-        if matches!(this.machine.isolated_op, IsolatedOp::Allow) {
-            let fd = this.read_scalar(miri_fd)?.to_i32()?;
-            if this.machine.file_handler.handles.get(&fd).map(|fd| fd.is_tty()) == Some(true) {
+        let fd = this.read_scalar(miri_fd)?.to_i32()?;
+        let error = if let Some(fd) = this.machine.file_handler.handles.get(&fd) {
+            if fd.is_tty(this.machine.communicate()) {
                 return Ok(Scalar::from_i32(1));
+            } else {
+                this.eval_libc("ENOTTY")
             }
-        }
-        // Fallback when the FD was not found or isolation is enabled.
-        let enotty = this.eval_libc("ENOTTY");
-        this.set_last_error(enotty)?;
+        } else {
+            // FD does not exist
+            this.eval_libc("EBADF")
+        };
+        this.set_last_error(error)?;
         Ok(Scalar::from_i32(0))
     }
 
diff --git a/src/tools/miri/src/shims/unix/linux/dlsym.rs b/src/tools/miri/src/shims/unix/linux/dlsym.rs
deleted file mode 100644
index a96c14c142b..00000000000
--- a/src/tools/miri/src/shims/unix/linux/dlsym.rs
+++ /dev/null
@@ -1,40 +0,0 @@
-use rustc_middle::mir;
-
-use crate::*;
-
-#[derive(Debug, Copy, Clone)]
-pub enum Dlsym {}
-
-impl Dlsym {
-    // Returns an error for unsupported symbols, and None if this symbol
-    // should become a NULL pointer (pretend it does not exist).
-    pub fn from_str<'tcx>(name: &str) -> InterpResult<'tcx, Option<Dlsym>> {
-        Ok(match name {
-            "__pthread_get_minstack" => None,
-            "getrandom" => None, // std falls back to syscall(SYS_getrandom, ...) when this is NULL.
-            "statx" => None,     // std falls back to syscall(SYS_statx, ...) when this is NULL.
-            _ => throw_unsup_format!("unsupported Linux dlsym: {}", name),
-        })
-    }
-}
-
-impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
-pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
-    fn call_dlsym(
-        &mut self,
-        dlsym: Dlsym,
-        _args: &[OpTy<'tcx, Provenance>],
-        _dest: &PlaceTy<'tcx, Provenance>,
-        ret: Option<mir::BasicBlock>,
-    ) -> InterpResult<'tcx> {
-        let this = self.eval_context_mut();
-        let _ret = ret.expect("we don't support any diverging dlsym");
-        assert!(this.tcx.sess.target.os == "linux");
-
-        match dlsym {}
-
-        //trace!("{:?}", this.dump_place(**dest));
-        //this.go_to_block(ret);
-        //Ok(())
-    }
-}
diff --git a/src/tools/miri/src/shims/unix/linux/foreign_items.rs b/src/tools/miri/src/shims/unix/linux/foreign_items.rs
index 1bd751c5981..6937e0f089e 100644
--- a/src/tools/miri/src/shims/unix/linux/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/linux/foreign_items.rs
@@ -4,7 +4,7 @@ use rustc_target::spec::abi::Abi;
 use crate::machine::SIGRTMAX;
 use crate::machine::SIGRTMIN;
 use crate::*;
-use shims::foreign_items::EmulateByNameResult;
+use shims::foreign_items::EmulateForeignItemResult;
 use shims::unix::fs::EvalContextExt as _;
 use shims::unix::linux::fd::EvalContextExt as _;
 use shims::unix::linux::mem::EvalContextExt as _;
@@ -12,18 +12,22 @@ use shims::unix::linux::sync::futex;
 use shims::unix::sync::EvalContextExt as _;
 use shims::unix::thread::EvalContextExt as _;
 
+pub fn is_dyn_sym(name: &str) -> bool {
+    matches!(name, "getrandom")
+}
+
 impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
 pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
-    fn emulate_foreign_item_by_name(
+    fn emulate_foreign_item_inner(
         &mut self,
         link_name: Symbol,
         abi: Abi,
         args: &[OpTy<'tcx, Provenance>],
         dest: &PlaceTy<'tcx, Provenance>,
-    ) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
+    ) -> InterpResult<'tcx, EmulateForeignItemResult> {
         let this = self.eval_context_mut();
 
-        // See `fn emulate_foreign_item_by_name` in `shims/foreign_items.rs` for the general pattern.
+        // See `fn emulate_foreign_item_inner` in `shims/foreign_items.rs` for the general pattern.
 
         match link_name.as_str() {
             // errno
@@ -182,7 +186,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                     }
                     id => {
                         this.handle_unsupported(format!("can't execute syscall with ID {id}"))?;
-                        return Ok(EmulateByNameResult::AlreadyJumped);
+                        return Ok(EmulateForeignItemResult::AlreadyJumped);
                     }
                 }
             }
@@ -213,10 +217,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                 this.write_null(dest)?;
             }
 
-            _ => return Ok(EmulateByNameResult::NotSupported),
+            _ => return Ok(EmulateForeignItemResult::NotSupported),
         };
 
-        Ok(EmulateByNameResult::NeedsJumping)
+        Ok(EmulateForeignItemResult::NeedsJumping)
     }
 }
 
diff --git a/src/tools/miri/src/shims/unix/linux/mod.rs b/src/tools/miri/src/shims/unix/linux/mod.rs
index 856ec226de8..fe18f1a32fd 100644
--- a/src/tools/miri/src/shims/unix/linux/mod.rs
+++ b/src/tools/miri/src/shims/unix/linux/mod.rs
@@ -1,4 +1,3 @@
-pub mod dlsym;
 pub mod fd;
 pub mod foreign_items;
 pub mod mem;
diff --git a/src/tools/miri/src/shims/unix/macos/dlsym.rs b/src/tools/miri/src/shims/unix/macos/dlsym.rs
deleted file mode 100644
index 63ad680de60..00000000000
--- a/src/tools/miri/src/shims/unix/macos/dlsym.rs
+++ /dev/null
@@ -1,52 +0,0 @@
-use rustc_middle::mir;
-
-use log::trace;
-
-use crate::*;
-use helpers::check_arg_count;
-
-#[derive(Debug, Copy, Clone)]
-#[allow(non_camel_case_types)]
-pub enum Dlsym {
-    getentropy,
-}
-
-impl Dlsym {
-    // Returns an error for unsupported symbols, and None if this symbol
-    // should become a NULL pointer (pretend it does not exist).
-    pub fn from_str<'tcx>(name: &str) -> InterpResult<'tcx, Option<Dlsym>> {
-        Ok(match name {
-            "getentropy" => Some(Dlsym::getentropy),
-            _ => throw_unsup_format!("unsupported macOS dlsym: {}", name),
-        })
-    }
-}
-
-impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
-pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
-    fn call_dlsym(
-        &mut self,
-        dlsym: Dlsym,
-        args: &[OpTy<'tcx, Provenance>],
-        dest: &PlaceTy<'tcx, Provenance>,
-        ret: Option<mir::BasicBlock>,
-    ) -> InterpResult<'tcx> {
-        let this = self.eval_context_mut();
-        let ret = ret.expect("we don't support any diverging dlsym");
-        assert!(this.tcx.sess.target.os == "macos");
-
-        match dlsym {
-            Dlsym::getentropy => {
-                let [ptr, len] = check_arg_count(args)?;
-                let ptr = this.read_pointer(ptr)?;
-                let len = this.read_target_usize(len)?;
-                this.gen_random(ptr, len)?;
-                this.write_null(dest)?;
-            }
-        }
-
-        trace!("{:?}", this.dump_place(dest));
-        this.go_to_block(ret);
-        Ok(())
-    }
-}
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 0ee3cea05d5..5881a3f46f2 100644
--- a/src/tools/miri/src/shims/unix/macos/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs
@@ -2,22 +2,26 @@ use rustc_span::Symbol;
 use rustc_target::spec::abi::Abi;
 
 use crate::*;
-use shims::foreign_items::EmulateByNameResult;
+use shims::foreign_items::EmulateForeignItemResult;
 use shims::unix::fs::EvalContextExt as _;
 use shims::unix::thread::EvalContextExt as _;
 
+pub fn is_dyn_sym(name: &str) -> bool {
+    matches!(name, "getentropy")
+}
+
 impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
 pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
-    fn emulate_foreign_item_by_name(
+    fn emulate_foreign_item_inner(
         &mut self,
         link_name: Symbol,
         abi: Abi,
         args: &[OpTy<'tcx, Provenance>],
         dest: &PlaceTy<'tcx, Provenance>,
-    ) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
+    ) -> InterpResult<'tcx, EmulateForeignItemResult> {
         let this = self.eval_context_mut();
 
-        // See `fn emulate_foreign_item_by_name` in `shims/foreign_items.rs` for the general pattern.
+        // See `fn emulate_foreign_item_inner` in `shims/foreign_items.rs` for the general pattern.
 
         match link_name.as_str() {
             // errno
@@ -109,6 +113,18 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                 this.write_scalar(result, dest)?;
             }
 
+            // Random generation related shims
+            "getentropy" => {
+                let [buf, bufsize] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let buf = this.read_pointer(buf)?;
+                let bufsize = this.read_target_usize(bufsize)?;
+
+                this.gen_random(buf, bufsize)?;
+
+                this.write_scalar(Scalar::from_i32(0), dest)?; // KERN_SUCCESS
+            }
+
             // Access to command-line arguments
             "_NSGetArgc" => {
                 let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
@@ -193,9 +209,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                 this.write_scalar(res, dest)?;
             }
 
-            _ => return Ok(EmulateByNameResult::NotSupported),
+            _ => return Ok(EmulateForeignItemResult::NotSupported),
         };
 
-        Ok(EmulateByNameResult::NeedsJumping)
+        Ok(EmulateForeignItemResult::NeedsJumping)
     }
 }
diff --git a/src/tools/miri/src/shims/unix/macos/mod.rs b/src/tools/miri/src/shims/unix/macos/mod.rs
index 434f5f30b5a..09c6507b24f 100644
--- a/src/tools/miri/src/shims/unix/macos/mod.rs
+++ b/src/tools/miri/src/shims/unix/macos/mod.rs
@@ -1,2 +1 @@
-pub mod dlsym;
 pub mod foreign_items;
diff --git a/src/tools/miri/src/shims/unix/mod.rs b/src/tools/miri/src/shims/unix/mod.rs
index a8ebd369aba..2f801493352 100644
--- a/src/tools/miri/src/shims/unix/mod.rs
+++ b/src/tools/miri/src/shims/unix/mod.rs
@@ -1,4 +1,3 @@
-pub mod dlsym;
 pub mod foreign_items;
 
 mod fs;
diff --git a/src/tools/miri/src/shims/windows/dlsym.rs b/src/tools/miri/src/shims/windows/dlsym.rs
deleted file mode 100644
index e5afee35905..00000000000
--- a/src/tools/miri/src/shims/windows/dlsym.rs
+++ /dev/null
@@ -1,82 +0,0 @@
-use rustc_middle::mir;
-use rustc_target::spec::abi::Abi;
-
-use log::trace;
-
-use crate::helpers::check_arg_count;
-use crate::shims::windows::handle::{EvalContextExt as _, Handle, PseudoHandle};
-use crate::shims::windows::sync::EvalContextExt as _;
-use crate::*;
-
-#[derive(Debug, Copy, Clone)]
-pub enum Dlsym {
-    SetThreadDescription,
-    WaitOnAddress,
-    WakeByAddressSingle,
-}
-
-impl Dlsym {
-    // Returns an error for unsupported symbols, and None if this symbol
-    // should become a NULL pointer (pretend it does not exist).
-    pub fn from_str<'tcx>(name: &str) -> InterpResult<'tcx, Option<Dlsym>> {
-        Ok(match name {
-            "GetSystemTimePreciseAsFileTime" => None,
-            "SetThreadDescription" => Some(Dlsym::SetThreadDescription),
-            "WaitOnAddress" => Some(Dlsym::WaitOnAddress),
-            "WakeByAddressSingle" => Some(Dlsym::WakeByAddressSingle),
-            _ => throw_unsup_format!("unsupported Windows dlsym: {}", name),
-        })
-    }
-}
-
-impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
-pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
-    fn call_dlsym(
-        &mut self,
-        dlsym: Dlsym,
-        abi: Abi,
-        args: &[OpTy<'tcx, Provenance>],
-        dest: &PlaceTy<'tcx, Provenance>,
-        ret: Option<mir::BasicBlock>,
-    ) -> InterpResult<'tcx> {
-        let this = self.eval_context_mut();
-        let ret = ret.expect("we don't support any diverging dlsym");
-        assert!(this.tcx.sess.target.os == "windows");
-
-        this.check_abi(abi, Abi::System { unwind: false })?;
-
-        match dlsym {
-            Dlsym::SetThreadDescription => {
-                let [handle, name] = check_arg_count(args)?;
-
-                let handle = this.read_scalar(handle)?;
-
-                let name = this.read_wide_str(this.read_pointer(name)?)?;
-
-                let thread = match Handle::from_scalar(handle, this)? {
-                    Some(Handle::Thread(thread)) => thread,
-                    Some(Handle::Pseudo(PseudoHandle::CurrentThread)) => this.get_active_thread(),
-                    _ => this.invalid_handle("SetThreadDescription")?,
-                };
-
-                this.set_thread_name_wide(thread, &name);
-
-                this.write_null(dest)?;
-            }
-            Dlsym::WaitOnAddress => {
-                let [ptr_op, compare_op, size_op, timeout_op] = check_arg_count(args)?;
-
-                this.WaitOnAddress(ptr_op, compare_op, size_op, timeout_op, dest)?;
-            }
-            Dlsym::WakeByAddressSingle => {
-                let [ptr_op] = check_arg_count(args)?;
-
-                this.WakeByAddressSingle(ptr_op)?;
-            }
-        }
-
-        trace!("{:?}", this.dump_place(dest));
-        this.go_to_block(ret);
-        Ok(())
-    }
-}
diff --git a/src/tools/miri/src/shims/windows/foreign_items.rs b/src/tools/miri/src/shims/windows/foreign_items.rs
index d76d01b0789..759a412c16a 100644
--- a/src/tools/miri/src/shims/windows/foreign_items.rs
+++ b/src/tools/miri/src/shims/windows/foreign_items.rs
@@ -1,27 +1,32 @@
 use std::iter;
+use std::str;
 
 use rustc_span::Symbol;
 use rustc_target::abi::Size;
 use rustc_target::spec::abi::Abi;
 
 use crate::*;
-use shims::foreign_items::EmulateByNameResult;
+use shims::foreign_items::EmulateForeignItemResult;
 use shims::windows::handle::{EvalContextExt as _, Handle, PseudoHandle};
 use shims::windows::sync::EvalContextExt as _;
 use shims::windows::thread::EvalContextExt as _;
 
+fn is_dyn_sym(name: &str) -> bool {
+    matches!(name, "SetThreadDescription" | "WaitOnAddress" | "WakeByAddressSingle")
+}
+
 impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
 pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
-    fn emulate_foreign_item_by_name(
+    fn emulate_foreign_item_inner(
         &mut self,
         link_name: Symbol,
         abi: Abi,
         args: &[OpTy<'tcx, Provenance>],
         dest: &PlaceTy<'tcx, Provenance>,
-    ) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
+    ) -> InterpResult<'tcx, EmulateForeignItemResult> {
         let this = self.eval_context_mut();
 
-        // See `fn emulate_foreign_item_by_name` in `shims/foreign_items.rs` for the general pattern.
+        // See `fn emulate_foreign_item_inner` in `shims/foreign_items.rs` for the general pattern.
 
         // Windows API stubs.
         // HANDLE = isize
@@ -326,6 +331,18 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
 
                 this.WakeAllConditionVariable(condvar)?;
             }
+            "WaitOnAddress" => {
+                let [ptr_op, compare_op, size_op, timeout_op] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+
+                this.WaitOnAddress(ptr_op, compare_op, size_op, timeout_op, dest)?;
+            }
+            "WakeByAddressSingle" => {
+                let [ptr_op] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+
+                this.WakeByAddressSingle(ptr_op)?;
+            }
 
             // Dynamic symbol loading
             "GetProcAddress" => {
@@ -334,14 +351,58 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                     this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
                 this.read_target_isize(hModule)?;
                 let name = this.read_c_str(this.read_pointer(lpProcName)?)?;
-                if let Some(dlsym) = Dlsym::from_str(name, &this.tcx.sess.target.os)? {
-                    let ptr = this.fn_ptr(FnVal::Other(dlsym));
+                if let Ok(name) = str::from_utf8(name) && is_dyn_sym(name) {
+                    let ptr = this.fn_ptr(FnVal::Other(DynSym::from_str(name)));
                     this.write_pointer(ptr, dest)?;
                 } else {
                     this.write_null(dest)?;
                 }
             }
 
+            // Threading
+            "CreateThread" => {
+                let [security, stacksize, start, arg, flags, thread] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+
+                let thread_id =
+                    this.CreateThread(security, stacksize, start, arg, flags, thread)?;
+
+                this.write_scalar(Handle::Thread(thread_id).to_scalar(this), dest)?;
+            }
+            "WaitForSingleObject" => {
+                let [handle, timeout] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+
+                let ret = this.WaitForSingleObject(handle, timeout)?;
+                this.write_scalar(Scalar::from_u32(ret), dest)?;
+            }
+            "GetCurrentThread" => {
+                let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+
+                this.write_scalar(
+                    Handle::Pseudo(PseudoHandle::CurrentThread).to_scalar(this),
+                    dest,
+                )?;
+            }
+            "SetThreadDescription" => {
+                let [handle, name] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+
+                let handle = this.read_scalar(handle)?;
+
+                let name = this.read_wide_str(this.read_pointer(name)?)?;
+
+                let thread = match Handle::from_scalar(handle, this)? {
+                    Some(Handle::Thread(thread)) => thread,
+                    Some(Handle::Pseudo(PseudoHandle::CurrentThread)) => this.get_active_thread(),
+                    _ => this.invalid_handle("SetThreadDescription")?,
+                };
+
+                this.set_thread_name_wide(thread, &name);
+
+                this.write_null(dest)?;
+            }
+
             // Miscellaneous
             "SystemFunction036" => {
                 // This is really 'RtlGenRandom'.
@@ -456,32 +517,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                 }
             }
 
-            // Threading
-            "CreateThread" => {
-                let [security, stacksize, start, arg, flags, thread] =
-                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
-
-                let thread_id =
-                    this.CreateThread(security, stacksize, start, arg, flags, thread)?;
-
-                this.write_scalar(Handle::Thread(thread_id).to_scalar(this), dest)?;
-            }
-            "WaitForSingleObject" => {
-                let [handle, timeout] =
-                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
-
-                let ret = this.WaitForSingleObject(handle, timeout)?;
-                this.write_scalar(Scalar::from_u32(ret), dest)?;
-            }
-            "GetCurrentThread" => {
-                let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
-
-                this.write_scalar(
-                    Handle::Pseudo(PseudoHandle::CurrentThread).to_scalar(this),
-                    dest,
-                )?;
-            }
-
             // Incomplete shims that we "stub out" just to get pre-main initialization code to work.
             // These shims are enabled only when the caller is in the standard library.
             "GetProcessHeap" if this.frame_in_std() => {
@@ -548,9 +583,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                 this.write_null(dest)?;
             }
 
-            _ => return Ok(EmulateByNameResult::NotSupported),
+            _ => return Ok(EmulateForeignItemResult::NotSupported),
         }
 
-        Ok(EmulateByNameResult::NeedsJumping)
+        Ok(EmulateForeignItemResult::NeedsJumping)
     }
 }
diff --git a/src/tools/miri/src/shims/windows/mod.rs b/src/tools/miri/src/shims/windows/mod.rs
index 40fe71b2dbd..7688abe412b 100644
--- a/src/tools/miri/src/shims/windows/mod.rs
+++ b/src/tools/miri/src/shims/windows/mod.rs
@@ -1,4 +1,3 @@
-pub mod dlsym;
 pub mod foreign_items;
 
 mod handle;
diff --git a/src/tools/miri/src/shims/x86/mod.rs b/src/tools/miri/src/shims/x86/mod.rs
index 7c280109cb0..53a4a1ef28a 100644
--- a/src/tools/miri/src/shims/x86/mod.rs
+++ b/src/tools/miri/src/shims/x86/mod.rs
@@ -5,7 +5,7 @@ use rustc_target::spec::abi::Abi;
 
 use crate::*;
 use helpers::bool_to_simd_element;
-use shims::foreign_items::EmulateByNameResult;
+use shims::foreign_items::EmulateForeignItemResult;
 
 mod sse;
 mod sse2;
@@ -22,7 +22,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
         abi: Abi,
         args: &[OpTy<'tcx, Provenance>],
         dest: &PlaceTy<'tcx, Provenance>,
-    ) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
+    ) -> InterpResult<'tcx, EmulateForeignItemResult> {
         let this = self.eval_context_mut();
         // Prefix should have already been checked.
         let unprefixed_name = link_name.as_str().strip_prefix("llvm.x86.").unwrap();
@@ -34,7 +34,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
             // https://www.intel.com/content/www/us/en/docs/cpp-compiler/developer-guide-reference/2021-8/addcarry-u32-addcarry-u64.html
             "addcarry.32" | "addcarry.64" => {
                 if unprefixed_name == "addcarry.64" && this.tcx.sess.target.arch != "x86_64" {
-                    return Ok(EmulateByNameResult::NotSupported);
+                    return Ok(EmulateForeignItemResult::NotSupported);
                 }
 
                 let [c_in, a, b] = this.check_shim(abi, Abi::Unadjusted, link_name, args)?;
@@ -60,7 +60,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
             // https://www.intel.com/content/www/us/en/docs/cpp-compiler/developer-guide-reference/2021-8/subborrow-u32-subborrow-u64.html
             "subborrow.32" | "subborrow.64" => {
                 if unprefixed_name == "subborrow.64" && this.tcx.sess.target.arch != "x86_64" {
-                    return Ok(EmulateByNameResult::NotSupported);
+                    return Ok(EmulateForeignItemResult::NotSupported);
                 }
 
                 let [b_in, a, b] = this.check_shim(abi, Abi::Unadjusted, link_name, args)?;
@@ -100,9 +100,9 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
                     this, link_name, abi, args, dest,
                 );
             }
-            _ => return Ok(EmulateByNameResult::NotSupported),
+            _ => return Ok(EmulateForeignItemResult::NotSupported),
         }
-        Ok(EmulateByNameResult::NeedsJumping)
+        Ok(EmulateForeignItemResult::NeedsJumping)
     }
 }
 
diff --git a/src/tools/miri/src/shims/x86/sse.rs b/src/tools/miri/src/shims/x86/sse.rs
index de1e695b6d1..6f0b76059f1 100644
--- a/src/tools/miri/src/shims/x86/sse.rs
+++ b/src/tools/miri/src/shims/x86/sse.rs
@@ -7,7 +7,7 @@ use rand::Rng as _;
 
 use super::{bin_op_simd_float_all, bin_op_simd_float_first, FloatBinOp, FloatCmpOp};
 use crate::*;
-use shims::foreign_items::EmulateByNameResult;
+use shims::foreign_items::EmulateForeignItemResult;
 
 impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
 pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
@@ -19,7 +19,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
         abi: Abi,
         args: &[OpTy<'tcx, Provenance>],
         dest: &PlaceTy<'tcx, Provenance>,
-    ) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
+    ) -> InterpResult<'tcx, EmulateForeignItemResult> {
         let this = self.eval_context_mut();
         // Prefix should have already been checked.
         let unprefixed_name = link_name.as_str().strip_prefix("llvm.x86.sse.").unwrap();
@@ -141,10 +141,10 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
 
                 let left = this.read_scalar(&this.project_index(&left, 0)?)?.to_f32()?;
                 let right = this.read_scalar(&this.project_index(&right, 0)?)?.to_f32()?;
-                // The difference between the com* and *ucom variants is signaling
+                // The difference between the com* and ucom* variants is signaling
                 // of exceptions when either argument is a quiet NaN. We do not
                 // support accessing the SSE status register from miri (or from Rust,
-                // for that matter), so we treat equally both variants.
+                // for that matter), so we treat both variants equally.
                 let res = match unprefixed_name {
                     "comieq.ss" | "ucomieq.ss" => left == right,
                     "comilt.ss" | "ucomilt.ss" => left < right,
@@ -228,9 +228,9 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
 
                 this.write_scalar(Scalar::from_u32(res), dest)?;
             }
-            _ => return Ok(EmulateByNameResult::NotSupported),
+            _ => return Ok(EmulateForeignItemResult::NotSupported),
         }
-        Ok(EmulateByNameResult::NeedsJumping)
+        Ok(EmulateForeignItemResult::NeedsJumping)
     }
 }
 
diff --git a/src/tools/miri/src/shims/x86/sse2.rs b/src/tools/miri/src/shims/x86/sse2.rs
index 2ef6a9b59ed..c6a847b5cf8 100644
--- a/src/tools/miri/src/shims/x86/sse2.rs
+++ b/src/tools/miri/src/shims/x86/sse2.rs
@@ -10,7 +10,7 @@ use rustc_target::spec::abi::Abi;
 
 use super::{bin_op_simd_float_all, bin_op_simd_float_first, FloatBinOp, FloatCmpOp};
 use crate::*;
-use shims::foreign_items::EmulateByNameResult;
+use shims::foreign_items::EmulateForeignItemResult;
 
 impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
 pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
@@ -22,7 +22,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
         abi: Abi,
         args: &[OpTy<'tcx, Provenance>],
         dest: &PlaceTy<'tcx, Provenance>,
-    ) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
+    ) -> InterpResult<'tcx, EmulateForeignItemResult> {
         let this = self.eval_context_mut();
         // Prefix should have already been checked.
         let unprefixed_name = link_name.as_str().strip_prefix("llvm.x86.sse2.").unwrap();
@@ -637,10 +637,10 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
 
                 let left = this.read_scalar(&this.project_index(&left, 0)?)?.to_f64()?;
                 let right = this.read_scalar(&this.project_index(&right, 0)?)?.to_f64()?;
-                // The difference between the com* and *ucom variants is signaling
+                // The difference between the com* and ucom* variants is signaling
                 // of exceptions when either argument is a quiet NaN. We do not
                 // support accessing the SSE status register from miri (or from Rust,
-                // for that matter), so we treat equally both variants.
+                // for that matter), so we treat both variants equally.
                 let res = match unprefixed_name {
                     "comieq.sd" | "ucomieq.sd" => left == right,
                     "comilt.sd" | "ucomilt.sd" => left < right,
@@ -797,9 +797,9 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
                 let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
                 this.yield_active_thread();
             }
-            _ => return Ok(EmulateByNameResult::NotSupported),
+            _ => return Ok(EmulateForeignItemResult::NotSupported),
         }
-        Ok(EmulateByNameResult::NeedsJumping)
+        Ok(EmulateForeignItemResult::NeedsJumping)
     }
 }
 
diff --git a/src/tools/miri/src/shims/x86/sse3.rs b/src/tools/miri/src/shims/x86/sse3.rs
index f5c30a521fa..a41de5dbf7e 100644
--- a/src/tools/miri/src/shims/x86/sse3.rs
+++ b/src/tools/miri/src/shims/x86/sse3.rs
@@ -5,7 +5,7 @@ use rustc_target::spec::abi::Abi;
 
 use super::horizontal_bin_op;
 use crate::*;
-use shims::foreign_items::EmulateByNameResult;
+use shims::foreign_items::EmulateForeignItemResult;
 
 impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
 pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
@@ -17,7 +17,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
         abi: Abi,
         args: &[OpTy<'tcx, Provenance>],
         dest: &PlaceTy<'tcx, Provenance>,
-    ) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
+    ) -> InterpResult<'tcx, EmulateForeignItemResult> {
         let this = self.eval_context_mut();
         // Prefix should have already been checked.
         let unprefixed_name = link_name.as_str().strip_prefix("llvm.x86.sse3.").unwrap();
@@ -83,8 +83,8 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
                     /*nonoverlapping*/ true,
                 )?;
             }
-            _ => return Ok(EmulateByNameResult::NotSupported),
+            _ => return Ok(EmulateForeignItemResult::NotSupported),
         }
-        Ok(EmulateByNameResult::NeedsJumping)
+        Ok(EmulateForeignItemResult::NeedsJumping)
     }
 }
diff --git a/src/tools/miri/src/shims/x86/ssse3.rs b/src/tools/miri/src/shims/x86/ssse3.rs
index b0154672234..dbc2b947b33 100644
--- a/src/tools/miri/src/shims/x86/ssse3.rs
+++ b/src/tools/miri/src/shims/x86/ssse3.rs
@@ -4,7 +4,7 @@ use rustc_target::spec::abi::Abi;
 
 use super::horizontal_bin_op;
 use crate::*;
-use shims::foreign_items::EmulateByNameResult;
+use shims::foreign_items::EmulateForeignItemResult;
 
 impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
 pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
@@ -16,7 +16,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
         abi: Abi,
         args: &[OpTy<'tcx, Provenance>],
         dest: &PlaceTy<'tcx, Provenance>,
-    ) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
+    ) -> InterpResult<'tcx, EmulateForeignItemResult> {
         let this = self.eval_context_mut();
         // Prefix should have already been checked.
         let unprefixed_name = link_name.as_str().strip_prefix("llvm.x86.ssse3.").unwrap();
@@ -192,8 +192,8 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
                     this.write_immediate(*res, &dest)?;
                 }
             }
-            _ => return Ok(EmulateByNameResult::NotSupported),
+            _ => return Ok(EmulateForeignItemResult::NotSupported),
         }
-        Ok(EmulateByNameResult::NeedsJumping)
+        Ok(EmulateForeignItemResult::NeedsJumping)
     }
 }
diff --git a/src/tools/miri/test_dependencies/Cargo.lock b/src/tools/miri/test_dependencies/Cargo.lock
index 3ed564b4cbb..8d18d8535d8 100644
--- a/src/tools/miri/test_dependencies/Cargo.lock
+++ b/src/tools/miri/test_dependencies/Cargo.lock
@@ -3,12 +3,42 @@
 version = 3
 
 [[package]]
+name = "addr2line"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
 name = "autocfg"
 version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
 [[package]]
+name = "backtrace"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
 name = "bitflags"
 version = "1.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -16,15 +46,24 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
 [[package]]
 name = "bumpalo"
-version = "3.11.1"
+version = "3.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
+checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
 
 [[package]]
 name = "bytes"
-version = "1.3.0"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
+checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
+
+[[package]]
+name = "cc"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "libc",
+]
 
 [[package]]
 name = "cfg-if"
@@ -45,9 +84,9 @@ dependencies = [
 
 [[package]]
 name = "getrandom"
-version = "0.2.8"
+version = "0.2.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
+checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
 dependencies = [
  "cfg-if",
  "js-sys",
@@ -57,34 +96,37 @@ dependencies = [
 ]
 
 [[package]]
+name = "gimli"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
+
+[[package]]
 name = "hermit-abi"
-version = "0.2.6"
+version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
-dependencies = [
- "libc",
-]
+checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
 
 [[package]]
 name = "js-sys"
-version = "0.3.60"
+version = "0.3.64"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
+checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a"
 dependencies = [
  "wasm-bindgen",
 ]
 
 [[package]]
 name = "libc"
-version = "0.2.139"
+version = "0.2.148"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
+checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
 
 [[package]]
 name = "lock_api"
-version = "0.4.9"
+version = "0.4.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
+checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16"
 dependencies = [
  "autocfg",
  "scopeguard",
@@ -92,27 +134,32 @@ dependencies = [
 
 [[package]]
 name = "log"
-version = "0.4.17"
+version = "0.4.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
-dependencies = [
- "cfg-if",
-]
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
 
 [[package]]
 name = "memchr"
-version = "2.5.0"
+version = "2.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+dependencies = [
+ "adler",
+]
 
 [[package]]
 name = "mio"
-version = "0.8.5"
+version = "0.8.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
+checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
 dependencies = [
  "libc",
- "log",
  "wasi 0.11.0+wasi-snapshot-preview1",
  "windows-sys",
 ]
@@ -122,7 +169,7 @@ name = "miri-test-deps"
 version = "0.1.0"
 dependencies = [
  "getrandom 0.1.16",
- "getrandom 0.2.8",
+ "getrandom 0.2.10",
  "libc",
  "num_cpus",
  "page_size",
@@ -132,25 +179,34 @@ dependencies = [
 
 [[package]]
 name = "num_cpus"
-version = "1.15.0"
+version = "1.16.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
+checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
 dependencies = [
  "hermit-abi",
  "libc",
 ]
 
 [[package]]
+name = "object"
+version = "0.32.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
 name = "once_cell"
-version = "1.17.0"
+version = "1.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
+checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
 
 [[package]]
 name = "page_size"
-version = "0.5.0"
+version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b7663cbd190cfd818d08efa8497f6cd383076688c49a391ef7c0d03cd12b561"
+checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
 dependencies = [
  "libc",
  "winapi",
@@ -168,22 +224,22 @@ dependencies = [
 
 [[package]]
 name = "parking_lot_core"
-version = "0.9.5"
+version = "0.9.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba"
+checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447"
 dependencies = [
  "cfg-if",
  "libc",
  "redox_syscall",
  "smallvec",
- "windows-sys",
+ "windows-targets",
 ]
 
 [[package]]
 name = "pin-project-lite"
-version = "0.2.9"
+version = "0.2.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
+checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
 
 [[package]]
 name = "ppv-lite86"
@@ -193,18 +249,18 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.60"
+version = "1.0.67"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406"
+checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.23"
+version = "1.0.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
+checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
 dependencies = [
  "proc-macro2",
 ]
@@ -236,54 +292,60 @@ version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
 dependencies = [
- "getrandom 0.2.8",
+ "getrandom 0.2.10",
 ]
 
 [[package]]
 name = "redox_syscall"
-version = "0.2.16"
+version = "0.3.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
 dependencies = [
  "bitflags",
 ]
 
 [[package]]
+name = "rustc-demangle"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
+
+[[package]]
 name = "scopeguard"
-version = "1.1.0"
+version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
 [[package]]
 name = "signal-hook-registry"
-version = "1.4.0"
+version = "1.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
 dependencies = [
  "libc",
 ]
 
 [[package]]
 name = "smallvec"
-version = "1.10.0"
+version = "1.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
+checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"
 
 [[package]]
 name = "socket2"
-version = "0.4.7"
+version = "0.5.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
+checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e"
 dependencies = [
  "libc",
- "winapi",
+ "windows-sys",
 ]
 
 [[package]]
 name = "syn"
-version = "1.0.107"
+version = "2.0.37"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
+checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -292,14 +354,13 @@ dependencies = [
 
 [[package]]
 name = "tokio"
-version = "1.24.2"
+version = "1.32.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb"
+checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9"
 dependencies = [
- "autocfg",
+ "backtrace",
  "bytes",
  "libc",
- "memchr",
  "mio",
  "num_cpus",
  "parking_lot",
@@ -312,9 +373,9 @@ dependencies = [
 
 [[package]]
 name = "tokio-macros"
-version = "1.8.2"
+version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
+checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -323,9 +384,9 @@ dependencies = [
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.6"
+version = "1.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
 
 [[package]]
 name = "wasi"
@@ -341,9 +402,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.83"
+version = "0.2.87"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
+checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342"
 dependencies = [
  "cfg-if",
  "wasm-bindgen-macro",
@@ -351,9 +412,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.83"
+version = "0.2.87"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
+checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd"
 dependencies = [
  "bumpalo",
  "log",
@@ -366,9 +427,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.83"
+version = "0.2.87"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
+checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -376,9 +437,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.83"
+version = "0.2.87"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
+checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -389,9 +450,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.83"
+version = "0.2.87"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
+checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
 
 [[package]]
 name = "winapi"
@@ -417,9 +478,18 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
 [[package]]
 name = "windows-sys"
-version = "0.42.0"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
 dependencies = [
  "windows_aarch64_gnullvm",
  "windows_aarch64_msvc",
@@ -432,42 +502,42 @@ dependencies = [
 
 [[package]]
 name = "windows_aarch64_gnullvm"
-version = "0.42.0"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
 
 [[package]]
 name = "windows_aarch64_msvc"
-version = "0.42.0"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
 
 [[package]]
 name = "windows_i686_gnu"
-version = "0.42.0"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
 
 [[package]]
 name = "windows_i686_msvc"
-version = "0.42.0"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
 
 [[package]]
 name = "windows_x86_64_gnu"
-version = "0.42.0"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
-version = "0.42.0"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
 
 [[package]]
 name = "windows_x86_64_msvc"
-version = "0.42.0"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
diff --git a/src/tools/miri/test_dependencies/Cargo.toml b/src/tools/miri/test_dependencies/Cargo.toml
index d1ff33379e4..670f5c895cb 100644
--- a/src/tools/miri/test_dependencies/Cargo.toml
+++ b/src/tools/miri/test_dependencies/Cargo.toml
@@ -17,7 +17,7 @@ getrandom = { version = "0.2", features = ["js"] }
 rand = { version = "0.8", features = ["small_rng"] }
 
 [target.'cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))'.dependencies]
-page_size = "0.5"
+page_size = "0.6"
 tokio = { version = "1.24", features = ["full"] }
 
 [workspace]
diff --git a/src/tools/miri/tests/compiletest.rs b/src/tools/miri/tests/compiletest.rs
index 035846f0ef9..c2dccf81377 100644
--- a/src/tools/miri/tests/compiletest.rs
+++ b/src/tools/miri/tests/compiletest.rs
@@ -93,9 +93,7 @@ fn test_config(target: &str, path: &str, mode: Mode, with_dependencies: bool) ->
         ..Config::rustc(path)
     };
 
-    let use_std = env::var_os("MIRI_NO_STD").is_none();
-
-    if with_dependencies && use_std {
+    if with_dependencies {
         config.dependencies_crate_manifest_path =
             Some(Path::new("test_dependencies").join("Cargo.toml"));
         let mut builder_args = vec!["run".into()];
diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.tree.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.tree.stderr
index 2a9b4d688a9..3271a04eae7 100644
--- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.tree.stderr
@@ -5,18 +5,18 @@ LL |     *x = 1;
    |     ^^^^^^ write access through <TAG> is forbidden
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
-   = help: the accessed tag <TAG> has state Frozen which forbids this child write access
+   = help: the accessed tag <TAG> has state Reserved (conflicted) which forbids this child write access
 help: the accessed tag <TAG> was created here, in the initial state Reserved
   --> $DIR/aliasing_mut1.rs:LL:CC
    |
 LL | pub fn safe(x: &mut i32, y: &mut i32) {
    |             ^
-help: the accessed tag <TAG> later transitioned to Frozen due to a reborrow (acting as a foreign read access) at offsets [0x0..0x4]
+help: the accessed tag <TAG> later transitioned to Reserved (conflicted) due to a reborrow (acting as a foreign read access) at offsets [0x0..0x4]
   --> $DIR/aliasing_mut1.rs:LL:CC
    |
 LL | pub fn safe(x: &mut i32, y: &mut i32) {
    |                          ^
-   = help: this transition corresponds to a loss of write permissions
+   = help: this transition corresponds to a temporary loss of write permissions until function exit
    = note: BACKTRACE (of the first span):
    = note: inside `safe` at $DIR/aliasing_mut1.rs:LL:CC
 note: inside `main`
diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.tree.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.tree.stderr
index d4858975ef1..f2694b51ca4 100644
--- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.tree.stderr
@@ -5,18 +5,18 @@ LL |     *y = 2;
    |     ^^^^^^ write access through <TAG> is forbidden
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
-   = help: the accessed tag <TAG> has state Frozen which forbids this child write access
+   = help: the accessed tag <TAG> has state Reserved (conflicted) which forbids this child write access
 help: the accessed tag <TAG> was created here, in the initial state Reserved
   --> $DIR/aliasing_mut2.rs:LL:CC
    |
 LL | pub fn safe(x: &i32, y: &mut i32) {
    |                      ^
-help: the accessed tag <TAG> later transitioned to Frozen due to a foreign read access at offsets [0x0..0x4]
+help: the accessed tag <TAG> later transitioned to Reserved (conflicted) due to a foreign read access at offsets [0x0..0x4]
   --> $DIR/aliasing_mut2.rs:LL:CC
    |
 LL |     let _v = *x;
    |              ^^
-   = help: this transition corresponds to a loss of write permissions
+   = help: this transition corresponds to a temporary loss of write permissions until function exit
    = note: BACKTRACE (of the first span):
    = note: inside `safe` at $DIR/aliasing_mut2.rs:LL:CC
 note: inside `main`
diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.tree.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.tree.stderr
index d1afca84a8b..595381c16ad 100644
--- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.tree.stderr
@@ -5,18 +5,18 @@ LL |     *x = 1;
    |     ^^^^^^ write access through <TAG> is forbidden
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
-   = help: the accessed tag <TAG> has state Frozen which forbids this child write access
+   = help: the accessed tag <TAG> has state Reserved (conflicted) which forbids this child write access
 help: the accessed tag <TAG> was created here, in the initial state Reserved
   --> $DIR/aliasing_mut3.rs:LL:CC
    |
 LL | pub fn safe(x: &mut i32, y: &i32) {
    |             ^
-help: the accessed tag <TAG> later transitioned to Frozen due to a reborrow (acting as a foreign read access) at offsets [0x0..0x4]
+help: the accessed tag <TAG> later transitioned to Reserved (conflicted) due to a reborrow (acting as a foreign read access) at offsets [0x0..0x4]
   --> $DIR/aliasing_mut3.rs:LL:CC
    |
 LL | pub fn safe(x: &mut i32, y: &i32) {
    |                          ^
-   = help: this transition corresponds to a loss of write permissions
+   = help: this transition corresponds to a temporary loss of write permissions until function exit
    = note: BACKTRACE (of the first span):
    = note: inside `safe` at $DIR/aliasing_mut3.rs:LL:CC
 note: inside `main`
diff --git a/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.tree.stderr b/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.tree.stderr
index 19a05ec5dd3..456af0f1eac 100644
--- a/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.tree.stderr
@@ -6,7 +6,7 @@ LL |     unsafe { __rust_dealloc(ptr, layout.size(), layout.align()) }
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
    = help: the accessed tag <TAG> is foreign to the protected tag <TAG> (i.e., it is not a child)
-   = help: this deallocation (acting as a foreign write access) would cause the protected tag <TAG> (currently Frozen) to become Disabled
+   = help: this deallocation (acting as a foreign write access) would cause the protected tag <TAG> (currently Reserved (conflicted)) to become Disabled
    = help: protected tags must never be Disabled
 help: the accessed tag <TAG> was created here
   --> $DIR/newtype_pair_retagging.rs:LL:CC
@@ -18,12 +18,12 @@ help: the protected tag <TAG> was created here, in the initial state Reserved
    |
 LL | fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) {
    |                          ^^
-help: the protected tag <TAG> later transitioned to Frozen due to a reborrow (acting as a foreign read access) at offsets [0x0..0x4]
+help: the protected tag <TAG> later transitioned to Reserved (conflicted) due to a reborrow (acting as a foreign read access) at offsets [0x0..0x4]
   --> $DIR/newtype_pair_retagging.rs:LL:CC
    |
 LL |             || drop(Box::from_raw(ptr)),
    |                     ^^^^^^^^^^^^^^^^^^
-   = help: this transition corresponds to a loss of write permissions
+   = help: this transition corresponds to a temporary loss of write permissions until function exit
    = note: BACKTRACE (of the first span):
    = note: inside `std::alloc::dealloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC
    = note: inside `<std::alloc::Global as std::alloc::Allocator>::deallocate` at RUSTLIB/alloc/src/alloc.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/both_borrows/newtype_retagging.tree.stderr b/src/tools/miri/tests/fail/both_borrows/newtype_retagging.tree.stderr
index 6364c51122a..d21ec9eddc2 100644
--- a/src/tools/miri/tests/fail/both_borrows/newtype_retagging.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/newtype_retagging.tree.stderr
@@ -6,7 +6,7 @@ LL |     unsafe { __rust_dealloc(ptr, layout.size(), layout.align()) }
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
    = help: the accessed tag <TAG> is foreign to the protected tag <TAG> (i.e., it is not a child)
-   = help: this deallocation (acting as a foreign write access) would cause the protected tag <TAG> (currently Frozen) to become Disabled
+   = help: this deallocation (acting as a foreign write access) would cause the protected tag <TAG> (currently Reserved (conflicted)) to become Disabled
    = help: protected tags must never be Disabled
 help: the accessed tag <TAG> was created here
   --> $DIR/newtype_retagging.rs:LL:CC
@@ -18,12 +18,12 @@ help: the protected tag <TAG> was created here, in the initial state Reserved
    |
 LL | fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) {
    |                          ^^
-help: the protected tag <TAG> later transitioned to Frozen due to a reborrow (acting as a foreign read access) at offsets [0x0..0x4]
+help: the protected tag <TAG> later transitioned to Reserved (conflicted) due to a reborrow (acting as a foreign read access) at offsets [0x0..0x4]
   --> $DIR/newtype_retagging.rs:LL:CC
    |
 LL |             || drop(Box::from_raw(ptr)),
    |                     ^^^^^^^^^^^^^^^^^^
-   = help: this transition corresponds to a loss of write permissions
+   = help: this transition corresponds to a temporary loss of write permissions until function exit
    = note: BACKTRACE (of the first span):
    = note: inside `std::alloc::dealloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC
    = note: inside `<std::alloc::Global as std::alloc::Allocator>::deallocate` at RUSTLIB/alloc/src/alloc.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/both_borrows/retag_data_race_protected_read.rs b/src/tools/miri/tests/fail/both_borrows/retag_data_race_protected_read.rs
deleted file mode 100644
index f192e76de13..00000000000
--- a/src/tools/miri/tests/fail/both_borrows/retag_data_race_protected_read.rs
+++ /dev/null
@@ -1,29 +0,0 @@
-//@revisions: stack tree
-//@compile-flags: -Zmiri-preemption-rate=0
-//@[tree]compile-flags: -Zmiri-tree-borrows
-use std::thread;
-
-#[derive(Copy, Clone)]
-struct SendPtr(*mut i32);
-unsafe impl Send for SendPtr {}
-
-fn main() {
-    let mut mem = 0;
-    let ptr = SendPtr(&mut mem as *mut _);
-
-    let t = thread::spawn(move || {
-        let ptr = ptr;
-        // We do a protected 2phase retag (but no write!) in this thread.
-        fn retag(_x: &mut i32) {} //~[tree]ERROR: Data race detected between (1) Read on thread `main` and (2) Write on thread `<unnamed>`
-        retag(unsafe { &mut *ptr.0 }); //~[stack]ERROR: Data race detected between (1) Read on thread `main` and (2) Write on thread `<unnamed>`
-    });
-
-    // We do a read in the main thread.
-    unsafe { ptr.0.read() };
-
-    // These two operations do not commute -- if the read happens after the retag, the retagged pointer
-    // gets frozen! So we want this to be considered UB so that we can still freely move the read around
-    // in this thread without worrying about reordering with retags in other threads.
-
-    t.join().unwrap();
-}
diff --git a/src/tools/miri/tests/fail/both_borrows/retag_data_race_protected_read.tree.stderr b/src/tools/miri/tests/fail/both_borrows/retag_data_race_protected_read.tree.stderr
deleted file mode 100644
index 173acf4b96c..00000000000
--- a/src/tools/miri/tests/fail/both_borrows/retag_data_race_protected_read.tree.stderr
+++ /dev/null
@@ -1,25 +0,0 @@
-error: Undefined Behavior: Data race detected between (1) Read on thread `main` and (2) Write on thread `<unnamed>` at ALLOC. (2) just happened here
-  --> $DIR/retag_data_race_protected_read.rs:LL:CC
-   |
-LL |         fn retag(_x: &mut i32) {}
-   |                  ^^ Data race detected between (1) Read on thread `main` and (2) Write on thread `<unnamed>` at ALLOC. (2) just happened here
-   |
-help: and (1) occurred earlier here
-  --> $DIR/retag_data_race_protected_read.rs:LL:CC
-   |
-LL |     unsafe { ptr.0.read() };
-   |              ^^^^^^^^^^^^
-   = 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 (of the first span):
-   = note: inside `main::{closure#0}::retag` at $DIR/retag_data_race_protected_read.rs:LL:CC
-note: inside closure
-  --> $DIR/retag_data_race_protected_read.rs:LL:CC
-   |
-LL | ...   retag(unsafe { &mut *ptr.0 });
-   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
-
-error: aborting due to previous error
-
diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.tree.stderr b/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.tree.stderr
index 544cd575ada..3d8ba68547b 100644
--- a/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.tree.stderr
+++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.tree.stderr
@@ -19,11 +19,17 @@ LL | |             let non_copy = S(42);
 LL | |
 LL | |     }
    | |_____^
-help: the protected tag <TAG> was created here, in the initial state Active
+help: the protected tag <TAG> was created here, in the initial state Reserved
   --> $DIR/arg_inplace_mutate.rs:LL:CC
    |
 LL |     unsafe { ptr.write(S(0)) };
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: the protected tag <TAG> later transitioned to Active due to a child write access at offsets [0x0..0x4]
+  --> $DIR/arg_inplace_mutate.rs:LL:CC
+   |
+LL |     unsafe { ptr.write(S(0)) };
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^
+   = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference
    = note: BACKTRACE (of the first span):
    = note: inside `callee` at $DIR/arg_inplace_mutate.rs:LL:CC
 note: inside `main`
diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.tree.stderr b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.tree.stderr
index c33645bdd28..7b1846a32db 100644
--- a/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.tree.stderr
+++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.tree.stderr
@@ -19,11 +19,17 @@ LL | |             let non_copy = S(42);
 LL | |
 LL | |     }
    | |_____^
-help: the protected tag <TAG> was created here, in the initial state Active
+help: the protected tag <TAG> was created here, in the initial state Reserved
   --> $DIR/arg_inplace_observe_during.rs:LL:CC
    |
 LL |     x.0 = 0;
    |     ^^^^^^^
+help: the protected tag <TAG> later transitioned to Active due to a child write access at offsets [0x0..0x4]
+  --> $DIR/arg_inplace_observe_during.rs:LL:CC
+   |
+LL |     x.0 = 0;
+   |     ^^^^^^^
+   = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference
    = note: BACKTRACE (of the first span):
    = note: inside `change_arg` at $DIR/arg_inplace_observe_during.rs:LL:CC
 note: inside `main`
diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.tree.stderr b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.tree.stderr
index 66c2fb8db19..deafbf02077 100644
--- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.tree.stderr
+++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.tree.stderr
@@ -19,11 +19,17 @@ LL | |             let ptr = &raw mut x;
 LL | |         }
 LL | |     }
    | |_____^
-help: the protected tag <TAG> was created here, in the initial state Active
+help: the protected tag <TAG> was created here, in the initial state Reserved
   --> $DIR/return_pointer_aliasing.rs:LL:CC
    |
 LL |     unsafe { ptr.read() };
    |     ^^^^^^^^^^^^^^^^^^^^^
+help: the protected tag <TAG> later transitioned to Active due to a child write access at offsets [0x0..0x4]
+  --> $DIR/return_pointer_aliasing.rs:LL:CC
+   |
+LL |     unsafe { ptr.read() };
+   |     ^^^^^^^^^^^^^^^^^^^^^
+   = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference
    = note: BACKTRACE (of the first span):
    = note: inside `myfun` at $DIR/return_pointer_aliasing.rs:LL:CC
 note: inside `main`
diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.stderr b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.stderr
index 443ee8643fc..e1b40a6bc18 100644
--- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.stderr
+++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.stderr
@@ -19,11 +19,17 @@ LL | |             let ptr = &raw mut _x;
 LL | |         }
 LL | |     }
    | |_____^
-help: the protected tag <TAG> was created here, in the initial state Active
+help: the protected tag <TAG> was created here, in the initial state Reserved
   --> $DIR/return_pointer_aliasing2.rs:LL:CC
    |
 LL |     unsafe { ptr.write(0) };
    |     ^^^^^^^^^^^^^^^^^^^^^^^
+help: the protected tag <TAG> later transitioned to Active due to a child write access at offsets [0x0..0x4]
+  --> $DIR/return_pointer_aliasing2.rs:LL:CC
+   |
+LL |     unsafe { ptr.write(0) };
+   |     ^^^^^^^^^^^^^^^^^^^^^^^
+   = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference
    = note: BACKTRACE (of the first span):
    = note: inside `myfun` at $DIR/return_pointer_aliasing2.rs:LL:CC
 note: inside `main`
diff --git a/src/tools/miri/tests/fail/function_calls/simd_feature_flag_difference.rs b/src/tools/miri/tests/fail/function_calls/simd_feature_flag_difference.rs
new file mode 100644
index 00000000000..e98a3abadf5
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/simd_feature_flag_difference.rs
@@ -0,0 +1,30 @@
+//@only-target-x86_64
+#![allow(improper_ctypes_definitions)]
+use std::arch::x86_64::*;
+use std::mem::transmute;
+
+#[no_mangle]
+#[target_feature(enable = "avx")]
+pub unsafe extern "C" fn foo(_y: f32, x: __m256) -> __m256 {
+    x
+}
+
+pub fn bar(x: __m256) -> __m256 {
+    // The first and second argument get mixed up here since caller
+    // and callee do not have the same feature flags.
+    // In Miri, we don't have a concept of "dynamically available feature flags",
+    // so this will always lead to an error due to calling a function that requires
+    // an unavailable feature. If we ever support dynamically available features,
+    // this will need some dedicated checks.
+    unsafe { foo(0.0, x) } //~ERROR: unavailable target features
+}
+
+fn assert_eq_m256(a: __m256, b: __m256) {
+    unsafe { assert_eq!(transmute::<_, [f32; 8]>(a), transmute::<_, [f32; 8]>(b)) }
+}
+
+fn main() {
+    let input = unsafe { transmute::<_, __m256>([1.0f32; 8]) };
+    let copy = bar(input);
+    assert_eq_m256(input, copy);
+}
diff --git a/src/tools/miri/tests/fail/function_calls/simd_feature_flag_difference.stderr b/src/tools/miri/tests/fail/function_calls/simd_feature_flag_difference.stderr
new file mode 100644
index 00000000000..ab3ff5fcdc1
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/simd_feature_flag_difference.stderr
@@ -0,0 +1,20 @@
+error: Undefined Behavior: calling a function that requires unavailable target features: avx
+  --> $DIR/simd_feature_flag_difference.rs:LL:CC
+   |
+LL |     unsafe { foo(0.0, x) }
+   |              ^^^^^^^^^^^ calling a function that requires unavailable target features: avx
+   |
+   = 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 `bar` at $DIR/simd_feature_flag_difference.rs:LL:CC
+note: inside `main`
+  --> $DIR/simd_feature_flag_difference.rs:LL:CC
+   |
+LL |     let copy = bar(input);
+   |                ^^^^^^^^^^
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to previous error
+
diff --git a/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_protected_read.rs b/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_protected_read.rs
new file mode 100644
index 00000000000..670fe9858ed
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_protected_read.rs
@@ -0,0 +1,30 @@
+//@compile-flags: -Zmiri-preemption-rate=0
+use std::thread;
+
+#[derive(Copy, Clone)]
+struct SendPtr(*mut i32);
+unsafe impl Send for SendPtr {}
+
+fn main() {
+    let mut mem = 0;
+    let ptr = SendPtr(&mut mem as *mut _);
+
+    let t = thread::spawn(move || {
+        let ptr = ptr;
+        // We do a protected mutable retag (but no write!) in this thread.
+        fn retag(_x: &mut i32) {}
+        retag(unsafe { &mut *ptr.0 }); //~ERROR: Data race detected between (1) Read on thread `main` and (2) Write on thread `<unnamed>`
+    });
+
+    // We do a read in the main thread.
+    unsafe { ptr.0.read() };
+
+    // These two operations do not commute!
+    // - In Stacked Borrows, if the read happens after the retag it will `Disable` the pointer.
+    // - In Tree Borrows, if the read happens after the retag, the retagged pointer gets frozen!
+    // Ideally we would want this to be considered UB so that we can still freely move the read around
+    // in this thread without worrying about reordering with retags in other threads,
+    // but in Tree Borrows we have found worse issues that occur if we make this a data race.
+
+    t.join().unwrap();
+}
diff --git a/src/tools/miri/tests/fail/both_borrows/retag_data_race_protected_read.stack.stderr b/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_protected_read.stderr
index 10fb1dece2a..10fb1dece2a 100644
--- a/src/tools/miri/tests/fail/both_borrows/retag_data_race_protected_read.stack.stderr
+++ b/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_protected_read.stderr
diff --git a/src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.stderr b/src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.stderr
index 998ab956e1a..fc92770ed16 100644
--- a/src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.stderr
+++ b/src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.stderr
@@ -1,12 +1,12 @@
 ──────────────────────────────────────────────────
 Warning: this tree is indicative only. Some tags may have been hidden.
-0..  1
-| Act|    └─┬──<TAG=root of the allocation>
-| Re*|      └─┬──<TAG=base>
-| Re*|        ├─┬──<TAG=x>
-| Re*|        │ └─┬──<TAG=caller:x>
-| Re*|        │   └────<TAG=callee:x> Strongly protected
-| Re*|        └────<TAG=y, callee:y, caller:y>
+0..   1
+| Act |    └─┬──<TAG=root of the allocation>
+| RsM |      └─┬──<TAG=base>
+| RsM |        ├─┬──<TAG=x>
+| RsM |        │ └─┬──<TAG=caller:x>
+| RsM |        │   └────<TAG=callee:x> Strongly protected
+| RsM |        └────<TAG=y, callee:y, caller:y>
 ──────────────────────────────────────────────────
 error: Undefined Behavior: write access through <TAG> (y, callee:y, caller:y) is forbidden
   --> $DIR/cell-protected-write.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.stderr b/src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.stderr
index f7e9fb9e3c3..4b4b8f24a68 100644
--- a/src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.stderr
+++ b/src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.stderr
@@ -1,12 +1,12 @@
 ──────────────────────────────────────────────────
 Warning: this tree is indicative only. Some tags may have been hidden.
-0..  1
-| Act|    └─┬──<TAG=root of the allocation>
-| Res|      └─┬──<TAG=n>
-| Res|        ├─┬──<TAG=x>
-| Res|        │ └─┬──<TAG=caller:x>
-| Res|        │   └────<TAG=callee:x> Strongly protected
-| Res|        └────<TAG=y, callee:y, caller:y>
+0..   1
+| Act |    └─┬──<TAG=root of the allocation>
+| Rs  |      └─┬──<TAG=n>
+| Rs  |        ├─┬──<TAG=x>
+| Rs  |        │ └─┬──<TAG=caller:x>
+| Rs  |        │   └────<TAG=callee:x> Strongly protected
+| Rs  |        └────<TAG=y, callee:y, caller:y>
 ──────────────────────────────────────────────────
 error: Undefined Behavior: write access through <TAG> (y, callee:y, caller:y) is forbidden
   --> $DIR/int-protected-write.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/tree_borrows/spurious_read.rs b/src/tools/miri/tests/fail/tree_borrows/spurious_read.rs
new file mode 100644
index 00000000000..3f39dcb4b76
--- /dev/null
+++ b/src/tools/miri/tests/fail/tree_borrows/spurious_read.rs
@@ -0,0 +1,117 @@
+// We ensure a deterministic execution.
+// Note that we are *also* using barriers: the barriers enforce the
+// specific interleaving of operations that we want, but only the preemption
+// rate guarantees that the error message is also deterministic.
+//@compile-flags: -Zmiri-preemption-rate=0
+//@compile-flags: -Zmiri-tree-borrows
+
+use std::sync::{Arc, Barrier};
+use std::thread;
+
+// A way to send raw pointers across threads.
+// Note that when using this in closures will require explicit copying
+// `let ptr = ptr;` to force the borrow checker to copy the `Send` wrapper
+// instead of just copying the inner `!Send` field.
+#[derive(Copy, Clone)]
+struct SendPtr(*mut u8);
+unsafe impl Send for SendPtr {}
+
+fn main() {
+    retagx_retagy_retx_writey_rety();
+}
+
+// We're going to enforce a specific interleaving of two
+// threads, we use this macro in an effort to make it feasible
+// to check in the output that the execution is properly synchronized.
+//
+// Provide `synchronized!(thread, msg)` where thread is
+// a `(thread_id: usize, barrier: Arc<Barrier>)`, and `msg` the message
+// to be displayed when the thread reaches this point in the execution.
+macro_rules! synchronized {
+    ($thread:expr, $msg:expr) => {{
+        let (thread_id, barrier) = &$thread;
+        eprintln!("Thread {} executing: {}", thread_id, $msg);
+        barrier.wait();
+    }};
+}
+
+// Interleaving:
+//   retag x (protect)
+//   retag y (protect)
+//   spurious read x (target only, which we are *not* executing)
+//   ret x
+//   write y
+//   ret y
+//
+// This is an interleaving that will never *not* have UB in the target
+// (`noalias` violation on `y`).
+// For the spurious read to be allowed, we need to ensure there *is* UB
+// in the source (i.e., without the spurious read).
+//
+// The interleaving differs from the one in `tests/pass/tree_borrows/spurious_read.rs` only
+// in that it has the `write y` while `y` is still protected.
+// When the write occurs after protection ends, both source and target are fine
+// (checked by the `pass` test); when the write occurs during protection, both source
+// and target are UB (checked by this test).
+fn retagx_retagy_retx_writey_rety() {
+    let mut data = 0u8;
+    let ptr = SendPtr(std::ptr::addr_of_mut!(data));
+    let barrier = Arc::new(Barrier::new(2));
+    let bx = Arc::clone(&barrier);
+    let by = Arc::clone(&barrier);
+
+    // This thread only needs to
+    // - retag `x` protected
+    // - do a read through `x`
+    // - remove `x`'s protector
+    // Most of the complexity here is synchronization.
+    let thread_x = thread::spawn(move || {
+        let b = (1, bx);
+        synchronized!(b, "start");
+        let ptr = ptr;
+        synchronized!(b, "retag x (&mut, protect)");
+        fn as_mut(x: &mut u8, b: (usize, Arc<Barrier>)) -> *mut u8 {
+            synchronized!(b, "retag y (&mut, protect)");
+            synchronized!(b, "location where spurious read of x would happen in the target");
+            // This is ensuring taht we have UB *without* the spurious read,
+            // so we don't read here.
+            synchronized!(b, "ret x");
+            synchronized!(b, "write y");
+            let x = x as *mut u8;
+            x
+        }
+        let _x = as_mut(unsafe { &mut *ptr.0 }, b.clone());
+        synchronized!(b, "ret y");
+        synchronized!(b, "end");
+    });
+
+    // This thread's job is to
+    // - retag `y` protected
+    // - (wait for the other thread to return so that there is no foreign protector when we write)
+    // - attempt a write through `y`.
+    // - (UB should have occured by now, but the next step would be to
+    //    remove `y`'s protector)
+    let thread_y = thread::spawn(move || {
+        let b = (2, by);
+        synchronized!(b, "start");
+        let ptr = ptr;
+        synchronized!(b, "retag x (&mut, protect)");
+        synchronized!(b, "retag y (&mut, protect)");
+        fn as_mut(y: &mut u8, b: (usize, Arc<Barrier>)) -> *mut u8 {
+            synchronized!(b, "location where spurious read of x would happen in the target");
+            synchronized!(b, "ret x");
+            let y = y as *mut u8;
+            synchronized!(b, "write y");
+            unsafe {
+                *y = 2; //~ERROR: /write access through .* is forbidden/
+            }
+            synchronized!(b, "ret y");
+            y
+        }
+        let _y = as_mut(unsafe { &mut *ptr.0 }, b.clone());
+        synchronized!(b, "end");
+    });
+
+    thread_x.join().unwrap();
+    thread_y.join().unwrap();
+}
diff --git a/src/tools/miri/tests/fail/tree_borrows/spurious_read.stderr b/src/tools/miri/tests/fail/tree_borrows/spurious_read.stderr
new file mode 100644
index 00000000000..99ffb849339
--- /dev/null
+++ b/src/tools/miri/tests/fail/tree_borrows/spurious_read.stderr
@@ -0,0 +1,44 @@
+Thread 1 executing: start
+Thread 2 executing: start
+Thread 2 executing: retag x (&mut, protect)
+Thread 1 executing: retag x (&mut, protect)
+Thread 1 executing: retag y (&mut, protect)
+Thread 2 executing: retag y (&mut, protect)
+Thread 2 executing: location where spurious read of x would happen in the target
+Thread 1 executing: location where spurious read of x would happen in the target
+Thread 1 executing: ret x
+Thread 2 executing: ret x
+Thread 2 executing: write y
+Thread 1 executing: write y
+Thread 1 executing: ret y
+error: Undefined Behavior: write access through <TAG> is forbidden
+  --> $DIR/spurious_read.rs:LL:CC
+   |
+LL |                 *y = 2;
+   |                 ^^^^^^ write access through <TAG> is forbidden
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: the accessed tag <TAG> has state Reserved (conflicted) which forbids this child write access
+help: the accessed tag <TAG> was created here, in the initial state Reserved
+  --> $DIR/spurious_read.rs:LL:CC
+   |
+LL |         fn as_mut(y: &mut u8, b: (usize, Arc<Barrier>)) -> *mut u8 {
+   |                   ^
+help: the accessed tag <TAG> later transitioned to Reserved (conflicted) due to a protector release (acting as a foreign read access) on every location previously accessed by this tag
+  --> $DIR/spurious_read.rs:LL:CC
+   |
+LL |         }
+   |          ^
+   = help: this transition corresponds to a temporary loss of write permissions until function exit
+   = note: BACKTRACE (of the first span):
+   = note: inside `retagx_retagy_retx_writey_rety::{closure#1}::as_mut` at $DIR/spurious_read.rs:LL:CC
+note: inside closure
+  --> $DIR/spurious_read.rs:LL:CC
+   |
+LL |         let _y = as_mut(unsafe { &mut *ptr.0 }, b.clone());
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to previous error
+
diff --git a/src/tools/miri/tests/pass-dep/shims/libc-misc.rs b/src/tools/miri/tests/pass-dep/shims/libc-misc.rs
index ebfeb863abf..82c49cfb17c 100644
--- a/src/tools/miri/tests/pass-dep/shims/libc-misc.rs
+++ b/src/tools/miri/tests/pass-dep/shims/libc-misc.rs
@@ -3,6 +3,7 @@
 #![feature(io_error_more)]
 
 use std::fs::{remove_file, File};
+use std::mem::transmute;
 use std::os::unix::io::AsRawFd;
 use std::path::PathBuf;
 
@@ -375,6 +376,18 @@ fn test_sigrt() {
     assert!(max - min >= 8)
 }
 
+fn test_dlsym() {
+    let addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, b"notasymbol\0".as_ptr().cast()) };
+    assert!(addr as usize == 0);
+
+    let addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, b"isatty\0".as_ptr().cast()) };
+    assert!(addr as usize != 0);
+    let isatty: extern "C" fn(i32) -> i32 = unsafe { transmute(addr) };
+    assert_eq!(isatty(999), 0);
+    let errno = std::io::Error::last_os_error().raw_os_error().unwrap();
+    assert_eq!(errno, libc::EBADF);
+}
+
 fn main() {
     test_posix_gettimeofday();
     test_posix_mkstemp();
@@ -387,6 +400,7 @@ fn main() {
 
     test_isatty();
     test_clocks();
+    test_dlsym();
 
     test_memcpy();
     test_strcpy();
diff --git a/src/tools/miri/tests/pass/tree_borrows/cell-alternate-writes.stderr b/src/tools/miri/tests/pass/tree_borrows/cell-alternate-writes.stderr
index f464e0b4f49..57caa09c888 100644
--- a/src/tools/miri/tests/pass/tree_borrows/cell-alternate-writes.stderr
+++ b/src/tools/miri/tests/pass/tree_borrows/cell-alternate-writes.stderr
@@ -1,12 +1,12 @@
 ──────────────────────────────────────────────────
 Warning: this tree is indicative only. Some tags may have been hidden.
-0..  1
-| Act|    └─┬──<TAG=root of the allocation>
-| Re*|      └────<TAG=data, x, y>
+0..   1
+| Act |    └─┬──<TAG=root of the allocation>
+| RsM |      └────<TAG=data, x, y>
 ──────────────────────────────────────────────────
 ──────────────────────────────────────────────────
 Warning: this tree is indicative only. Some tags may have been hidden.
-0..  1
-| Act|    └─┬──<TAG=root of the allocation>
-| Act|      └────<TAG=data, x, y>
+0..   1
+| Act |    └─┬──<TAG=root of the allocation>
+| Act |      └────<TAG=data, x, y>
 ──────────────────────────────────────────────────
diff --git a/src/tools/miri/tests/pass/tree_borrows/end-of-protector.stderr b/src/tools/miri/tests/pass/tree_borrows/end-of-protector.stderr
index 265f6dfc9c8..69b8a17dc5e 100644
--- a/src/tools/miri/tests/pass/tree_borrows/end-of-protector.stderr
+++ b/src/tools/miri/tests/pass/tree_borrows/end-of-protector.stderr
@@ -1,36 +1,36 @@
 ──────────────────────────────────────────────────
 Warning: this tree is indicative only. Some tags may have been hidden.
-0..  1
-| Act|    └─┬──<TAG=root of the allocation>
-| Res|      └─┬──<TAG=data>
-| Res|        └────<TAG=x>
+0..   1
+| Act |    └─┬──<TAG=root of the allocation>
+| Rs  |      └─┬──<TAG=data>
+| Rs  |        └────<TAG=x>
 ──────────────────────────────────────────────────
 ──────────────────────────────────────────────────
 Warning: this tree is indicative only. Some tags may have been hidden.
-0..  1
-| Act|    └─┬──<TAG=root of the allocation>
-| Res|      └─┬──<TAG=data>
-| Res|        └─┬──<TAG=x>
-| Res|          └─┬──<TAG=caller:x>
-| Res|            └────<TAG=callee:x> Strongly protected
+0..   1
+| Act |    └─┬──<TAG=root of the allocation>
+| Rs  |      └─┬──<TAG=data>
+| Rs  |        └─┬──<TAG=x>
+| Rs  |          └─┬──<TAG=caller:x>
+| Rs  |            └────<TAG=callee:x> Strongly protected
 ──────────────────────────────────────────────────
 ──────────────────────────────────────────────────
 Warning: this tree is indicative only. Some tags may have been hidden.
-0..  1
-| Act|    └─┬──<TAG=root of the allocation>
-| Res|      └─┬──<TAG=data>
-| Res|        ├─┬──<TAG=x>
-| Res|        │ └─┬──<TAG=caller:x>
-| Res|        │   └────<TAG=callee:x>
-| Res|        └────<TAG=y>
+0..   1
+| Act |    └─┬──<TAG=root of the allocation>
+| Rs  |      └─┬──<TAG=data>
+| Rs  |        ├─┬──<TAG=x>
+| Rs  |        │ └─┬──<TAG=caller:x>
+| Rs  |        │   └────<TAG=callee:x>
+| Rs  |        └────<TAG=y>
 ──────────────────────────────────────────────────
 ──────────────────────────────────────────────────
 Warning: this tree is indicative only. Some tags may have been hidden.
-0..  1
-| Act|    └─┬──<TAG=root of the allocation>
-| Act|      └─┬──<TAG=data>
-| Dis|        ├─┬──<TAG=x>
-| Dis|        │ └─┬──<TAG=caller:x>
-| Dis|        │   └────<TAG=callee:x>
-| Act|        └────<TAG=y>
+0..   1
+| Act |    └─┬──<TAG=root of the allocation>
+| Act |      └─┬──<TAG=data>
+| Dis |        ├─┬──<TAG=x>
+| Dis |        │ └─┬──<TAG=caller:x>
+| Dis |        │   └────<TAG=callee:x>
+| Act |        └────<TAG=y>
 ──────────────────────────────────────────────────
diff --git a/src/tools/miri/tests/pass/tree_borrows/formatting.stderr b/src/tools/miri/tests/pass/tree_borrows/formatting.stderr
index 673dae6210d..235ab68fe01 100644
--- a/src/tools/miri/tests/pass/tree_borrows/formatting.stderr
+++ b/src/tools/miri/tests/pass/tree_borrows/formatting.stderr
@@ -1,31 +1,31 @@
 ──────────────────────────────────────────────────
 Warning: this tree is indicative only. Some tags may have been hidden.
-0..  1..  2.. 10.. 11..100..101..1000..1001..1024
-| Act| Act| Act| Act| Act| Act|  Act|  Act|  Act|    └─┬──<TAG=root of the allocation>
-| Res| Act| Res| Act| Res| Act|  Res|  Act|  Res|      └─┬──<TAG=data, data>
-|----| Act|----|?Dis|----|?Dis| ----| ?Dis| ----|        ├────<TAG=data[1]>
-|----|----|----| Act|----|?Dis| ----| ?Dis| ----|        ├────<TAG=data[10]>
-|----|----|----|----|----| Frz| ----| ?Dis| ----|        ├────<TAG=data[100]>
-|----|----|----|----|----|----| ----|  Act| ----|        └────<TAG=data[1000]>
+0..   1..   2..  10..  11.. 100.. 101..1000..1001..1024
+| Act | Act | Act | Act | Act | Act | Act | Act | Act |    └─┬──<TAG=root of the allocation>
+| Rs  | Act | Rs  | Act | Rs  | Act | Rs  | Act | Rs  |      └─┬──<TAG=data, data>
+|-----| Act |-----|?Dis |-----|?Dis |-----|?Dis |-----|        ├────<TAG=data[1]>
+|-----|-----|-----| Act |-----|?Dis |-----|?Dis |-----|        ├────<TAG=data[10]>
+|-----|-----|-----|-----|-----| Frz |-----|?Dis |-----|        ├────<TAG=data[100]>
+|-----|-----|-----|-----|-----|-----|-----| Act |-----|        └────<TAG=data[1000]>
 ──────────────────────────────────────────────────
 ──────────────────────────────────────────────────
 Warning: this tree is indicative only. Some tags may have been hidden.
-0..  1
-| Act|    └─┬──<TAG=root of the allocation>
-| Frz|      └─┬──<TAG=x>
-| Frz|        ├─┬──<TAG=xa>
-| Frz|        │ ├────<TAG=xaa>
-| Frz|        │ └────<TAG=xab>
-| Frz|        ├─┬──<TAG=xb>
-| Frz|        │ └─┬──<TAG=xba>
-| Frz|        │   └─┬──<TAG=xbaa>
-| Frz|        │     └─┬──<TAG=xbaaa>
-| Frz|        │       └────<TAG=xbaaaa>
-| Frz|        └─┬──<TAG=xc>
-| Frz|          ├─┬──<TAG=xca>
-| Frz|          │ ├────<TAG=xcaa>
-| Frz|          │ └────<TAG=xcab>
-| Frz|          └─┬──<TAG=xcb>
-| Frz|            ├────<TAG=xcba>
-| Frz|            └────<TAG=xcbb>
+0..   1
+| Act |    └─┬──<TAG=root of the allocation>
+| Frz |      └─┬──<TAG=x>
+| Frz |        ├─┬──<TAG=xa>
+| Frz |        │ ├────<TAG=xaa>
+| Frz |        │ └────<TAG=xab>
+| Frz |        ├─┬──<TAG=xb>
+| Frz |        │ └─┬──<TAG=xba>
+| Frz |        │   └─┬──<TAG=xbaa>
+| Frz |        │     └─┬──<TAG=xbaaa>
+| Frz |        │       └────<TAG=xbaaaa>
+| Frz |        └─┬──<TAG=xc>
+| Frz |          ├─┬──<TAG=xca>
+| Frz |          │ ├────<TAG=xcaa>
+| Frz |          │ └────<TAG=xcab>
+| Frz |          └─┬──<TAG=xcb>
+| Frz |            ├────<TAG=xcba>
+| Frz |            └────<TAG=xcbb>
 ──────────────────────────────────────────────────
diff --git a/src/tools/miri/tests/pass/tree_borrows/read_retag_no_race.rs b/src/tools/miri/tests/pass/tree_borrows/read_retag_no_race.rs
new file mode 100644
index 00000000000..d9897a1033f
--- /dev/null
+++ b/src/tools/miri/tests/pass/tree_borrows/read_retag_no_race.rs
@@ -0,0 +1,114 @@
+//@compile-flags: -Zmiri-tree-borrows
+// This test relies on a specific interleaving that cannot be enforced
+// with just barriers. We must remove preemption so that the execution and the
+// error messages are deterministic.
+//@compile-flags: -Zmiri-preemption-rate=0
+use std::ptr::addr_of_mut;
+use std::sync::{Arc, Barrier};
+use std::thread;
+
+#[derive(Copy, Clone)]
+struct SendPtr(*mut u8);
+
+unsafe impl Send for SendPtr {}
+
+// This test features the problematic pattern
+//
+// read x     || retag y (&mut, protect)
+//        -- sync --
+//            || write y
+//
+// In which
+// - one interleaving (`1:read; 2:retag; 2:write`) does not have UB if retags
+//   count only as reads for the data race model,
+// - the other interleaving (`2:retag; 1:read; 2:write`) has UB (`noalias` violation).
+//
+// The interleaving executed here is the one that does not have UB,
+// i.e.
+//      1:read x
+//      2:retag y
+//      2:write y
+//
+// Tree Borrows considers that the read of `x` cannot be in conflict
+// with `y` because `y` did not even exist yet when `x` was accessed.
+//
+// As long as we are not emitting any writes for the data race model
+// upon retags of mutable references, it should not have any issue with
+// this code either.
+// We do not want to emit a write for the data race model, because
+// although there is race-like behavior going on in this pattern
+// (where some but not all interleavings contain UB), making this an actual
+// data race has the confusing consequence of one single access being treated
+// as being of different `AccessKind`s by different parts of Miri
+// (a retag would be always a read for the aliasing model, and sometimes a write
+// for the data race model).
+
+// The other interleaving is a subsequence of `tests/fail/tree_borrows/spurious_read.rs`
+// which asserts that
+//      2:retag y
+//      1:read x
+//      2:write y
+// is UB.
+
+type IdxBarrier = (usize, Arc<Barrier>);
+// We're going to enforce a specific interleaving of two
+// threads, we use this macro in an effort to make it feasible
+// to check in the output that the execution is properly synchronized.
+//
+// Provide `synchronized!(thread, msg)` where thread is
+// a `(thread_id: usize, barrier: Arc<Barrier>)`, and `msg` the message
+// to be displayed when the thread reaches this point in the execution.
+macro_rules! synchronized {
+    ($thread:expr, $msg:expr) => {{
+        let (thread_id, barrier) = &$thread;
+        eprintln!("Thread {} executing: {}", thread_id, $msg);
+        barrier.wait();
+    }};
+}
+
+fn thread_1(x: SendPtr, barrier: IdxBarrier) {
+    let x = unsafe { &mut *x.0 };
+    synchronized!(barrier, "spawn");
+
+    synchronized!(barrier, "read x || retag y");
+    // This is the interleaving without UB: by the time
+    // the other thread starts retagging, this thread
+    // has already finished all its work using `y`.
+    let _v = *x;
+    synchronized!(barrier, "write y");
+    synchronized!(barrier, "exit");
+}
+
+fn thread_2(y: SendPtr, barrier: IdxBarrier) {
+    let y = unsafe { &mut *y.0 };
+    synchronized!(barrier, "spawn");
+
+    fn write(y: &mut u8, v: u8, barrier: &IdxBarrier) {
+        synchronized!(barrier, "write y");
+        *y = v;
+    }
+    synchronized!(barrier, "read x || retag y");
+    // We don't use a barrier here so that *if* the retag counted as a write
+    // for the data race model, then it would be UB.
+    // We still want to make sure that the other thread goes first as per the
+    // interleaving that we are testing, so we use `yield_now + preemption-rate=0`
+    // which has the effect of forcing a specific interleaving while still
+    // not counting as "synchronization" from the point of view of the data
+    // race model.
+    thread::yield_now();
+    write(&mut *y, 42, &barrier);
+    synchronized!(barrier, "exit");
+}
+
+fn main() {
+    let mut data = 0u8;
+    let p = SendPtr(addr_of_mut!(data));
+    let barrier = Arc::new(Barrier::new(2));
+    let b1 = (1, Arc::clone(&barrier));
+    let b2 = (2, Arc::clone(&barrier));
+
+    let h1 = thread::spawn(move || thread_1(p, b1));
+    let h2 = thread::spawn(move || thread_2(p, b2));
+    h1.join().unwrap();
+    h2.join().unwrap();
+}
diff --git a/src/tools/miri/tests/pass/tree_borrows/read_retag_no_race.stderr b/src/tools/miri/tests/pass/tree_borrows/read_retag_no_race.stderr
new file mode 100644
index 00000000000..f0903008511
--- /dev/null
+++ b/src/tools/miri/tests/pass/tree_borrows/read_retag_no_race.stderr
@@ -0,0 +1,8 @@
+Thread 1 executing: spawn
+Thread 2 executing: spawn
+Thread 2 executing: read x || retag y
+Thread 1 executing: read x || retag y
+Thread 1 executing: write y
+Thread 2 executing: write y
+Thread 2 executing: exit
+Thread 1 executing: exit
diff --git a/src/tools/miri/tests/pass/tree_borrows/reborrow-is-read.stderr b/src/tools/miri/tests/pass/tree_borrows/reborrow-is-read.stderr
index b23d78a7156..f09aa52f1a1 100644
--- a/src/tools/miri/tests/pass/tree_borrows/reborrow-is-read.stderr
+++ b/src/tools/miri/tests/pass/tree_borrows/reborrow-is-read.stderr
@@ -1,15 +1,15 @@
 ──────────────────────────────────────────────────
 Warning: this tree is indicative only. Some tags may have been hidden.
-0..  1
-| Act|    └─┬──<TAG=root of the allocation>
-| Act|      └─┬──<TAG=parent>
-| Act|        └────<TAG=x>
+0..   1
+| Act |    └─┬──<TAG=root of the allocation>
+| Act |      └─┬──<TAG=parent>
+| Act |        └────<TAG=x>
 ──────────────────────────────────────────────────
 ──────────────────────────────────────────────────
 Warning: this tree is indicative only. Some tags may have been hidden.
-0..  1
-| Act|    └─┬──<TAG=root of the allocation>
-| Act|      └─┬──<TAG=parent>
-| Frz|        ├────<TAG=x>
-| Res|        └────<TAG=y>
+0..   1
+| Act |    └─┬──<TAG=root of the allocation>
+| Act |      └─┬──<TAG=parent>
+| Frz |        ├────<TAG=x>
+| Rs  |        └────<TAG=y>
 ──────────────────────────────────────────────────
diff --git a/src/tools/miri/tests/pass/tree_borrows/reserved.stderr b/src/tools/miri/tests/pass/tree_borrows/reserved.stderr
index 691fe8b7744..0d0d52c717f 100644
--- a/src/tools/miri/tests/pass/tree_borrows/reserved.stderr
+++ b/src/tools/miri/tests/pass/tree_borrows/reserved.stderr
@@ -1,58 +1,58 @@
 [interior mut + protected] Foreign Read: Re* -> Frz
 ──────────────────────────────────────────────────
 Warning: this tree is indicative only. Some tags may have been hidden.
-0..  1
-| Act|    └─┬──<TAG=root of the allocation>
-| Re*|      └─┬──<TAG=base>
-| Re*|        ├─┬──<TAG=x>
-| Re*|        │ └─┬──<TAG=caller:x>
-| Frz|        │   └────<TAG=callee:x>
-| Re*|        └────<TAG=y, caller:y, callee:y>
+0..   1
+| Act |    └─┬──<TAG=root of the allocation>
+| RsM |      └─┬──<TAG=base>
+| RsM |        ├─┬──<TAG=x>
+| RsM |        │ └─┬──<TAG=caller:x>
+| RsCM|        │   └────<TAG=callee:x>
+| RsM |        └────<TAG=y, caller:y, callee:y>
 ──────────────────────────────────────────────────
 [interior mut] Foreign Read: Re* -> Re*
 ──────────────────────────────────────────────────
 Warning: this tree is indicative only. Some tags may have been hidden.
-0..  8
-| Act|    └─┬──<TAG=root of the allocation>
-| Re*|      └─┬──<TAG=base>
-| Re*|        ├────<TAG=x>
-| Re*|        └────<TAG=y>
+0..   8
+| Act |    └─┬──<TAG=root of the allocation>
+| RsM |      └─┬──<TAG=base>
+| RsM |        ├────<TAG=x>
+| RsM |        └────<TAG=y>
 ──────────────────────────────────────────────────
 [interior mut] Foreign Write: Re* -> Re*
 ──────────────────────────────────────────────────
 Warning: this tree is indicative only. Some tags may have been hidden.
-0..  8
-| Act|    └─┬──<TAG=root of the allocation>
-| Act|      └─┬──<TAG=base>
-| Re*|        ├────<TAG=x>
-| Act|        └────<TAG=y>
+0..   8
+| Act |    └─┬──<TAG=root of the allocation>
+| Act |      └─┬──<TAG=base>
+| RsM |        ├────<TAG=x>
+| Act |        └────<TAG=y>
 ──────────────────────────────────────────────────
 [protected] Foreign Read: Res -> Frz
 ──────────────────────────────────────────────────
 Warning: this tree is indicative only. Some tags may have been hidden.
-0..  1
-| Act|    └─┬──<TAG=root of the allocation>
-| Res|      └─┬──<TAG=base>
-| Res|        ├─┬──<TAG=x>
-| Res|        │ └─┬──<TAG=caller:x>
-| Frz|        │   └────<TAG=callee:x>
-| Res|        └────<TAG=y, caller:y, callee:y>
+0..   1
+| Act |    └─┬──<TAG=root of the allocation>
+| Rs  |      └─┬──<TAG=base>
+| Rs  |        ├─┬──<TAG=x>
+| Rs  |        │ └─┬──<TAG=caller:x>
+| RsC |        │   └────<TAG=callee:x>
+| Rs  |        └────<TAG=y, caller:y, callee:y>
 ──────────────────────────────────────────────────
 [] Foreign Read: Res -> Res
 ──────────────────────────────────────────────────
 Warning: this tree is indicative only. Some tags may have been hidden.
-0..  1
-| Act|    └─┬──<TAG=root of the allocation>
-| Res|      └─┬──<TAG=base>
-| Res|        ├────<TAG=x>
-| Res|        └────<TAG=y>
+0..   1
+| Act |    └─┬──<TAG=root of the allocation>
+| Rs  |      └─┬──<TAG=base>
+| Rs  |        ├────<TAG=x>
+| Rs  |        └────<TAG=y>
 ──────────────────────────────────────────────────
 [] Foreign Write: Res -> Dis
 ──────────────────────────────────────────────────
 Warning: this tree is indicative only. Some tags may have been hidden.
-0..  1
-| Act|    └─┬──<TAG=root of the allocation>
-| Act|      └─┬──<TAG=base>
-| Dis|        ├────<TAG=x>
-| Act|        └────<TAG=y>
+0..   1
+| Act |    └─┬──<TAG=root of the allocation>
+| Act |      └─┬──<TAG=base>
+| Dis |        ├────<TAG=x>
+| Act |        └────<TAG=y>
 ──────────────────────────────────────────────────
diff --git a/src/tools/miri/tests/pass/tree_borrows/spurious_read.rs b/src/tools/miri/tests/pass/tree_borrows/spurious_read.rs
new file mode 100644
index 00000000000..71e93d2f84f
--- /dev/null
+++ b/src/tools/miri/tests/pass/tree_borrows/spurious_read.rs
@@ -0,0 +1,115 @@
+// We ensure a deterministic execution.
+// Note that we are *also* using barriers: the barriers enforce the
+// specific interleaving of operations that we want, but only the preemption
+// rate guarantees that the error message is also deterministic.
+//@compile-flags: -Zmiri-preemption-rate=0
+//@compile-flags: -Zmiri-tree-borrows
+
+use std::sync::{Arc, Barrier};
+use std::thread;
+
+// A way to send raw pointers across threads.
+// Note that when using this in closures will require explicit copying
+// `let ptr = ptr;` to force the borrow checker to copy the `Send` wrapper
+// instead of just copying the inner `!Send` field.
+#[derive(Copy, Clone)]
+struct SendPtr(*mut u8);
+unsafe impl Send for SendPtr {}
+
+fn main() {
+    retagx_retagy_spuriousx_retx_rety_writey();
+}
+
+// We're going to enforce a specific interleaving of two
+// threads, we use this macro in an effort to make it feasible
+// to check in the output that the execution is properly synchronized.
+//
+// Provide `synchronized!(thread, msg)` where thread is
+// a `(thread_id: usize, barrier: Arc<Barrier>)`, and `msg` the message
+// to be displayed when the thread reaches this point in the execution.
+macro_rules! synchronized {
+    ($thread:expr, $msg:expr) => {{
+        let (thread_id, barrier) = &$thread;
+        eprintln!("Thread {} executing: {}", thread_id, $msg);
+        barrier.wait();
+    }};
+}
+
+// Interleaving:
+//   retag x (protect)
+//   retag y (protect)
+//   spurious read x (target only, which we are executing)
+//   ret x
+//   ret y
+//   write y
+//
+// This is an interleaving that will never have UB in the source
+// (`x` is never accessed for the entire time that `y` is protected).
+// For the spurious read to be allowed, we need to check that there is
+// no UB in the target (i.e., *with* the spurious read).
+//
+// The interleaving differs from the one in `tests/fail/tree_borrows/spurious_read.rs` only
+// in that it has the `write y` while `y` is no longer protected.
+// When the write occurs after protection ends, both source and target are fine
+// (checked by this test); when the write occurs during protection, both source
+// and target are UB (checked by the `fail` test).
+fn retagx_retagy_spuriousx_retx_rety_writey() {
+    let mut data = 0u8;
+    let ptr = SendPtr(std::ptr::addr_of_mut!(data));
+    let barrier = Arc::new(Barrier::new(2));
+    let bx = Arc::clone(&barrier);
+    let by = Arc::clone(&barrier);
+
+    // This thread only needs to
+    // - retag `x` protected
+    // - do a read through `x`
+    // - remove `x`'s protector
+    // Most of the complexity here is synchronization.
+    let thread_x = thread::spawn(move || {
+        let b = (1, bx);
+        synchronized!(b, "start");
+        let ptr = ptr;
+        synchronized!(b, "retag x (&mut, protect)");
+        fn as_mut(x: &mut u8, b: (usize, Arc<Barrier>)) -> *mut u8 {
+            synchronized!(b, "retag y (&mut, protect)");
+            synchronized!(b, "spurious read x");
+            let _v = *x;
+            synchronized!(b, "ret x");
+            let x = x as *mut u8;
+            x
+        }
+        let _x = as_mut(unsafe { &mut *ptr.0 }, b.clone());
+        synchronized!(b, "ret y");
+        synchronized!(b, "write y");
+        synchronized!(b, "end");
+    });
+
+    // This thread's job is to
+    // - retag `y` protected
+    // - (wait a bit that the other thread performs its spurious read)
+    // - remove `y`'s protector
+    // - attempt a write through `y`.
+    let thread_y = thread::spawn(move || {
+        let b = (2, by);
+        synchronized!(b, "start");
+        let ptr = ptr;
+        synchronized!(b, "retag x (&mut, protect)");
+        synchronized!(b, "retag y (&mut, protect)");
+        fn as_mut(y: &mut u8, b: (usize, Arc<Barrier>)) -> *mut u8 {
+            synchronized!(b, "spurious read x");
+            synchronized!(b, "ret x");
+            let y = y as *mut u8;
+            y
+        }
+        let y = as_mut(unsafe { &mut *ptr.0 }, b.clone());
+        synchronized!(b, "ret y");
+        synchronized!(b, "write y");
+        unsafe {
+            *y = 2;
+        }
+        synchronized!(b, "end");
+    });
+
+    thread_x.join().unwrap();
+    thread_y.join().unwrap();
+}
diff --git a/src/tools/miri/tests/pass/tree_borrows/spurious_read.stderr b/src/tools/miri/tests/pass/tree_borrows/spurious_read.stderr
new file mode 100644
index 00000000000..45191eaf9c4
--- /dev/null
+++ b/src/tools/miri/tests/pass/tree_borrows/spurious_read.stderr
@@ -0,0 +1,16 @@
+Thread 1 executing: start
+Thread 2 executing: start
+Thread 2 executing: retag x (&mut, protect)
+Thread 1 executing: retag x (&mut, protect)
+Thread 1 executing: retag y (&mut, protect)
+Thread 2 executing: retag y (&mut, protect)
+Thread 2 executing: spurious read x
+Thread 1 executing: spurious read x
+Thread 1 executing: ret x
+Thread 2 executing: ret x
+Thread 2 executing: ret y
+Thread 1 executing: ret y
+Thread 1 executing: write y
+Thread 2 executing: write y
+Thread 2 executing: end
+Thread 1 executing: end
diff --git a/src/tools/miri/tests/pass/tree_borrows/tree-borrows.rs b/src/tools/miri/tests/pass/tree_borrows/tree-borrows.rs
index 531543441c2..89752bffe9f 100644
--- a/src/tools/miri/tests/pass/tree_borrows/tree-borrows.rs
+++ b/src/tools/miri/tests/pass/tree_borrows/tree-borrows.rs
@@ -12,6 +12,7 @@ fn main() {
     two_mut_protected_same_alloc();
     direct_mut_to_const_raw();
     local_addr_of_mut();
+    returned_mut_is_usable();
 
     // Stacked Borrows tests
     read_does_not_invalidate1();
@@ -93,6 +94,24 @@ fn two_mut_protected_same_alloc() {
     write_second(&mut data.0, &mut data.1);
 }
 
+// This checks that a reborrowed mutable reference returned from a function
+// is actually writeable.
+// The fact that this is not obvious is due to the addition of
+// implicit reads on function exit that might freeze the return value.
+fn returned_mut_is_usable() {
+    fn reborrow(x: &mut u8) -> &mut u8 {
+        let y = &mut *x;
+        // Activate the reference so that it is vulnerable to foreign reads.
+        *y = *y;
+        y
+        // An implicit read through `x` is inserted here.
+    }
+    let mut data = 0;
+    let x = &mut data;
+    let y = reborrow(x);
+    *y = 1;
+}
+
 // ----- The tests below were taken from Stacked Borrows ----
 
 // Make sure that reading from an `&mut` does, like reborrowing to `&`,
diff --git a/src/tools/miri/tests/pass/tree_borrows/unique.default.stderr b/src/tools/miri/tests/pass/tree_borrows/unique.default.stderr
index f870d3bdec0..6e774e5014d 100644
--- a/src/tools/miri/tests/pass/tree_borrows/unique.default.stderr
+++ b/src/tools/miri/tests/pass/tree_borrows/unique.default.stderr
@@ -1,21 +1,21 @@
 ──────────────────────────────────────────────────
 Warning: this tree is indicative only. Some tags may have been hidden.
-0..  1
-| Act|    └─┬──<TAG=root of the allocation>
-| Res|      └─┬──<TAG=base>
-| Res|        └────<TAG=raw, uniq, uniq>
+0..   1
+| Act |    └─┬──<TAG=root of the allocation>
+| Rs  |      └─┬──<TAG=base>
+| Rs  |        └────<TAG=raw, uniq, uniq>
 ──────────────────────────────────────────────────
 ──────────────────────────────────────────────────
 Warning: this tree is indicative only. Some tags may have been hidden.
-0..  1
-| Act|    └─┬──<TAG=root of the allocation>
-| Act|      └─┬──<TAG=base>
-| Act|        └────<TAG=raw, uniq, uniq>
+0..   1
+| Act |    └─┬──<TAG=root of the allocation>
+| Act |      └─┬──<TAG=base>
+| Act |        └────<TAG=raw, uniq, uniq>
 ──────────────────────────────────────────────────
 ──────────────────────────────────────────────────
 Warning: this tree is indicative only. Some tags may have been hidden.
-0..  1
-| Act|    └─┬──<TAG=root of the allocation>
-| Act|      └─┬──<TAG=base>
-| Act|        └────<TAG=raw, uniq, uniq>
+0..   1
+| Act |    └─┬──<TAG=root of the allocation>
+| Act |      └─┬──<TAG=base>
+| Act |        └────<TAG=raw, uniq, uniq>
 ──────────────────────────────────────────────────
diff --git a/src/tools/miri/tests/pass/tree_borrows/unique.uniq.stderr b/src/tools/miri/tests/pass/tree_borrows/unique.uniq.stderr
index 9ab6b879aa7..26d9ad2ad38 100644
--- a/src/tools/miri/tests/pass/tree_borrows/unique.uniq.stderr
+++ b/src/tools/miri/tests/pass/tree_borrows/unique.uniq.stderr
@@ -1,24 +1,24 @@
 ──────────────────────────────────────────────────
 Warning: this tree is indicative only. Some tags may have been hidden.
-0..  1
-| Act|    └─┬──<TAG=root of the allocation>
-| Res|      └─┬──<TAG=base>
-| Res|        └─┬──<TAG=raw>
-|----|          └────<TAG=uniq, uniq>
+0..   1
+| Act |    └─┬──<TAG=root of the allocation>
+| Rs  |      └─┬──<TAG=base>
+| Rs  |        └─┬──<TAG=raw>
+|-----|          └────<TAG=uniq, uniq>
 ──────────────────────────────────────────────────
 ──────────────────────────────────────────────────
 Warning: this tree is indicative only. Some tags may have been hidden.
-0..  1
-| Act|    └─┬──<TAG=root of the allocation>
-| Act|      └─┬──<TAG=base>
-| Act|        └─┬──<TAG=raw>
-| Act|          └────<TAG=uniq, uniq>
+0..   1
+| Act |    └─┬──<TAG=root of the allocation>
+| Act |      └─┬──<TAG=base>
+| Act |        └─┬──<TAG=raw>
+| Act |          └────<TAG=uniq, uniq>
 ──────────────────────────────────────────────────
 ──────────────────────────────────────────────────
 Warning: this tree is indicative only. Some tags may have been hidden.
-0..  1
-| Act|    └─┬──<TAG=root of the allocation>
-| Act|      └─┬──<TAG=base>
-| Act|        └─┬──<TAG=raw>
-| Dis|          └────<TAG=uniq, uniq>
+0..   1
+| Act |    └─┬──<TAG=root of the allocation>
+| Act |      └─┬──<TAG=base>
+| Act |        └─┬──<TAG=raw>
+| Dis |          └────<TAG=uniq, uniq>
 ──────────────────────────────────────────────────
diff --git a/src/tools/miri/tests/pass/tree_borrows/vec_unique.default.stderr b/src/tools/miri/tests/pass/tree_borrows/vec_unique.default.stderr
index a7712ae91fb..f63aa1f6834 100644
--- a/src/tools/miri/tests/pass/tree_borrows/vec_unique.default.stderr
+++ b/src/tools/miri/tests/pass/tree_borrows/vec_unique.default.stderr
@@ -1,6 +1,6 @@
 ──────────────────────────────────────────────────
 Warning: this tree is indicative only. Some tags may have been hidden.
-0..  2
-| Act|    └─┬──<TAG=root of the allocation>
-| Res|      └────<TAG=base.as_ptr(), base.as_ptr(), raw_parts.0, reconstructed.as_ptr(), reconstructed.as_ptr()>
+0..   2
+| Act |    └─┬──<TAG=root of the allocation>
+| Rs  |      └────<TAG=base.as_ptr(), base.as_ptr(), raw_parts.0, reconstructed.as_ptr(), reconstructed.as_ptr()>
 ──────────────────────────────────────────────────
diff --git a/src/tools/miri/tests/pass/tree_borrows/vec_unique.uniq.stderr b/src/tools/miri/tests/pass/tree_borrows/vec_unique.uniq.stderr
index e9f1cb3b1ed..7942e9884f4 100644
--- a/src/tools/miri/tests/pass/tree_borrows/vec_unique.uniq.stderr
+++ b/src/tools/miri/tests/pass/tree_borrows/vec_unique.uniq.stderr
@@ -1,8 +1,8 @@
 ──────────────────────────────────────────────────
 Warning: this tree is indicative only. Some tags may have been hidden.
-0..  2
-| Act|    └─┬──<TAG=root of the allocation>
-|----|      └─┬──<TAG=base.as_ptr(), base.as_ptr()>
-|----|        └─┬──<TAG=raw_parts.0>
-|----|          └────<TAG=reconstructed.as_ptr(), reconstructed.as_ptr()>
+0..   2
+| Act |    └─┬──<TAG=root of the allocation>
+|-----|      └─┬──<TAG=base.as_ptr(), base.as_ptr()>
+|-----|        └─┬──<TAG=raw_parts.0>
+|-----|          └────<TAG=reconstructed.as_ptr(), reconstructed.as_ptr()>
 ──────────────────────────────────────────────────
diff --git a/src/tools/tidy/src/deps.rs b/src/tools/tidy/src/deps.rs
index 7d3ef4197a7..49210967c44 100644
--- a/src/tools/tidy/src/deps.rs
+++ b/src/tools/tidy/src/deps.rs
@@ -47,6 +47,7 @@ const EXCEPTIONS: &[(&str, &str)] = &[
     ("instant", "BSD-3-Clause"),                             // rustc_driver/tracing-subscriber/parking_lot
     ("mdbook", "MPL-2.0"),                                   // mdbook
     ("openssl", "Apache-2.0"),                               // opt-dist
+    ("option-ext", "MPL-2.0"),                               // cargo-miri (via `directories`)
     ("rustc_apfloat", "Apache-2.0 WITH LLVM-exception"),     // rustc (license is the same as LLVM uses)
     ("ryu", "Apache-2.0 OR BSL-1.0"),                        // cargo/... (because of serde)
     ("self_cell", "Apache-2.0"),                             // rustc (fluent translations)