diff --git a/.gitmodules b/.gitmodules
index bc2dd39774154..c850493780613 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -13,9 +13,6 @@
 [submodule "src/doc/book"]
 	path = src/doc/book
 	url = https://github.com/rust-lang/book.git
-[submodule "src/tools/miri"]
-	path = src/tools/miri
-	url = https://github.com/rust-lang/miri.git
 [submodule "src/doc/rust-by-example"]
 	path = src/doc/rust-by-example
 	url = https://github.com/rust-lang/rust-by-example.git
diff --git a/src/README.md b/src/README.md
index 3d2e6acd5764b..90ab802697088 100644
--- a/src/README.md
+++ b/src/README.md
@@ -2,7 +2,7 @@ This directory contains the source code of the rust project, including:
 
 - The test suite
 - The bootstrapping build system
-- Various submodules for tools, like cargo, miri, etc.
+- Various submodules for tools, like cargo, etc.
 
 For more information on how various parts of the compiler work, see the [rustc dev guide].
 
diff --git a/src/bootstrap/check.rs b/src/bootstrap/check.rs
index 5c085bedf0eef..229851238f1d0 100644
--- a/src/bootstrap/check.rs
+++ b/src/bootstrap/check.rs
@@ -456,6 +456,8 @@ tool_check_step!(Rustdoc, "src/tools/rustdoc", "src/librustdoc", SourceType::InT
 // behavior, treat it as in-tree so that any new warnings in clippy will be
 // rejected.
 tool_check_step!(Clippy, "src/tools/clippy", SourceType::InTree);
+// Miri on the other hand is treated as out of tree, since InTree also causes it to
+// be run as part of `check`, which can fail on platforms which libffi-sys has no support for.
 tool_check_step!(Miri, "src/tools/miri", SourceType::Submodule);
 tool_check_step!(Rls, "src/tools/rls", SourceType::InTree);
 tool_check_step!(Rustfmt, "src/tools/rustfmt", SourceType::InTree);
diff --git a/src/bootstrap/dist.rs b/src/bootstrap/dist.rs
index c9ee3c1c7d65d..05664ca21794f 100644
--- a/src/bootstrap/dist.rs
+++ b/src/bootstrap/dist.rs
@@ -35,18 +35,6 @@ pub fn tmpdir(builder: &Builder<'_>) -> PathBuf {
     builder.out.join("tmp/dist")
 }
 
-fn missing_tool(tool_name: &str, skip: bool) {
-    if skip {
-        println!("Unable to build {}, skipping dist", tool_name)
-    } else {
-        let help = "note: not all tools are available on all nightlies\nhelp: see https://forge.rust-lang.org/infra/toolstate.html for more information";
-        panic!(
-            "Unable to build submodule tool {} (use `missing-tools = true` to ignore this failure)\n{}",
-            tool_name, help
-        )
-    }
-}
-
 fn should_build_extended_tool(builder: &Builder<'_>, tool: &str) -> bool {
     if !builder.config.extended {
         return false;
@@ -1209,18 +1197,9 @@ impl Step for Miri {
         let compiler = self.compiler;
         let target = self.target;
 
-        let miri = builder
-            .ensure(tool::Miri { compiler, target, extra_features: Vec::new() })
-            .or_else(|| {
-                missing_tool("miri", builder.build.config.missing_tools);
-                None
-            })?;
-        let cargomiri = builder
-            .ensure(tool::CargoMiri { compiler, target, extra_features: Vec::new() })
-            .or_else(|| {
-                missing_tool("cargo miri", builder.build.config.missing_tools);
-                None
-            })?;
+        let miri = builder.ensure(tool::Miri { compiler, target, extra_features: Vec::new() })?;
+        let cargomiri =
+            builder.ensure(tool::CargoMiri { compiler, target, extra_features: Vec::new() })?;
 
         let mut tarball = Tarball::new(builder, "miri", &target.triple);
         tarball.set_overlay(OverlayKind::Miri);
@@ -1451,7 +1430,7 @@ impl Step for Extended {
 
         let xform = |p: &Path| {
             let mut contents = t!(fs::read_to_string(p));
-            for tool in &["rust-demangler", "rust-analyzer", "miri", "rustfmt"] {
+            for tool in &["rust-demangler", "rust-analyzer", "rustfmt"] {
                 if !built_tools.contains(tool) {
                     contents = filter(&contents, tool);
                 }
@@ -1491,7 +1470,8 @@ impl Step for Extended {
             prepare("rust-std");
             prepare("rust-analysis");
             prepare("clippy");
-            for tool in &["rust-docs", "rust-demangler", "rust-analyzer", "miri"] {
+            prepare("miri");
+            for tool in &["rust-docs", "rust-demangler", "rust-analyzer"] {
                 if built_tools.contains(tool) {
                     prepare(tool);
                 }
@@ -1550,7 +1530,8 @@ impl Step for Extended {
             prepare("rust-docs");
             prepare("rust-std");
             prepare("clippy");
-            for tool in &["rust-demangler", "rust-analyzer", "miri"] {
+            prepare("miri");
+            for tool in &["rust-demangler", "rust-analyzer"] {
                 if built_tools.contains(tool) {
                     prepare(tool);
                 }
@@ -1689,25 +1670,23 @@ impl Step for Extended {
                         .arg(etc.join("msi/remove-duplicates.xsl")),
                 );
             }
-            if built_tools.contains("miri") {
-                builder.run(
-                    Command::new(&heat)
-                        .current_dir(&exe)
-                        .arg("dir")
-                        .arg("miri")
-                        .args(&heat_flags)
-                        .arg("-cg")
-                        .arg("MiriGroup")
-                        .arg("-dr")
-                        .arg("Miri")
-                        .arg("-var")
-                        .arg("var.MiriDir")
-                        .arg("-out")
-                        .arg(exe.join("MiriGroup.wxs"))
-                        .arg("-t")
-                        .arg(etc.join("msi/remove-duplicates.xsl")),
-                );
-            }
+            builder.run(
+                Command::new(&heat)
+                    .current_dir(&exe)
+                    .arg("dir")
+                    .arg("miri")
+                    .args(&heat_flags)
+                    .arg("-cg")
+                    .arg("MiriGroup")
+                    .arg("-dr")
+                    .arg("Miri")
+                    .arg("-var")
+                    .arg("var.MiriDir")
+                    .arg("-out")
+                    .arg(exe.join("MiriGroup.wxs"))
+                    .arg("-t")
+                    .arg(etc.join("msi/remove-duplicates.xsl")),
+            );
             builder.run(
                 Command::new(&heat)
                     .current_dir(&exe)
@@ -1755,6 +1734,7 @@ impl Step for Extended {
                     .arg("-dStdDir=rust-std")
                     .arg("-dAnalysisDir=rust-analysis")
                     .arg("-dClippyDir=clippy")
+                    .arg("-dMiriDir=miri")
                     .arg("-arch")
                     .arg(&arch)
                     .arg("-out")
@@ -1768,9 +1748,6 @@ impl Step for Extended {
                 if built_tools.contains("rust-analyzer") {
                     cmd.arg("-dRustAnalyzerDir=rust-analyzer");
                 }
-                if built_tools.contains("miri") {
-                    cmd.arg("-dMiriDir=miri");
-                }
                 if target.ends_with("windows-gnu") {
                     cmd.arg("-dGccDir=rust-mingw");
                 }
@@ -1784,15 +1761,13 @@ impl Step for Extended {
             candle("CargoGroup.wxs".as_ref());
             candle("StdGroup.wxs".as_ref());
             candle("ClippyGroup.wxs".as_ref());
+            candle("MiriGroup.wxs".as_ref());
             if built_tools.contains("rust-demangler") {
                 candle("RustDemanglerGroup.wxs".as_ref());
             }
             if built_tools.contains("rust-analyzer") {
                 candle("RustAnalyzerGroup.wxs".as_ref());
             }
-            if built_tools.contains("miri") {
-                candle("MiriGroup.wxs".as_ref());
-            }
             candle("AnalysisGroup.wxs".as_ref());
 
             if target.ends_with("windows-gnu") {
@@ -1822,6 +1797,7 @@ impl Step for Extended {
                 .arg("StdGroup.wixobj")
                 .arg("AnalysisGroup.wixobj")
                 .arg("ClippyGroup.wixobj")
+                .arg("MiriGroup.wixobj")
                 .current_dir(&exe);
 
             if built_tools.contains("rust-analyzer") {
@@ -1830,9 +1806,6 @@ impl Step for Extended {
             if built_tools.contains("rust-demangler") {
                 cmd.arg("RustDemanglerGroup.wixobj");
             }
-            if built_tools.contains("miri") {
-                cmd.arg("MiriGroup.wixobj");
-            }
 
             if target.ends_with("windows-gnu") {
                 cmd.arg("GccGroup.wixobj");
diff --git a/src/bootstrap/install.rs b/src/bootstrap/install.rs
index d34aa15c51539..7672b7c913594 100644
--- a/src/bootstrap/install.rs
+++ b/src/bootstrap/install.rs
@@ -200,13 +200,10 @@ install!((self, builder, _config),
         install_sh(builder, "clippy", self.compiler.stage, Some(self.target), &tarball);
     };
     Miri, alias = "miri", Self::should_build(_config), only_hosts: true, {
-        if let Some(tarball) = builder.ensure(dist::Miri { compiler: self.compiler, target: self.target }) {
-            install_sh(builder, "miri", self.compiler.stage, Some(self.target), &tarball);
-        } else {
-            builder.info(
-                &format!("skipping Install miri stage{} ({})", self.compiler.stage, self.target),
-            );
-        }
+        let tarball = builder
+            .ensure(dist::Miri { compiler: self.compiler, target: self.target })
+            .expect("missing miri");
+        install_sh(builder, "miri", self.compiler.stage, Some(self.target), &tarball);
     };
     Rustfmt, alias = "rustfmt", Self::should_build(_config), only_hosts: true, {
         if let Some(tarball) = builder.ensure(dist::Rustfmt {
diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs
index b7d271e6494b1..2b8c2a2c134b0 100644
--- a/src/bootstrap/lib.rs
+++ b/src/bootstrap/lib.rs
@@ -540,13 +540,8 @@ impl Build {
 
         // Make sure we update these before gathering metadata so we don't get an error about missing
         // Cargo.toml files.
-        let rust_submodules = [
-            "src/tools/rust-installer",
-            "src/tools/cargo",
-            "src/tools/miri",
-            "library/backtrace",
-            "library/stdarch",
-        ];
+        let rust_submodules =
+            ["src/tools/rust-installer", "src/tools/cargo", "library/backtrace", "library/stdarch"];
         for s in rust_submodules {
             build.update_submodule(Path::new(s));
         }
diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs
index 9d286ddd6d164..1617875ec231c 100644
--- a/src/bootstrap/test.rs
+++ b/src/bootstrap/test.rs
@@ -484,116 +484,103 @@ impl Step for Miri {
         // Except if we are at stage 2, the bootstrap loop is complete and we can stick with our current stage.
         let compiler_std = builder.compiler(if stage < 2 { stage + 1 } else { stage }, host);
 
-        let miri =
-            builder.ensure(tool::Miri { compiler, target: self.host, extra_features: Vec::new() });
-        let cargo_miri = builder.ensure(tool::CargoMiri {
-            compiler,
-            target: self.host,
-            extra_features: Vec::new(),
-        });
+        let miri = builder
+            .ensure(tool::Miri { compiler, target: self.host, extra_features: Vec::new() })
+            .expect("in-tree tool");
+        let _cargo_miri = builder
+            .ensure(tool::CargoMiri { compiler, target: self.host, extra_features: Vec::new() })
+            .expect("in-tree tool");
         // The stdlib we need might be at a different stage. And just asking for the
         // sysroot does not seem to populate it, so we do that first.
         builder.ensure(compile::Std::new(compiler_std, host));
         let sysroot = builder.sysroot(compiler_std);
-        if let (Some(miri), Some(_cargo_miri)) = (miri, cargo_miri) {
-            let mut cargo =
-                builder.cargo(compiler, Mode::ToolRustc, SourceType::Submodule, host, "install");
-            cargo.arg("xargo");
-            // Configure `cargo install` path. cargo adds a `bin/`.
-            cargo.env("CARGO_INSTALL_ROOT", &builder.out);
-
-            let mut cargo = Command::from(cargo);
-            if !try_run(builder, &mut cargo) {
-                return;
-            }
-
-            // # Run `cargo miri setup`.
-            let mut cargo = tool::prepare_tool_cargo(
-                builder,
-                compiler,
-                Mode::ToolRustc,
-                host,
-                "run",
-                "src/tools/miri/cargo-miri",
-                SourceType::Submodule,
-                &[],
-            );
-            cargo.add_rustc_lib_path(builder, compiler);
-            cargo.arg("--").arg("miri").arg("setup");
-
-            // Tell `cargo miri setup` where to find the sources.
-            cargo.env("XARGO_RUST_SRC", builder.src.join("library"));
-            // Tell it where to find Miri.
-            cargo.env("MIRI", &miri);
-            // Debug things.
-            cargo.env("RUST_BACKTRACE", "1");
-            // Let cargo-miri know where xargo ended up.
-            cargo.env("XARGO_CHECK", builder.out.join("bin").join("xargo-check"));
-
-            let mut cargo = Command::from(cargo);
-            if !try_run(builder, &mut cargo) {
-                return;
-            }
+        let mut cargo =
+            builder.cargo(compiler, Mode::ToolRustc, SourceType::Submodule, host, "install");
+        cargo.arg("xargo");
+        // Configure `cargo install` path. cargo adds a `bin/`.
+        cargo.env("CARGO_INSTALL_ROOT", &builder.out);
 
-            // # Determine where Miri put its sysroot.
-            // To this end, we run `cargo miri setup --print-sysroot` and capture the output.
-            // (We do this separately from the above so that when the setup actually
-            // happens we get some output.)
-            // We re-use the `cargo` from above.
-            cargo.arg("--print-sysroot");
-
-            // FIXME: Is there a way in which we can re-use the usual `run` helpers?
-            let miri_sysroot = if builder.config.dry_run {
-                String::new()
-            } else {
-                builder.verbose(&format!("running: {:?}", cargo));
-                let out = cargo
-                    .output()
-                    .expect("We already ran `cargo miri setup` before and that worked");
-                assert!(out.status.success(), "`cargo miri setup` returned with non-0 exit code");
-                // Output is "<sysroot>\n".
-                let stdout = String::from_utf8(out.stdout)
-                    .expect("`cargo miri setup` stdout is not valid UTF-8");
-                let sysroot = stdout.trim_end();
-                builder.verbose(&format!("`cargo miri setup --print-sysroot` said: {:?}", sysroot));
-                sysroot.to_owned()
-            };
-
-            // # Run `cargo test`.
-            let mut cargo = tool::prepare_tool_cargo(
-                builder,
-                compiler,
-                Mode::ToolRustc,
-                host,
-                "test",
-                "src/tools/miri",
-                SourceType::Submodule,
-                &[],
-            );
-            cargo.add_rustc_lib_path(builder, compiler);
-
-            // miri tests need to know about the stage sysroot
-            cargo.env("MIRI_SYSROOT", miri_sysroot);
-            cargo.env("MIRI_HOST_SYSROOT", sysroot);
-            cargo.env("RUSTC_LIB_PATH", builder.rustc_libdir(compiler));
-            cargo.env("MIRI", miri);
-            // propagate --bless
-            if builder.config.cmd.bless() {
-                cargo.env("MIRI_BLESS", "Gesundheit");
-            }
+        let mut cargo = Command::from(cargo);
+        if !try_run(builder, &mut cargo) {
+            return;
+        }
 
-            cargo.arg("--").args(builder.config.cmd.test_args());
+        // # Run `cargo miri setup`.
+        let mut cargo = tool::prepare_tool_cargo(
+            builder,
+            compiler,
+            Mode::ToolRustc,
+            host,
+            "run",
+            "src/tools/miri/cargo-miri",
+            SourceType::Submodule,
+            &[],
+        );
+        cargo.add_rustc_lib_path(builder, compiler);
+        cargo.arg("--").arg("miri").arg("setup");
+
+        // Tell `cargo miri setup` where to find the sources.
+        cargo.env("XARGO_RUST_SRC", builder.src.join("library"));
+        // Tell it where to find Miri.
+        cargo.env("MIRI", &miri);
+        // Debug things.
+        cargo.env("RUST_BACKTRACE", "1");
+        // Let cargo-miri know where xargo ended up.
+        cargo.env("XARGO_CHECK", builder.out.join("bin").join("xargo-check"));
+
+        let mut cargo = Command::from(cargo);
+        builder.run(&mut cargo);
+
+        // # Determine where Miri put its sysroot.
+        // To this end, we run `cargo miri setup --print-sysroot` and capture the output.
+        // (We do this separately from the above so that when the setup actually
+        // happens we get some output.)
+        // We re-use the `cargo` from above.
+        cargo.arg("--print-sysroot");
+
+        // FIXME: Is there a way in which we can re-use the usual `run` helpers?
+        let miri_sysroot = if builder.config.dry_run {
+            String::new()
+        } else {
+            builder.verbose(&format!("running: {:?}", cargo));
+            let out =
+                cargo.output().expect("We already ran `cargo miri setup` before and that worked");
+            assert!(out.status.success(), "`cargo miri setup` returned with non-0 exit code");
+            // Output is "<sysroot>\n".
+            let stdout = String::from_utf8(out.stdout)
+                .expect("`cargo miri setup` stdout is not valid UTF-8");
+            let sysroot = stdout.trim_end();
+            builder.verbose(&format!("`cargo miri setup --print-sysroot` said: {:?}", sysroot));
+            sysroot.to_owned()
+        };
 
-            let mut cargo = Command::from(cargo);
-            if !try_run(builder, &mut cargo) {
-                return;
-            }
+        // # Run `cargo test`.
+        let mut cargo = tool::prepare_tool_cargo(
+            builder,
+            compiler,
+            Mode::ToolRustc,
+            host,
+            "test",
+            "src/tools/miri",
+            SourceType::Submodule,
+            &[],
+        );
+        cargo.add_rustc_lib_path(builder, compiler);
 
-            // # Done!
-            builder.save_toolstate("miri", ToolState::TestPass);
-        } else {
-            eprintln!("failed to test miri: could not build");
+        // miri tests need to know about the stage sysroot
+        cargo.env("MIRI_SYSROOT", miri_sysroot);
+        cargo.env("MIRI_HOST_SYSROOT", sysroot);
+        cargo.env("RUSTC_LIB_PATH", builder.rustc_libdir(compiler));
+        cargo.env("MIRI", miri);
+        // propagate --bless
+        if builder.config.cmd.bless() {
+            cargo.env("MIRI_BLESS", "Gesundheit");
         }
+
+        cargo.arg("--").args(builder.config.cmd.test_args());
+
+        let mut cargo = Command::from(cargo);
+        builder.run(&mut cargo);
     }
 }
 
diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs
index 5d0c7d2bd9d44..ff6f7909a5a3b 100644
--- a/src/bootstrap/tool.rs
+++ b/src/bootstrap/tool.rs
@@ -868,8 +868,8 @@ tool_extended!((self, builder),
     Cargofmt, "src/tools/rustfmt", "cargo-fmt", stable=true, in_tree=true, {};
     CargoClippy, "src/tools/clippy", "cargo-clippy", stable=true, in_tree=true, {};
     Clippy, "src/tools/clippy", "clippy-driver", stable=true, in_tree=true, {};
-    Miri, "src/tools/miri", "miri", stable=false, {};
-    CargoMiri, "src/tools/miri/cargo-miri", "cargo-miri", stable=false, {};
+    Miri, "src/tools/miri", "miri", stable=false, in_tree=true, {};
+    CargoMiri, "src/tools/miri/cargo-miri", "cargo-miri", stable=false, in_tree=true, {};
     Rls, "src/tools/rls", "rls", stable=true, {};
     // FIXME: tool_std is not quite right, we shouldn't allow nightly features.
     // But `builder.cargo` doesn't know how to handle ToolBootstrap in stages other than 0,
diff --git a/src/bootstrap/toolstate.rs b/src/bootstrap/toolstate.rs
index f3a6759ab846b..1a17744322753 100644
--- a/src/bootstrap/toolstate.rs
+++ b/src/bootstrap/toolstate.rs
@@ -77,7 +77,6 @@ static STABLE_TOOLS: &[(&str, &str)] = &[
 // though, as otherwise we will be unable to file an issue if they start
 // failing.
 static NIGHTLY_TOOLS: &[(&str, &str)] = &[
-    ("miri", "src/tools/miri"),
     ("embedded-book", "src/doc/embedded-book"),
     // ("rustc-dev-guide", "src/doc/rustc-dev-guide"),
 ];
diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-tools/checktools.sh b/src/ci/docker/host-x86_64/x86_64-gnu-tools/checktools.sh
index 0fb8f41a7ec66..cf00c285b0a63 100755
--- a/src/ci/docker/host-x86_64/x86_64-gnu-tools/checktools.sh
+++ b/src/ci/docker/host-x86_64/x86_64-gnu-tools/checktools.sh
@@ -14,7 +14,6 @@ python3 "$X_PY" test --stage 2 --no-fail-fast \
     src/doc/rust-by-example \
     src/doc/embedded-book \
     src/doc/edition-guide \
-    src/tools/miri \
 
 set -e
 
@@ -23,3 +22,4 @@ cat /tmp/toolstate/toolstates.json
 python3 "$X_PY" test --stage 2 check-tools
 python3 "$X_PY" test --stage 2 src/tools/clippy
 python3 "$X_PY" test --stage 2 src/tools/rustfmt
+python3 "$X_PY" test --stage 2 src/tools/miri
diff --git a/src/tools/build-manifest/src/main.rs b/src/tools/build-manifest/src/main.rs
index 1a6760d8c68b9..02af0b9ee792a 100644
--- a/src/tools/build-manifest/src/main.rs
+++ b/src/tools/build-manifest/src/main.rs
@@ -11,9 +11,9 @@ mod versions;
 use crate::checksum::Checksums;
 use crate::manifest::{Component, Manifest, Package, Rename, Target};
 use crate::versions::{PkgType, Versions};
-use std::collections::{BTreeMap, HashMap, HashSet};
+use std::collections::{BTreeMap, HashSet};
 use std::env;
-use std::fs::{self, File};
+use std::fs;
 use std::path::{Path, PathBuf};
 
 static HOSTS: &[&str] = &[
@@ -239,7 +239,6 @@ fn main() {
 
 impl Builder {
     fn build(&mut self) {
-        self.check_toolstate();
         let manifest = self.build_manifest();
 
         let channel = self.versions.channel().to_string();
@@ -261,29 +260,6 @@ impl Builder {
         t!(self.checksums.store_cache());
     }
 
-    /// If a tool does not pass its tests on *any* of Linux and Windows, don't ship
-    /// it on *all* targets, because tools like Miri can "cross-run" programs for
-    /// different targets, for example, run a program for `x86_64-pc-windows-msvc`
-    /// on `x86_64-unknown-linux-gnu`.
-    /// Right now, we do this only for Miri.
-    fn check_toolstate(&mut self) {
-        for file in &["toolstates-linux.json", "toolstates-windows.json"] {
-            let toolstates: Option<HashMap<String, String>> = File::open(self.input.join(file))
-                .ok()
-                .and_then(|f| serde_json::from_reader(&f).ok());
-            let toolstates = toolstates.unwrap_or_else(|| {
-                println!("WARNING: `{}` missing/malformed; assuming all tools failed", file);
-                HashMap::default() // Use empty map if anything went wrong.
-            });
-            // Mark some tools as missing based on toolstate.
-            if toolstates.get("miri").map(|s| &*s as &str) != Some("test-pass") {
-                println!("Miri tests are not passing, removing component");
-                self.versions.disable_version(&PkgType::Miri);
-                break;
-            }
-        }
-    }
-
     fn build_manifest(&mut self) -> Manifest {
         let mut manifest = Manifest {
             manifest_version: "2".to_string(),
diff --git a/src/tools/build-manifest/src/versions.rs b/src/tools/build-manifest/src/versions.rs
index 95c2297de264b..92ef9968fe525 100644
--- a/src/tools/build-manifest/src/versions.rs
+++ b/src/tools/build-manifest/src/versions.rs
@@ -157,17 +157,6 @@ impl Versions {
         Ok(VersionInfo { version, git_commit, present: true })
     }
 
-    pub(crate) fn disable_version(&mut self, package: &PkgType) {
-        match self.versions.get_mut(package) {
-            Some(version) => {
-                *version = VersionInfo::default();
-            }
-            None => {
-                self.versions.insert(package.clone(), VersionInfo::default());
-            }
-        }
-    }
-
     pub(crate) fn archive_name(
         &self,
         package: &PkgType,
diff --git a/src/tools/miri b/src/tools/miri
deleted file mode 160000
index beed5eddb0f73..0000000000000
--- a/src/tools/miri
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit beed5eddb0f73f6721681560c73a51e3f15b8681
diff --git a/src/tools/miri/.editorconfig b/src/tools/miri/.editorconfig
new file mode 100644
index 0000000000000..3c1f41bdcca6c
--- /dev/null
+++ b/src/tools/miri/.editorconfig
@@ -0,0 +1,25 @@
+# EditorConfig helps developers define and maintain consistent
+# coding styles between different editors and IDEs
+# editorconfig.org
+
+root = true
+
+
+[*]
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+indent_style = space
+indent_size = 4
+
+[*.rs]
+indent_style = space
+indent_size = 4
+
+[*.toml]
+indent_style = space
+indent_size = 4
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/src/tools/miri/.gitattributes b/src/tools/miri/.gitattributes
new file mode 100644
index 0000000000000..2742e4d1d5b9b
--- /dev/null
+++ b/src/tools/miri/.gitattributes
@@ -0,0 +1,6 @@
+* text=auto eol=lf
+
+# Older git versions try to fix line endings on images, this prevents it.
+*.png binary
+*.jpg binary
+*.ico binary
diff --git a/src/tools/miri/.github/workflows/ci.yml b/src/tools/miri/.github/workflows/ci.yml
new file mode 100644
index 0000000000000..80d150e1df15d
--- /dev/null
+++ b/src/tools/miri/.github/workflows/ci.yml
@@ -0,0 +1,156 @@
+name: CI
+
+on:
+  push:
+    # Run in PRs and for bors, but not on master.
+    branches:
+      - 'auto'
+      - 'try'
+  pull_request:
+    branches:
+      - 'master'
+  schedule:
+    - cron: '5 15 * * *' # At 15:05 UTC every day.
+
+jobs:
+  build:
+    runs-on: ${{ matrix.os }}
+    env:
+      RUST_BACKTRACE: 1
+      HOST_TARGET: ${{ matrix.host_target }}
+    strategy:
+      matrix:
+        build: [linux64, macos, win32]
+        include:
+          - build: linux64
+            os: ubuntu-latest
+            host_target: x86_64-unknown-linux-gnu
+          - build: macos
+            os: macos-latest
+            host_target: x86_64-apple-darwin
+          - build: win32
+            os: windows-latest
+            host_target: i686-pc-windows-msvc
+    steps:
+      - uses: actions/checkout@v3
+
+      - name: Set the tag GC interval to 1 on linux
+        if: runner.os == 'Linux'
+        run: echo "MIRIFLAGS=-Zmiri-tag-gc=1" >> $GITHUB_ENV
+
+      # We install gnu-tar because BSD tar is buggy on macOS builders of GHA.
+      # See <https://github.com/actions/cache/issues/403>.
+      - name: Install GNU tar
+        if: runner.os == 'macOS'
+        run: |
+          brew install gnu-tar
+          echo "/usr/local/opt/gnu-tar/libexec/gnubin" >> $GITHUB_PATH
+
+      # Cache the global cargo directory, but NOT the local `target` directory which
+      # we cannot reuse anyway when the nightly changes (and it grows quite large
+      # over time).
+      - name: Add cache for cargo
+        id: cache
+        uses: actions/cache@v3
+        with:
+          path: |
+            # Taken from <https://doc.rust-lang.org/nightly/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci>.
+            ~/.cargo/bin
+            ~/.cargo/registry/index
+            ~/.cargo/registry/cache
+            ~/.cargo/git/db
+            # contains package information of crates installed via `cargo install`.
+            ~/.cargo/.crates.toml
+            ~/.cargo/.crates2.json
+          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock', 'cargo-miri/src/version.rs') }}
+          restore-keys: ${{ runner.os }}-cargo
+
+      - name: Install rustup-toolchain-install-master and xargo
+        if: ${{ steps.cache.outputs.cache-hit == 'false' }}
+        shell: bash
+        run: |
+          cargo install rustup-toolchain-install-master
+          cargo install xargo
+
+      - name: Install "master" toolchain
+        shell: bash
+        run: |
+          if [[ ${{ github.event_name }} == 'schedule' ]]; then
+            ./rustup-toolchain HEAD --host ${{ matrix.host_target }}
+          else
+            ./rustup-toolchain "" --host ${{ matrix.host_target }}
+          fi
+
+      - name: Show Rust version
+        run: |
+          rustup show
+          rustc -Vv
+          cargo -V
+
+      - name: Test
+        run: bash ./ci.sh
+
+  style:
+    name: style checks
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+      - name: Install required toolchain
+        # We need a toolchain that can actually build Miri, just a nightly won't do.
+        run: |
+          cargo install rustup-toolchain-install-master # TODO: cache this?
+          ./rustup-toolchain "" -c clippy
+      - name: rustfmt
+        run: ./miri fmt --check
+      - name: clippy
+        run: ./miri clippy -- -D warnings
+      - name: rustdoc
+        run: RUSTDOCFLAGS="-Dwarnings" cargo doc --document-private-items
+
+  # These jobs doesn't actually test anything, but they're only used to tell
+  # bors the build completed, as there is no practical way to detect when a
+  # workflow is successful listening to webhooks only.
+  #
+  # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB!
+  # (`fmt` is deliberately not listed, we want bors to ignore it.)
+  end-success:
+    name: bors build finished
+    runs-on: ubuntu-latest
+    needs: [build, style]
+    if: github.event.pusher.name == 'bors' && success()
+    steps:
+      - name: mark the job as a success
+        run: exit 0
+  end-failure:
+    name: bors build finished
+    runs-on: ubuntu-latest
+    needs: [build, style]
+    if: github.event.pusher.name == 'bors' && (failure() || cancelled())
+    steps:
+      - name: mark the job as a failure
+        run: exit 1
+
+  # Send a Zulip notification when a cron job fails
+  cron-fail-notify:
+    name: cronjob failure notification
+    runs-on: ubuntu-latest
+    needs: [build, style]
+    if: github.event_name == 'schedule' && (failure() || cancelled())
+    steps:
+      - name: Install zulip-send
+        run: pip3 install zulip
+      - name: Send Zulip notification
+        shell: bash
+        env:
+          ZULIP_BOT_EMAIL: ${{ secrets.ZULIP_BOT_EMAIL }}
+          ZULIP_API_TOKEN: ${{ secrets.ZULIP_API_TOKEN }}
+        run: |
+          ~/.local/bin/zulip-send --stream miri --subject "Cron Job Failure (miri, $(date -u +%Y-%m))" \
+            --message 'Dear @*T-miri*,
+
+          It would appear that the Miri cron job build failed. Would you mind investigating this issue?
+
+          Thanks in advance!
+          Sincerely,
+          The Miri Cronjobs Bot' \
+            --user $ZULIP_BOT_EMAIL --api-key $ZULIP_API_TOKEN --site https://rust-lang.zulipchat.com
diff --git a/src/tools/miri/.gitignore b/src/tools/miri/.gitignore
new file mode 100644
index 0000000000000..924a93e807fe3
--- /dev/null
+++ b/src/tools/miri/.gitignore
@@ -0,0 +1,13 @@
+target
+/doc
+tex/*/out
+*.dot
+*.out
+*.rs.bk
+.vscode
+*.mm_profdata
+perf.data
+perf.data.old
+flamegraph.svg
+tests/extern-so/libtestlib.so
+.auto-*
diff --git a/src/tools/miri/.gitpod.yml b/src/tools/miri/.gitpod.yml
new file mode 100644
index 0000000000000..36bd991740a82
--- /dev/null
+++ b/src/tools/miri/.gitpod.yml
@@ -0,0 +1,9 @@
+image: ubuntu:latest
+
+tasks:
+  - before: echo "..."
+    init: |
+      cargo install rustup-toolchain-install-master
+      ./rustup-toolchain
+      ./miri build
+    command: echo "Run tests with ./miri test"
\ No newline at end of file
diff --git a/src/tools/miri/CONTRIBUTING.md b/src/tools/miri/CONTRIBUTING.md
new file mode 100644
index 0000000000000..8d965ae8fcb8d
--- /dev/null
+++ b/src/tools/miri/CONTRIBUTING.md
@@ -0,0 +1,277 @@
+# Contribution Guide
+
+If you want to hack on Miri yourself, great!  Here are some resources you might
+find useful.
+
+## Getting started
+
+Check out the issues on this GitHub repository for some ideas. In particular,
+look for the green `E-*` labels which mark issues that should be rather
+well-suited for onboarding. For more ideas or help with hacking on Miri, you can
+contact us (`oli-obk` and `RalfJ`) on the [Rust Zulip].
+
+[Rust Zulip]: https://rust-lang.zulipchat.com
+
+## Preparing the build environment
+
+Miri heavily relies on internal and unstable rustc interfaces to execute MIR,
+which means it is important that you install a version of rustc that Miri
+actually works with.
+
+The `rust-version` file contains the commit hash of rustc that Miri is currently
+tested against. Other versions will likely not work. After installing
+[`rustup-toolchain-install-master`], you can run the following command to
+install that exact version of rustc as a toolchain:
+```
+./rustup-toolchain
+```
+This will set up a rustup toolchain called `miri` and set it as an override for
+the current directory.
+
+You can also create a `.auto-everything` file (contents don't matter, can be empty), which
+will cause any `./miri` command to automatically call `rustup-toolchain`, `clippy` and `rustfmt`
+for you. If you don't want all of these to happen, you can add individual `.auto-toolchain`,
+`.auto-clippy` and `.auto-fmt` files respectively.
+
+[`rustup-toolchain-install-master`]: https://github.com/kennytm/rustup-toolchain-install-master
+
+## Building and testing Miri
+
+Invoking Miri requires getting a bunch of flags right and setting up a custom
+sysroot with xargo. The `miri` script takes care of that for you. With the
+build environment prepared, compiling Miri is just one command away:
+
+```
+./miri build
+```
+
+Run `./miri` without arguments to see the other commands our build tool
+supports.
+
+### Testing the Miri driver
+
+The Miri driver compiled from `src/bin/miri.rs` is the "heart" of Miri: it is
+basically a version of `rustc` that, instead of compiling your code, runs it.
+It accepts all the same flags as `rustc` (though the ones only affecting code
+generation and linking obviously will have no effect) [and more][miri-flags].
+
+[miri-flags]: README.md#miri--z-flags-and-environment-variables
+
+For example, you can (cross-)run the driver on a particular file by doing
+
+```sh
+./miri run tests/pass/format.rs
+./miri run tests/pass/hello.rs --target i686-unknown-linux-gnu
+```
+
+and you can (cross-)run the entire test suite using:
+
+```
+./miri test
+MIRI_TEST_TARGET=i686-unknown-linux-gnu ./miri test
+```
+
+If your target doesn't support libstd, you can run miri with
+
+```
+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).
+
+You can get a trace of which MIR statements are being executed by setting the
+`MIRI_LOG` environment variable.  For example:
+
+```sh
+MIRI_LOG=info ./miri run tests/pass/vec.rs
+```
+
+Setting `MIRI_LOG` like this will configure logging for Miri itself as well as
+the `rustc_middle::mir::interpret` and `rustc_mir::interpret` modules in rustc. You
+can also do more targeted configuration, e.g. the following helps debug the
+stacked borrows implementation:
+
+```sh
+MIRI_LOG=rustc_mir::interpret=info,miri::stacked_borrows ./miri run tests/pass/vec.rs
+```
+
+In addition, you can set `MIRI_BACKTRACE=1` to get a backtrace of where an
+evaluation error was originally raised.
+
+#### UI testing
+
+We use ui-testing in Miri, meaning we generate `.stderr` and `.stdout` files for the output
+produced by Miri. You can use `./miri bless` to automatically (re)generate these files when
+you add new tests or change how Miri presents certain output.
+
+Note that when you also use `MIRIFLAGS` to change optimizations and similar, the ui output
+will change in unexpected ways. In order to still be able
+to run the other checks while ignoring the ui output, use `MIRI_SKIP_UI_CHECKS=1 ./miri test`.
+
+For more info on how to configure ui tests see [the documentation on the ui test crate][ui_test]
+
+[ui_test]: ui_test/README.md
+
+### Testing `cargo miri`
+
+Working with the driver directly gives you full control, but you also lose all
+the convenience provided by cargo. Once your test case depends on a crate, it
+is probably easier to test it with the cargo wrapper. You can install your
+development version of Miri using
+
+```
+./miri install
+```
+
+and then you can use it as if it was installed by `rustup`.  Make sure you use
+the same toolchain when calling `cargo miri` that you used when installing Miri!
+Usually this means you have to write `cargo +miri miri ...` to select the `miri`
+toolchain that was installed by `./rustup-toolchain`.
+
+There's a test for the cargo wrapper in the `test-cargo-miri` directory; run
+`./run-test.py` in there to execute it. Like `./miri test`, this respects the
+`MIRI_TEST_TARGET` environment variable to execute the test for another target.
+
+### Using a modified standard library
+
+Miri re-builds the standard library into a custom sysroot, so it is fairly easy
+to test Miri against a modified standard library -- you do not even have to
+build Miri yourself, the Miri shipped by `rustup` will work. All you have to do
+is set the `MIRI_LIB_SRC` environment variable to the `library` folder of a
+`rust-lang/rust` repository checkout. Note that changing files in that directory
+does not automatically trigger a re-build of the standard library; you have to
+clear the Miri build cache manually (on Linux, `rm -rf ~/.cache/miri`;
+and on Windows, `rmdir /S "%LOCALAPPDATA%\rust-lang\miri\cache"`).
+
+### Benchmarking
+
+Miri comes with a few benchmarks; you can run `./miri bench` to run them with the locally built
+Miri. Note: this will run `./miri install` as a side-effect. Also requires `hyperfine` to be
+installed (`cargo install hyperfine`).
+
+## Configuring `rust-analyzer`
+
+To configure `rust-analyzer` and VS Code for working on Miri, save the following
+to `.vscode/settings.json` in your local Miri clone:
+
+```json
+{
+    "rust-analyzer.rustc.source": "discover",
+    "rust-analyzer.linkedProjects": [
+        "./Cargo.toml",
+        "./cargo-miri/Cargo.toml"
+    ],
+    "rust-analyzer.checkOnSave.overrideCommand": [
+        "env",
+        "MIRI_AUTO_OPS=no",
+        "./miri",
+        "cargo",
+        "clippy", // make this `check` when working with a locally built rustc
+        "--message-format=json"
+    ],
+    // Contrary to what the name suggests, this also affects proc macros.
+    "rust-analyzer.cargo.buildScripts.overrideCommand": [
+        "env",
+        "MIRI_AUTO_OPS=no",
+        "./miri",
+        "cargo",
+        "check",
+        "--message-format=json",
+    ],
+}
+```
+
+> #### Note
+>
+> If you are [building Miri with a locally built rustc][], set
+> `rust-analyzer.rustcSource` to the relative path from your Miri clone to the
+> root `Cargo.toml` of the locally built rustc. For example, the path might look
+> like `../rust/Cargo.toml`.
+
+See the rustc-dev-guide's docs on ["Configuring `rust-analyzer` for `rustc`"][rdg-r-a]
+for more information about configuring VS Code and `rust-analyzer`.
+
+[rdg-r-a]: https://rustc-dev-guide.rust-lang.org/building/suggested.html#configuring-rust-analyzer-for-rustc
+
+## Advanced topic: other build environments
+
+We described above the simplest way to get a working build environment for Miri,
+which is to use the version of rustc indicated by `rustc-version`. But
+sometimes, that is not enough.
+
+### Updating `rustc-version`
+
+The `rustc-version` file is regularly updated to keep Miri close to the latest
+version of rustc. Usually, new contributors do not have to worry about this. But
+sometimes a newer rustc is needed for a patch, and sometimes Miri needs fixing
+for changes in rustc. In both cases, `rustc-version` needs updating.
+
+To update the `rustc-version` file and install the latest rustc, you can run:
+```
+./rustup-toolchain HEAD
+```
+
+Now edit Miri until `./miri test` passes, and submit a PR. Generally, it is
+preferred to separate updating `rustc-version` and doing what it takes to get
+Miri working again, from implementing new features that rely on the updated
+rustc. This avoids blocking all Miri development on landing a big PR.
+
+### Building Miri with a locally built rustc
+
+[building Miri with a locally built rustc]: #building-miri-with-a-locally-built-rustc
+
+A big part of the Miri driver lives in rustc, so working on Miri will sometimes
+require using a locally built rustc. The bug you want to fix may actually be on
+the rustc side, or you just need to get more detailed trace of the execution
+than what is possible with release builds -- in both cases, you should develop
+Miri against a rustc you compiled yourself, with debug assertions (and hence
+tracing) enabled.
+
+The setup for a local rustc works as follows:
+```sh
+# Clone the rust-lang/rust repo.
+git clone https://github.com/rust-lang/rust rustc
+cd rustc
+# Create a config.toml with defaults for working on Miri.
+./x.py setup compiler
+ # Now edit `config.toml` and under `[rust]` set `debug-assertions = true`.
+
+# Build a stage 2 rustc, and build the rustc libraries with that rustc.
+# This step can take 30 minutes or more.
+./x.py build --stage 2 compiler/rustc
+# If you change something, you can get a faster rebuild by doing
+./x.py build --keep-stage 0 --stage 2 compiler/rustc
+# You may have to change the architecture in the next command
+rustup toolchain link stage2 build/x86_64-unknown-linux-gnu/stage2
+# Now cd to your Miri directory, then configure rustup
+rustup override set stage2
+```
+
+Note: When you are working with a locally built rustc or any other toolchain that
+is not the same as the one in `rust-version`, you should not have `.auto-everything` or
+`.auto-toolchain` as that will keep resetting your toolchain.
+
+```
+rm -f .auto-everything .auto-toolchain
+```
+
+Important: You need to delete the Miri cache when you change the stdlib; otherwise the
+old, chached version will be used. On Linux, the cache is located at `~/.cache/miri`,
+and on Windows, it is located at `%LOCALAPPDATA%\rust-lang\miri\cache`; the exact
+location is printed after the library build: "A libstd for Miri is now available in ...".
+
+Note: `./x.py --stage 2 compiler/rustc` currently errors with `thread 'main'
+panicked at 'fs::read(stamp) failed with No such file or directory (os error 2)`,
+you can simply ignore that error; Miri will build anyway.
+
+For more information about building and configuring a local compiler,
+see <https://rustc-dev-guide.rust-lang.org/building/how-to-build-and-run.html>.
+
+With this, you should now have a working development setup! See
+[above](#building-and-testing-miri) for how to proceed working on Miri.
diff --git a/src/tools/miri/Cargo.lock b/src/tools/miri/Cargo.lock
new file mode 100644
index 0000000000000..9df35ec0deb2e
--- /dev/null
+++ b/src/tools/miri/Cargo.lock
@@ -0,0 +1,813 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[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.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61"
+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"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "camino"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "869119e97797867fd90f5e22af7d0bd274bd4635ebb9eb68c04f3f513ae6c412"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cargo-platform"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cargo_metadata"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3abb7553d5b9b8421c6de7cb02606ff15e0c6eea7d8eadd75ef013fd636bec36"
+dependencies = [
+ "camino",
+ "cargo-platform",
+ "semver",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "color-eyre"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ebf286c900a6d5867aeff75cfee3192857bb7f24b547d4f0df2ed6baa812c90"
+dependencies = [
+ "backtrace",
+ "color-spantrace",
+ "eyre",
+ "indenter",
+ "once_cell",
+ "owo-colors",
+ "tracing-error",
+]
+
+[[package]]
+name = "color-spantrace"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce"
+dependencies = [
+ "once_cell",
+ "owo-colors",
+ "tracing-core",
+ "tracing-error",
+]
+
+[[package]]
+name = "colored"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd"
+dependencies = [
+ "atty",
+ "lazy_static",
+ "winapi",
+]
+
+[[package]]
+name = "crossbeam"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ae5588f6b3c3cb05239e90bd110f257254aecd01e4635400391aeae07497845"
+dependencies = [
+ "cfg-if",
+ "crossbeam-channel",
+ "crossbeam-deque",
+ "crossbeam-epoch",
+ "crossbeam-queue",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e"
+dependencies = [
+ "cfg-if",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "crossbeam-utils",
+ "lazy_static",
+ "memoffset",
+ "scopeguard",
+]
+
+[[package]]
+name = "crossbeam-queue"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f25d8400f4a7a5778f0e4e52384a48cbd9b5c495d110786187fc750075277a2"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38"
+dependencies = [
+ "cfg-if",
+ "lazy_static",
+]
+
+[[package]]
+name = "diff"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
+
+[[package]]
+name = "env_logger"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
+dependencies = [
+ "atty",
+ "humantime",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "eyre"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb"
+dependencies = [
+ "indenter",
+ "once_cell",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "gimli"
+version = "0.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "indenter"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
+
+[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.112"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
+
+[[package]]
+name = "libffi"
+version = "3.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e454b3efb16fba3b17810ae5e41df02b649e564ab3c5a34b3b93ed07ad287e6"
+dependencies = [
+ "libc",
+ "libffi-sys",
+]
+
+[[package]]
+name = "libffi-sys"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab4106b7f09d7b87d021334d5618fac1dfcfb824d4c5fe111ff0074dfd242e15"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "libloading"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd"
+dependencies = [
+ "cfg-if",
+ "winapi",
+]
+
+[[package]]
+name = "lock_api"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "measureme"
+version = "10.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbdc226fa10994e8f66a4d2f6f000148bc563a1c671b6dcd2135737018033d8a"
+dependencies = [
+ "log",
+ "memmap2",
+ "parking_lot",
+ "perf-event-open-sys",
+ "rustc-hash",
+ "smallvec",
+]
+
+[[package]]
+name = "memchr"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
+
+[[package]]
+name = "memmap2"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "723e3ebdcdc5c023db1df315364573789f8857c11b631a2fdfad7c00f5c046b4"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "miri"
+version = "0.1.0"
+dependencies = [
+ "colored",
+ "env_logger",
+ "getrandom",
+ "lazy_static",
+ "libc",
+ "libffi",
+ "libloading",
+ "log",
+ "measureme",
+ "rand",
+ "regex",
+ "rustc-workspace-hack",
+ "shell-escape",
+ "smallvec",
+ "ui_test",
+]
+
+[[package]]
+name = "object"
+version = "0.28.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
+
+[[package]]
+name = "owo-colors"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b"
+
+[[package]]
+name = "parking_lot"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
+dependencies = [
+ "instant",
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
+dependencies = [
+ "cfg-if",
+ "instant",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "winapi",
+]
+
+[[package]]
+name = "perf-event-open-sys"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce9bedf5da2c234fdf2391ede2b90fabf585355f33100689bc364a3ea558561a"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+ "rand_hc",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "regex"
+version = "1.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "rustc-workspace-hack"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc71d2faa173b74b232dedc235e3ee1696581bb132fc116fa3626d6151a1a8fb"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "semver"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.137"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.137"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "shell-escape"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f"
+
+[[package]]
+name = "smallvec"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
+
+[[package]]
+name = "syn"
+version = "1.0.95"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "tracing"
+version = "0.1.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160"
+dependencies = [
+ "cfg-if",
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-error"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e"
+dependencies = [
+ "tracing",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a713421342a5a666b7577783721d3117f1b69a393df803ee17bb73b1e122a59"
+dependencies = [
+ "sharded-slab",
+ "thread_local",
+ "tracing-core",
+]
+
+[[package]]
+name = "ui_test"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d1f546a5883ae78da735bba529ec1116661e2f73582f23920d994dc97da3a22"
+dependencies = [
+ "cargo_metadata",
+ "color-eyre",
+ "colored",
+ "crossbeam",
+ "diff",
+ "lazy_static",
+ "regex",
+ "rustc_version",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
+
+[[package]]
+name = "valuable"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+
+[[package]]
+name = "wasi"
+version = "0.10.2+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
+
+[[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-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/src/tools/miri/Cargo.toml b/src/tools/miri/Cargo.toml
new file mode 100644
index 0000000000000..0c547d585d198
--- /dev/null
+++ b/src/tools/miri/Cargo.toml
@@ -0,0 +1,61 @@
+[package]
+authors = ["Miri Team"]
+description = "An experimental interpreter for Rust MIR (core driver)."
+license = "MIT OR Apache-2.0"
+name = "miri"
+repository = "https://github.com/rust-lang/miri"
+version = "0.1.0"
+default-run = "miri"
+edition = "2021"
+
+[lib]
+test = true # we have unit tests
+doctest = false # but no doc tests
+
+[[bin]]
+name = "miri"
+test = false # we have no unit tests
+doctest = false # and no doc tests
+
+[dependencies]
+getrandom = { version = "0.2", features = ["std"] }
+env_logger = "0.9"
+log = "0.4"
+shell-escape = "0.1.4"
+rand = "0.8"
+smallvec = "1.7"
+
+# A noop dependency that changes in the Rust repository, it's a bit of a hack.
+# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust`
+# for more information.
+rustc-workspace-hack = "1.0.0"
+measureme = "10.0.0"
+
+[target."cfg(unix)".dependencies]
+libc = "0.2"
+libffi = "3.0.0"
+libloading = "0.7"
+
+[dev-dependencies]
+colored = "2"
+ui_test = "0.3.1"
+# Features chosen to match those required by env_logger, to avoid rebuilds
+regex = { version = "1.5.5", default-features = false, features = ["perf", "std"] }
+lazy_static = "1.4.0"
+
+[package.metadata.rust-analyzer]
+# This crate uses #[feature(rustc_private)].
+# See https://github.com/rust-analyzer/rust-analyzer/pull/7891
+rustc_private = true
+
+[[test]]
+name = "compiletest"
+harness = false
+
+[features]
+default = ["stack-cache"]
+stack-cache = []
+
+# Be aware that this file is inside a workspace when used via the
+# submodule in the rustc repo. That means there are many cargo features
+# we cannot use, such as profiles.
diff --git a/src/tools/miri/LICENSE-APACHE b/src/tools/miri/LICENSE-APACHE
new file mode 100644
index 0000000000000..1b5ec8b78e237
--- /dev/null
+++ b/src/tools/miri/LICENSE-APACHE
@@ -0,0 +1,176 @@
+                              Apache License
+                        Version 2.0, January 2004
+                     http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+   "License" shall mean the terms and conditions for use, reproduction,
+   and distribution as defined by Sections 1 through 9 of this document.
+
+   "Licensor" shall mean the copyright owner or entity authorized by
+   the copyright owner that is granting the License.
+
+   "Legal Entity" shall mean the union of the acting entity and all
+   other entities that control, are controlled by, or are under common
+   control with that entity. For the purposes of this definition,
+   "control" means (i) the power, direct or indirect, to cause the
+   direction or management of such entity, whether by contract or
+   otherwise, or (ii) ownership of fifty percent (50%) or more of the
+   outstanding shares, or (iii) beneficial ownership of such entity.
+
+   "You" (or "Your") shall mean an individual or Legal Entity
+   exercising permissions granted by this License.
+
+   "Source" form shall mean the preferred form for making modifications,
+   including but not limited to software source code, documentation
+   source, and configuration files.
+
+   "Object" form shall mean any form resulting from mechanical
+   transformation or translation of a Source form, including but
+   not limited to compiled object code, generated documentation,
+   and conversions to other media types.
+
+   "Work" shall mean the work of authorship, whether in Source or
+   Object form, made available under the License, as indicated by a
+   copyright notice that is included in or attached to the work
+   (an example is provided in the Appendix below).
+
+   "Derivative Works" shall mean any work, whether in Source or Object
+   form, that is based on (or derived from) the Work and for which the
+   editorial revisions, annotations, elaborations, or other modifications
+   represent, as a whole, an original work of authorship. For the purposes
+   of this License, Derivative Works shall not include works that remain
+   separable from, or merely link (or bind by name) to the interfaces of,
+   the Work and Derivative Works thereof.
+
+   "Contribution" shall mean any work of authorship, including
+   the original version of the Work and any modifications or additions
+   to that Work or Derivative Works thereof, that is intentionally
+   submitted to Licensor for inclusion in the Work by the copyright owner
+   or by an individual or Legal Entity authorized to submit on behalf of
+   the copyright owner. For the purposes of this definition, "submitted"
+   means any form of electronic, verbal, or written communication sent
+   to the Licensor or its representatives, including but not limited to
+   communication on electronic mailing lists, source code control systems,
+   and issue tracking systems that are managed by, or on behalf of, the
+   Licensor for the purpose of discussing and improving the Work, but
+   excluding communication that is conspicuously marked or otherwise
+   designated in writing by the copyright owner as "Not a Contribution."
+
+   "Contributor" shall mean Licensor and any individual or Legal Entity
+   on behalf of whom a Contribution has been received by Licensor and
+   subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   copyright license to reproduce, prepare Derivative Works of,
+   publicly display, publicly perform, sublicense, and distribute the
+   Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   (except as stated in this section) patent license to make, have made,
+   use, offer to sell, sell, import, and otherwise transfer the Work,
+   where such license applies only to those patent claims licensable
+   by such Contributor that are necessarily infringed by their
+   Contribution(s) alone or by combination of their Contribution(s)
+   with the Work to which such Contribution(s) was submitted. If You
+   institute patent litigation against any entity (including a
+   cross-claim or counterclaim in a lawsuit) alleging that the Work
+   or a Contribution incorporated within the Work constitutes direct
+   or contributory patent infringement, then any patent licenses
+   granted to You under this License for that Work shall terminate
+   as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+   Work or Derivative Works thereof in any medium, with or without
+   modifications, and in Source or Object form, provided that You
+   meet the following conditions:
+
+   (a) You must give any other recipients of the Work or
+       Derivative Works a copy of this License; and
+
+   (b) You must cause any modified files to carry prominent notices
+       stating that You changed the files; and
+
+   (c) You must retain, in the Source form of any Derivative Works
+       that You distribute, all copyright, patent, trademark, and
+       attribution notices from the Source form of the Work,
+       excluding those notices that do not pertain to any part of
+       the Derivative Works; and
+
+   (d) If the Work includes a "NOTICE" text file as part of its
+       distribution, then any Derivative Works that You distribute must
+       include a readable copy of the attribution notices contained
+       within such NOTICE file, excluding those notices that do not
+       pertain to any part of the Derivative Works, in at least one
+       of the following places: within a NOTICE text file distributed
+       as part of the Derivative Works; within the Source form or
+       documentation, if provided along with the Derivative Works; or,
+       within a display generated by the Derivative Works, if and
+       wherever such third-party notices normally appear. The contents
+       of the NOTICE file are for informational purposes only and
+       do not modify the License. You may add Your own attribution
+       notices within Derivative Works that You distribute, alongside
+       or as an addendum to the NOTICE text from the Work, provided
+       that such additional attribution notices cannot be construed
+       as modifying the License.
+
+   You may add Your own copyright statement to Your modifications and
+   may provide additional or different license terms and conditions
+   for use, reproduction, or distribution of Your modifications, or
+   for any such Derivative Works as a whole, provided Your use,
+   reproduction, and distribution of the Work otherwise complies with
+   the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+   any Contribution intentionally submitted for inclusion in the Work
+   by You to the Licensor shall be under the terms and conditions of
+   this License, without any additional terms or conditions.
+   Notwithstanding the above, nothing herein shall supersede or modify
+   the terms of any separate license agreement you may have executed
+   with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+   names, trademarks, service marks, or product names of the Licensor,
+   except as required for reasonable and customary use in describing the
+   origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+   agreed to in writing, Licensor provides the Work (and each
+   Contributor provides its Contributions) on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+   implied, including, without limitation, any warranties or conditions
+   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+   PARTICULAR PURPOSE. You are solely responsible for determining the
+   appropriateness of using or redistributing the Work and assume any
+   risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+   whether in tort (including negligence), contract, or otherwise,
+   unless required by applicable law (such as deliberate and grossly
+   negligent acts) or agreed to in writing, shall any Contributor be
+   liable to You for damages, including any direct, indirect, special,
+   incidental, or consequential damages of any character arising as a
+   result of this License or out of the use or inability to use the
+   Work (including but not limited to damages for loss of goodwill,
+   work stoppage, computer failure or malfunction, or any and all
+   other commercial damages or losses), even if such Contributor
+   has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+   the Work or Derivative Works thereof, You may choose to offer,
+   and charge a fee for, acceptance of support, warranty, indemnity,
+   or other liability obligations and/or rights consistent with this
+   License. However, in accepting such obligations, You may act only
+   on Your own behalf and on Your sole responsibility, not on behalf
+   of any other Contributor, and only if You agree to indemnify,
+   defend, and hold each Contributor harmless for any liability
+   incurred by, or claims asserted against, such Contributor by reason
+   of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
diff --git a/src/tools/miri/LICENSE-MIT b/src/tools/miri/LICENSE-MIT
new file mode 100644
index 0000000000000..31aa79387f27e
--- /dev/null
+++ b/src/tools/miri/LICENSE-MIT
@@ -0,0 +1,23 @@
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md
new file mode 100644
index 0000000000000..1f4c52d5b8507
--- /dev/null
+++ b/src/tools/miri/README.md
@@ -0,0 +1,639 @@
+# Miri
+
+[![Actions build status][actions-badge]][actions-url]
+
+[actions-badge]: https://github.com/rust-lang/miri/workflows/CI/badge.svg?branch=master
+[actions-url]: https://github.com/rust-lang/miri/actions
+
+An experimental interpreter for [Rust][rust]'s
+[mid-level intermediate representation][mir] (MIR). It can run binaries and
+test suites of cargo projects and detect certain classes of
+[undefined behavior](https://doc.rust-lang.org/reference/behavior-considered-undefined.html),
+for example:
+
+* Out-of-bounds memory accesses and use-after-free
+* Invalid use of uninitialized data
+* Violation of intrinsic preconditions (an [`unreachable_unchecked`] being
+  reached, calling [`copy_nonoverlapping`] with overlapping ranges, ...)
+* Not sufficiently aligned memory accesses and references
+* Violation of *some* basic type invariants (a `bool` that is not 0 or 1, for example,
+  or an invalid enum discriminant)
+* **Experimental**: Violations of the [Stacked Borrows] rules governing aliasing
+  for reference types
+* **Experimental**: Data races
+
+On top of that, Miri will also tell you about memory leaks: when there is memory
+still allocated at the end of the execution, and that memory is not reachable
+from a global `static`, Miri will raise an error.
+
+Miri supports almost all Rust language features; in particular, unwinding and
+concurrency are properly supported (including some experimental emulation of
+weak memory effects, i.e., reads can return outdated values).
+
+You can use Miri to emulate programs on other targets, e.g. to ensure that
+byte-level data manipulation works correctly both on little-endian and
+big-endian systems. See
+[cross-interpretation](#cross-interpretation-running-for-different-targets)
+below.
+
+Miri has already discovered some [real-world bugs](#bugs-found-by-miri). If you
+found a bug with Miri, we'd appreciate if you tell us and we'll add it to the
+list!
+
+By default, Miri ensures a fully deterministic execution and isolates the
+program from the host system. Some APIs that would usually access the host, such
+as gathering entropy for random number generators, environment variables, and
+clocks, are replaced by deterministic "fake" implementations. Set
+`MIRIFLAGS="-Zmiri-disable-isolation"` to access the real system APIs instead.
+(In particular, the "fake" system RNG APIs make Miri **not suited for
+cryptographic use**! Do not generate keys using Miri.)
+
+All that said, be aware that Miri will **not catch all cases of undefined
+behavior** in your program, and cannot run all programs:
+
+* There are still plenty of open questions around the basic invariants for some
+  types and when these invariants even have to hold. Miri tries to avoid false
+  positives here, so if your program runs fine in Miri right now that is by no
+  means a guarantee that it is UB-free when these questions get answered.
+
+    In particular, Miri does currently not check that references point to valid data.
+* If the program relies on unspecified details of how data is laid out, it will
+  still run fine in Miri -- but might break (including causing UB) on different
+  compiler versions or different platforms.
+* Program execution is non-deterministic when it depends, for example, on where
+  exactly in memory allocations end up, or on the exact interleaving of
+  concurrent threads. Miri tests one of many possible executions of your
+  program. You can alleviate this to some extent by running Miri with different
+  values for `-Zmiri-seed`, but that will still by far not explore all possible
+  executions.
+* Miri runs the program as a platform-independent interpreter, so the program
+  has no access to most platform-specific APIs or FFI. A few APIs have been
+  implemented (such as printing to stdout, accessing environment variables, and
+  basic file system access) but most have not: for example, Miri currently does
+  not support networking. System API support varies between targets; if you run
+  on Windows it is a good idea to use `--target x86_64-unknown-linux-gnu` to get
+  better support.
+* Weak memory emulation may [produce weak behaviours](https://github.com/rust-lang/miri/issues/2301)
+  unobservable by compiled programs running on real hardware when `SeqCst` fences are used, and it
+  cannot produce all behaviors possibly observable on real hardware.
+
+[rust]: https://www.rust-lang.org/
+[mir]: https://github.com/rust-lang/rfcs/blob/master/text/1211-mir.md
+[`unreachable_unchecked`]: https://doc.rust-lang.org/stable/std/hint/fn.unreachable_unchecked.html
+[`copy_nonoverlapping`]: https://doc.rust-lang.org/stable/std/ptr/fn.copy_nonoverlapping.html
+[Stacked Borrows]: https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md
+
+
+## Using Miri
+
+Install Miri on Rust nightly via `rustup`:
+
+```sh
+rustup +nightly component add miri
+```
+
+If `rustup` says the `miri` component is unavailable, that's because not all
+nightly releases come with all tools. Check out
+[this website](https://rust-lang.github.io/rustup-components-history) to
+determine a nightly version that comes with Miri and install that using `rustup
+toolchain install nightly-YYYY-MM-DD`. Either way, all of the following commands
+assume the right toolchain is pinned via `rustup override set nightly` or
+`rustup override set nightly-YYYY-MM-DD`. (Alternatively, use `cargo
++nightly`/`cargo +nightly-YYYY-MM-DD` for each of the following commands.)
+
+Now you can run your project in Miri:
+
+1. Run `cargo clean` to eliminate any cached dependencies. Miri needs your
+   dependencies to be compiled the right way, that would not happen if they have
+   previously already been compiled.
+2. To run all tests in your project through Miri, use `cargo miri test`.
+3. If you have a binary project, you can run it through Miri using `cargo miri run`.
+
+The first time you run Miri, it will perform some extra setup and install some
+dependencies. It will ask you for confirmation before installing anything.
+
+`cargo miri run/test` supports the exact same flags as `cargo run/test`. For
+example, `cargo miri test filter` only runs the tests containing `filter` in
+their name.
+
+You can pass arguments to Miri via `MIRIFLAGS`. For example,
+`MIRIFLAGS="-Zmiri-disable-stacked-borrows" cargo miri run` runs the program
+without checking the aliasing of references.
+
+When compiling code via `cargo miri`, the `cfg(miri)` config flag is set for code
+that will be interpret under Miri. You can use this to ignore test cases that fail
+under Miri because they do things Miri does not support:
+
+```rust
+#[test]
+#[cfg_attr(miri, ignore)]
+fn does_not_work_on_miri() {
+    tokio::run(futures::future::ok::<_, ()>(()));
+}
+```
+
+There is no way to list all the infinite things Miri cannot do, but the
+interpreter will explicitly tell you when it finds something unsupported:
+
+```
+error: unsupported operation: can't call foreign function: bind
+    ...
+    = help: this is likely not a bug in the program; it indicates that the program \
+            performed an operation that the interpreter does not support
+```
+
+### Cross-interpretation: running for different targets
+
+Miri can not only run a binary or test suite for your host target, it can also
+perform cross-interpretation for arbitrary foreign targets: `cargo miri run
+--target x86_64-unknown-linux-gnu` will run your program as if it was a Linux
+program, no matter your host OS. This is particularly useful if you are using
+Windows, as the Linux target is much better supported than Windows targets.
+
+You can also use this to test platforms with different properties than your host
+platform. For example `cargo miri test --target mips64-unknown-linux-gnuabi64`
+will run your test suite on a big-endian target, which is useful for testing
+endian-sensitive code.
+
+### Running Miri on CI
+
+To run Miri on CI, make sure that you handle the case where the latest nightly
+does not ship the Miri component because it currently does not build. `rustup
+toolchain install --component` knows how to handle this situation, so the
+following snippet should always work:
+
+```sh
+rustup toolchain install nightly --component miri
+rustup override set nightly
+
+cargo miri test
+```
+
+Here is an example job for GitHub Actions:
+
+```yaml
+  miri:
+    name: "Miri"
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+      - name: Install Miri
+        run: |
+          rustup toolchain install nightly --component miri
+          rustup override set nightly
+          cargo miri setup
+      - name: Test with Miri
+        run: cargo miri test
+```
+
+The explicit `cargo miri setup` helps to keep the output of the actual test step
+clean.
+
+### Testing for alignment issues
+
+Miri can sometimes miss misaligned accesses since allocations can "happen to be"
+aligned just right. You can use `-Zmiri-symbolic-alignment-check` to definitely
+catch all such issues, but that flag will also cause false positives when code
+does manual pointer arithmetic to account for alignment. Another alternative is
+to call Miri with various values for `-Zmiri-seed`; that will alter the
+randomness that is used to determine allocation base addresses. The following
+snippet calls Miri in a loop with different values for the seed:
+
+```
+for SEED in $({ echo obase=16; seq 0 255; } | bc); do
+  echo "Trying seed: $SEED"
+  MIRIFLAGS=-Zmiri-seed=$SEED cargo miri test || { echo "Failing seed: $SEED"; break; };
+done
+```
+
+### Supported targets
+
+Miri does not support all targets supported by Rust. The good news, however, is
+that no matter your host OS/platform, it is easy to run code for *any* target
+using `--target`!
+
+The following targets are tested on CI and thus should always work (to the
+degree documented below):
+
+- The best-supported target is `x86_64-unknown-linux-gnu`. Miri releases are
+  blocked on things working with this target. Most other Linux targets should
+  also work well; we do run the test suite on `i686-unknown-linux-gnu` as a
+  32bit target and `mips64-unknown-linux-gnuabi64` as a big-endian target.
+- `x86_64-apple-darwin` should work basically as well as Linux. We also test
+  `aarch64-apple-darwin`. However, we might ship Miri with a nightly even when
+  some features on these targets regress.
+- `x86_64-pc-windows-msvc` works, but supports fewer features than the Linux and
+  Apple targets. For example, file system access and concurrency are not
+  supported on Windows. We also test `i686-pc-windows-msvc`, with the same
+  reduced feature set. We might ship Miri with a nightly even when some features
+  on these targets regress.
+
+### Common Problems
+
+When using the above instructions, you may encounter a number of confusing compiler
+errors.
+
+#### "note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace"
+
+You may see this when trying to get Miri to display a backtrace. By default, Miri
+doesn't expose any environment to the program, so running
+`RUST_BACKTRACE=1 cargo miri test` will not do what you expect.
+
+To get a backtrace, you need to disable isolation
+[using `-Zmiri-disable-isolation`][miri-flags]:
+
+```sh
+RUST_BACKTRACE=1 MIRIFLAGS="-Zmiri-disable-isolation" cargo miri test
+```
+
+#### "found possibly newer version of crate `std` which `<dependency>` depends on"
+
+Your build directory may contain artifacts from an earlier build that have/have
+not been built for Miri. Run `cargo clean` before switching from non-Miri to
+Miri builds and vice-versa.
+
+#### "found crate `std` compiled by an incompatible version of rustc"
+
+You may be running `cargo miri` with a different compiler version than the one
+used to build the custom libstd that Miri uses, and Miri failed to detect that.
+Try deleting `~/.cache/miri`.
+
+#### "no mir for `std::rt::lang_start_internal`"
+
+This means the sysroot you are using was not compiled with Miri in mind.  This
+should never happen when you use `cargo miri` because that takes care of setting
+up the sysroot.  If you are using `miri` (the Miri driver) directly, see the
+[contributors' guide](CONTRIBUTING.md) for how to use `./miri` to best do that.
+
+
+## Miri `-Z` flags and environment variables
+[miri-flags]: #miri--z-flags-and-environment-variables
+
+Miri adds its own set of `-Z` flags, which are usually set via the `MIRIFLAGS`
+environment variable. We first document the most relevant and most commonly used flags:
+
+* `-Zmiri-compare-exchange-weak-failure-rate=<rate>` changes the failure rate of
+  `compare_exchange_weak` operations. The default is `0.8` (so 4 out of 5 weak ops will fail).
+  You can change it to any value between `0.0` and `1.0`, where `1.0` means it
+  will always fail and `0.0` means it will never fail. Note than setting it to
+  `1.0` will likely cause hangs, since it means programs using
+  `compare_exchange_weak` cannot make progress.
+* `-Zmiri-disable-isolation` disables host isolation.  As a consequence,
+  the program has access to host resources such as environment variables, file
+  systems, and randomness.
+* `-Zmiri-isolation-error=<action>` configures Miri's response to operations
+  requiring host access while isolation is enabled. `abort`, `hide`, `warn`,
+  and `warn-nobacktrace` are the supported actions. The default is to `abort`,
+  which halts the machine. Some (but not all) operations also support continuing
+  execution with a "permission denied" error being returned to the program.
+  `warn` prints a full backtrace when that happens; `warn-nobacktrace` is less
+  verbose. `hide` hides the warning entirely.
+* `-Zmiri-env-forward=<var>` forwards the `var` environment variable to the interpreted program. Can
+  be used multiple times to forward several variables. Execution will still be deterministic if the
+  value of forwarded variables stays the same. Has no effect if `-Zmiri-disable-isolation` is set.
+* `-Zmiri-ignore-leaks` disables the memory leak checker, and also allows some
+  remaining threads to exist when the main thread exits.
+* `-Zmiri-permissive-provenance` disables the warning for integer-to-pointer casts and
+  [`ptr::from_exposed_addr`](https://doc.rust-lang.org/nightly/std/ptr/fn.from_exposed_addr.html).
+  This will necessarily miss some bugs as those operations are not efficiently and accurately
+  implementable in a sanitizer, but it will only miss bugs that concern memory/pointers which is
+  subject to these operations.
+* `-Zmiri-preemption-rate` configures the probability that at the end of a basic block, the active
+  thread will be preempted. The default is `0.01` (i.e., 1%). Setting this to `0` disables
+  preemption.
+* `-Zmiri-report-progress` makes Miri print the current stacktrace every now and then, so you can
+  tell what it is doing when a program just keeps running. You can customize how frequently the
+  report is printed via `-Zmiri-report-progress=<blocks>`, which prints the report every N basic
+  blocks.
+* `-Zmiri-seed=<hex>` configures the seed of the RNG that Miri uses to resolve non-determinism. This
+  RNG is used to pick base addresses for allocations, to determine preemption and failure of
+  `compare_exchange_weak`, and to control store buffering for weak memory emulation. When isolation
+  is enabled (the default), this is also used to emulate system entropy. The default seed is 0. You
+  can increase test coverage by running Miri multiple times with different seeds.
+* `-Zmiri-strict-provenance` enables [strict
+  provenance](https://github.com/rust-lang/rust/issues/95228) checking in Miri. This means that
+  casting an integer to a pointer yields a result with 'invalid' provenance, i.e., with provenance
+  that cannot be used for any memory access.
+* `-Zmiri-symbolic-alignment-check` makes the alignment check more strict.  By default, alignment is
+  checked by casting the pointer to an integer, and making sure that is a multiple of the alignment.
+  This can lead to cases where a program passes the alignment check by pure chance, because things
+  "happened to be" sufficiently aligned -- there is no UB in this execution but there would be UB in
+  others.  To avoid such cases, the symbolic alignment check only takes into account the requested
+  alignment of the relevant allocation, and the offset into that allocation.  This avoids missing
+  such bugs, but it also incurs some false positives when the code does manual integer arithmetic to
+  ensure alignment.  (The standard library `align_to` method works fine in both modes; under
+  symbolic alignment it only fills the middle slice when the allocation guarantees sufficient
+  alignment.)
+* `-Zmiri-tag-gc=<blocks>` configures how often the pointer tag garbage collector runs. The default
+  is to search for and remove unreachable tags once every `10,000` basic blocks. Setting this to
+  `0` disables the garbage collector, which causes some programs to have explosive memory usage
+  and/or super-linear runtime.
+
+The remaining flags are for advanced use only, and more likely to change or be removed.
+Some of these are **unsound**, which means they can lead
+to Miri failing to detect cases of undefined behavior in a program.
+
+* `-Zmiri-disable-abi-check` disables checking [function ABI]. Using this flag
+  is **unsound**.
+* `-Zmiri-disable-alignment-check` disables checking pointer alignment, so you
+  can focus on other failures, but it means Miri can miss bugs in your program.
+  Using this flag is **unsound**.
+* `-Zmiri-disable-data-race-detector` disables checking for data races.  Using
+  this flag is **unsound**. This implies `-Zmiri-disable-weak-memory-emulation`.
+* `-Zmiri-disable-stacked-borrows` disables checking the experimental
+  [Stacked Borrows] aliasing rules.  This can make Miri run faster, but it also
+  means no aliasing violations will be detected.  Using this flag is **unsound**
+  (but the affected soundness rules are experimental).
+* `-Zmiri-disable-validation` disables enforcing validity invariants, which are
+  enforced by default.  This is mostly useful to focus on other failures (such
+  as out-of-bounds accesses) first.  Setting this flag means Miri can miss bugs
+  in your program.  However, this can also help to make Miri run faster.  Using
+  this flag is **unsound**.
+* `-Zmiri-disable-weak-memory-emulation` disables the emulation of some C++11 weak
+  memory effects.
+* `-Zmiri-extern-so-file=<path to a shared object file>` is an experimental flag for providing support
+  for FFI calls. Functions not provided by that file are still executed via the usual Miri shims.
+  **WARNING**: If an invalid/incorrect `.so` file is specified, this can cause undefined behaviour in Miri itself!
+  And of course, Miri cannot do any checks on the actions taken by the external code.
+  Note that Miri has its own handling of file descriptors, so if you want to replace *some* functions
+  working on file descriptors, you will have to replace *all* of them, or the two kinds of
+  file descriptors will be mixed up.
+  This is **work in progress**; currently, only integer arguments and return values are
+  supported (and no, pointer/integer casts to work around this limitation will not work;
+  they will fail horribly). It also only works on unix hosts for now.
+  Follow [the discussion on supporting other types](https://github.com/rust-lang/miri/issues/2365). 
+* `-Zmiri-measureme=<name>` enables `measureme` profiling for the interpreted program.
+   This can be used to find which parts of your program are executing slowly under Miri.
+   The profile is written out to a file with the prefix `<name>`, and can be processed
+   using the tools in the repository https://github.com/rust-lang/measureme.
+* `-Zmiri-mute-stdout-stderr` silently ignores all writes to stdout and stderr,
+  but reports to the program that it did actually write. This is useful when you
+  are not interested in the actual program's output, but only want to see Miri's
+  errors and warnings.
+* `-Zmiri-panic-on-unsupported` will makes some forms of unsupported functionality,
+  such as FFI and unsupported syscalls, panic within the context of the emulated
+  application instead of raising an error within the context of Miri (and halting
+  execution). Note that code might not expect these operations to ever panic, so
+  this flag can lead to strange (mis)behavior.
+* `-Zmiri-retag-fields` changes Stacked Borrows retagging to recurse into fields.
+  This means that references in fields of structs/enums/tuples/arrays/... are retagged,
+  and in particular, they are protected when passed as function arguments.
+* `-Zmiri-track-alloc-id=<id1>,<id2>,...` shows a backtrace when the given allocations are
+  being allocated or freed.  This helps in debugging memory leaks and
+  use after free bugs. Specifying this argument multiple times does not overwrite the previous
+  values, instead it appends its values to the list. Listing an id multiple times has no effect.
+* `-Zmiri-track-call-id=<id1>,<id2>,...` shows a backtrace when the given call ids are
+  assigned to a stack frame.  This helps in debugging UB related to Stacked
+  Borrows "protectors". Specifying this argument multiple times does not overwrite the previous
+  values, instead it appends its values to the list. Listing an id multiple times has no effect.
+* `-Zmiri-track-pointer-tag=<tag1>,<tag2>,...` shows a backtrace when a given pointer tag
+  is created and when (if ever) it is popped from a borrow stack (which is where the tag becomes invalid 
+  and any future use of it will error).  This helps you in finding out why UB is
+  happening and where in your code would be a good place to look for it.
+  Specifying this argument multiple times does not overwrite the previous
+  values, instead it appends its values to the list. Listing a tag multiple times has no effect.
+* `-Zmiri-track-weak-memory-loads` shows a backtrace when weak memory emulation returns an outdated
+  value from a load. This can help diagnose problems that disappear under
+  `-Zmiri-disable-weak-memory-emulation`.
+
+[function ABI]: https://doc.rust-lang.org/reference/items/functions.html#extern-function-qualifier
+
+Some native rustc `-Z` flags are also very relevant for Miri:
+
+* `-Zmir-opt-level` controls how many MIR optimizations are performed.  Miri
+  overrides the default to be `0`; be advised that using any higher level can
+  make Miri miss bugs in your program because they got optimized away.
+* `-Zalways-encode-mir` makes rustc dump MIR even for completely monomorphic
+  functions.  This is needed so that Miri can execute such functions, so Miri
+  sets this flag per default.
+* `-Zmir-emit-retag` controls whether `Retag` statements are emitted. Miri
+  enables this per default because it is needed for [Stacked Borrows].
+
+Moreover, Miri recognizes some environment variables:
+
+* `MIRI_AUTO_OPS` indicates whether the automatic execution of rustfmt, clippy and rustup-toolchain
+  should be skipped. If it is set to any value, they are skipped. This is used for avoiding
+  infinite recursion in `./miri` and to allow automated IDE actions to avoid the auto ops.
+* `MIRI_LOG`, `MIRI_BACKTRACE` control logging and backtrace printing during
+  Miri executions, also [see "Testing the Miri driver" in `CONTRIBUTING.md`][testing-miri].
+* `MIRIFLAGS` (recognized by `cargo miri` and the test suite) defines extra
+  flags to be passed to Miri.
+* `MIRI_LIB_SRC` defines the directory where Miri expects the sources of the
+  standard library that it will build and use for interpretation. This directory
+  must point to the `library` subdirectory of a `rust-lang/rust` repository
+  checkout. Note that changing files in that directory does not automatically
+  trigger a re-build of the standard library; you have to clear the Miri build
+  cache manually (on Linux, `rm -rf ~/.cache/miri`).
+* `MIRI_SYSROOT` (recognized by `cargo miri` and the Miri driver) indicates the sysroot to use. When
+  using `cargo miri`, only set this if you do not want to use the automatically created sysroot. For
+  directly invoking the Miri driver, this variable (or a `--sysroot` flag) is mandatory.
+* `MIRI_TEST_TARGET` (recognized by the test suite and the `./miri` script) indicates which target
+  architecture to test against.  `miri` and `cargo miri` accept the `--target` flag for the same
+  purpose.
+* `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_BLESS` (recognized by the test suite) 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
+  `stderr` or `stdout` files match the actual output. Useful for the rustc test suite
+  which has subtle differences that we don't care about.
+
+The following environment variables are *internal* and must not be used by
+anyone but Miri itself. They are used to communicate between different Miri
+binaries, and as such worth documenting:
+
+* `MIRI_BE_RUSTC` can be set to `host` or `target`. It tells the Miri driver to
+  actually not interpret the code but compile it like rustc would. With `target`, Miri sets
+  some compiler flags to prepare the code for interpretation; with `host`, this is not done.
+  This environment variable is useful to be sure that the compiled `rlib`s are compatible
+  with Miri.
+* `MIRI_CALLED_FROM_XARGO` is set during the Miri-induced `xargo` sysroot build,
+  which will re-invoke `cargo-miri` as the `rustc` to use for this build.
+* `MIRI_CALLED_FROM_RUSTDOC` when set to any value tells `cargo-miri` that it is
+  running as a child process of `rustdoc`, which invokes it twice for each doc-test
+  and requires special treatment, most notably a check-only build before interpretation.
+  This is set by `cargo-miri` itself when running as a `rustdoc`-wrapper.
+* `MIRI_CWD` when set to any value tells the Miri driver to change to the given
+  directory after loading all the source files, but before commencing
+  interpretation. This is useful if the interpreted program wants a different
+  working directory at run-time than at build-time.
+* `MIRI_LOCAL_CRATES` is set by `cargo-miri` to tell the Miri driver which
+  crates should be given special treatment in diagnostics, in addition to the
+  crate currently being compiled.
+* `MIRI_VERBOSE` when set to any value tells the various `cargo-miri` phases to
+  perform verbose logging.
+* `MIRI_HOST_SYSROOT` is set by bootstrap to tell `cargo-miri` which sysroot to use for *host*
+  operations.
+
+[testing-miri]: CONTRIBUTING.md#testing-the-miri-driver
+
+## Miri `extern` functions
+
+Miri provides some `extern` functions that programs can import to access
+Miri-specific functionality:
+
+```rust
+#[cfg(miri)]
+extern "Rust" {
+    /// Miri-provided extern function to mark the block `ptr` points to as a "root"
+    /// for some static memory. This memory and everything reachable by it is not
+    /// considered leaking even if it still exists when the program terminates.
+    ///
+    /// `ptr` has to point to the beginning of an allocated block.
+    fn miri_static_root(ptr: *const u8);
+
+    // Miri-provided extern function to get the amount of frames in the current backtrace.
+    // The `flags` argument must be `0`.
+    fn miri_backtrace_size(flags: u64) -> usize;
+
+    /// Miri-provided extern function to obtain a backtrace of the current call stack.
+    /// This writes a slice of pointers into `buf` - each pointer is an opaque value
+    /// that is only useful when passed to `miri_resolve_frame`.
+    /// `buf` must have `miri_backtrace_size(0) * pointer_size` bytes of space.
+    /// The `flags` argument must be `1`.
+    fn miri_get_backtrace(flags: u64, buf: *mut *mut ());
+
+    /// Miri-provided extern function to resolve a frame pointer obtained
+    /// from `miri_get_backtrace`. The `flags` argument must be `1`,
+    /// and `MiriFrame` should be declared as follows:
+    ///
+    /// ```rust
+    /// #[repr(C)]
+    /// struct MiriFrame {
+    ///     // The size of the name of the function being executed, encoded in UTF-8
+    ///     name_len: usize,
+    ///     // The size of filename of the function being executed, encoded in UTF-8
+    ///     filename_len: usize,
+    ///     // The line number currently being executed in `filename`, starting from '1'.
+    ///     lineno: u32,
+    ///     // The column number currently being executed in `filename`, starting from '1'.
+    ///     colno: u32,
+    ///     // The function pointer to the function currently being executed.
+    ///     // This can be compared against function pointers obtained by
+    ///     // casting a function (e.g. `my_fn as *mut ()`)
+    ///     fn_ptr: *mut ()
+    /// }
+    /// ```
+    ///
+    /// The fields must be declared in exactly the same order as they appear in `MiriFrame` above.
+    /// This function can be called on any thread (not just the one which obtained `frame`).
+    fn miri_resolve_frame(frame: *mut (), flags: u64) -> MiriFrame;
+
+    /// Miri-provided extern function to get the name and filename of the frame provided by `miri_resolve_frame`.
+    /// `name_buf` and `filename_buf` should be allocated with the `name_len` and `filename_len` fields of `MiriFrame`.
+    /// The flags argument must be `0`.
+    fn miri_resolve_frame_names(ptr: *mut (), flags: u64, name_buf: *mut u8, filename_buf: *mut u8);
+
+    /// Miri-provided extern function to begin unwinding with the given payload.
+    ///
+    /// This is internal and unstable and should not be used; we give it here
+    /// just to be complete.
+    fn miri_start_panic(payload: *mut u8) -> !;
+}
+```
+
+## Contributing and getting help
+
+If you want to contribute to Miri, great!  Please check out our
+[contribution guide](CONTRIBUTING.md).
+
+For help with running Miri, you can open an issue here on
+GitHub or use the [Miri stream on the Rust Zulip][zulip].
+
+[zulip]: https://rust-lang.zulipchat.com/#narrow/stream/269128-miri
+
+## History
+
+This project began as part of an undergraduate research course in 2015 by
+@solson at the [University of Saskatchewan][usask].  There are [slides] and a
+[report] available from that project.  In 2016, @oli-obk joined to prepare Miri
+for eventually being used as const evaluator in the Rust compiler itself
+(basically, for `const` and `static` stuff), replacing the old evaluator that
+worked directly on the AST.  In 2017, @RalfJung did an internship with Mozilla
+and began developing Miri towards a tool for detecting undefined behavior, and
+also using Miri as a way to explore the consequences of various possible
+definitions for undefined behavior in Rust.  @oli-obk's move of the Miri engine
+into the compiler finally came to completion in early 2018.  Meanwhile, later
+that year, @RalfJung did a second internship, developing Miri further with
+support for checking basic type invariants and verifying that references are
+used according to their aliasing restrictions.
+
+[usask]: https://www.usask.ca/
+[slides]: https://solson.me/miri-slides.pdf
+[report]: https://solson.me/miri-report.pdf
+
+## Bugs found by Miri
+
+Miri has already found a number of bugs in the Rust standard library and beyond, which we collect here.
+
+Definite bugs found:
+
+* [`Debug for vec_deque::Iter` accessing uninitialized memory](https://github.com/rust-lang/rust/issues/53566)
+* [`Vec::into_iter` doing an unaligned ZST read](https://github.com/rust-lang/rust/pull/53804)
+* [`From<&[T]> for Rc` creating a not sufficiently aligned reference](https://github.com/rust-lang/rust/issues/54908)
+* [`BTreeMap` creating a shared reference pointing to a too small allocation](https://github.com/rust-lang/rust/issues/54957)
+* [`Vec::append` creating a dangling reference](https://github.com/rust-lang/rust/pull/61082)
+* [Futures turning a shared reference into a mutable one](https://github.com/rust-lang/rust/pull/56319)
+* [`str` turning a shared reference into a mutable one](https://github.com/rust-lang/rust/pull/58200)
+* [`rand` performing unaligned reads](https://github.com/rust-random/rand/issues/779)
+* [The Unix allocator calling `posix_memalign` in an invalid way](https://github.com/rust-lang/rust/issues/62251)
+* [`getrandom` calling the `getrandom` syscall in an invalid way](https://github.com/rust-random/getrandom/pull/73)
+* [`Vec`](https://github.com/rust-lang/rust/issues/69770) and [`BTreeMap`](https://github.com/rust-lang/rust/issues/69769) leaking memory under some (panicky) conditions
+* [`beef` leaking memory](https://github.com/maciejhirsz/beef/issues/12)
+* [`EbrCell` using uninitialized memory incorrectly](https://github.com/Firstyear/concread/commit/b15be53b6ec076acb295a5c0483cdb4bf9be838f#diff-6282b2fc8e98bd089a1f0c86f648157cR229)
+* [TiKV performing an unaligned pointer access](https://github.com/tikv/tikv/issues/7613)
+* [`servo_arc` creating a dangling shared reference](https://github.com/servo/servo/issues/26357)
+* [TiKV constructing out-of-bounds pointers (and overlapping mutable references)](https://github.com/tikv/tikv/pull/7751)
+* [`encoding_rs` doing out-of-bounds pointer arithmetic](https://github.com/hsivonen/encoding_rs/pull/53)
+* [TiKV using `Vec::from_raw_parts` incorrectly](https://github.com/tikv/agatedb/pull/24)
+* Incorrect doctests for [`AtomicPtr`](https://github.com/rust-lang/rust/pull/84052) and [`Box::from_raw_in`](https://github.com/rust-lang/rust/pull/84053)
+* [Insufficient alignment in `ThinVec`](https://github.com/Gankra/thin-vec/pull/27)
+* [`crossbeam-epoch` calling `assume_init` on a partly-initialized `MaybeUninit`](https://github.com/crossbeam-rs/crossbeam/pull/779)
+* [`integer-encoding` dereferencing a misaligned pointer](https://github.com/dermesser/integer-encoding-rs/pull/23)
+* [`rkyv` constructing a `Box<[u8]>` from an overaligned allocation](https://github.com/rkyv/rkyv/commit/a9417193a34757e12e24263178be8b2eebb72456)
+* [Data race in `thread::scope`](https://github.com/rust-lang/rust/issues/98498)
+* [`regex` incorrectly handling unaligned `Vec<u8>` buffers](https://www.reddit.com/r/rust/comments/vq3mmu/comment/ienc7t0?context=3)
+* [Incorrect use of `compare_exchange_weak` in `once_cell`](https://github.com/matklad/once_cell/issues/186)
+
+Violations of [Stacked Borrows] found that are likely bugs (but Stacked Borrows is currently just an experiment):
+
+* [`VecDeque::drain` creating overlapping mutable references](https://github.com/rust-lang/rust/pull/56161)
+* Various `BTreeMap` problems
+    * [`BTreeMap` iterators creating mutable references that overlap with shared references](https://github.com/rust-lang/rust/pull/58431)
+    * [`BTreeMap::iter_mut` creating overlapping mutable references](https://github.com/rust-lang/rust/issues/73915)
+    * [`BTreeMap` node insertion using raw pointers outside their valid memory area](https://github.com/rust-lang/rust/issues/78477)
+* [`LinkedList` cursor insertion creating overlapping mutable references](https://github.com/rust-lang/rust/pull/60072)
+* [`Vec::push` invalidating existing references into the vector](https://github.com/rust-lang/rust/issues/60847)
+* [`align_to_mut` violating uniqueness of mutable references](https://github.com/rust-lang/rust/issues/68549)
+* [`sized-chunks` creating aliasing mutable references](https://github.com/bodil/sized-chunks/issues/8)
+* [`String::push_str` invalidating existing references into the string](https://github.com/rust-lang/rust/issues/70301)
+* [`ryu` using raw pointers outside their valid memory area](https://github.com/dtolnay/ryu/issues/24)
+* [ink! creating overlapping mutable references](https://github.com/rust-lang/miri/issues/1364)
+* [TiKV creating overlapping mutable reference and raw pointer](https://github.com/tikv/tikv/pull/7709)
+* [Windows `Env` iterator using a raw pointer outside its valid memory area](https://github.com/rust-lang/rust/pull/70479)
+* [`VecDeque::iter_mut` creating overlapping mutable references](https://github.com/rust-lang/rust/issues/74029)
+* [Various standard library aliasing issues involving raw pointers](https://github.com/rust-lang/rust/pull/78602)
+* [`<[T]>::copy_within` using a loan after invalidating it](https://github.com/rust-lang/rust/pull/85610)
+
+## Scientific papers employing Miri
+
+* [Stacked Borrows: An Aliasing Model for Rust](https://plv.mpi-sws.org/rustbelt/stacked-borrows/)
+* [Using Lightweight Formal Methods to Validate a Key-Value Storage Node in Amazon S3](https://www.amazon.science/publications/using-lightweight-formal-methods-to-validate-a-key-value-storage-node-in-amazon-s3)
+* [SyRust: Automatic Testing of Rust Libraries with Semantic-Aware Program Synthesis](https://dl.acm.org/doi/10.1145/3453483.3454084)
+
+## License
+
+Licensed under either of
+
+  * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
+    http://www.apache.org/licenses/LICENSE-2.0)
+  * MIT license ([LICENSE-MIT](LICENSE-MIT) or
+    http://opensource.org/licenses/MIT)
+
+at your option.
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in the work by you shall be dual licensed as above, without any
+additional terms or conditions.
diff --git a/src/tools/miri/bench-cargo-miri/backtraces/Cargo.lock b/src/tools/miri/bench-cargo-miri/backtraces/Cargo.lock
new file mode 100644
index 0000000000000..375b129a7e59b
--- /dev/null
+++ b/src/tools/miri/bench-cargo-miri/backtraces/Cargo.lock
@@ -0,0 +1,94 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "backtrace"
+version = "0.3.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "backtraces"
+version = "0.1.0"
+dependencies = [
+ "backtrace",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "gimli"
+version = "0.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
+
+[[package]]
+name = "libc"
+version = "0.2.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
+
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "object"
+version = "0.28.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
diff --git a/src/tools/miri/bench-cargo-miri/backtraces/Cargo.toml b/src/tools/miri/bench-cargo-miri/backtraces/Cargo.toml
new file mode 100644
index 0000000000000..1ba96b19395d2
--- /dev/null
+++ b/src/tools/miri/bench-cargo-miri/backtraces/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "backtraces"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+backtrace = "0.3.65"
diff --git a/src/tools/miri/bench-cargo-miri/backtraces/src/main.rs b/src/tools/miri/bench-cargo-miri/backtraces/src/main.rs
new file mode 100644
index 0000000000000..eba51c60dbc5b
--- /dev/null
+++ b/src/tools/miri/bench-cargo-miri/backtraces/src/main.rs
@@ -0,0 +1,29 @@
+//! Extracted from the backtrace crate's test test_frame_conversion
+
+use backtrace::{Backtrace, BacktraceFrame};
+use std::fmt::Write;
+
+fn main() {
+    let mut frames = vec![];
+    backtrace::trace(|frame| {
+        let converted = BacktraceFrame::from(frame.clone());
+        frames.push(converted);
+        true
+    });
+
+    let mut manual = Backtrace::from(frames);
+    manual.resolve();
+    let frames = manual.frames();
+
+    let mut output = String::new();
+    for frame in frames {
+        // Originally these were println! but we'd prefer our benchmarks to not emit a lot of
+        // output to stdout/stderr. Unfortunately writeln! to a String is faster, but we still
+        // manage to exercise interesting code paths in Miri.
+        writeln!(output, "{:?}", frame.ip()).unwrap();
+        writeln!(output, "{:?}", frame.symbol_address()).unwrap();
+        writeln!(output, "{:?}", frame.module_base_address()).unwrap();
+        writeln!(output, "{:?}", frame.symbols()).unwrap();
+    }
+    drop(output);
+}
diff --git a/src/tools/miri/bench-cargo-miri/mse/Cargo.lock b/src/tools/miri/bench-cargo-miri/mse/Cargo.lock
new file mode 100644
index 0000000000000..d2b1aa341bbb6
--- /dev/null
+++ b/src/tools/miri/bench-cargo-miri/mse/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "mse"
+version = "0.1.0"
diff --git a/src/tools/miri/bench-cargo-miri/mse/Cargo.toml b/src/tools/miri/bench-cargo-miri/mse/Cargo.toml
new file mode 100644
index 0000000000000..7b4c2dc758faa
--- /dev/null
+++ b/src/tools/miri/bench-cargo-miri/mse/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "mse"
+version = "0.1.0"
+authors = ["Ralf Jung <post@ralfj.de>"]
+edition = "2018"
+
+[dependencies]
diff --git a/src/tools/miri/bench-cargo-miri/mse/src/main.rs b/src/tools/miri/bench-cargo-miri/mse/src/main.rs
new file mode 100644
index 0000000000000..c09dc2484d380
--- /dev/null
+++ b/src/tools/miri/bench-cargo-miri/mse/src/main.rs
@@ -0,0 +1,32 @@
+#[rustfmt::skip] // no need to wrap these arrays onto hundreds of lines
+static EXPECTED: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 1, 0, 255, 255, 1, 0, 255, 255, 1, 0, 255, 255, 1, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 254, 255, 0, 0, 254, 255, 0, 0, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 255, 255, 1, 0, 255, 255, 1, 0, 0, 0, 1, 0, 255, 255, 2, 0, 255, 255, 2, 0, 255, 255, 2, 0, 255, 255, 2, 0, 255, 255, 2, 0, 255, 255, 1, 0, 255, 255, 1, 0, 255, 255, 0, 0, 254, 255, 0, 0, 254, 255, 0, 0, 254, 255, 255, 255, 254, 255, 254, 255, 254, 255, 253, 255, 253, 255, 253, 255, 253, 255, 252, 255, 254, 255, 251, 255, 254, 255, 251, 255, 254, 255, 252, 255, 255, 255, 252, 255, 0, 0, 252, 255, 0, 0, 252, 255, 1, 0, 252, 255, 1, 0, 252, 255, 2, 0, 252, 255, 2, 0, 252, 255, 2, 0, 252, 255, 2, 0, 252, 255, 2, 0, 253, 255, 1, 0, 252, 255, 0, 0, 252, 255, 255, 255, 251, 255, 0, 0, 251, 255, 0, 0, 251, 255, 0, 0, 252, 255, 2, 0, 252, 255, 3, 0, 252, 255, 4, 0, 253, 255, 5, 0, 254, 255, 5, 0, 253, 255, 6, 0, 253, 255, 6, 0, 253, 255, 5, 0, 253, 255, 5, 0, 254, 255, 4, 0, 254, 255, 3, 0, 251, 255, 0, 0, 250, 255, 255, 255, 253, 255, 254, 255, 252, 255, 252, 255, 247, 255, 251, 255, 247, 255, 252, 255, 252, 255, 254, 255, 252, 255, 254, 255, 252, 255, 255, 255, 254, 255, 1, 0, 1, 0, 1, 0, 4, 0, 2, 0, 8, 0, 2, 0, 12, 0, 1, 0, 13, 0, 0, 0, 12, 0, 0, 0, 11, 0, 255, 255, 8, 0, 254, 255, 7, 0, 0, 0, 7, 0, 253, 255, 11, 0, 248, 255, 15, 0, 247, 255, 17, 0, 250, 255, 17, 0, 251, 255, 13, 0, 253, 255, 7, 0, 255, 255, 3, 0, 255, 255, 254, 255, 255, 255, 252, 255, 255, 255, 252, 255, 254, 255, 250, 255, 255, 255, 242, 255, 254, 255, 239, 255, 252, 255, 248, 255, 255, 255, 249, 255, 5, 0, 239, 255, 7, 0, 238, 255, 10, 0, 249, 255, 18, 0, 254, 255, 25, 0, 253, 255, 27, 0, 0, 0, 31, 0, 4, 0, 34, 0, 4, 0, 34, 0, 8, 0, 36, 0, 8, 0, 37, 0, 2, 0, 36, 0, 4, 0, 34, 0, 8, 0, 28, 0, 3, 0, 15, 0, 255, 255, 11, 0, 0, 0, 12, 0, 251, 255, 8, 0, 252, 255, 10, 0, 0, 0, 23, 0, 252, 255, 31, 0, 248, 255, 30, 0, 254, 255, 30, 0, 255, 255, 26, 0, 250, 255, 22, 0, 250, 255, 20, 0, 244, 255, 15, 0, 237, 255, 10, 0, 246, 255, 13, 0, 242, 255, 6, 0, 213, 255, 243, 255, 213, 255, 240, 255, 247, 255, 244, 255, 246, 255, 227, 255, 214, 255, 216, 255, 219, 255, 228, 255, 251, 255, 235, 255, 1, 0, 232, 255, 248, 255, 236, 255, 4, 0, 238, 255, 26, 0, 232, 255, 44, 0, 230, 255, 66, 0, 226, 255, 86, 0, 219, 255, 88, 0, 215, 255, 72, 0, 210, 255, 50, 0, 225, 255, 28, 0, 23, 0, 14, 0, 64, 0, 16, 0, 51, 0, 26, 0, 32, 0, 34, 0, 39, 0, 42, 0, 48, 0, 35, 0, 58, 0, 255, 255, 72, 0, 220, 255, 69, 0, 197, 255, 58, 0, 158, 255, 54, 0, 132, 255, 36, 0, 153, 255, 12, 0, 146, 255, 5, 0, 83, 255, 237, 255, 110, 255, 197, 255, 252, 255, 214, 255, 51, 0, 1, 0, 233, 255, 250, 255, 226, 255, 250, 255, 45, 0, 46, 0, 47, 0, 70, 0, 6, 0, 55, 0, 19, 0, 60, 0, 38, 0, 62, 0, 42, 0, 47, 0, 61, 0, 46, 0, 40, 0, 42, 0, 237, 255, 22, 0, 222, 255, 6, 0, 221, 255, 206, 255, 195, 255, 115, 255, 219, 255, 85, 255, 17, 0, 93, 255, 26, 0, 76, 255, 46, 0, 102, 255, 80, 0, 193, 255, 48, 0, 252, 255, 18, 0, 20, 0, 50, 0, 47, 0, 58, 0, 53, 0, 44, 0, 61, 0, 57, 0, 85, 0, 37, 0, 80, 0, 0, 0, 86, 0, 248, 255, 106, 0, 161, 255, 49, 0, 43, 255, 248, 255, 125, 255, 47, 0, 49, 0, 63, 0, 40, 0, 217, 255, 187, 255, 182, 255, 219, 255, 236, 255, 63, 0, 244, 255, 58, 0, 242, 255, 244, 255, 25, 0, 225, 255, 41, 0, 11, 0, 45, 0, 76, 0, 47, 0, 167, 0, 5, 0, 5, 1, 219, 255, 21, 1, 173, 255, 183, 0, 84, 255, 35, 0, 134, 255, 177, 255, 138, 0, 186, 255, 10, 1, 69, 0, 124, 0, 228, 0, 0, 0, 135, 1, 227, 255, 82, 2, 172, 255, 190, 2, 178, 255, 115, 2, 248, 255, 39, 2, 243, 255, 253, 1, 13, 0, 116, 1, 120, 0, 96, 1, 125, 0, 110, 2, 127, 0, 179, 2, 223, 0, 106, 1, 126, 0, 130, 1, 223, 255, 147, 3, 198, 0, 190, 3, 201, 1, 200, 1, 42, 1, 244, 1, 233, 0, 3, 4, 213, 1, 72, 4, 170, 1, 150, 3, 160, 0, 43, 4, 141, 0, 196, 4, 189, 0, 221, 4, 164, 0, 95, 5, 41, 1, 98, 5, 247, 1, 19, 5, 190, 2, 14, 6, 161, 3, 7, 7, 87, 3, 216, 6, 35, 2, 38, 7, 90, 2, 136, 7, 64, 3, 200, 6, 28, 3, 199, 6, 165, 3, 169, 7, 105, 5, 143, 7, 26, 6, 57, 8, 205, 5, 156, 10, 169, 5, 132, 11, 25, 5, 208, 10, 181, 4, 156, 10, 66, 5, 227, 9, 170, 5, 166, 9, 117, 6, 45, 12, 63, 8, 42, 13, 128, 8, 136, 10, 155, 7, 109, 11, 1, 9, 6, 15, 98, 10, 121, 9, 136, 8, 147, 252, 189, 7, 43, 247, 63, 10, 147, 249, 92, 11, 172, 248, 172, 10, 112, 245, 137, 11, 76, 246, 44, 12, 184, 247, 138, 11, 118, 246, 144, 12, 94, 246, 171, 13, 112, 247, 162, 12, 168, 246, 33, 13, 63, 246, 29, 15, 226, 247, 188, 14, 190, 248, 75, 15, 238, 247, 86, 17, 19, 247, 118, 11, 10, 247, 232, 254, 238, 247, 30, 249, 56, 248, 124, 250, 6, 247, 1, 250, 161, 246, 3, 249, 81, 247, 117, 250, 60, 247, 202, 250, 212, 247, 60, 250, 15, 249, 140, 250, 34, 248, 221, 249, 105, 247, 218, 249, 205, 248, 113, 251, 138, 248, 90, 250, 41, 248, 230, 248];
+#[rustfmt::skip]
+static PCM: &[i16] = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, -2, 0, -2, 0, -2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 1, 0, 1, 0, 0, -2, 0, -2, 0, -2, 0, -2, -2, -2, -3, -3, -3, -3, -4, -2, -5, -2, -5, -2, -4, 0, -4, 0, -4, 0, -4, 1, -4, 1, -4, 2, -4, 2, -4, 2, -4, 2, -4, 2, -3, 1, -4, 0, -4, 0, -5, 0, -5, 0, -5, 0, -4, 2, -4, 3, -4, 4, -3, 5, -2, 5, -3, 6, -3, 6, -3, 5, -3, 5, -2, 4, -2, 3, -5, 0, -6, 0, -3, -2, -4, -4, -9, -5, -9, -4, -4, -2, -4, -2, -4, 0, -2, 1, 1, 1, 4, 2, 8, 2, 12, 1, 13, 0, 12, 0, 11, 0, 8, -2, 7, 0, 7, -3, 11, -8, 15, -9, 17, -6, 17, -5, 13, -3, 7, 0, 3, 0, -2, 0, -4, 0, -4, -2, -6, 0, -14, -2, -17, -4, -8, 0, -7, 5, -17, 7, -18, 10, -7, 18, -2, 25, -3, 27, 0, 31, 4, 34, 4, 34, 8, 36, 8, 37, 2, 36, 4, 34, 8, 28, 3, 15, 0, 11, 0, 12, -5, 8, -4, 10, 0, 23, -4, 31, -8, 30, -2, 30, 0, 26, -6, 22, -6, 20, -12, 15, -19, 10, -10, 13, -14, 6, -43, -13, -43, -16, -9, -12, -10, -29, -42, -40, -37, -28, -5, -21, 1, -24, -8, -20, 4, -18, 26, -24, 44, -26, 66, -30, 86, -37, 88, -41, 72, -46, 50, -31, 28, 23, 14, 64, 16, 51, 26, 32, 34, 39, 42, 48, 35, 58, 0, 72, -36, 69, -59, 58, -98, 54, -124, 36, -103, 12, -110, 5, -173, -19, -146, -59, -4, -42, 51, 1, -23, -6, -30, -6, 45, 46, 47, 70, 6, 55, 19, 60, 38, 62, 42, 47, 61, 46, 40, 42, -19, 22, -34, 6, -35, -50, -61, -141, -37, -171, 17, -163, 26, -180, 46, -154, 80, -63, 48, -4, 18, 20, 50, 47, 58, 53, 44, 61, 57, 85, 37, 80, 0, 86, -8, 106, -95, 49, -213, -8, -131, 47, 49, 63, 40, -39, -69, -74, -37, -20, 63, -12, 58, -14, -12, 25, -31, 41, 11, 45, 76, 47, 167, 5, 261, -37, 277, -83, 183, -172, 35, -122, -79, 138, -70, 266, 69, 124, 228, 0, 391, -29, 594, -84, 702, -78, 627, -8, 551, -13, 509, 13, 372, 120, 352, 125, 622, 127, 691, 223, 362, 126, 386, -33, 915, 198, 958, 457, 456, 298, 500, 233, 1027, 469, 1096, 426, 918, 160, 1067, 141, 1220, 189, 1245, 164, 1375, 297, 1378, 503, 1299, 702, 1550, 929, 1799, 855, 1752, 547, 1830, 602, 1928, 832, 1736, 796, 1735, 933, 1961, 1385, 1935, 1562, 2105, 1485, 2716, 1449, 2948, 1305, 2768, 1205, 2716, 1346, 2531, 1450, 2470, 1653, 3117, 2111, 3370, 2176, 2696, 1947, 2925, 2305, 3846, 2658, 2425, 2184, -877, 1981, -2261, 2623, -1645, 2908, -1876, 2732, -2704, 2953, -2484, 3116, -2120, 2954, -2442, 3216, -2466, 3499, -2192, 3234, -2392, 3361, -2497, 3869, -2078, 3772, -1858, 3915, -2066, 4438, -2285, 2934, -2294, -280, -2066, -1762, -1992, -1412, -2298, -1535, -2399, -1789, -2223, -1419, -2244, -1334, -2092, -1476, -1777, -1396, -2014, -1571, -2199, -1574, -1843, -1167, -1910, -1446, -2007, -1818];
+
+fn main() {
+    #[cfg(increase_thread_usage)]
+    let thread = std::thread::spawn(|| 4);
+
+    for _ in 0..2 {
+        mse(PCM.len(), PCM, EXPECTED);
+    }
+}
+
+fn read_i16(buffer: &[u8], index: usize) -> i16 {
+    const SIZE: usize = std::mem::size_of::<i16>();
+    let mut bytes: [u8; SIZE] = [0u8; SIZE];
+    bytes.copy_from_slice(&buffer[(index * SIZE)..(index * SIZE + SIZE)]);
+    unsafe { std::mem::transmute(bytes) }
+}
+
+fn mse(samples: usize, frame_buf: &[i16], buf_ref: &[u8]) -> f64 {
+    let mut mse = 0.0;
+    let max_samples = std::cmp::min(buf_ref.len() / 2, samples as usize);
+    for i in 0..max_samples {
+        let ref_res = read_i16(buf_ref, i);
+        let info_res = frame_buf[i as usize];
+        let diff = (ref_res - info_res).abs();
+        mse += f64::from(diff.pow(2));
+    }
+    mse / max_samples as f64
+}
diff --git a/src/tools/miri/bench-cargo-miri/serde1/Cargo.lock b/src/tools/miri/bench-cargo-miri/serde1/Cargo.lock
new file mode 100644
index 0000000000000..4875057613543
--- /dev/null
+++ b/src/tools/miri/bench-cargo-miri/serde1/Cargo.lock
@@ -0,0 +1,89 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "cargo-miri-test"
+version = "0.1.0"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
+
+[[package]]
+name = "serde"
+version = "1.0.137"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.137"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.96"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
diff --git a/src/tools/miri/bench-cargo-miri/serde1/Cargo.toml b/src/tools/miri/bench-cargo-miri/serde1/Cargo.toml
new file mode 100644
index 0000000000000..7cb863a7abf33
--- /dev/null
+++ b/src/tools/miri/bench-cargo-miri/serde1/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "cargo-miri-test"
+version = "0.1.0"
+authors = ["Oliver Schneider <git-spam-no-reply9815368754983@oli-obk.de>"]
+edition = "2018"
+
+[dependencies]
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
diff --git a/src/tools/miri/bench-cargo-miri/serde1/src/main.rs b/src/tools/miri/bench-cargo-miri/serde1/src/main.rs
new file mode 100644
index 0000000000000..2712156d61b4a
--- /dev/null
+++ b/src/tools/miri/bench-cargo-miri/serde1/src/main.rs
@@ -0,0 +1,13 @@
+static JSON: &str = r#"{"buffer":[-29,-42,-40,-37,-28,-5,-21,1,-24,-8,-20,4,-18,26,-24,44,-26,66,-30,86,-37,88,-41,72,-46,50,-31,28,23,14,64,16,51,26,32,34,39,42,48,35,58,0,72,-36,69,-59,58,-98,54,-124,36,-103,12,-110,5,-173,-19,-146,-59,-4,-42,51,1,-23,-6,-30,-6,45,46,47,70,6,55,19,60,38,62,42,47,61,46,40,42,-19,22,-34,6,-35,-50,-61,-141,-37,-171,17,-163,26,-180,46,-154,80,-63,48,-4,18,20,50,47,58,53,44,61,57,85,37,80,0,86,-8,106,-95,49,-213,-8,-131,47,49,63,40,-39,-69,-74,-37,-20,63,-12,58,-14,-12,25,-31,41,11,45,76,47,167,5,261,-37,277,-83,183,-172,35,-122,-79,138,-70,266,69,124,228,0,391,-29,594,-84,702,-78,627,-8,551,-13,509,13,372,120,352,125,622,127,691,223,362,126,386,-33,915,198,958,457,456,298,500,233,1027,469,1096,426,918,160,1067,141,1220,189,1245,164,1375,297,1378,503,1299,702,1550,929,1799,855,1752,547,1830,602,1928,832,1736,796,1735,933,1961,1385,1935,1562,2105,1485,2716,1449,2948,1305,2768,1205,2716,1346,2531,1450,2470,1653,3117,2111,3370,2176,2696,1947,2925,2305,3846,2658,2425,2184,-877,1981,-2261,2623,-1645,2908,-1876,2732,-2704,2953,-2484,3116,-2120,2954,-2442,3216,-2466,3499,-2192,3234,-2392,3361,-2497,3869,-2078,3772,-1858,3915,-2066,4438,-2285,2934,-2294,-280,-2066,-1762,-1992,-1412,-2298,-1535,-2399,-1789,-2223,-1419,-2244,-1334,-2092,-1476,-1777,-1396,-2014,-1571,-2199,-1574,-1843,-1167,-1910,-1446,-2007,-1818,-1506,-1331,-2526,-2048,-5535,-4573,-7148,-5828,-6422,-5327,-5840,-5488,-5992,-6144,-6014,-6164,-6109,-6234,-6271,-6388,-6288,-6156,-6517,-6249,-6794,-6602,-6822,-6418,-6788,-6245,-6490,-6560,-6394,-6794,-7920,-6937,-10397,-7140,-11428,-6972,-11019,-6610,-11141,-6665,-11913,-7046,-11979,-7235,-11599,-7015,-11854,-6912,-12161,-7441,-12136,-7761,-12861,-7292,-13390,-7254,-12345,-7809,-12490,-7463,-13983,-6969,-10489,-8465,-2382,-11054,1272,-12247,-270,-12060,-323,-12113,502,-12486,-697,-12251,-1086,-12141,-181,-13116,-670,-13509,-1173,-12592,-443,-12811,-449,-13698,-934,-12850,-747,-13083,-873,-15036,-1161,-11478,-1047,-2669,-1407,1006,-1658,-1146,-1195,-1297,-1421,-73,-1946,-977,-1590,-1499,-1577,-1010,-1862,-1256,-1389,-962,-1692,-509,-2613,-1317,-2087,-1359,-1997,-1034,-2891,-2024,-119,-84,5651,5723,8074,8306,7156,6870,6985,7106,7312,8403,7114,8096,7173,7848,7082,7827,6761,7189,6985,7368,7076,7835,6992,7297,7453,7260,7016,7755,6025,7429,8533,7352,14150,7628,17142,7077,16399,6947,15939,7475,16564,7069,16463,6882,16400,7602,17031,7233,16543,6517,15395,7018,15985,7104,16689,6869,15655,7622,16155,7198,17884,6022,14056,8856,5665,14484,1815,16782,3034,15786,3107,15664,2312,16517,2965,16443,3036,16120,2287,16584,2479,16720,2693,16073,2535,16159,2958,16609,3067,16086,2716,16579,3035,17752,3092,13704,2499,5265,2620,1452,2808,3024,2444,3275,2839,2267,3340,2857,2968,3232,3066,2867,3152,3072,2248,2961,2413,2807,3238,3237,2368,2699,2262,2392,3537,3339,827,823,-5020,-5359,-7095,-7857,-5973,-6274,-6208,-6279,-6934,-7181,-6893,-6647,-7146,-6687,-7026,-7328,-6451,-6924,-6763,-6535,-7109,-6639,-6926,-6559,-7188,-6799,-6727,-6955,-5786,-6554,-8543,-6796,-14465,-7190,-17356,-6641,-16372,-6529,-15941,-6898,-16526,-6434,-16219,-6520,-16222,-7449,-17077,-7097,-16665,-6476,-15675,-7026,-16498,-6848,-17147,-6271,-15894,-7069,-16266,-7032,-17817,-5991,-13796,-8594,-5421,-14349,-1649,-17288,-2847,-16525,-2974,-15945,-2324,-16482,-3022,-16593,-3097,-16451,-2420,-16780,-2649,-16641,-2836,-15900,-2660,-16214,-3050,-16827,-3111,-15993,-2741,-16151,-2994,-17537,-2933,-13812,-2314,-5216,-2475,-1125,-2648,-2801,-2290,-3285,-2796,-2243,-3415,-2642,-3109,-3000,-3271,-2839,-3408,-3161,-2497,-2876,-2603,-2570,-3351,-3173,-2416,-2832,-2235,-2408,-3405,-3186,-613,-768,5271,5201,7376,7644,6241,6176,6366,6275,6964,7124,6831,6508,6998,6566,6836,7230,6277,6777,6589,6376,6934,6536,6819,6494,7160,6749,6736,6900,5822,6476,8593,6747,14520,7204,17448,6637,16490,6483,16033,6906,16600,6511,16304,6568,16279,7438,17079,7072,16624,6463,15577,7028,16343,6877,16990,6331,15760,7121,16140,7023,17719,5944,13748,8575,5401,14336,1645,17210,2880,16419,3036,15896,2382,16483,3074,16584,3143,16425,2443,16782,2650,16695,2825,15978,2632,16272,3015,16880,3084,16096,2709,16289,2965,17641,2932,13887,2323,5330,2474,1286,2656,2954,2309,3410,2803,2373,3414,2795,3106,3151,3263,2952,3403,3241,2483,2969,2568,2681,3316,3245,2383,2837,2199,2390,3396,3165,641,706,-5230,-5323,-7307,-7790,-6136,-6317,-6268,-6419,-6884,-7278,-6766,-6666,-6976,-6731,-6853,-7406,-6308,-6958,-6636,-6553,-6978,-6703,-6829,-6647,-7156,-6883,-6737,-7017,-5814,-6581,-8575,-6833,-14490,-7270,-17411,-6699,-16466,-6539,-16016,-6931,-16571,-6504,-16257,-6551,-16202,-7408,-16983,-7021,-16545,-6410,-15512,-6976,-16305,-6803,-17017,-6243,-15820,-7037,-16197,-6923,-17802,-5820,-13840,-8455,-5475,-14227,-1724,-17099,-2923,-16314,-3008,-15801,-2362,-16392,-3088,-16506,-3163,-16356,-2503,-16700,-2717,-16605,-2855,-15904,-2710,-16226,-3108,-16870,-3089,-16101,-2747,-16257,-3087,-17584,-2975,-13868,-2324,-5343,-2548,-1275,-2673,-2917,-2213,-3363,-2694,-2311,-3251,-2744,-2867,-3129,-3034,-2939,-3190,-3234,-2346,-2964,-2639,-2658,-3558,-3241,-2670,-2892,-2453,-2437,-3564,-3175,-771,-779,5105,5171,7308,7655,6265,6204,6397,6288,7024,7172,6903,6586,7002,6627,6777,7308,6190,6889,6537,6465,7011,6613,6985,6631,7393,6934,7073,7072,6112,6615,8751,6859,14672,7282,17448,6652,16146,6448,15565,6899,16151,6547,15860,6591,16048,7446,17065,7064,16661,6368,15774,6857,16524,6677,16825,6071,15577,6900,16119,7040,17490,6118,13495,8696,5432,14446,1678,17366,3036,16488,3624,15834,3012,16382,3575,16465,3685,16301,2815,16708,2982,16679,3356,15952,2934,16049,3290,16352,3964,15605,3612,16222,3647,17764,4272,13865,3977,5384,3592,1580,3794,3243,3627,3670,3622,2758,4007,3130,3835,3294,3964,3065,4468,3408,3933,3234,3789,3118,4634,3643,4211,3174,4155,3176,5512,4400,2792,1730,-3702,-4499,-5940,-6691,-4265,-5094,-4381,-5215,-4918,-5746,-4217,-4871,-4402,-4981,-4479,-5525,-3732,-4968,-4118,-4924,-4300,-5349,-3422,-5021,-3876,-4886,-4087,-4860,-2790,-4254,-5025,-4196,-10898,-4415,-13419,-4007,-12198,-4121,-11995,-4413,-12471,-3808,-11937,-3920,-11792,-4583,-12284,-3776,-12085,-3107,-11421,-3583,-11226,-3081,-11157,-2768,-10580,-3914,-10424,-3197,-11040,-1715,-9822,-5144,-6189,-11154,-4236,-13029,-5134,-11598,-5507,-10949,-4921,-11142,-4999,-11180,-4883,-11184,-4366,-11090,-4548,-10887,-4818,-10708,-4866,-10534,-5253,-10272,-5179,-9894,-4633,-10029,-4773,-10382,-4977,-8674,-4668,-5292,-4651,-3928,-4629,-4465,-4312,-3994,-4459,-3528,-4570,-4400,-4272,-4601,-4482,-4035,-4627,-4334,-4080,-4498,-4045,-3835,-4204,-3526,-3695,-3646,-4045,-4101,-4856,-4628,-3338,-3235,-673,-508,28,147,-453,-639,11,0,8,-2,7,0,7,-3,11,-8,15,-9,17,-6,17,-5,13,-3,7,0,3,0,-2,0,-4,0,-4,-2,-6,0,-14,-2,-17,-4,-8,0,-7,5,-17,7,-18,10,-7,18,-2,25,-3,27,0,31,4,34,4,34,8,36,8,37,2,36,4,34,8,28,3,15,0,11,0,12,-5,8,-4,10,0,23,-4,31,-8,30,-2,30,0,26,-6,22,-6,20,-12,15,-19,10,-10,13,-14,6,-43,-13,-43,-16,-9,-12,-10,-29,-42,-40,-37,-28,-5,-21,1,-24,-8,-20,4,-18,26,-24,44,-26,66,-30,86,-37,88,-41,72,-46,50,-31,28,23,14,64,16,51,26,32,34,39,42,48,35,58,0,72,-36,69,-59,58,-98,54,-124,36,-103,12,-110,5,-173,-19,-146,-59,-4,-42,51,1,-23,-6,-30,-6,45,46,47,70,6,55,19,60,38,62,42,47,61,46,40,42,-19,22,-34,6,-35,-50,-61,-141,-37,-171,17,-163,26,-180,46,-154,80,-63,48,-4,18,20,50,47,58,53,44,61,57,85,37,80,0,86,-8,106,-95,49,-213,-8,-131,47,49,63,40,-39,-69,-74,-37,-20,63,-12,58,-14,-12,25,-31,41,11,45,76,47,167,5,261,-37,277,-83,183,-172,35,-122,-79,138,-70,266,69,124,228,0,391,-29,594,-84,702,-78,627,-8,551,-13,509,13,372,120,352,125,622,127,691,223,362,126,386,-33,915,198,958,457,456,298,500,233,1027,469,1096,426,918,160,1067,141,1220,189,1245,164,1375,297,1378,503,1299,702,1550,929,1799,855,1752,547,1830,602,1928,832,1736,796,1735,933,1961,1385,1935,1562,2105,1485,2716,1449,2948,1305,2768,1205,2716,1346,2531,1450,2470,1653,3117,2111,3370,2176,2696,1947,2925,2305,3846,2658,2425,2184,-877,1981,-2261,2623,-1645,2908,-1876,2732,-2704,2953,-2484,3116,-2120,2954,-2442,3216,-2466,3499,-2192,3234,-2392,3361,-2497,3869,-2078,3772,-1858,3915,-2066,4438,-2285,2934,-2294,-280,-2066,-1762,-1992,-1412,-2298,-1535,-2399,-1789,-2223,-1419,-2244,-1334,-2092,-1476,-1777,-1396,-2014,-1571,-2199,-1574,-1843,-1167,-1910,-1446,-2007,-1818,-1506,-1331,-2526,-2048,-5535,-4573,-7148,-5828,-6422,-5327,-5840,-5488,-5992,-6144,-6014,-6164,-6109,-6234,-6271,-6388,-6288,-6156,-6517,-6249,-6794,-6602,-6822,-6418,-6788,-6245,-6490,-6560,-6394,-6794,-7920,-6937,-10397,-7140,-11428,-6972,-11019,-6610,-11141,-6665,-11913,-7046,-11979,-7235,-11599,-7015,-11854,-6912,-12161,-7441,-12136,-7761,-12861,-7292,-13390,-7254,-12345,-7809,-12490,-7463,-13983,-6969,-10489,-8465,-2382,-11054,1272,-12247,-270,-12060,-323,-12113,502,-12486,-697,-12251,-1086,-12141,-181,-13116,-670,-13509,-1173,-12592,-443,-12811,-449,-13698,-934,-12850,-747,-13083,-873,-15036,-1161,-11478,-1047,-2669,-1407,1006,-1658,-1146,-1195,-1297,-1421,-73,-1946,-977,-1590,-1499,-1577,-1010,-1862,-1256,-1389,-962,-1692,-509,-2613,-1317,-2087,-1359,-1997,-1034,-2891,-2024,-119,-84,5651,5723,8074,8306,7156,6870,6985,7106,7312,8403,7114,8096,7173,7848,7082,7827,6761,7189,6985,7368]}"#;
+
+use serde::Deserialize;
+
+#[derive(Deserialize)]
+struct DeriveStruct {
+    buffer: Vec<i16>,
+}
+
+fn main() {
+    let info: DeriveStruct = serde_json::from_str(JSON).unwrap();
+    println!("{}", info.buffer.len());
+}
diff --git a/src/tools/miri/bench-cargo-miri/serde2/Cargo.lock b/src/tools/miri/bench-cargo-miri/serde2/Cargo.lock
new file mode 100644
index 0000000000000..4875057613543
--- /dev/null
+++ b/src/tools/miri/bench-cargo-miri/serde2/Cargo.lock
@@ -0,0 +1,89 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "cargo-miri-test"
+version = "0.1.0"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
+
+[[package]]
+name = "serde"
+version = "1.0.137"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.137"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.96"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
diff --git a/src/tools/miri/bench-cargo-miri/serde2/Cargo.toml b/src/tools/miri/bench-cargo-miri/serde2/Cargo.toml
new file mode 100644
index 0000000000000..7cb863a7abf33
--- /dev/null
+++ b/src/tools/miri/bench-cargo-miri/serde2/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "cargo-miri-test"
+version = "0.1.0"
+authors = ["Oliver Schneider <git-spam-no-reply9815368754983@oli-obk.de>"]
+edition = "2018"
+
+[dependencies]
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
diff --git a/src/tools/miri/bench-cargo-miri/serde2/src/main.rs b/src/tools/miri/bench-cargo-miri/serde2/src/main.rs
new file mode 100644
index 0000000000000..c81b32c13fefb
--- /dev/null
+++ b/src/tools/miri/bench-cargo-miri/serde2/src/main.rs
@@ -0,0 +1,18 @@
+// Like serde1, but in two threads concurrently. And we don't print.
+static JSON: &str = r#"{"buffer":[-29,-42,-40,-37,-28,-5,-21,1,-24,-8,-20,4,-18,26,-24,44,-26,66,-30,86,-37,88,-41,72,-46,50,-31,28,23,14,64,16,51,26,32,34,39,42,48,35,58,0,72,-36,69,-59,58,-98,54,-124,36,-103,12,-110,5,-173,-19,-146,-59,-4,-42,51,1,-23,-6,-30,-6,45,46,47,70,6,55,19,60,38,62,42,47,61,46,40,42,-19,22,-34,6,-35,-50,-61,-141,-37,-171,17,-163,26,-180,46,-154,80,-63,48,-4,18,20,50,47,58,53,44,61,57,85,37,80,0,86,-8,106,-95,49,-213,-8,-131,47,49,63,40,-39,-69,-74,-37,-20,63,-12,58,-14,-12,25,-31,41,11,45,76,47,167,5,261,-37,277,-83,183,-172,35,-122,-79,138,-70,266,69,124,228,0,391,-29,594,-84,702,-78,627,-8,551,-13,509,13,372,120,352,125,622,127,691,223,362,126,386,-33,915,198,958,457,456,298,500,233,1027,469,1096,426,918,160,1067,141,1220,189,1245,164,1375,297,1378,503,1299,702,1550,929,1799,855,1752,547,1830,602,1928,832,1736,796,1735,933,1961,1385,1935,1562,2105,1485,2716,1449,2948,1305,2768,1205,2716,1346,2531,1450,2470,1653,3117,2111,3370,2176,2696,1947,2925,2305,3846,2658,2425,2184,-877,1981,-2261,2623,-1645,2908,-1876,2732,-2704,2953,-2484,3116,-2120,2954,-2442,3216,-2466,3499,-2192,3234,-2392,3361,-2497,3869,-2078,3772,-1858,3915,-2066,4438,-2285,2934,-2294,-280,-2066,-1762,-1992,-1412,-2298,-1535,-2399,-1789,-2223,-1419,-2244,-1334,-2092,-1476,-1777,-1396,-2014,-1571,-2199,-1574,-1843,-1167,-1910,-1446,-2007,-1818,-1506,-1331,-2526,-2048,-5535,-4573,-7148,-5828,-6422,-5327,-5840,-5488,-5992,-6144,-6014,-6164,-6109,-6234,-6271,-6388,-6288,-6156,-6517,-6249,-6794,-6602,-6822,-6418,-6788,-6245,-6490,-6560,-6394,-6794,-7920,-6937,-10397,-7140,-11428,-6972,-11019,-6610,-11141,-6665,-11913,-7046,-11979,-7235,-11599,-7015,-11854,-6912,-12161,-7441,-12136,-7761,-12861,-7292,-13390,-7254,-12345,-7809,-12490,-7463,-13983,-6969,-10489,-8465,-2382,-11054,1272,-12247,-270,-12060,-323,-12113,502,-12486,-697,-12251,-1086,-12141,-181,-13116,-670,-13509,-1173,-12592,-443,-12811,-449,-13698,-934,-12850,-747,-13083,-873,-15036,-1161,-11478,-1047,-2669,-1407,1006,-1658,-1146,-1195,-1297,-1421,-73,-1946,-977,-1590,-1499,-1577,-1010,-1862,-1256,-1389,-962,-1692,-509,-2613,-1317,-2087,-1359,-1997,-1034,-2891,-2024,-119,-84,5651,5723,8074,8306,7156,6870,6985,7106,7312,8403,7114,8096,7173,7848,7082,7827,6761,7189,6985,7368,7076,7835,6992,7297,7453,7260,7016,7755,6025,7429,8533,7352,14150,7628,17142,7077,16399,6947,15939,7475,16564,7069,16463,6882,16400,7602,17031,7233,16543,6517,15395,7018,15985,7104,16689,6869,15655,7622,16155,7198,17884,6022,14056,8856,5665,14484,1815,16782,3034,15786,3107,15664,2312,16517,2965,16443,3036,16120,2287,16584,2479,16720,2693,16073,2535,16159,2958,16609,3067,16086,2716,16579,3035,17752,3092,13704,2499,5265,2620,1452,2808,3024,2444,3275,2839,2267,3340,2857,2968,3232,3066,2867,3152,3072,2248,2961,2413,2807,3238,3237,2368,2699,2262,2392,3537,3339,827,823,-5020,-5359,-7095,-7857,-5973,-6274,-6208,-6279,-6934,-7181,-6893,-6647,-7146,-6687,-7026,-7328,-6451,-6924,-6763,-6535,-7109,-6639,-6926,-6559,-7188,-6799,-6727,-6955,-5786,-6554,-8543,-6796,-14465,-7190,-17356,-6641,-16372,-6529,-15941,-6898,-16526,-6434,-16219,-6520,-16222,-7449,-17077,-7097,-16665,-6476,-15675,-7026,-16498,-6848,-17147,-6271,-15894,-7069,-16266,-7032,-17817,-5991,-13796,-8594,-5421,-14349,-1649,-17288,-2847,-16525,-2974,-15945,-2324,-16482,-3022,-16593,-3097,-16451,-2420,-16780,-2649,-16641,-2836,-15900,-2660,-16214,-3050,-16827,-3111,-15993,-2741,-16151,-2994,-17537,-2933,-13812,-2314,-5216,-2475,-1125,-2648,-2801,-2290,-3285,-2796,-2243,-3415,-2642,-3109,-3000,-3271,-2839,-3408,-3161,-2497,-2876,-2603,-2570,-3351,-3173,-2416,-2832,-2235,-2408,-3405,-3186,-613,-768,5271,5201,7376,7644,6241,6176,6366,6275,6964,7124,6831,6508,6998,6566,6836,7230,6277,6777,6589,6376,6934,6536,6819,6494,7160,6749,6736,6900,5822,6476,8593,6747,14520,7204,17448,6637,16490,6483,16033,6906,16600,6511,16304,6568,16279,7438,17079,7072,16624,6463,15577,7028,16343,6877,16990,6331,15760,7121,16140,7023,17719,5944,13748,8575,5401,14336,1645,17210,2880,16419,3036,15896,2382,16483,3074,16584,3143,16425,2443,16782,2650,16695,2825,15978,2632,16272,3015,16880,3084,16096,2709,16289,2965,17641,2932,13887,2323,5330,2474,1286,2656,2954,2309,3410,2803,2373,3414,2795,3106,3151,3263,2952,3403,3241,2483,2969,2568,2681,3316,3245,2383,2837,2199,2390,3396,3165,641,706,-5230,-5323,-7307,-7790,-6136,-6317,-6268,-6419,-6884,-7278,-6766,-6666,-6976,-6731,-6853,-7406,-6308,-6958,-6636,-6553,-6978,-6703,-6829,-6647,-7156,-6883,-6737,-7017,-5814,-6581,-8575,-6833,-14490,-7270,-17411,-6699,-16466,-6539,-16016,-6931,-16571,-6504,-16257,-6551,-16202,-7408,-16983,-7021,-16545,-6410,-15512,-6976,-16305,-6803,-17017,-6243,-15820,-7037,-16197,-6923,-17802,-5820,-13840,-8455,-5475,-14227,-1724,-17099,-2923,-16314,-3008,-15801,-2362,-16392,-3088,-16506,-3163,-16356,-2503,-16700,-2717,-16605,-2855,-15904,-2710,-16226,-3108,-16870,-3089,-16101,-2747,-16257,-3087,-17584,-2975,-13868,-2324,-5343,-2548,-1275,-2673,-2917,-2213,-3363,-2694,-2311,-3251,-2744,-2867,-3129,-3034,-2939,-3190,-3234,-2346,-2964,-2639,-2658,-3558,-3241,-2670,-2892,-2453,-2437,-3564,-3175,-771,-779,5105,5171,7308,7655,6265,6204,6397,6288,7024,7172,6903,6586,7002,6627,6777,7308,6190,6889,6537,6465,7011,6613,6985,6631,7393,6934,7073,7072,6112,6615,8751,6859,14672,7282,17448,6652,16146,6448,15565,6899,16151,6547,15860,6591,16048,7446,17065,7064,16661,6368,15774,6857,16524,6677,16825,6071,15577,6900,16119,7040,17490,6118,13495,8696,5432,14446,1678,17366,3036,16488,3624,15834,3012,16382,3575,16465,3685,16301,2815,16708,2982,16679,3356,15952,2934,16049,3290,16352,3964,15605,3612,16222,3647,17764,4272,13865,3977,5384,3592,1580,3794,3243,3627,3670,3622,2758,4007,3130,3835,3294,3964,3065,4468,3408,3933,3234,3789,3118,4634,3643,4211,3174,4155,3176,5512,4400,2792,1730,-3702,-4499,-5940,-6691,-4265,-5094,-4381,-5215,-4918,-5746,-4217,-4871,-4402,-4981,-4479,-5525,-3732,-4968,-4118,-4924,-4300,-5349,-3422,-5021,-3876,-4886,-4087,-4860,-2790,-4254,-5025,-4196,-10898,-4415,-13419,-4007,-12198,-4121,-11995,-4413,-12471,-3808,-11937,-3920,-11792,-4583,-12284,-3776,-12085,-3107,-11421,-3583,-11226,-3081,-11157,-2768,-10580,-3914,-10424,-3197,-11040,-1715,-9822,-5144,-6189,-11154,-4236,-13029,-5134,-11598,-5507,-10949,-4921,-11142,-4999,-11180,-4883,-11184,-4366,-11090,-4548,-10887,-4818,-10708,-4866,-10534,-5253,-10272,-5179,-9894,-4633,-10029,-4773,-10382,-4977,-8674,-4668,-5292,-4651,-3928,-4629,-4465,-4312,-3994,-4459,-3528,-4570,-4400,-4272,-4601,-4482,-4035,-4627,-4334,-4080,-4498,-4045,-3835,-4204,-3526,-3695,-3646,-4045,-4101,-4856,-4628,-3338,-3235,-673,-508,28,147,-453,-639,11,0,8,-2,7,0,7,-3,11,-8,15,-9,17,-6,17,-5,13,-3,7,0,3,0,-2,0,-4,0,-4,-2,-6,0,-14,-2,-17,-4,-8,0,-7,5,-17,7,-18,10,-7,18,-2,25,-3,27,0,31,4,34,4,34,8,36,8,37,2,36,4,34,8,28,3,15,0,11,0,12,-5,8,-4,10,0,23,-4,31,-8,30,-2,30,0,26,-6,22,-6,20,-12,15,-19,10,-10,13,-14,6,-43,-13,-43,-16,-9,-12,-10,-29,-42,-40,-37,-28,-5,-21,1,-24,-8,-20,4,-18,26,-24,44,-26,66,-30,86,-37,88,-41,72,-46,50,-31,28,23,14,64,16,51,26,32,34,39,42,48,35,58,0,72,-36,69,-59,58,-98,54,-124,36,-103,12,-110,5,-173,-19,-146,-59,-4,-42,51,1,-23,-6,-30,-6,45,46,47,70,6,55,19,60,38,62,42,47,61,46,40,42,-19,22,-34,6,-35,-50,-61,-141,-37,-171,17,-163,26,-180,46,-154,80,-63,48,-4,18,20,50,47,58,53,44,61,57,85,37,80,0,86,-8,106,-95,49,-213,-8,-131,47,49,63,40,-39,-69,-74,-37,-20,63,-12,58,-14,-12,25,-31,41,11,45,76,47,167,5,261,-37,277,-83,183,-172,35,-122,-79,138,-70,266,69,124,228,0,391,-29,594,-84,702,-78,627,-8,551,-13,509,13,372,120,352,125,622,127,691,223,362,126,386,-33,915,198,958,457,456,298,500,233,1027,469,1096,426,918,160,1067,141,1220,189,1245,164,1375,297,1378,503,1299,702,1550,929,1799,855,1752,547,1830,602,1928,832,1736,796,1735,933,1961,1385,1935,1562,2105,1485,2716,1449,2948,1305,2768,1205,2716,1346,2531,1450,2470,1653,3117,2111,3370,2176,2696,1947,2925,2305,3846,2658,2425,2184,-877,1981,-2261,2623,-1645,2908,-1876,2732,-2704,2953,-2484,3116,-2120,2954,-2442,3216,-2466,3499,-2192,3234,-2392,3361,-2497,3869,-2078,3772,-1858,3915,-2066,4438,-2285,2934,-2294,-280,-2066,-1762,-1992,-1412,-2298,-1535,-2399,-1789,-2223,-1419,-2244,-1334,-2092,-1476,-1777,-1396,-2014,-1571,-2199,-1574,-1843,-1167,-1910,-1446,-2007,-1818,-1506,-1331,-2526,-2048,-5535,-4573,-7148,-5828,-6422,-5327,-5840,-5488,-5992,-6144,-6014,-6164,-6109,-6234,-6271,-6388,-6288,-6156,-6517,-6249,-6794,-6602,-6822,-6418,-6788,-6245,-6490,-6560,-6394,-6794,-7920,-6937,-10397,-7140,-11428,-6972,-11019,-6610,-11141,-6665,-11913,-7046,-11979,-7235,-11599,-7015,-11854,-6912,-12161,-7441,-12136,-7761,-12861,-7292,-13390,-7254,-12345,-7809,-12490,-7463,-13983,-6969,-10489,-8465,-2382,-11054,1272,-12247,-270,-12060,-323,-12113,502,-12486,-697,-12251,-1086,-12141,-181,-13116,-670,-13509,-1173,-12592,-443,-12811,-449,-13698,-934,-12850,-747,-13083,-873,-15036,-1161,-11478,-1047,-2669,-1407,1006,-1658,-1146,-1195,-1297,-1421,-73,-1946,-977,-1590,-1499,-1577,-1010,-1862,-1256,-1389,-962,-1692,-509,-2613,-1317,-2087,-1359,-1997,-1034,-2891,-2024,-119,-84,5651,5723,8074,8306,7156,6870,6985,7106,7312,8403,7114,8096,7173,7848,7082,7827,6761,7189,6985,7368]}"#;
+
+use serde::Deserialize;
+use std::thread;
+
+#[derive(Deserialize)]
+struct DeriveStruct {
+    buffer: Vec<i16>,
+}
+
+fn main() {
+    let t = thread::spawn(|| {
+        let _info: DeriveStruct = serde_json::from_str(JSON).unwrap();
+    });
+    let _info: DeriveStruct = serde_json::from_str(JSON).unwrap();
+    t.join().unwrap();
+}
diff --git a/src/tools/miri/bench-cargo-miri/slice-get-unchecked/Cargo.lock b/src/tools/miri/bench-cargo-miri/slice-get-unchecked/Cargo.lock
new file mode 100644
index 0000000000000..a375afaed3098
--- /dev/null
+++ b/src/tools/miri/bench-cargo-miri/slice-get-unchecked/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "slice-get-unchecked"
+version = "0.1.0"
diff --git a/src/tools/miri/bench-cargo-miri/slice-get-unchecked/Cargo.toml b/src/tools/miri/bench-cargo-miri/slice-get-unchecked/Cargo.toml
new file mode 100644
index 0000000000000..1ac2276866ff7
--- /dev/null
+++ b/src/tools/miri/bench-cargo-miri/slice-get-unchecked/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "slice-get-unchecked"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/src/tools/miri/bench-cargo-miri/slice-get-unchecked/src/main.rs b/src/tools/miri/bench-cargo-miri/slice-get-unchecked/src/main.rs
new file mode 100644
index 0000000000000..a72083bd9de31
--- /dev/null
+++ b/src/tools/miri/bench-cargo-miri/slice-get-unchecked/src/main.rs
@@ -0,0 +1,12 @@
+//! This is a stripped-down version of the code pattern that causes runtime blowup when printing
+//! backtraces in a failed test under cargo miri test with -Zmiri-disable-isolation.
+//! See https://github.com/rust-lang/miri/issues/2273
+
+fn main() {
+    let x = vec![0u8; 4096];
+    let mut i = 0;
+    while i < x.len() {
+        let _element = unsafe { *x.get_unchecked(i) };
+        i += 1;
+    }
+}
diff --git a/src/tools/miri/bench-cargo-miri/unicode/Cargo.lock b/src/tools/miri/bench-cargo-miri/unicode/Cargo.lock
new file mode 100644
index 0000000000000..80d013b7d6d84
--- /dev/null
+++ b/src/tools/miri/bench-cargo-miri/unicode/Cargo.lock
@@ -0,0 +1,16 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "unicode"
+version = "0.1.0"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
diff --git a/src/tools/miri/bench-cargo-miri/unicode/Cargo.toml b/src/tools/miri/bench-cargo-miri/unicode/Cargo.toml
new file mode 100644
index 0000000000000..7e8708b03f19c
--- /dev/null
+++ b/src/tools/miri/bench-cargo-miri/unicode/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "unicode"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+unicode-xid = "0.2.3"
diff --git a/src/tools/miri/bench-cargo-miri/unicode/src/main.rs b/src/tools/miri/bench-cargo-miri/unicode/src/main.rs
new file mode 100644
index 0000000000000..3cf25ba9cf6c2
--- /dev/null
+++ b/src/tools/miri/bench-cargo-miri/unicode/src/main.rs
@@ -0,0 +1,20 @@
+//! Extracted from the unicode-xid exhaustive test all_valid_chars_do_not_panic_for_is_xid_continue
+
+use unicode_xid::UnicodeXID;
+
+/// A `char` in Rust is a Unicode Scalar Value
+///
+/// See: http://www.unicode.org/glossary/#unicode_scalar_value
+fn all_valid_chars() -> impl Iterator<Item = char> {
+    (0u32..=0xD7FF).chain(0xE000u32..=0x10FFFF).map(|u| {
+        core::convert::TryFrom::try_from(u)
+            .expect("The selected range should be infallible if the docs match impl")
+    })
+}
+
+fn main() {
+    // Take only the first few chars because we don't want to wait all day
+    for c in all_valid_chars().take(1_500) {
+        let _ = UnicodeXID::is_xid_continue(c);
+    }
+}
diff --git a/src/tools/miri/build.rs b/src/tools/miri/build.rs
new file mode 100644
index 0000000000000..37c626baab58a
--- /dev/null
+++ b/src/tools/miri/build.rs
@@ -0,0 +1,8 @@
+fn main() {
+    // Don't rebuild miri when nothing changed.
+    println!("cargo:rerun-if-changed=build.rs");
+    // Re-export the TARGET environment variable so it can
+    // be accessed by miri.
+    let target = std::env::var("TARGET").unwrap();
+    println!("cargo:rustc-env=TARGET={}", target);
+}
diff --git a/src/tools/miri/cargo-miri/Cargo.lock b/src/tools/miri/cargo-miri/Cargo.lock
new file mode 100644
index 0000000000000..95c2bda505c5b
--- /dev/null
+++ b/src/tools/miri/cargo-miri/Cargo.lock
@@ -0,0 +1,554 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "anyhow"
+version = "1.0.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203"
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "camino"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "869119e97797867fd90f5e22af7d0bd274bd4635ebb9eb68c04f3f513ae6c412"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cargo-miri"
+version = "0.1.0"
+dependencies = [
+ "cargo_metadata",
+ "directories",
+ "rustc-workspace-hack",
+ "rustc_version",
+ "serde",
+ "serde_json",
+ "vergen",
+]
+
+[[package]]
+name = "cargo-platform"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cargo_metadata"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3abb7553d5b9b8421c6de7cb02606ff15e0c6eea7d8eadd75ef013fd636bec36"
+dependencies = [
+ "camino",
+ "cargo-platform",
+ "semver",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.72"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
+dependencies = [
+ "jobserver",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
+dependencies = [
+ "libc",
+ "num-integer",
+ "num-traits",
+ "time",
+ "winapi",
+]
+
+[[package]]
+name = "directories"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e69600ff1703123957937708eb27f7a564e48885c537782722ed0ba3189ce1d7"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
+name = "enum-iterator"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6"
+dependencies = [
+ "enum-iterator-derive",
+]
+
+[[package]]
+name = "enum-iterator-derive"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "form_urlencoded"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
+dependencies = [
+ "matches",
+ "percent-encoding",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "getset"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9"
+dependencies = [
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "git2"
+version = "0.13.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f29229cc1b24c0e6062f6e742aa3e256492a5323365e5ed3413599f8a5eff7d6"
+dependencies = [
+ "bitflags",
+ "libc",
+ "libgit2-sys",
+ "log",
+ "url",
+]
+
+[[package]]
+name = "idna"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
+dependencies = [
+ "matches",
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
+
+[[package]]
+name = "jobserver"
+version = "0.1.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.112"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
+
+[[package]]
+name = "libgit2-sys"
+version = "0.12.26+1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19e1c899248e606fbfe68dcb31d8b0176ebab833b103824af31bddf4b7457494"
+dependencies = [
+ "cc",
+ "libc",
+ "libz-sys",
+ "pkg-config",
+]
+
+[[package]]
+name = "libz-sys"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "matches"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
+
+[[package]]
+name = "num-integer"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c278e965f1d8cf32d6e0e96de3d3e79712178ae67986d9cf9151f51e95aac89b"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
+dependencies = [
+ "getrandom",
+ "redox_syscall",
+]
+
+[[package]]
+name = "rustc-workspace-hack"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc71d2faa173b74b232dedc235e3ee1696581bb132fc116fa3626d6151a1a8fb"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f"
+
+[[package]]
+name = "ryu"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
+
+[[package]]
+name = "semver"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.140"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.140"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f2122636b9fe3b81f1cb25099fcf2d3f542cdb1d45940d56c713158884a05da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.82"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.98"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "time"
+version = "0.1.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "url"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "matches",
+ "percent-encoding",
+]
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "vergen"
+version = "5.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6cf88d94e969e7956d924ba70741316796177fa0c79a2c9f4ab04998d96e966e"
+dependencies = [
+ "anyhow",
+ "cfg-if",
+ "chrono",
+ "enum-iterator",
+ "getset",
+ "git2",
+ "rustversion",
+ "thiserror",
+]
+
+[[package]]
+name = "version_check"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
+
+[[package]]
+name = "wasi"
+version = "0.10.2+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
+
+[[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"
diff --git a/src/tools/miri/cargo-miri/Cargo.toml b/src/tools/miri/cargo-miri/Cargo.toml
new file mode 100644
index 0000000000000..9ac170c5b5377
--- /dev/null
+++ b/src/tools/miri/cargo-miri/Cargo.toml
@@ -0,0 +1,32 @@
+[package]
+authors = ["Miri Team"]
+description = "An experimental interpreter for Rust MIR (cargo wrapper)."
+license = "MIT OR Apache-2.0"
+name = "cargo-miri"
+repository = "https://github.com/rust-lang/miri"
+version = "0.1.0"
+edition = "2021"
+
+[[bin]]
+name = "cargo-miri"
+path = "src/main.rs"
+test = false # we have no unit tests
+doctest = false # and no doc tests
+
+[dependencies]
+directories = "3"
+rustc_version = "0.4"
+serde_json = "1.0.40"
+cargo_metadata = "0.15.0"
+
+# A noop dependency that changes in the Rust repository, it's a bit of a hack.
+# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust`
+# for more information.
+rustc-workspace-hack = "1.0.0"
+
+# Enable some feature flags that dev-dependencies need but dependencies
+# do not.  This makes `./miri install` after `./miri build` faster.
+serde = { version = "*", features = ["derive"] }
+
+[build-dependencies]
+vergen = { version = "5", default_features = false, features = ["git"] }
diff --git a/src/tools/miri/cargo-miri/build.rs b/src/tools/miri/cargo-miri/build.rs
new file mode 100644
index 0000000000000..ebd8e7003d5f7
--- /dev/null
+++ b/src/tools/miri/cargo-miri/build.rs
@@ -0,0 +1,11 @@
+use vergen::vergen;
+
+fn main() {
+    // Don't rebuild miri when nothing changed.
+    println!("cargo:rerun-if-changed=build.rs");
+    // vergen
+    let mut gen_config = vergen::Config::default();
+    *gen_config.git_mut().sha_kind_mut() = vergen::ShaKind::Short;
+    *gen_config.git_mut().commit_timestamp_kind_mut() = vergen::TimestampKind::DateOnly;
+    vergen(gen_config).ok(); // Ignore failure (in case we are built outside a git repo)
+}
diff --git a/src/tools/miri/cargo-miri/miri b/src/tools/miri/cargo-miri/miri
new file mode 100755
index 0000000000000..cf3ad06788ab1
--- /dev/null
+++ b/src/tools/miri/cargo-miri/miri
@@ -0,0 +1,4 @@
+#!/bin/sh
+# RA invokes `./miri cargo ...` for each workspace, so we need to forward that to the main `miri`
+# script. See <https://github.com/rust-analyzer/rust-analyzer/issues/10793>.
+exec "$(dirname "$0")"/../miri "$@"
diff --git a/src/tools/miri/cargo-miri/src/arg.rs b/src/tools/miri/cargo-miri/src/arg.rs
new file mode 100644
index 0000000000000..e8bac4625f710
--- /dev/null
+++ b/src/tools/miri/cargo-miri/src/arg.rs
@@ -0,0 +1,134 @@
+//! Utilities for dealing with argument flags
+
+use std::borrow::Cow;
+use std::env;
+
+/// Determines whether a `--flag` is present.
+pub fn has_arg_flag(name: &str) -> bool {
+    num_arg_flag(name) > 0
+}
+
+/// Determines how many times a `--flag` is present.
+pub fn num_arg_flag(name: &str) -> usize {
+    env::args().take_while(|val| val != "--").filter(|val| val == name).count()
+}
+
+/// Yields all values of command line flag `name` as `Ok(arg)`, and all other arguments except
+/// the flag as `Err(arg)`. (The flag `name` itself is not yielded at all, only its values are.)
+pub struct ArgSplitFlagValue<'a, I> {
+    args: Option<I>,
+    name: &'a str,
+}
+
+impl<'a, I: Iterator> ArgSplitFlagValue<'a, I> {
+    fn new(args: I, name: &'a str) -> Self {
+        Self { args: Some(args), name }
+    }
+}
+
+impl<'s, I: Iterator<Item = Cow<'s, str>>> Iterator for ArgSplitFlagValue<'_, I> {
+    // If the original iterator was all `Owned`, then we will only ever yield `Owned`
+    // (so `into_owned()` is cheap).
+    type Item = Result<Cow<'s, str>, Cow<'s, str>>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let Some(args) = self.args.as_mut() else {
+            // We already canceled this iterator.
+            return None;
+        };
+        let arg = args.next()?;
+        if arg == "--" {
+            // Stop searching at `--`.
+            self.args = None;
+            return None;
+        }
+        // These branches cannot be merged if we want to avoid the allocation in the `Borrowed` branch.
+        match &arg {
+            Cow::Borrowed(arg) =>
+                if let Some(suffix) = arg.strip_prefix(self.name) {
+                    // Strip leading `name`.
+                    if suffix.is_empty() {
+                        // This argument is exactly `name`; the next one is the value.
+                        return args.next().map(Ok);
+                    } else if let Some(suffix) = suffix.strip_prefix('=') {
+                        // This argument is `name=value`; get the value.
+                        return Some(Ok(Cow::Borrowed(suffix)));
+                    }
+                },
+            Cow::Owned(arg) =>
+                if let Some(suffix) = arg.strip_prefix(self.name) {
+                    // Strip leading `name`.
+                    if suffix.is_empty() {
+                        // This argument is exactly `name`; the next one is the value.
+                        return args.next().map(Ok);
+                    } else if let Some(suffix) = suffix.strip_prefix('=') {
+                        // This argument is `name=value`; get the value. We need to do an allocation
+                        // here as a `String` cannot be subsliced (what would the lifetime be?).
+                        return Some(Ok(Cow::Owned(suffix.to_owned())));
+                    }
+                },
+        }
+        Some(Err(arg))
+    }
+}
+
+impl<'a, I: Iterator<Item = String> + 'a> ArgSplitFlagValue<'a, I> {
+    pub fn from_string_iter(
+        args: I,
+        name: &'a str,
+    ) -> impl Iterator<Item = Result<String, String>> + 'a {
+        ArgSplitFlagValue::new(args.map(Cow::Owned), name).map(|x| {
+            match x {
+                Ok(Cow::Owned(s)) => Ok(s),
+                Err(Cow::Owned(s)) => Err(s),
+                _ => panic!("iterator converted owned to borrowed"),
+            }
+        })
+    }
+}
+
+impl<'x: 'a, 'a, I: Iterator<Item = &'x str> + 'a> ArgSplitFlagValue<'a, I> {
+    pub fn from_str_iter(
+        args: I,
+        name: &'a str,
+    ) -> impl Iterator<Item = Result<&'x str, &'x str>> + 'a {
+        ArgSplitFlagValue::new(args.map(Cow::Borrowed), name).map(|x| {
+            match x {
+                Ok(Cow::Borrowed(s)) => Ok(s),
+                Err(Cow::Borrowed(s)) => Err(s),
+                _ => panic!("iterator converted borrowed to owned"),
+            }
+        })
+    }
+}
+
+/// Yields all values of command line flag `name`.
+pub struct ArgFlagValueIter;
+
+impl ArgFlagValueIter {
+    pub fn from_string_iter<'a, I: Iterator<Item = String> + 'a>(
+        args: I,
+        name: &'a str,
+    ) -> impl Iterator<Item = String> + 'a {
+        ArgSplitFlagValue::from_string_iter(args, name).filter_map(Result::ok)
+    }
+}
+
+impl ArgFlagValueIter {
+    pub fn from_str_iter<'x: 'a, 'a, I: Iterator<Item = &'x str> + 'a>(
+        args: I,
+        name: &'a str,
+    ) -> impl Iterator<Item = &'x str> + 'a {
+        ArgSplitFlagValue::from_str_iter(args, name).filter_map(Result::ok)
+    }
+}
+
+/// Gets the values of a `--flag`.
+pub fn get_arg_flag_values(name: &str) -> impl Iterator<Item = String> + '_ {
+    ArgFlagValueIter::from_string_iter(env::args(), name)
+}
+
+/// Gets the value of a `--flag`.
+pub fn get_arg_flag_value(name: &str) -> Option<String> {
+    get_arg_flag_values(name).next()
+}
diff --git a/src/tools/miri/cargo-miri/src/main.rs b/src/tools/miri/cargo-miri/src/main.rs
new file mode 100644
index 0000000000000..331c4c9c2b0e7
--- /dev/null
+++ b/src/tools/miri/cargo-miri/src/main.rs
@@ -0,0 +1,97 @@
+#![allow(clippy::useless_format, clippy::derive_partial_eq_without_eq, rustc::internal)]
+
+#[macro_use]
+mod util;
+
+mod arg;
+mod phases;
+mod setup;
+mod version;
+
+use std::{env, iter};
+
+use crate::phases::*;
+
+fn main() {
+    // Rustc does not support non-UTF-8 arguments so we make no attempt either.
+    // (We do support non-UTF-8 environment variables though.)
+    let mut args = std::env::args();
+    // Skip binary name.
+    args.next().unwrap();
+
+    // Dispatch to `cargo-miri` phase. Here is a rough idea of "who calls who".
+    //
+    // Initially, we are invoked as `cargo-miri miri run/test`. We first run the setup phase:
+    // - We call `xargo`, and set `RUSTC` back to us, together with `MIRI_CALLED_FROM_XARGO`,
+    //   so that xargo's rustc invocations end up in `phase_rustc` with `RustcPhase::Setup`.
+    //   There we then call the Miri driver with `MIRI_BE_RUSTC` to perform the actual build.
+    //
+    // Then we call `cargo run/test`, exactly forwarding all user flags, plus some configuration so
+    // that we control every binary invoked by cargo:
+    // - We set RUSTC_WRAPPER to ourselves, so for (almost) all rustc invocations, we end up in
+    //   `phase_rustc` with `RustcPhase::Build`. This will in turn either determine that a
+    //   dependency needs to be built (for which it invokes the Miri driver with `MIRI_BE_RUSTC`),
+    //   or determine that this is a binary Miri should run, in which case we generate a JSON file
+    //   with all the information needed to build and run this crate.
+    //   (We don't run it yet since cargo thinks this is a build step, not a run step -- running the
+    //   binary here would lead to a bad user experience.)
+    // - We set RUSTC to the Miri driver and also set `MIRI_BE_RUSTC`, so that gets called by build
+    //   scripts (and cargo uses it for the version query).
+    // - We set `target.*.runner` to `cargo-miri runner`, which ends up calling `phase_runner` for
+    //   `RunnerPhase::Cargo`. This parses the JSON file written in `phase_rustc` and then invokes
+    //   the actual Miri driver for interpretation.
+    // - We set RUSTDOC to ourselves, which ends up in `phase_rustdoc`. There we call regular
+    //   rustdoc with some extra flags, and we set `MIRI_CALLED_FROM_RUSTDOC` to recognize this
+    //   phase in our recursive invocations:
+    //   - We set the `--test-builder` flag of rustdoc to ourselves, which ends up in `phase_rustc`
+    //     with `RustcPhase::Rustdoc`. There we perform a check-build (needed to get the expected
+    //     build failures for `compile_fail` doctests) and then store a JSON file with the
+    //     information needed to run this test.
+    //   - We also set `--runtool` to ourselves, which ends up in `phase_runner` with
+    //     `RunnerPhase::Rustdoc`. There we parse the JSON file written in `phase_rustc` and invoke
+    //     the Miri driver for interpretation.
+
+    // Dispatch running as part of sysroot compilation.
+    if env::var_os("MIRI_CALLED_FROM_XARGO").is_some() {
+        phase_rustc(args, RustcPhase::Setup);
+        return;
+    }
+
+    // The way rustdoc invokes rustc is indistuingishable from the way cargo invokes rustdoc by the
+    // arguments alone. `phase_cargo_rustdoc` sets this environment variable to let us disambiguate.
+    if env::var_os("MIRI_CALLED_FROM_RUSTDOC").is_some() {
+        // ...however, we then also see this variable when rustdoc invokes us as the testrunner!
+        // The runner is invoked as `$runtool ($runtool-arg)* output_file`;
+        // since we don't specify any runtool-args, and rustdoc supplies multiple arguments to
+        // the test-builder unconditionally, we can just check the number of remaining arguments:
+        if args.len() == 1 {
+            phase_runner(args, RunnerPhase::Rustdoc);
+        } else {
+            phase_rustc(args, RustcPhase::Rustdoc);
+        }
+
+        return;
+    }
+
+    let Some(first) = args.next() else {
+        show_error!(
+            "`cargo-miri` called without first argument; please only invoke this binary through `cargo miri`"
+        )
+    };
+    match first.as_str() {
+        "miri" => phase_cargo_miri(args),
+        "runner" => phase_runner(args, RunnerPhase::Cargo),
+        arg if arg == env::var("RUSTC").unwrap() => {
+            // If the first arg is equal to the RUSTC env ariable (which should be set at this
+            // point), then we need to behave as rustc. This is the somewhat counter-intuitive
+            // behavior of having both RUSTC and RUSTC_WRAPPER set
+            // (see https://github.com/rust-lang/cargo/issues/10886).
+            phase_rustc(args, RustcPhase::Build)
+        }
+        _ => {
+            // Everything else must be rustdoc. But we need to get `first` "back onto the iterator",
+            // it is some part of the rustdoc invocation.
+            phase_rustdoc(iter::once(first).chain(args));
+        }
+    }
+}
diff --git a/src/tools/miri/cargo-miri/src/phases.rs b/src/tools/miri/cargo-miri/src/phases.rs
new file mode 100644
index 0000000000000..93eb3cb174659
--- /dev/null
+++ b/src/tools/miri/cargo-miri/src/phases.rs
@@ -0,0 +1,601 @@
+//! Implements the various phases of `cargo miri run/test`.
+
+use std::env;
+use std::fmt::Write as _;
+use std::fs::{self, File};
+use std::io::BufReader;
+use std::path::PathBuf;
+use std::process::Command;
+
+use crate::{setup::*, util::*};
+
+const CARGO_MIRI_HELP: &str = r#"Runs binary crates and tests in Miri
+
+Usage:
+    cargo miri [subcommand] [<cargo options>...] [--] [<program/test suite options>...]
+
+Subcommands:
+    run, r                   Run binaries
+    test, t                  Run tests
+    nextest                  Run tests with nextest (requires cargo-nextest installed)
+    setup                    Only perform automatic setup, but without asking questions (for getting a proper libstd)
+
+The cargo options are exactly the same as for `cargo run` and `cargo test`, respectively.
+
+Examples:
+    cargo miri run
+    cargo miri test -- test-suite-filter
+
+    cargo miri setup --print sysroot
+        This will print the path to the generated sysroot (and nothing else) on stdout.
+        stderr will still contain progress information about how the build is doing.
+
+"#;
+
+fn show_help() {
+    println!("{}", CARGO_MIRI_HELP);
+}
+
+fn show_version() {
+    let mut version = format!("miri {}", env!("CARGO_PKG_VERSION"));
+    // Only use `option_env` on vergen variables to ensure the build succeeds
+    // when vergen failed to find the git info.
+    if let Some(sha) = option_env!("VERGEN_GIT_SHA_SHORT") {
+        // This `unwrap` can never fail because if VERGEN_GIT_SHA_SHORT exists, then so does
+        // VERGEN_GIT_COMMIT_DATE.
+        #[allow(clippy::option_env_unwrap)]
+        write!(&mut version, " ({} {})", sha, option_env!("VERGEN_GIT_COMMIT_DATE").unwrap())
+            .unwrap();
+    }
+    println!("{}", version);
+}
+
+fn forward_patched_extern_arg(args: &mut impl Iterator<Item = String>, cmd: &mut Command) {
+    cmd.arg("--extern"); // always forward flag, but adjust filename:
+    let path = args.next().expect("`--extern` should be followed by a filename");
+    if let Some(lib) = path.strip_suffix(".rlib") {
+        // If this is an rlib, make it an rmeta.
+        cmd.arg(format!("{}.rmeta", lib));
+    } else {
+        // Some other extern file (e.g. a `.so`). Forward unchanged.
+        cmd.arg(path);
+    }
+}
+
+pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
+    // Check for version and help flags even when invoked as `cargo-miri`.
+    if has_arg_flag("--help") || has_arg_flag("-h") {
+        show_help();
+        return;
+    }
+    if has_arg_flag("--version") || has_arg_flag("-V") {
+        show_version();
+        return;
+    }
+
+    // Require a subcommand before any flags.
+    // We cannot know which of those flags take arguments and which do not,
+    // so we cannot detect subcommands later.
+    let Some(subcommand) = args.next() else {
+        show_error!("`cargo miri` needs to be called with a subcommand (`run`, `test`)");
+    };
+    let subcommand = match &*subcommand {
+        "setup" => MiriCommand::Setup,
+        "test" | "t" | "run" | "r" | "nextest" => MiriCommand::Forward(subcommand),
+        _ =>
+            show_error!(
+                "`cargo miri` supports the following subcommands: `run`, `test`, `nextest`, and `setup`."
+            ),
+    };
+    let verbose = num_arg_flag("-v");
+
+    // Determine the involved architectures.
+    let host = version_info().host;
+    let target = get_arg_flag_value("--target");
+    let target = target.as_ref().unwrap_or(&host);
+
+    // We always setup.
+    setup(&subcommand, &host, target);
+
+    // Invoke actual cargo for the job, but with different flags.
+    // We re-use `cargo test` and `cargo run`, which makes target and binary handling very easy but
+    // requires some extra work to make the build check-only (see all the `--emit` hacks below).
+    // <https://github.com/rust-lang/miri/pull/1540#issuecomment-693553191> describes an alternative
+    // approach that uses `cargo check`, making that part easier but target and binary handling
+    // harder.
+    let cargo_miri_path = std::env::current_exe()
+        .expect("current executable path invalid")
+        .into_os_string()
+        .into_string()
+        .expect("current executable path is not valid UTF-8");
+    let cargo_cmd = match subcommand {
+        MiriCommand::Forward(s) => s,
+        MiriCommand::Setup => return, // `cargo miri setup` stops here.
+    };
+    let metadata = get_cargo_metadata();
+    let mut cmd = cargo();
+    cmd.arg(cargo_cmd);
+
+    // Forward all arguments before `--` other than `--target-dir` and its value to Cargo.
+    // (We want to *change* the target-dir value, so we must not forward it.)
+    let mut target_dir = None;
+    for arg in ArgSplitFlagValue::from_string_iter(&mut args, "--target-dir") {
+        match arg {
+            Ok(value) => {
+                if target_dir.is_some() {
+                    show_error!("`--target-dir` is provided more than once");
+                }
+                target_dir = Some(value.into());
+            }
+            Err(arg) => {
+                cmd.arg(arg);
+            }
+        }
+    }
+    // Detect the target directory if it's not specified via `--target-dir`.
+    // (`cargo metadata` does not support `--target-dir`, that's why we have to handle this ourselves.)
+    let target_dir = target_dir.get_or_insert_with(|| metadata.target_directory.clone());
+    // Set `--target-dir` to `miri` inside the original target directory.
+    target_dir.push("miri");
+    cmd.arg("--target-dir").arg(target_dir);
+
+    // Make sure the build target is explicitly set.
+    // This is needed to make the `target.runner` settings do something,
+    // and it later helps us detect which crates are proc-macro/build-script
+    // (host crates) and which crates are needed for the program itself.
+    if get_arg_flag_value("--target").is_none() {
+        // No target given. Explicitly pick the host.
+        cmd.arg("--target");
+        cmd.arg(&host);
+    }
+
+    // Set ourselves as runner for al binaries invoked by cargo.
+    // We use `all()` since `true` is not a thing in cfg-lang, but the empty conjunction is. :)
+    let cargo_miri_path_for_toml = escape_for_toml(&cargo_miri_path);
+    cmd.arg("--config")
+        .arg(format!("target.'cfg(all())'.runner=[{cargo_miri_path_for_toml}, 'runner']"));
+
+    // Forward all further arguments after `--` to cargo.
+    cmd.arg("--").args(args);
+
+    // Set `RUSTC_WRAPPER` to ourselves.  Cargo will prepend that binary to its usual invocation,
+    // i.e., the first argument is `rustc` -- which is what we use in `main` to distinguish
+    // the two codepaths. (That extra argument is why we prefer this over setting `RUSTC`.)
+    if env::var_os("RUSTC_WRAPPER").is_some() {
+        println!(
+            "WARNING: Ignoring `RUSTC_WRAPPER` environment variable, Miri does not support wrapping."
+        );
+    }
+    cmd.env("RUSTC_WRAPPER", &cargo_miri_path);
+    // We are going to invoke `MIRI` for everything, not `RUSTC`.
+    if env::var_os("RUSTC").is_some() && env::var_os("MIRI").is_none() {
+        println!(
+            "WARNING: Ignoring `RUSTC` environment variable; set `MIRI` if you want to control the binary used as the driver."
+        );
+    }
+    // Build scripts (and also cargo: https://github.com/rust-lang/cargo/issues/10885) will invoke
+    // `rustc` even when `RUSTC_WRAPPER` is set. To make sure everything is coherent, we want that
+    // to be the Miri driver, but acting as rustc, on the target level. (Target, rather than host,
+    // is needed for cross-interpretation situations.) This is not a perfect emulation of real rustc
+    // (it might be unable to produce binaries since the sysroot is check-only), but it's as close
+    // as we can get, and it's good enough for autocfg.
+    //
+    // In `main`, we need the value of `RUSTC` to distinguish RUSTC_WRAPPER invocations from rustdoc
+    // or TARGET_RUNNER invocations, so we canonicalize it here to make it exceedingly unlikely that
+    // there would be a collision with other invocations of cargo-miri (as rustdoc or as runner). We
+    // explicitly do this even if RUSTC_STAGE is set, since for these builds we do *not* want the
+    // bootstrap `rustc` thing in our way! Instead, we have MIRI_HOST_SYSROOT to use for host
+    // builds.
+    cmd.env("RUSTC", &fs::canonicalize(find_miri()).unwrap());
+    cmd.env("MIRI_BE_RUSTC", "target"); // we better remember to *unset* this in the other phases!
+
+    // Set rustdoc to us as well, so we can run doctests.
+    cmd.env("RUSTDOC", &cargo_miri_path);
+
+    cmd.env("MIRI_LOCAL_CRATES", local_crates(&metadata));
+    if verbose > 0 {
+        cmd.env("MIRI_VERBOSE", verbose.to_string()); // This makes the other phases verbose.
+    }
+
+    // Run cargo.
+    debug_cmd("[cargo-miri miri]", verbose, &cmd);
+    exec(cmd)
+}
+
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub enum RustcPhase {
+    /// `rustc` called via `xargo` for sysroot build.
+    Setup,
+    /// `rustc` called by `cargo` for regular build.
+    Build,
+    /// `rustc` called by `rustdoc` for doctest.
+    Rustdoc,
+}
+
+pub fn phase_rustc(mut args: impl Iterator<Item = String>, phase: RustcPhase) {
+    /// Determines if we are being invoked (as rustc) to build a crate for
+    /// the "target" architecture, in contrast to the "host" architecture.
+    /// Host crates are for build scripts and proc macros and still need to
+    /// be built like normal; target crates need to be built for or interpreted
+    /// by Miri.
+    ///
+    /// Currently, we detect this by checking for "--target=", which is
+    /// never set for host crates. This matches what rustc bootstrap does,
+    /// which hopefully makes it "reliable enough". This relies on us always
+    /// invoking cargo itself with `--target`, which `in_cargo_miri` ensures.
+    fn is_target_crate() -> bool {
+        get_arg_flag_value("--target").is_some()
+    }
+
+    /// Returns whether or not Cargo invoked the wrapper (this binary) to compile
+    /// the final, binary crate (either a test for 'cargo test', or a binary for 'cargo run')
+    /// Cargo does not give us this information directly, so we need to check
+    /// various command-line flags.
+    fn is_runnable_crate() -> bool {
+        let is_bin = get_arg_flag_value("--crate-type").as_deref().unwrap_or("bin") == "bin";
+        let is_test = has_arg_flag("--test");
+        is_bin || is_test
+    }
+
+    fn out_filename(prefix: &str, suffix: &str) -> PathBuf {
+        if let Some(out_dir) = get_arg_flag_value("--out-dir") {
+            let mut path = PathBuf::from(out_dir);
+            path.push(format!(
+                "{}{}{}{}",
+                prefix,
+                get_arg_flag_value("--crate-name").unwrap(),
+                // This is technically a `-C` flag but the prefix seems unique enough...
+                // (and cargo passes this before the filename so it should be unique)
+                get_arg_flag_value("extra-filename").unwrap_or_default(),
+                suffix,
+            ));
+            path
+        } else {
+            let out_file = get_arg_flag_value("-o").unwrap();
+            PathBuf::from(out_file)
+        }
+    }
+
+    // phase_cargo_miri set `MIRI_BE_RUSTC` for when build scripts directly invoke the driver;
+    // however, if we get called back by cargo here, we'll carefully compute the right flags
+    // ourselves, so we first un-do what the earlier phase did.
+    env::remove_var("MIRI_BE_RUSTC");
+
+    let verbose = std::env::var("MIRI_VERBOSE")
+        .map_or(0, |verbose| verbose.parse().expect("verbosity flag must be an integer"));
+    let target_crate = is_target_crate();
+    // Determine whether this is cargo/xargo invoking rustc to get some infos.
+    let info_query = get_arg_flag_value("--print").is_some() || has_arg_flag("-vV");
+
+    let store_json = |info: CrateRunInfo| {
+        // Create a stub .d file to stop Cargo from "rebuilding" the crate:
+        // https://github.com/rust-lang/miri/issues/1724#issuecomment-787115693
+        // As we store a JSON file instead of building the crate here, an empty file is fine.
+        let dep_info_name = out_filename("", ".d");
+        if verbose > 0 {
+            eprintln!("[cargo-miri rustc] writing stub dep-info to `{}`", dep_info_name.display());
+        }
+        File::create(dep_info_name).expect("failed to create fake .d file");
+
+        let filename = out_filename("", "");
+        if verbose > 0 {
+            eprintln!("[cargo-miri rustc] writing run info to `{}`", filename.display());
+        }
+        info.store(&filename);
+        // For Windows, do the same thing again with `.exe` appended to the filename.
+        // (Need to do this here as cargo moves that "binary" to a different place before running it.)
+        info.store(&out_filename("", ".exe"));
+    };
+
+    let runnable_crate = !info_query && is_runnable_crate();
+
+    if runnable_crate && target_crate {
+        assert!(
+            phase != RustcPhase::Setup,
+            "there should be no interpretation during sysroot build"
+        );
+        let inside_rustdoc = phase == RustcPhase::Rustdoc;
+        // This is the binary or test crate that we want to interpret under Miri.
+        // But we cannot run it here, as cargo invoked us as a compiler -- our stdin and stdout are not
+        // like we want them.
+        // Instead of compiling, we write JSON into the output file with all the relevant command-line flags
+        // and environment variables; this is used when cargo calls us again in the CARGO_TARGET_RUNNER phase.
+        let env = CrateRunEnv::collect(args, inside_rustdoc);
+
+        store_json(CrateRunInfo::RunWith(env.clone()));
+
+        // Rustdoc expects us to exit with an error code if the test is marked as `compile_fail`,
+        // just creating the JSON file is not enough: we need to detect syntax errors,
+        // so we need to run Miri with `MIRI_BE_RUSTC` for a check-only build.
+        if inside_rustdoc {
+            let mut cmd = miri();
+
+            // Ensure --emit argument for a check-only build is present.
+            if let Some(val) =
+                ArgFlagValueIter::from_str_iter(env.args.iter().map(|s| s as &str), "--emit").next()
+            {
+                // For `no_run` tests, rustdoc passes a `--emit` flag; make sure it has the right shape.
+                assert_eq!(val, "metadata");
+            } else {
+                // For all other kinds of tests, we can just add our flag.
+                cmd.arg("--emit=metadata");
+            }
+
+            // Alter the `-o` parameter so that it does not overwrite the JSON file we stored above.
+            let mut args = env.args;
+            for i in 0..args.len() {
+                if args[i] == "-o" {
+                    args[i + 1].push_str(".miri");
+                }
+            }
+
+            cmd.args(&args);
+            cmd.env("MIRI_BE_RUSTC", "target");
+
+            if verbose > 0 {
+                eprintln!(
+                    "[cargo-miri rustc inside rustdoc] captured input:\n{}",
+                    std::str::from_utf8(&env.stdin).unwrap()
+                );
+                eprintln!("[cargo-miri rustc inside rustdoc] going to run:\n{:?}", cmd);
+            }
+
+            exec_with_pipe(cmd, &env.stdin, format!("{}.stdin", out_filename("", "").display()));
+        }
+
+        return;
+    }
+
+    if runnable_crate && get_arg_flag_values("--extern").any(|krate| krate == "proc_macro") {
+        // This is a "runnable" `proc-macro` crate (unit tests). We do not support
+        // interpreting that under Miri now, so we write a JSON file to (display a
+        // helpful message and) skip it in the runner phase.
+        store_json(CrateRunInfo::SkipProcMacroTest);
+        return;
+    }
+
+    let mut cmd = miri();
+    let mut emit_link_hack = false;
+    // Arguments are treated very differently depending on whether this crate is
+    // for interpretation by Miri, or for use by a build script / proc macro.
+    if !info_query && target_crate {
+        // Forward arguments, but remove "link" from "--emit" to make this a check-only build.
+        let emit_flag = "--emit";
+        while let Some(arg) = args.next() {
+            if let Some(val) = arg.strip_prefix(emit_flag) {
+                // Patch this argument. First, extract its value.
+                let val =
+                    val.strip_prefix('=').expect("`cargo` should pass `--emit=X` as one argument");
+                let mut val: Vec<_> = val.split(',').collect();
+                // Now make sure "link" is not in there, but "metadata" is.
+                if let Some(i) = val.iter().position(|&s| s == "link") {
+                    emit_link_hack = true;
+                    val.remove(i);
+                    if !val.iter().any(|&s| s == "metadata") {
+                        val.push("metadata");
+                    }
+                }
+                cmd.arg(format!("{}={}", emit_flag, val.join(",")));
+            } else if arg == "--extern" {
+                // Patch `--extern` filenames, since Cargo sometimes passes stub `.rlib` files:
+                // https://github.com/rust-lang/miri/issues/1705
+                forward_patched_extern_arg(&mut args, &mut cmd);
+            } else {
+                cmd.arg(arg);
+            }
+        }
+
+        // During setup, patch the panic runtime for `libpanic_abort` (mirroring what bootstrap usually does).
+        if phase == RustcPhase::Setup
+            && get_arg_flag_value("--crate-name").as_deref() == Some("panic_abort")
+        {
+            cmd.arg("-C").arg("panic=abort");
+        }
+    } else {
+        // For host crates (but not when we are just printing some info),
+        // we might still have to set the sysroot.
+        if !info_query {
+            // When we're running `cargo-miri` from `x.py` we need to pass the sysroot explicitly
+            // due to bootstrap complications.
+            if let Some(sysroot) = std::env::var_os("MIRI_HOST_SYSROOT") {
+                cmd.arg("--sysroot").arg(sysroot);
+            }
+        }
+
+        // For host crates or when we are printing, just forward everything.
+        cmd.args(args);
+    }
+
+    // We want to compile, not interpret. We still use Miri to make sure the compiler version etc
+    // are the exact same as what is used for interpretation.
+    // MIRI_DEFAULT_ARGS should not be used to build host crates, hence setting "target" or "host"
+    // as the value here to help Miri differentiate them.
+    cmd.env("MIRI_BE_RUSTC", if target_crate { "target" } else { "host" });
+
+    // Run it.
+    if verbose > 0 {
+        eprintln!(
+            "[cargo-miri rustc] target_crate={target_crate} runnable_crate={runnable_crate} info_query={info_query}"
+        );
+    }
+
+    // Create a stub .rlib file if "link" was requested by cargo.
+    // This is necessary to prevent cargo from doing rebuilds all the time.
+    if emit_link_hack {
+        // Some platforms prepend "lib", some do not... let's just create both files.
+        File::create(out_filename("lib", ".rlib")).expect("failed to create fake .rlib file");
+        File::create(out_filename("", ".rlib")).expect("failed to create fake .rlib file");
+        // Just in case this is a cdylib or staticlib, also create those fake files.
+        File::create(out_filename("lib", ".so")).expect("failed to create fake .so file");
+        File::create(out_filename("lib", ".a")).expect("failed to create fake .a file");
+        File::create(out_filename("lib", ".dylib")).expect("failed to create fake .dylib file");
+        File::create(out_filename("", ".dll")).expect("failed to create fake .dll file");
+        File::create(out_filename("", ".lib")).expect("failed to create fake .lib file");
+    }
+
+    debug_cmd("[cargo-miri rustc]", verbose, &cmd);
+    exec(cmd);
+}
+
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub enum RunnerPhase {
+    /// `cargo` is running a binary
+    Cargo,
+    /// `rustdoc` is running a binary
+    Rustdoc,
+}
+
+pub fn phase_runner(mut binary_args: impl Iterator<Item = String>, phase: RunnerPhase) {
+    // phase_cargo_miri set `MIRI_BE_RUSTC` for when build scripts directly invoke the driver;
+    // however, if we get called back by cargo here, we'll carefully compute the right flags
+    // ourselves, so we first un-do what the earlier phase did.
+    env::remove_var("MIRI_BE_RUSTC");
+
+    let verbose = std::env::var("MIRI_VERBOSE")
+        .map_or(0, |verbose| verbose.parse().expect("verbosity flag must be an integer"));
+
+    let binary = binary_args.next().unwrap();
+    let file = File::open(&binary)
+        .unwrap_or_else(|_| show_error!(
+            "file {:?} not found or `cargo-miri` invoked incorrectly; please only invoke this binary through `cargo miri`", binary
+        ));
+    let file = BufReader::new(file);
+
+    let info = serde_json::from_reader(file).unwrap_or_else(|_| {
+        show_error!("file {:?} contains outdated or invalid JSON; try `cargo clean`", binary)
+    });
+    let info = match info {
+        CrateRunInfo::RunWith(info) => info,
+        CrateRunInfo::SkipProcMacroTest => {
+            eprintln!(
+                "Running unit tests of `proc-macro` crates is not currently supported by Miri."
+            );
+            return;
+        }
+    };
+
+    let mut cmd = miri();
+
+    // Set missing env vars. We prefer build-time env vars over run-time ones; see
+    // <https://github.com/rust-lang/miri/issues/1661> for the kind of issue that fixes.
+    for (name, val) in info.env {
+        if let Some(old_val) = env::var_os(&name) {
+            if old_val == val {
+                // This one did not actually change, no need to re-set it.
+                // (This keeps the `debug_cmd` below more manageable.)
+                continue;
+            } else if verbose > 0 {
+                eprintln!(
+                    "[cargo-miri runner] Overwriting run-time env var {:?}={:?} with build-time value {:?}",
+                    name, old_val, val
+                );
+            }
+        }
+        cmd.env(name, val);
+    }
+
+    // Forward rustc arguments.
+    // We need to patch "--extern" filenames because we forced a check-only
+    // build without cargo knowing about that: replace `.rlib` suffix by
+    // `.rmeta`.
+    // We also need to remove `--error-format` as cargo specifies that to be JSON,
+    // but when we run here, cargo does not interpret the JSON any more. `--json`
+    // then also nees to be dropped.
+    let mut args = info.args.into_iter();
+    let error_format_flag = "--error-format";
+    let json_flag = "--json";
+    while let Some(arg) = args.next() {
+        if arg == "--extern" {
+            forward_patched_extern_arg(&mut args, &mut cmd);
+        } else if let Some(suffix) = arg.strip_prefix(error_format_flag) {
+            assert!(suffix.starts_with('='));
+            // Drop this argument.
+        } else if let Some(suffix) = arg.strip_prefix(json_flag) {
+            assert!(suffix.starts_with('='));
+            // Drop this argument.
+        } else {
+            cmd.arg(arg);
+        }
+    }
+    // Respect `MIRIFLAGS`.
+    if let Ok(a) = env::var("MIRIFLAGS") {
+        // This code is taken from `RUSTFLAGS` handling in cargo.
+        let args = a.split(' ').map(str::trim).filter(|s| !s.is_empty()).map(str::to_string);
+        cmd.args(args);
+    }
+
+    // Then pass binary arguments.
+    cmd.arg("--");
+    cmd.args(binary_args);
+
+    // Make sure we use the build-time working directory for interpreting Miri/rustc arguments.
+    // But then we need to switch to the run-time one, which we instruct Miri do do by setting `MIRI_CWD`.
+    cmd.current_dir(info.current_dir);
+    cmd.env("MIRI_CWD", env::current_dir().unwrap());
+
+    // Run it.
+    debug_cmd("[cargo-miri runner]", verbose, &cmd);
+    match phase {
+        RunnerPhase::Rustdoc => exec_with_pipe(cmd, &info.stdin, format!("{}.stdin", binary)),
+        RunnerPhase::Cargo => exec(cmd),
+    }
+}
+
+pub fn phase_rustdoc(mut args: impl Iterator<Item = String>) {
+    let verbose = std::env::var("MIRI_VERBOSE")
+        .map_or(0, |verbose| verbose.parse().expect("verbosity flag must be an integer"));
+
+    // phase_cargo_miri sets the RUSTDOC env var to ourselves, so we can't use that here;
+    // just default to a straight-forward invocation for now:
+    let mut cmd = Command::new("rustdoc");
+
+    let extern_flag = "--extern";
+    let runtool_flag = "--runtool";
+    while let Some(arg) = args.next() {
+        if arg == extern_flag {
+            // Patch --extern arguments to use *.rmeta files, since phase_cargo_rustc only creates stub *.rlib files.
+            forward_patched_extern_arg(&mut args, &mut cmd);
+        } else if arg == runtool_flag {
+            // An existing --runtool flag indicates cargo is running in cross-target mode, which we don't support.
+            // Note that this is only passed when cargo is run with the unstable -Zdoctest-xcompile flag;
+            // otherwise, we won't be called as rustdoc at all.
+            show_error!("cross-interpreting doctests is not currently supported by Miri.");
+        } else {
+            cmd.arg(arg);
+        }
+    }
+
+    // Doctests of `proc-macro` crates (and their dependencies) are always built for the host,
+    // so we are not able to run them in Miri.
+    if get_arg_flag_values("--crate-type").any(|crate_type| crate_type == "proc-macro") {
+        eprintln!("Running doctests of `proc-macro` crates is not currently supported by Miri.");
+        return;
+    }
+
+    // For each doctest, rustdoc starts two child processes: first the test is compiled,
+    // then the produced executable is invoked. We want to reroute both of these to cargo-miri,
+    // such that the first time we'll enter phase_cargo_rustc, and phase_cargo_runner second.
+    //
+    // rustdoc invokes the test-builder by forwarding most of its own arguments, which makes
+    // it difficult to determine when phase_cargo_rustc should run instead of phase_cargo_rustdoc.
+    // Furthermore, the test code is passed via stdin, rather than a temporary file, so we need
+    // to let phase_cargo_rustc know to expect that. We'll use this environment variable as a flag:
+    cmd.env("MIRI_CALLED_FROM_RUSTDOC", "1");
+
+    // The `--test-builder` and `--runtool` arguments are unstable rustdoc features,
+    // which are disabled by default. We first need to enable them explicitly:
+    cmd.arg("-Z").arg("unstable-options");
+
+    // rustdoc needs to know the right sysroot.
+    cmd.arg("--sysroot").arg(env::var_os("MIRI_SYSROOT").unwrap());
+    // make sure the 'miri' flag is set for rustdoc
+    cmd.arg("--cfg").arg("miri");
+
+    // Make rustdoc call us back.
+    let cargo_miri_path = std::env::current_exe().expect("current executable path invalid");
+    cmd.arg("--test-builder").arg(&cargo_miri_path); // invoked by forwarding most arguments
+    cmd.arg("--runtool").arg(&cargo_miri_path); // invoked with just a single path argument
+
+    debug_cmd("[cargo-miri rustdoc]", verbose, &cmd);
+    exec(cmd)
+}
diff --git a/src/tools/miri/cargo-miri/src/setup.rs b/src/tools/miri/cargo-miri/src/setup.rs
new file mode 100644
index 0000000000000..c27bb18631758
--- /dev/null
+++ b/src/tools/miri/cargo-miri/src/setup.rs
@@ -0,0 +1,245 @@
+//! Implements `cargo miri setup` via xargo
+
+use std::env;
+use std::ffi::OsStr;
+use std::fs::{self};
+use std::io::BufRead;
+use std::ops::Not;
+use std::path::{Path, PathBuf};
+use std::process::{self, Command};
+
+use crate::{util::*, version::*};
+
+fn xargo_version() -> Option<(u32, u32, u32)> {
+    let out = xargo_check().arg("--version").output().ok()?;
+    if !out.status.success() {
+        return None;
+    }
+    // Parse output. The first line looks like "xargo 0.3.12 (b004f1c 2018-12-13)".
+    let line = out
+        .stderr
+        .lines()
+        .next()
+        .expect("malformed `xargo --version` output: not at least one line")
+        .expect("malformed `xargo --version` output: error reading first line");
+    let (name, version) = {
+        let mut split = line.split(' ');
+        (
+            split.next().expect("malformed `xargo --version` output: empty"),
+            split.next().expect("malformed `xargo --version` output: not at least two words"),
+        )
+    };
+    if name != "xargo" {
+        // This is some fork of xargo
+        return None;
+    }
+    let mut version_pieces = version.split('.');
+    let major = version_pieces
+        .next()
+        .expect("malformed `xargo --version` output: not a major version piece")
+        .parse()
+        .expect("malformed `xargo --version` output: major version is not an integer");
+    let minor = version_pieces
+        .next()
+        .expect("malformed `xargo --version` output: not a minor version piece")
+        .parse()
+        .expect("malformed `xargo --version` output: minor version is not an integer");
+    let patch = version_pieces
+        .next()
+        .expect("malformed `xargo --version` output: not a patch version piece")
+        .parse()
+        .expect("malformed `xargo --version` output: patch version is not an integer");
+    if version_pieces.next().is_some() {
+        panic!("malformed `xargo --version` output: more than three pieces in version");
+    }
+    Some((major, minor, patch))
+}
+
+/// Performs the setup required to make `cargo miri` work: Getting a custom-built libstd. Then sets
+/// `MIRI_SYSROOT`. Skipped if `MIRI_SYSROOT` is already set, in which case we expect the user has
+/// done all this already.
+pub fn setup(subcommand: &MiriCommand, host: &str, target: &str) {
+    let only_setup = matches!(subcommand, MiriCommand::Setup);
+    let ask_user = !only_setup;
+    let print_sysroot = only_setup && has_arg_flag("--print-sysroot"); // whether we just print the sysroot path
+    if std::env::var_os("MIRI_SYSROOT").is_some() {
+        if only_setup {
+            println!("WARNING: MIRI_SYSROOT already set, not doing anything.")
+        }
+        return;
+    }
+
+    // First, we need xargo.
+    if xargo_version().map_or(true, |v| v < XARGO_MIN_VERSION) {
+        if std::env::var_os("XARGO_CHECK").is_some() {
+            // The user manually gave us a xargo binary; don't do anything automatically.
+            show_error!("xargo is too old; please upgrade to the latest version")
+        }
+        let mut cmd = cargo();
+        cmd.args(["install", "xargo"]);
+        ask_to_run(cmd, ask_user, "install a recent enough xargo");
+    }
+
+    // Determine where the rust sources are located.  The env vars manually setting the source
+    // (`MIRI_LIB_SRC`, `XARGO_RUST_SRC`) trump auto-detection.
+    let rust_src_env_var =
+        std::env::var_os("MIRI_LIB_SRC").or_else(|| std::env::var_os("XARGO_RUST_SRC"));
+    let rust_src = match rust_src_env_var {
+        Some(path) => {
+            let path = PathBuf::from(path);
+            // Make path absolute if possible.
+            path.canonicalize().unwrap_or(path)
+        }
+        None => {
+            // Check for `rust-src` rustup component.
+            let output = miri_for_host()
+                .args(["--print", "sysroot"])
+                .output()
+                .expect("failed to determine sysroot");
+            if !output.status.success() {
+                show_error!(
+                    "Failed to determine sysroot; Miri said:\n{}",
+                    String::from_utf8_lossy(&output.stderr).trim_end()
+                );
+            }
+            let sysroot = std::str::from_utf8(&output.stdout).unwrap();
+            let sysroot = Path::new(sysroot.trim_end_matches('\n'));
+            // Check for `$SYSROOT/lib/rustlib/src/rust/library`; test if that contains `std/Cargo.toml`.
+            let rustup_src =
+                sysroot.join("lib").join("rustlib").join("src").join("rust").join("library");
+            if !rustup_src.join("std").join("Cargo.toml").exists() {
+                // Ask the user to install the `rust-src` component, and use that.
+                let mut cmd = Command::new("rustup");
+                cmd.args(["component", "add", "rust-src"]);
+                ask_to_run(
+                    cmd,
+                    ask_user,
+                    "install the `rust-src` component for the selected toolchain",
+                );
+            }
+            rustup_src
+        }
+    };
+    if !rust_src.exists() {
+        show_error!("given Rust source directory `{}` does not exist.", rust_src.display());
+    }
+    if rust_src.file_name().and_then(OsStr::to_str) != Some("library") {
+        show_error!(
+            "given Rust source directory `{}` does not seem to be the `library` subdirectory of \
+             a Rust source checkout.",
+            rust_src.display()
+        );
+    }
+
+    // Next, we need our own libstd. Prepare a xargo project for that purpose.
+    // We will do this work in whatever is a good cache dir for this platform.
+    let dirs = directories::ProjectDirs::from("org", "rust-lang", "miri").unwrap();
+    let dir = dirs.cache_dir();
+    if !dir.exists() {
+        fs::create_dir_all(dir).unwrap();
+    }
+    // The interesting bit: Xargo.toml (only needs content if we actually need std)
+    let xargo_toml = if std::env::var_os("MIRI_NO_STD").is_some() {
+        ""
+    } else {
+        r#"
+[dependencies.std]
+default_features = false
+# We support unwinding, so enable that panic runtime.
+features = ["panic_unwind", "backtrace"]
+
+[dependencies.test]
+"#
+    };
+    write_to_file(&dir.join("Xargo.toml"), xargo_toml);
+    // The boring bits: a dummy project for xargo.
+    // FIXME: With xargo-check, can we avoid doing this?
+    write_to_file(
+        &dir.join("Cargo.toml"),
+        r#"
+[package]
+name = "miri-xargo"
+description = "A dummy project for building libstd with xargo."
+version = "0.0.0"
+
+[lib]
+path = "lib.rs"
+"#,
+    );
+    write_to_file(&dir.join("lib.rs"), "#![no_std]");
+
+    // Figure out where xargo will build its stuff.
+    // Unfortunately, it puts things into a different directory when the
+    // architecture matches the host.
+    let sysroot = if target == host { dir.join("HOST") } else { PathBuf::from(dir) };
+    // Make sure all target-level Miri invocations know their sysroot.
+    std::env::set_var("MIRI_SYSROOT", &sysroot);
+
+    // Now invoke xargo.
+    let mut command = xargo_check();
+    command.arg("check").arg("-q");
+    command.current_dir(dir);
+    command.env("XARGO_HOME", dir);
+    command.env("XARGO_RUST_SRC", &rust_src);
+    // We always need to set a target so rustc bootstrap can tell apart host from target crates.
+    command.arg("--target").arg(target);
+    // Use Miri as rustc to build a libstd compatible with us (and use the right flags).
+    // However, when we are running in bootstrap, we cannot just overwrite `RUSTC`,
+    // because we still need bootstrap to distinguish between host and target crates.
+    // In that case we overwrite `RUSTC_REAL` instead which determines the rustc used
+    // for target crates.
+    // We set ourselves (`cargo-miri`) instead of Miri directly to be able to patch the flags
+    // for `libpanic_abort` (usually this is done by bootstrap but we have to do it ourselves).
+    // The `MIRI_CALLED_FROM_XARGO` will mean we dispatch to `phase_setup_rustc`.
+    let cargo_miri_path = std::env::current_exe().expect("current executable path invalid");
+    if env::var_os("RUSTC_STAGE").is_some() {
+        assert!(env::var_os("RUSTC").is_some());
+        command.env("RUSTC_REAL", &cargo_miri_path);
+    } else {
+        command.env("RUSTC", &cargo_miri_path);
+    }
+    command.env("MIRI_CALLED_FROM_XARGO", "1");
+    // Make sure there are no other wrappers getting in our way
+    // (Cc https://github.com/rust-lang/miri/issues/1421, https://github.com/rust-lang/miri/issues/2429).
+    // Looks like setting `RUSTC_WRAPPER` to the empty string overwrites `build.rustc-wrapper` set via `config.toml`.
+    command.env("RUSTC_WRAPPER", "");
+    // Disable debug assertions in the standard library -- Miri is already slow enough. But keep the
+    // overflow checks, they are cheap. This completely overwrites flags the user might have set,
+    // which is consistent with normal `cargo build` that does not apply `RUSTFLAGS` to the sysroot
+    // either.
+    command.env("RUSTFLAGS", "-Cdebug-assertions=off -Coverflow-checks=on");
+    // Manage the output the user sees.
+    if only_setup {
+        // We want to be explicit.
+        eprintln!("Preparing a sysroot for Miri (target: {target})...");
+        if print_sysroot {
+            // Be extra sure there is no noise on stdout.
+            command.stdout(process::Stdio::null());
+        }
+    } else {
+        // We want to be quiet, but still let the user know that something is happening.
+        eprint!("Preparing a sysroot for Miri (target: {target})... ");
+        command.stdout(process::Stdio::null());
+        command.stderr(process::Stdio::null());
+    }
+
+    // Finally run it!
+    if command.status().expect("failed to run xargo").success().not() {
+        if only_setup {
+            show_error!("failed to run xargo, see error details above")
+        } else {
+            show_error!("failed to run xargo; run `cargo miri setup` to see the error details")
+        }
+    }
+
+    // Figure out what to print.
+    if only_setup {
+        eprintln!("A sysroot for Miri is now available in `{}`.", sysroot.display());
+    } else {
+        eprintln!("done");
+    }
+    if print_sysroot {
+        // Print just the sysroot and nothing else to stdout; this way we do not need any escaping.
+        println!("{}", sysroot.display());
+    }
+}
diff --git a/src/tools/miri/cargo-miri/src/util.rs b/src/tools/miri/cargo-miri/src/util.rs
new file mode 100644
index 0000000000000..8f29eebaac100
--- /dev/null
+++ b/src/tools/miri/cargo-miri/src/util.rs
@@ -0,0 +1,314 @@
+use std::collections::HashMap;
+use std::env;
+use std::ffi::OsString;
+use std::fmt::Write as _;
+use std::fs::{self, File};
+use std::io::{self, BufWriter, Read, Write};
+use std::ops::Not;
+use std::path::{Path, PathBuf};
+use std::process::Command;
+
+use cargo_metadata::{Metadata, MetadataCommand};
+use rustc_version::VersionMeta;
+use serde::{Deserialize, Serialize};
+
+pub use crate::arg::*;
+
+pub fn show_error(msg: &impl std::fmt::Display) -> ! {
+    eprintln!("fatal error: {msg}");
+    std::process::exit(1)
+}
+
+macro_rules! show_error {
+    ($($tt:tt)*) => { crate::util::show_error(&format_args!($($tt)*)) };
+}
+
+/// The information to run a crate with the given environment.
+#[derive(Clone, Serialize, Deserialize)]
+pub struct CrateRunEnv {
+    /// The command-line arguments.
+    pub args: Vec<String>,
+    /// The environment.
+    pub env: Vec<(OsString, OsString)>,
+    /// The current working directory.
+    pub current_dir: OsString,
+    /// The contents passed via standard input.
+    pub stdin: Vec<u8>,
+}
+
+impl CrateRunEnv {
+    /// Gather all the information we need.
+    pub fn collect(args: impl Iterator<Item = String>, capture_stdin: bool) -> Self {
+        let args = args.collect();
+        let env = env::vars_os().collect();
+        let current_dir = env::current_dir().unwrap().into_os_string();
+
+        let mut stdin = Vec::new();
+        if capture_stdin {
+            std::io::stdin().lock().read_to_end(&mut stdin).expect("cannot read stdin");
+        }
+
+        CrateRunEnv { args, env, current_dir, stdin }
+    }
+}
+
+/// The information Miri needs to run a crate. Stored as JSON when the crate is "compiled".
+#[derive(Serialize, Deserialize)]
+pub enum CrateRunInfo {
+    /// Run it with the given environment.
+    RunWith(CrateRunEnv),
+    /// Skip it as Miri does not support interpreting such kind of crates.
+    SkipProcMacroTest,
+}
+
+impl CrateRunInfo {
+    pub fn store(&self, filename: &Path) {
+        let file = File::create(filename)
+            .unwrap_or_else(|_| show_error!("cannot create `{}`", filename.display()));
+        let file = BufWriter::new(file);
+        serde_json::ser::to_writer(file, self)
+            .unwrap_or_else(|_| show_error!("cannot write to `{}`", filename.display()));
+    }
+}
+
+#[derive(Clone, Debug)]
+pub enum MiriCommand {
+    /// Our own special 'setup' command.
+    Setup,
+    /// A command to be forwarded to cargo.
+    Forward(String),
+}
+
+/// Escapes `s` in a way that is suitable for using it as a string literal in TOML syntax.
+pub fn escape_for_toml(s: &str) -> String {
+    // We want to surround this string in quotes `"`. So we first escape all quotes,
+    // and also all backslashes (that are used to escape quotes).
+    let s = s.replace('\\', r#"\\"#).replace('"', r#"\""#);
+    format!("\"{}\"", s)
+}
+
+/// Returns the path to the `miri` binary
+pub fn find_miri() -> PathBuf {
+    if let Some(path) = env::var_os("MIRI") {
+        return path.into();
+    }
+    let mut path = std::env::current_exe().expect("current executable path invalid");
+    if cfg!(windows) {
+        path.set_file_name("miri.exe");
+    } else {
+        path.set_file_name("miri");
+    }
+    path
+}
+
+pub fn miri() -> Command {
+    Command::new(find_miri())
+}
+
+pub fn miri_for_host() -> Command {
+    let mut cmd = miri();
+    cmd.env("MIRI_BE_RUSTC", "host");
+    cmd
+}
+
+pub fn version_info() -> VersionMeta {
+    VersionMeta::for_command(miri_for_host())
+        .expect("failed to determine underlying rustc version of Miri")
+}
+
+pub fn cargo() -> Command {
+    Command::new(env::var_os("CARGO").unwrap_or_else(|| OsString::from("cargo")))
+}
+
+pub fn xargo_check() -> Command {
+    Command::new(env::var_os("XARGO_CHECK").unwrap_or_else(|| OsString::from("xargo-check")))
+}
+
+/// Execute the `Command`, where possible by replacing the current process with a new process
+/// described by the `Command`. Then exit this process with the exit code of the new process.
+pub fn exec(mut cmd: Command) -> ! {
+    // On non-Unix imitate POSIX exec as closely as we can
+    #[cfg(not(unix))]
+    {
+        let exit_status = cmd.status().expect("failed to run command");
+        std::process::exit(exit_status.code().unwrap_or(-1))
+    }
+    // On Unix targets, actually exec.
+    // If exec returns, process setup has failed. This is the same error condition as the expect in
+    // the non-Unix case.
+    #[cfg(unix)]
+    {
+        use std::os::unix::process::CommandExt;
+        let error = cmd.exec();
+        Err(error).expect("failed to run command")
+    }
+}
+
+/// Execute the `Command`, where possible by replacing the current process with a new process
+/// described by the `Command`. Then exit this process with the exit code of the new process.
+/// `input` is also piped to the new process's stdin, on cfg(unix) platforms by writing its
+/// contents to `path` first, then setting stdin to that file.
+pub fn exec_with_pipe<P>(mut cmd: Command, input: &[u8], path: P) -> !
+where
+    P: AsRef<Path>,
+{
+    #[cfg(unix)]
+    {
+        // Write the bytes we want to send to stdin out to a file
+        std::fs::write(&path, input).unwrap();
+        // Open the file for reading, and set our new stdin to it
+        let stdin = File::open(&path).unwrap();
+        cmd.stdin(stdin);
+        // Unlink the file so that it is fully cleaned up as soon as the new process exits
+        std::fs::remove_file(&path).unwrap();
+        // Finally, we can hand off control.
+        exec(cmd)
+    }
+    #[cfg(not(unix))]
+    {
+        drop(path); // We don't need the path, we can pipe the bytes directly
+        cmd.stdin(std::process::Stdio::piped());
+        let mut child = cmd.spawn().expect("failed to spawn process");
+        {
+            let stdin = child.stdin.as_mut().expect("failed to open stdin");
+            stdin.write_all(input).expect("failed to write out test source");
+        }
+        let exit_status = child.wait().expect("failed to run command");
+        std::process::exit(exit_status.code().unwrap_or(-1))
+    }
+}
+
+pub fn ask_to_run(mut cmd: Command, ask: bool, text: &str) {
+    // Disable interactive prompts in CI (GitHub Actions, Travis, AppVeyor, etc).
+    // Azure doesn't set `CI` though (nothing to see here, just Microsoft being Microsoft),
+    // so we also check their `TF_BUILD`.
+    let is_ci = env::var_os("CI").is_some() || env::var_os("TF_BUILD").is_some();
+    if ask && !is_ci {
+        let mut buf = String::new();
+        print!("I will run `{:?}` to {}. Proceed? [Y/n] ", cmd, text);
+        io::stdout().flush().unwrap();
+        io::stdin().read_line(&mut buf).unwrap();
+        match buf.trim().to_lowercase().as_ref() {
+            // Proceed.
+            "" | "y" | "yes" => {}
+            "n" | "no" => show_error!("aborting as per your request"),
+            a => show_error!("invalid answer `{}`", a),
+        };
+    } else {
+        eprintln!("Running `{:?}` to {}.", cmd, text);
+    }
+
+    if cmd.status().unwrap_or_else(|_| panic!("failed to execute {:?}", cmd)).success().not() {
+        show_error!("failed to {}", text);
+    }
+}
+
+/// Writes the given content to the given file *cross-process atomically*, in the sense that another
+/// process concurrently reading that file will see either the old content or the new content, but
+/// not some intermediate (e.g., empty) state.
+///
+/// We assume no other parts of this same process are trying to read or write that file.
+pub fn write_to_file(filename: &Path, content: &str) {
+    // Create a temporary file with the desired contents.
+    let mut temp_filename = filename.as_os_str().to_os_string();
+    temp_filename.push(&format!(".{}", std::process::id()));
+    let mut temp_file = File::create(&temp_filename).unwrap();
+    temp_file.write_all(content.as_bytes()).unwrap();
+    drop(temp_file);
+
+    // Move file to the desired location.
+    fs::rename(temp_filename, filename).unwrap();
+}
+
+// Computes the extra flags that need to be passed to cargo to make it behave like the current
+// cargo invocation.
+fn cargo_extra_flags() -> Vec<String> {
+    let mut flags = Vec::new();
+    // `-Zunstable-options` is required by `--config`.
+    flags.push("-Zunstable-options".to_string());
+
+    // Forward `--config` flags.
+    let config_flag = "--config";
+    for arg in get_arg_flag_values(config_flag) {
+        flags.push(config_flag.to_string());
+        flags.push(arg);
+    }
+
+    // Forward `--manifest-path`.
+    let manifest_flag = "--manifest-path";
+    if let Some(manifest) = get_arg_flag_value(manifest_flag) {
+        flags.push(manifest_flag.to_string());
+        flags.push(manifest);
+    }
+
+    // Forwarding `--target-dir` would make sense, but `cargo metadata` does not support that flag.
+
+    flags
+}
+
+pub fn get_cargo_metadata() -> Metadata {
+    // This will honor the `CARGO` env var the same way our `cargo()` does.
+    MetadataCommand::new().no_deps().other_options(cargo_extra_flags()).exec().unwrap()
+}
+
+/// Pulls all the crates in this workspace from the cargo metadata.
+/// Workspace members are emitted like "miri 0.1.0 (path+file:///path/to/miri)"
+/// Additionally, somewhere between cargo metadata and TyCtxt, '-' gets replaced with '_' so we
+/// make that same transformation here.
+pub fn local_crates(metadata: &Metadata) -> String {
+    assert!(!metadata.workspace_members.is_empty());
+    let mut local_crates = String::new();
+    for member in &metadata.workspace_members {
+        let name = member.repr.split(' ').next().unwrap();
+        let name = name.replace('-', "_");
+        local_crates.push_str(&name);
+        local_crates.push(',');
+    }
+    local_crates.pop(); // Remove the trailing ','
+
+    local_crates
+}
+
+fn env_vars_from_cmd(cmd: &Command) -> Vec<(String, String)> {
+    let mut envs = HashMap::new();
+    for (key, value) in std::env::vars() {
+        envs.insert(key, value);
+    }
+    for (key, value) in cmd.get_envs() {
+        if let Some(value) = value {
+            envs.insert(key.to_string_lossy().to_string(), value.to_string_lossy().to_string());
+        } else {
+            envs.remove(&key.to_string_lossy().to_string());
+        }
+    }
+    let mut envs: Vec<_> = envs.into_iter().collect();
+    envs.sort();
+    envs
+}
+
+/// Debug-print a command that is going to be run.
+pub fn debug_cmd(prefix: &str, verbose: usize, cmd: &Command) {
+    if verbose == 0 {
+        return;
+    }
+    // We only do a single `eprintln!` call to minimize concurrency interactions.
+    let mut out = prefix.to_string();
+    writeln!(out, " running command: env \\").unwrap();
+    if verbose > 1 {
+        // Print the full environment this will be called in.
+        for (key, value) in env_vars_from_cmd(cmd) {
+            writeln!(out, "{key}={value:?} \\").unwrap();
+        }
+    } else {
+        // Print only what has been changed for this `cmd`.
+        for (var, val) in cmd.get_envs() {
+            if let Some(val) = val {
+                writeln!(out, "{}={:?} \\", var.to_string_lossy(), val).unwrap();
+            } else {
+                writeln!(out, "--unset={}", var.to_string_lossy()).unwrap();
+            }
+        }
+    }
+    write!(out, "{cmd:?}").unwrap();
+    eprintln!("{}", out);
+}
diff --git a/src/tools/miri/cargo-miri/src/version.rs b/src/tools/miri/cargo-miri/src/version.rs
new file mode 100644
index 0000000000000..366e90df17983
--- /dev/null
+++ b/src/tools/miri/cargo-miri/src/version.rs
@@ -0,0 +1,2 @@
+// We put this in a separate file so that it can be hashed for GHA caching.
+pub const XARGO_MIN_VERSION: (u32, u32, u32) = (0, 3, 26);
diff --git a/src/tools/miri/ci.sh b/src/tools/miri/ci.sh
new file mode 100755
index 0000000000000..aa322e54a31da
--- /dev/null
+++ b/src/tools/miri/ci.sh
@@ -0,0 +1,101 @@
+#!/bin/bash
+set -euo pipefail
+set -x
+
+# Determine configuration for installed build
+echo "Installing release version of Miri"
+export RUSTFLAGS="-D warnings"
+export CARGO_INCREMENTAL=0
+./miri install # implicitly locked
+
+# Prepare debug build for direct `./miri` invocations
+echo "Building debug version of Miri"
+export CARGO_EXTRA_FLAGS="--locked"
+./miri check --no-default-features # make sure this can be built
+./miri check --all-features # and this, too
+./miri build --all-targets # the build that all the `./miri test` below will use
+echo
+
+# Test
+function run_tests {
+  if [ -n "${MIRI_TEST_TARGET+exists}" ]; then
+    echo "Testing foreign architecture $MIRI_TEST_TARGET"
+  else
+    echo "Testing host architecture"
+  fi
+
+  ## ui test suite
+  ./miri test
+  if [ -z "${MIRI_TEST_TARGET+exists}" ]; then
+    # Only for host architecture: tests with optimizations (`-O` is what cargo passes, but crank MIR
+    # optimizations up all the way).
+    # Optimizations change diagnostics (mostly backtraces), so we don't check them
+    #FIXME(#2155): we want to only run the pass and panic tests here, not the fail tests.
+    MIRIFLAGS="${MIRIFLAGS:-} -O -Zmir-opt-level=4" MIRI_SKIP_UI_CHECKS=1 ./miri test -- tests/{pass,panic}
+  fi
+
+  ## test-cargo-miri
+  # On Windows, there is always "python", not "python3" or "python2".
+  if command -v python3 > /dev/null; then
+    PYTHON=python3
+  else
+    PYTHON=python
+  fi
+  # Some environment setup that attempts to confuse the heck out of cargo-miri.
+  if [ "$HOST_TARGET" = x86_64-unknown-linux-gnu ]; then
+    # These act up on Windows (`which miri` produces a filename that does not exist?!?),
+    # so let's do this only on Linux. Also makes sure things work without these set.
+    export RUSTC=$(which rustc)
+    export MIRI=$(which miri)
+  fi
+  mkdir -p .cargo
+  echo 'build.rustc-wrapper = "thisdoesnotexist"' > .cargo/config.toml
+  # Run the actual test
+  ${PYTHON} test-cargo-miri/run-test.py
+  echo
+  # Clean up
+  unset RUSTC MIRI
+  rm -rf .cargo
+
+  # Ensure that our benchmarks all work, but only on Linux hosts.
+  if [ -z "${MIRI_TEST_TARGET+exists}" ] && [ "$HOST_TARGET" = x86_64-unknown-linux-gnu ] ; then
+    for BENCH in $(ls "bench-cargo-miri"); do
+      cargo miri run --manifest-path bench-cargo-miri/$BENCH/Cargo.toml
+    done
+  fi
+}
+
+function run_tests_minimal {
+  if [ -n "${MIRI_TEST_TARGET+exists}" ]; then
+    echo "Testing MINIMAL foreign architecture $MIRI_TEST_TARGET: only testing $@"
+  else
+    echo "Testing MINIMAL host architecture: only testing $@"
+  fi
+
+  ./miri test -- "$@"
+}
+
+# host
+run_tests
+
+case $HOST_TARGET in
+  x86_64-unknown-linux-gnu)
+    MIRI_TEST_TARGET=i686-unknown-linux-gnu run_tests
+    MIRI_TEST_TARGET=aarch64-apple-darwin run_tests
+    MIRI_TEST_TARGET=i686-pc-windows-msvc run_tests
+    MIRI_TEST_TARGET=x86_64-unknown-freebsd run_tests_minimal hello integer vec panic/panic concurrency/simple atomic data_race env/var
+    MIRI_TEST_TARGET=aarch64-linux-android run_tests_minimal hello integer vec panic/panic
+    MIRI_TEST_TARGET=thumbv7em-none-eabihf MIRI_NO_STD=1 run_tests_minimal no_std # no_std embedded architecture
+    ;;
+  x86_64-apple-darwin)
+    MIRI_TEST_TARGET=mips64-unknown-linux-gnuabi64 run_tests # big-endian architecture
+    MIRI_TEST_TARGET=x86_64-pc-windows-msvc run_tests
+    ;;
+  i686-pc-windows-msvc)
+    MIRI_TEST_TARGET=x86_64-unknown-linux-gnu run_tests
+    ;;
+  *)
+    echo "FATAL: unknown OS"
+    exit 1
+    ;;
+esac
diff --git a/src/tools/miri/miri b/src/tools/miri/miri
new file mode 100755
index 0000000000000..19f1a987ace4a
--- /dev/null
+++ b/src/tools/miri/miri
@@ -0,0 +1,236 @@
+#!/bin/bash
+set -e
+USAGE=$(cat <<"EOF"
+  COMMANDS
+
+./miri install <flags>:
+Installs the miri driver and cargo-miri. <flags> are passed to `cargo
+install`. Sets up the rpath such that the installed binary should work in any
+working directory. However, the rustup toolchain when invoking `cargo miri`
+needs to be the same one used for `./miri install`.
+
+./miri build <flags>:
+Just build miri. <flags> are passed to `cargo build`.
+
+./miri check <flags>:
+Just check miri. <flags> are passed to `cargo check`.
+
+./miri test <flags>:
+Build miri, set up a sysroot and then run the test suite. <flags> are passed
+to the final `cargo test` invocation.
+
+./miri run <flags>:
+Build miri, set up a sysroot and then run the driver with the given <flags>.
+(Also respects MIRIFLAGS environment variable.)
+
+./miri fmt <flags>:
+Format all sources and tests. <flags> are passed to `rustfmt`.
+
+./miri clippy <flags>:
+Runs clippy on all sources. <flags> are passed to `cargo clippy`.
+
+./miri cargo <flags>:
+Runs just `cargo <flags>` with the Miri-specific environment variables.
+Mainly meant to be invoked by rust-analyzer.
+
+./miri many-seeds <command>:
+Runs <command> over and over again with different seeds for Miri. The MIRIFLAGS
+variable is set to its original value appended with ` -Zmiri-seed=$SEED` for
+many different seeds.
+
+./miri bench <benches>:
+Runs the benchmarks from bench-cargo-miri in hyperfine. hyperfine needs to be installed.
+<benches> can explicitly list the benchmarks to run; by default, all of them are run.
+
+  ENVIRONMENT VARIABLES
+
+MIRI_SYSROOT:
+If already set, the "sysroot setup" step is skipped.
+
+CARGO_EXTRA_FLAGS:
+Pass extra flags to all cargo invocations. (Ignored by `./miri cargo`.)
+EOF
+)
+
+## We need to know where we are.
+# macOS does not have a useful readlink/realpath so we have to use Python instead...
+MIRIDIR=$(python3 -c 'import os, sys; print(os.path.dirname(os.path.realpath(sys.argv[1])))' "$0")
+
+## Run the auto-things.
+if [ -z "$MIRI_AUTO_OPS" ]; then
+    export MIRI_AUTO_OPS=42
+
+    # Run this first, so that the toolchain doesn't change after
+    # other code has run.
+    if [ -f "$MIRIDIR/.auto-everything" ] || [ -f "$MIRIDIR/.auto-toolchain" ] ; then
+        (cd "$MIRIDIR" && ./rustup-toolchain)
+    fi
+
+    if [ -f "$MIRIDIR/.auto-everything" ] || [ -f "$MIRIDIR/.auto-fmt" ] ; then
+        $0 fmt
+    fi
+
+    if [ -f "$MIRIDIR/.auto-everything" ] || [ -f "$MIRIDIR/.auto-clippy" ] ; then
+        $0 clippy -- -D warnings
+    fi
+fi
+
+## Determine command and toolchain.
+COMMAND="$1"
+[ $# -gt 0 ] && shift
+# Doing this *after* auto-toolchain logic above, since that might change the toolchain.
+TOOLCHAIN=$(cd "$MIRIDIR"; rustup show active-toolchain | head -n 1 | cut -d ' ' -f 1)
+
+## Handle some commands early, since they should *not* alter the environment.
+case "$COMMAND" in
+many-seeds)
+    for SEED in $({ echo obase=16; seq 0 255; } | bc); do
+        echo "Trying seed: $SEED"
+        MIRIFLAGS="$MIRIFLAGS -Zmiri-seed=$SEED" $@ || { echo "Failing seed: $SEED"; break; }
+    done
+    exit 0
+    ;;
+bench)
+    # Make sure we have an up-to-date Miri installed
+    "$0" install
+    # Run the requested benchmarks
+    if [ -z "${1+exists}" ]; then
+        BENCHES=( $(ls "$MIRIDIR/bench-cargo-miri" ) )
+    else
+        BENCHES=("$@")
+    fi
+    for BENCH in "${BENCHES[@]}"; do
+        hyperfine -w 1 -m 5 --shell=none "cargo +$TOOLCHAIN miri run --manifest-path $MIRIDIR/bench-cargo-miri/$BENCH/Cargo.toml"
+    done
+    exit 0
+    ;;
+esac
+
+## Prepare the environment
+# Determine some toolchain properties
+# export the target so its available in miri
+TARGET=$(rustc +$TOOLCHAIN --version --verbose | grep "^host:" | cut -d ' ' -f 2)
+SYSROOT=$(rustc +$TOOLCHAIN --print sysroot)
+LIBDIR=$SYSROOT/lib/rustlib/$TARGET/lib
+if ! test -d "$LIBDIR"; then
+    echo "Something went wrong determining the library dir."
+    echo "I got $LIBDIR but that does not exist."
+    echo "Please report a bug at https://github.com/rust-lang/miri/issues."
+    exit 2
+fi
+
+# Prepare flags for cargo and rustc.
+CARGO="cargo +$TOOLCHAIN"
+# Share target dir between `miri` and `cargo-miri`.
+if [ -z "$CARGO_TARGET_DIR" ]; then
+    export CARGO_TARGET_DIR="$MIRIDIR/target"
+fi
+# We configure dev builds to not be unusably slow.
+if [ -z "$CARGO_PROFILE_DEV_OPT_LEVEL" ]; then
+    export CARGO_PROFILE_DEV_OPT_LEVEL=2
+fi
+# Enable rustc-specific lints (ignored without `-Zunstable-options`).
+export RUSTFLAGS="-Zunstable-options -Wrustc::internal $RUSTFLAGS"
+# We set the rpath so that Miri finds the private rustc libraries it needs.
+export RUSTFLAGS="-C link-args=-Wl,-rpath,$LIBDIR $RUSTFLAGS"
+
+## Helper functions
+
+# Build a sysroot and set MIRI_SYSROOT to use it. Arguments are passed to `cargo miri setup`.
+build_sysroot() {
+    if ! MIRI_SYSROOT="$($CARGO run $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml -q -- miri setup --print-sysroot "$@")"; then
+        echo "'cargo miri setup' failed"
+        exit 1
+    fi
+    export MIRI_SYSROOT
+}
+
+# Prepare and set MIRI_SYSROOT. Respects `MIRI_TEST_TARGET` and takes into account
+# locally built vs. distributed rustc.
+find_sysroot() {
+    if [ -n "$MIRI_SYSROOT" ]; then
+        # Sysroot already set, use that.
+        return 0
+    fi
+    # We need to build a sysroot.
+    if [ -n "$MIRI_TEST_TARGET" ]; then
+        build_sysroot --target "$MIRI_TEST_TARGET"
+    else
+        build_sysroot
+    fi
+}
+
+## Main
+
+# Run command.
+case "$COMMAND" in
+install)
+    # "--locked" to respect the Cargo.lock file if it exists.
+    $CARGO install $CARGO_EXTRA_FLAGS --path "$MIRIDIR" --force --locked "$@"
+    $CARGO install $CARGO_EXTRA_FLAGS --path "$MIRIDIR"/cargo-miri --force --locked "$@"
+    ;;
+check)
+    # Check, and let caller control flags.
+    $CARGO check $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml --all-targets "$@"
+    $CARGO check $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml "$@"
+    ;;
+build)
+    # Build, and let caller control flags.
+    $CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml "$@"
+    $CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml "$@"
+    ;;
+test|bless)
+    # First build and get a sysroot.
+    $CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml
+    find_sysroot
+    if [ "$COMMAND" = "bless" ]; then
+        export MIRI_BLESS="Gesundheit"
+    fi
+    # Then test, and let caller control flags.
+    # Only in root project as `cargo-miri` has no tests.
+    $CARGO test $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml "$@"
+    ;;
+run)
+    # Scan for "--target" to overwrite the "MIRI_TEST_TARGET" env var so
+    # that we set the MIRI_SYSROOT up the right way.
+    FOUND_TARGET_OPT=0
+    for ARG in "$@"; do
+        if [ "$LAST_ARG" = "--target" ]; then
+            # Found it!
+            export MIRI_TEST_TARGET="$ARG"
+            FOUND_TARGET_OPT=1
+            break
+        fi
+        LAST_ARG="$ARG"
+    done
+    if [ "$FOUND_TARGET_OPT" = "0" ] && [ -n "$MIRI_TEST_TARGET" ]; then
+        # Make sure Miri actually uses this target.
+        MIRIFLAGS="$MIRIFLAGS --target $MIRI_TEST_TARGET"
+    fi
+    # First build and get a sysroot.
+    $CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml
+    find_sysroot
+    # Then run the actual command.
+    exec $CARGO run $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml -- $MIRIFLAGS "$@"
+    ;;
+fmt)
+    find "$MIRIDIR" -not \( -name target -prune \) -name '*.rs' \
+        | xargs rustfmt +$TOOLCHAIN --edition=2021 --config-path "$MIRIDIR/rustfmt.toml" "$@"
+    ;;
+clippy)
+    $CARGO clippy $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml --all-targets "$@"
+    $CARGO clippy $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml "$@"
+    ;;
+cargo)
+    # We carefully kept the working dir intact, so this will run cargo *on the workspace in the
+    # current working dir*, not on the main Miri workspace. That is exactly what RA needs.
+    $CARGO "$@"
+    ;;
+*)
+    if [ -n "$COMMAND" ]; then
+      echo "Unknown command: $COMMAND"
+      echo
+    fi
+    echo "$USAGE"
+    exit 1
+esac
diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version
new file mode 100644
index 0000000000000..78b9a110aa7c7
--- /dev/null
+++ b/src/tools/miri/rust-version
@@ -0,0 +1 @@
+acb8934fd57b3c2740c4abac0a5728c2c9b1423b
diff --git a/src/tools/miri/rustfmt.toml b/src/tools/miri/rustfmt.toml
new file mode 100644
index 0000000000000..be5af7379eae7
--- /dev/null
+++ b/src/tools/miri/rustfmt.toml
@@ -0,0 +1,5 @@
+version = "Two"
+use_small_heuristics = "Max"
+match_arm_blocks = false
+match_arm_leading_pipes = "Preserve"
+force_multiline_blocks = true
diff --git a/src/tools/miri/rustup-toolchain b/src/tools/miri/rustup-toolchain
new file mode 100755
index 0000000000000..d7730f2b06d36
--- /dev/null
+++ b/src/tools/miri/rustup-toolchain
@@ -0,0 +1,53 @@
+#!/bin/bash
+set -e
+# Manages a rustup toolchain called "miri".
+#
+# All commands set "miri" as the override toolchain for the current directory,
+# and make the `rust-version` file match that toolchain.
+#
+# USAGE:
+#
+# ./rustup-toolchain: Update "miri" toolchain to match `rust-version` (the known-good version for this commit).
+#
+# ./rustup-toolchain HEAD: Update "miri" toolchain and `rust-version` file to latest rustc HEAD.
+#
+# ./rustup-toolchain $COMMIT: Update "miri" toolchain and `rust-version` file to match that commit.
+#
+# Any extra parameters are passed to `rustup-toolchain-install-master`.
+
+# Make sure rustup-toolchain-install-master is installed.
+if ! which rustup-toolchain-install-master >/dev/null; then
+    echo "Please install rustup-toolchain-install-master by running 'cargo install rustup-toolchain-install-master'"
+    exit 1
+fi
+
+# Determine new commit.
+if [[ "$1" == "" ]]; then
+    NEW_COMMIT=$(cat rust-version)
+elif [[ "$1" == "HEAD" ]]; then
+    NEW_COMMIT=$(git ls-remote https://github.com/rust-lang/rust/ HEAD | cut -f 1)
+else
+    NEW_COMMIT="$1"
+fi
+echo "$NEW_COMMIT" > rust-version
+shift || true # don't fail if shifting fails
+
+# Check if we already are at that commit.
+CUR_COMMIT=$(rustc +miri --version -v 2>/dev/null | grep "^commit-hash: " | cut -d " " -f 2)
+if [[ "$CUR_COMMIT" == "$NEW_COMMIT" ]]; then
+    echo "miri toolchain is already at commit $CUR_COMMIT."
+    rustup override set miri
+    exit 0
+fi
+
+# Install and setup new toolchain.
+rustup toolchain uninstall miri
+rustup-toolchain-install-master -n miri -c cargo -c rust-src -c rustc-dev -c llvm-tools -c rustfmt -c clippy "$@" -- "$NEW_COMMIT"
+rustup override set miri
+
+# Cleanup.
+cargo clean
+
+# Call 'cargo metadata' on the sources in case that changes the lockfile
+# (which fails under some setups when it is done from inside vscode).
+cargo metadata --format-version 1 --manifest-path "$(rustc --print sysroot)/lib/rustlib/rustc-src/rust/compiler/rustc/Cargo.toml" >/dev/null
diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs
new file mode 100644
index 0000000000000..7d32ee4257326
--- /dev/null
+++ b/src/tools/miri/src/bin/miri.rs
@@ -0,0 +1,562 @@
+#![feature(rustc_private, stmt_expr_attributes)]
+#![allow(
+    clippy::manual_range_contains,
+    clippy::useless_format,
+    clippy::field_reassign_with_default
+)]
+
+extern crate rustc_data_structures;
+extern crate rustc_driver;
+extern crate rustc_hir;
+extern crate rustc_interface;
+extern crate rustc_metadata;
+extern crate rustc_middle;
+extern crate rustc_session;
+
+use std::env;
+use std::num::NonZeroU64;
+use std::path::PathBuf;
+use std::str::FromStr;
+
+use log::debug;
+
+use rustc_data_structures::sync::Lrc;
+use rustc_driver::Compilation;
+use rustc_hir::{self as hir, def_id::LOCAL_CRATE, Node};
+use rustc_interface::interface::Config;
+use rustc_middle::{
+    middle::exported_symbols::{
+        ExportedSymbol, SymbolExportInfo, SymbolExportKind, SymbolExportLevel,
+    },
+    ty::{query::ExternProviders, TyCtxt},
+};
+use rustc_session::{config::CrateType, search_paths::PathKind, CtfeBacktrace};
+
+use miri::{BacktraceStyle, ProvenanceMode};
+
+struct MiriCompilerCalls {
+    miri_config: miri::MiriConfig,
+}
+
+impl rustc_driver::Callbacks for MiriCompilerCalls {
+    fn config(&mut self, config: &mut Config) {
+        config.override_queries = Some(|_, _, external_providers| {
+            external_providers.used_crate_source = |tcx, cnum| {
+                let mut providers = ExternProviders::default();
+                rustc_metadata::provide_extern(&mut providers);
+                let mut crate_source = (providers.used_crate_source)(tcx, cnum);
+                // HACK: rustc will emit "crate ... required to be available in rlib format, but
+                // was not found in this form" errors once we use `tcx.dependency_formats()` if
+                // there's no rlib provided, so setting a dummy path here to workaround those errors.
+                Lrc::make_mut(&mut crate_source).rlib = Some((PathBuf::new(), PathKind::All));
+                crate_source
+            };
+        });
+    }
+
+    fn after_analysis<'tcx>(
+        &mut self,
+        compiler: &rustc_interface::interface::Compiler,
+        queries: &'tcx rustc_interface::Queries<'tcx>,
+    ) -> Compilation {
+        compiler.session().abort_if_errors();
+
+        queries.global_ctxt().unwrap().peek_mut().enter(|tcx| {
+            init_late_loggers(tcx);
+            if !tcx.sess.crate_types().contains(&CrateType::Executable) {
+                tcx.sess.fatal("miri only makes sense on bin crates");
+            }
+
+            let (entry_def_id, entry_type) = if let Some(entry_def) = tcx.entry_fn(()) {
+                entry_def
+            } else {
+                tcx.sess.fatal("miri can only run programs that have a main function");
+            };
+            let mut config = self.miri_config.clone();
+
+            // Add filename to `miri` arguments.
+            config.args.insert(0, compiler.input().filestem().to_string());
+
+            // Adjust working directory for interpretation.
+            if let Some(cwd) = env::var_os("MIRI_CWD") {
+                env::set_current_dir(cwd).unwrap();
+            }
+
+            if let Some(return_code) = miri::eval_entry(tcx, entry_def_id, entry_type, config) {
+                std::process::exit(
+                    i32::try_from(return_code).expect("Return value was too large!"),
+                );
+            }
+        });
+
+        compiler.session().abort_if_errors();
+
+        Compilation::Stop
+    }
+}
+
+struct MiriBeRustCompilerCalls {
+    target_crate: bool,
+}
+
+impl rustc_driver::Callbacks for MiriBeRustCompilerCalls {
+    #[allow(rustc::potential_query_instability)] // rustc_codegen_ssa (where this code is copied from) also allows this lint
+    fn config(&mut self, config: &mut Config) {
+        if config.opts.prints.is_empty() && self.target_crate {
+            // Queries overriden here affect the data stored in `rmeta` files of dependencies,
+            // which will be used later in non-`MIRI_BE_RUSTC` mode.
+            config.override_queries = Some(|_, local_providers, _| {
+                // `exported_symbols` and `reachable_non_generics` provided by rustc always returns
+                // an empty result if `tcx.sess.opts.output_types.should_codegen()` is false.
+                local_providers.exported_symbols = |tcx, cnum| {
+                    assert_eq!(cnum, LOCAL_CRATE);
+                    tcx.arena.alloc_from_iter(
+                        // This is based on:
+                        // https://github.com/rust-lang/rust/blob/2962e7c0089d5c136f4e9600b7abccfbbde4973d/compiler/rustc_codegen_ssa/src/back/symbol_export.rs#L62-L63
+                        // https://github.com/rust-lang/rust/blob/2962e7c0089d5c136f4e9600b7abccfbbde4973d/compiler/rustc_codegen_ssa/src/back/symbol_export.rs#L174
+                        tcx.reachable_set(()).iter().filter_map(|&local_def_id| {
+                            // Do the same filtering that rustc does:
+                            // https://github.com/rust-lang/rust/blob/2962e7c0089d5c136f4e9600b7abccfbbde4973d/compiler/rustc_codegen_ssa/src/back/symbol_export.rs#L84-L102
+                            // Otherwise it may cause unexpected behaviours and ICEs
+                            // (https://github.com/rust-lang/rust/issues/86261).
+                            let is_reachable_non_generic = matches!(
+                                tcx.hir().get(tcx.hir().local_def_id_to_hir_id(local_def_id)),
+                                Node::Item(&hir::Item {
+                                    kind: hir::ItemKind::Static(..) | hir::ItemKind::Fn(..),
+                                    ..
+                                }) | Node::ImplItem(&hir::ImplItem {
+                                    kind: hir::ImplItemKind::Fn(..),
+                                    ..
+                                })
+                                if !tcx.generics_of(local_def_id).requires_monomorphization(tcx)
+                            );
+                            (is_reachable_non_generic
+                                && tcx.codegen_fn_attrs(local_def_id).contains_extern_indicator())
+                            .then_some((
+                                ExportedSymbol::NonGeneric(local_def_id.to_def_id()),
+                                // Some dummy `SymbolExportInfo` here. We only use
+                                // `exported_symbols` in shims/foreign_items.rs and the export info
+                                // is ignored.
+                                SymbolExportInfo {
+                                    level: SymbolExportLevel::C,
+                                    kind: SymbolExportKind::Text,
+                                    used: false,
+                                },
+                            ))
+                        }),
+                    )
+                }
+            });
+        }
+    }
+}
+
+fn show_error(msg: &impl std::fmt::Display) -> ! {
+    eprintln!("fatal error: {msg}");
+    std::process::exit(1)
+}
+
+macro_rules! show_error {
+    ($($tt:tt)*) => { show_error(&format_args!($($tt)*)) };
+}
+
+fn init_early_loggers() {
+    // Note that our `extern crate log` is *not* the same as rustc's; as a result, we have to
+    // initialize them both, and we always initialize `miri`'s first.
+    let env = env_logger::Env::new().filter("MIRI_LOG").write_style("MIRI_LOG_STYLE");
+    env_logger::init_from_env(env);
+    // Enable verbose entry/exit logging by default if MIRI_LOG is set.
+    if env::var_os("MIRI_LOG").is_some() && env::var_os("RUSTC_LOG_ENTRY_EXIT").is_none() {
+        env::set_var("RUSTC_LOG_ENTRY_EXIT", "1");
+    }
+    // We only initialize `rustc` if the env var is set (so the user asked for it).
+    // If it is not set, we avoid initializing now so that we can initialize
+    // later with our custom settings, and *not* log anything for what happens before
+    // `miri` gets started.
+    if env::var_os("RUSTC_LOG").is_some() {
+        rustc_driver::init_rustc_env_logger();
+    }
+}
+
+fn init_late_loggers(tcx: TyCtxt<'_>) {
+    // We initialize loggers right before we start evaluation. We overwrite the `RUSTC_LOG`
+    // env var if it is not set, control it based on `MIRI_LOG`.
+    // (FIXME: use `var_os`, but then we need to manually concatenate instead of `format!`.)
+    if let Ok(var) = env::var("MIRI_LOG") {
+        if env::var_os("RUSTC_LOG").is_none() {
+            // We try to be a bit clever here: if `MIRI_LOG` is just a single level
+            // used for everything, we only apply it to the parts of rustc that are
+            // CTFE-related. Otherwise, we use it verbatim for `RUSTC_LOG`.
+            // This way, if you set `MIRI_LOG=trace`, you get only the right parts of
+            // rustc traced, but you can also do `MIRI_LOG=miri=trace,rustc_const_eval::interpret=debug`.
+            if log::Level::from_str(&var).is_ok() {
+                env::set_var(
+                    "RUSTC_LOG",
+                    &format!(
+                        "rustc_middle::mir::interpret={0},rustc_const_eval::interpret={0}",
+                        var
+                    ),
+                );
+            } else {
+                env::set_var("RUSTC_LOG", &var);
+            }
+            rustc_driver::init_rustc_env_logger();
+        }
+    }
+
+    // If `MIRI_BACKTRACE` is set and `RUSTC_CTFE_BACKTRACE` is not, set `RUSTC_CTFE_BACKTRACE`.
+    // Do this late, so we ideally only apply this to Miri's errors.
+    if let Some(val) = env::var_os("MIRI_BACKTRACE") {
+        let ctfe_backtrace = match &*val.to_string_lossy() {
+            "immediate" => CtfeBacktrace::Immediate,
+            "0" => CtfeBacktrace::Disabled,
+            _ => CtfeBacktrace::Capture,
+        };
+        *tcx.sess.ctfe_backtrace.borrow_mut() = ctfe_backtrace;
+    }
+}
+
+/// Returns the "default sysroot" that Miri will use for host things if no `--sysroot` flag is set.
+/// Should be a compile-time constant.
+fn host_sysroot() -> Option<String> {
+    if option_env!("RUSTC_STAGE").is_some() {
+        // This is being built as part of rustc, and gets shipped with rustup.
+        // We can rely on the sysroot computation in librustc_session.
+        return None;
+    }
+    // For builds outside rustc, we need to ensure that we got a sysroot
+    // that gets used as a default.  The sysroot computation in librustc_session would
+    // end up somewhere in the build dir (see `get_or_default_sysroot`).
+    // Taken from PR <https://github.com/Manishearth/rust-clippy/pull/911>.
+    let home = option_env!("RUSTUP_HOME").or(option_env!("MULTIRUST_HOME"));
+    let toolchain = option_env!("RUSTUP_TOOLCHAIN").or(option_env!("MULTIRUST_TOOLCHAIN"));
+    Some(match (home, toolchain) {
+        (Some(home), Some(toolchain)) => {
+            // Check that at runtime, we are still in this toolchain (if there is any toolchain).
+            if let Some(toolchain_runtime) =
+                env::var_os("RUSTUP_TOOLCHAIN").or_else(|| env::var_os("MULTIRUST_TOOLCHAIN"))
+            {
+                if toolchain_runtime != toolchain {
+                    show_error!(
+                        "This Miri got built with local toolchain `{toolchain}`, but now is being run under a different toolchain. \n\
+                        Make sure to run Miri in the toolchain it got built with, e.g. via `cargo +{toolchain} miri`."
+                    )
+                }
+            }
+            format!("{}/toolchains/{}", home, toolchain)
+        }
+        _ => option_env!("RUST_SYSROOT")
+            .unwrap_or_else(|| {
+                show_error!(
+                    "To build Miri without rustup, set the `RUST_SYSROOT` env var at build time",
+                )
+            })
+            .to_owned(),
+    })
+}
+
+/// Execute a compiler with the given CLI arguments and callbacks.
+fn run_compiler(
+    mut args: Vec<String>,
+    target_crate: bool,
+    callbacks: &mut (dyn rustc_driver::Callbacks + Send),
+) -> ! {
+    // Make sure we use the right default sysroot. The default sysroot is wrong,
+    // because `get_or_default_sysroot` in `librustc_session` bases that on `current_exe`.
+    //
+    // Make sure we always call `host_sysroot` as that also does some sanity-checks
+    // of the environment we were built in and whether it matches what we are running in.
+    let host_default_sysroot = host_sysroot();
+    // Now see if we even need to set something.
+    let sysroot_flag = "--sysroot";
+    if !args.iter().any(|e| e == sysroot_flag) {
+        // No sysroot was set, let's see if we have a custom default we want to configure.
+        let default_sysroot = if target_crate {
+            // Using the built-in default here would be plain wrong, so we *require*
+            // the env var to make sure things make sense.
+            Some(env::var("MIRI_SYSROOT").unwrap_or_else(|_| {
+                show_error!(
+                    "Miri was invoked in 'target' mode without `MIRI_SYSROOT` or `--sysroot` being set"
+                )
+            }))
+        } else {
+            host_default_sysroot
+        };
+        if let Some(sysroot) = default_sysroot {
+            // We need to overwrite the default that librustc_session would compute.
+            args.push(sysroot_flag.to_owned());
+            args.push(sysroot);
+        }
+    }
+
+    // Don't insert `MIRI_DEFAULT_ARGS`, in particular, `--cfg=miri`, if we are building
+    // a "host" crate. That may cause procedural macros (and probably build scripts) to
+    // depend on Miri-only symbols, such as `miri_resolve_frame`:
+    // https://github.com/rust-lang/miri/issues/1760
+    if target_crate {
+        // Some options have different defaults in Miri than in plain rustc; apply those by making
+        // them the first arguments after the binary name (but later arguments can overwrite them).
+        args.splice(1..1, miri::MIRI_DEFAULT_ARGS.iter().map(ToString::to_string));
+    }
+
+    // Invoke compiler, and handle return code.
+    let exit_code = rustc_driver::catch_with_exit_code(move || {
+        rustc_driver::RunCompiler::new(&args, callbacks).run()
+    });
+    std::process::exit(exit_code)
+}
+
+/// Parses a comma separated list of `T` from the given string:
+///
+/// `<value1>,<value2>,<value3>,...`
+fn parse_comma_list<T: FromStr>(input: &str) -> Result<Vec<T>, T::Err> {
+    input.split(',').map(str::parse::<T>).collect()
+}
+
+fn main() {
+    // Snapshot a copy of the environment before `rustc` starts messing with it.
+    // (`install_ice_hook` might change `RUST_BACKTRACE`.)
+    let env_snapshot = env::vars_os().collect::<Vec<_>>();
+
+    // Earliest rustc setup.
+    rustc_driver::install_ice_hook();
+
+    // If the environment asks us to actually be rustc, then do that.
+    if let Some(crate_kind) = env::var_os("MIRI_BE_RUSTC") {
+        rustc_driver::init_rustc_env_logger();
+
+        let target_crate = if crate_kind == "target" {
+            true
+        } else if crate_kind == "host" {
+            false
+        } else {
+            panic!("invalid `MIRI_BE_RUSTC` value: {:?}", crate_kind)
+        };
+
+        // We cannot use `rustc_driver::main` as we need to adjust the CLI arguments.
+        run_compiler(
+            env::args().collect(),
+            target_crate,
+            &mut MiriBeRustCompilerCalls { target_crate },
+        )
+    }
+
+    // Init loggers the Miri way.
+    init_early_loggers();
+
+    // Parse our arguments and split them across `rustc` and `miri`.
+    let mut miri_config = miri::MiriConfig::default();
+    miri_config.env = env_snapshot;
+
+    let mut rustc_args = vec![];
+    let mut after_dashdash = false;
+
+    // If user has explicitly enabled/disabled isolation
+    let mut isolation_enabled: Option<bool> = None;
+    for arg in env::args() {
+        if rustc_args.is_empty() {
+            // Very first arg: binary name.
+            rustc_args.push(arg);
+        } else if after_dashdash {
+            // Everything that comes after `--` is forwarded to the interpreted crate.
+            miri_config.args.push(arg);
+        } else if arg == "--" {
+            after_dashdash = true;
+        } else if arg == "-Zmiri-disable-validation" {
+            miri_config.validate = false;
+        } else if arg == "-Zmiri-disable-stacked-borrows" {
+            miri_config.stacked_borrows = false;
+        } else if arg == "-Zmiri-disable-data-race-detector" {
+            miri_config.data_race_detector = false;
+            miri_config.weak_memory_emulation = false;
+        } else if arg == "-Zmiri-disable-alignment-check" {
+            miri_config.check_alignment = miri::AlignmentCheck::None;
+        } else if arg == "-Zmiri-symbolic-alignment-check" {
+            miri_config.check_alignment = miri::AlignmentCheck::Symbolic;
+        } else if arg == "-Zmiri-check-number-validity" {
+            eprintln!(
+                "WARNING: the flag `-Zmiri-check-number-validity` no longer has any effect \
+                        since it is now enabled by default"
+            );
+        } else if arg == "-Zmiri-disable-abi-check" {
+            miri_config.check_abi = false;
+        } else if arg == "-Zmiri-disable-isolation" {
+            if matches!(isolation_enabled, Some(true)) {
+                show_error!(
+                    "-Zmiri-disable-isolation cannot be used along with -Zmiri-isolation-error"
+                );
+            } else {
+                isolation_enabled = Some(false);
+            }
+            miri_config.isolated_op = miri::IsolatedOp::Allow;
+        } else if arg == "-Zmiri-disable-weak-memory-emulation" {
+            miri_config.weak_memory_emulation = false;
+        } else if arg == "-Zmiri-track-weak-memory-loads" {
+            miri_config.track_outdated_loads = true;
+        } else if let Some(param) = arg.strip_prefix("-Zmiri-isolation-error=") {
+            if matches!(isolation_enabled, Some(false)) {
+                show_error!(
+                    "-Zmiri-isolation-error cannot be used along with -Zmiri-disable-isolation"
+                );
+            } else {
+                isolation_enabled = Some(true);
+            }
+
+            miri_config.isolated_op = match param {
+                "abort" => miri::IsolatedOp::Reject(miri::RejectOpWith::Abort),
+                "hide" => miri::IsolatedOp::Reject(miri::RejectOpWith::NoWarning),
+                "warn" => miri::IsolatedOp::Reject(miri::RejectOpWith::Warning),
+                "warn-nobacktrace" =>
+                    miri::IsolatedOp::Reject(miri::RejectOpWith::WarningWithoutBacktrace),
+                _ =>
+                    show_error!(
+                        "-Zmiri-isolation-error must be `abort`, `hide`, `warn`, or `warn-nobacktrace`"
+                    ),
+            };
+        } else if arg == "-Zmiri-ignore-leaks" {
+            miri_config.ignore_leaks = true;
+        } else if arg == "-Zmiri-panic-on-unsupported" {
+            miri_config.panic_on_unsupported = true;
+        } else if arg == "-Zmiri-tag-raw-pointers" {
+            eprintln!("WARNING: `-Zmiri-tag-raw-pointers` has no effect; it is enabled by default");
+        } else if arg == "-Zmiri-strict-provenance" {
+            miri_config.provenance_mode = ProvenanceMode::Strict;
+        } else if arg == "-Zmiri-permissive-provenance" {
+            miri_config.provenance_mode = ProvenanceMode::Permissive;
+        } else if arg == "-Zmiri-mute-stdout-stderr" {
+            miri_config.mute_stdout_stderr = true;
+        } else if arg == "-Zmiri-retag-fields" {
+            miri_config.retag_fields = true;
+        } else if arg == "-Zmiri-track-raw-pointers" {
+            eprintln!(
+                "WARNING: `-Zmiri-track-raw-pointers` has no effect; it is enabled by default"
+            );
+        } else if let Some(param) = arg.strip_prefix("-Zmiri-seed=") {
+            if miri_config.seed.is_some() {
+                show_error!("Cannot specify -Zmiri-seed multiple times!");
+            }
+            let seed = u64::from_str_radix(param, 16)
+                        .unwrap_or_else(|_| show_error!(
+                            "-Zmiri-seed should only contain valid hex digits [0-9a-fA-F] and must fit into a u64 (max 16 characters)"
+                        ));
+            miri_config.seed = Some(seed);
+        } else if let Some(_param) = arg.strip_prefix("-Zmiri-env-exclude=") {
+            show_error!(
+                "`-Zmiri-env-exclude` has been removed; unset env vars before starting Miri instead"
+            );
+        } else if let Some(param) = arg.strip_prefix("-Zmiri-env-forward=") {
+            miri_config.forwarded_env_vars.push(param.to_owned());
+        } else if let Some(param) = arg.strip_prefix("-Zmiri-track-pointer-tag=") {
+            let ids: Vec<u64> = match parse_comma_list(param) {
+                Ok(ids) => ids,
+                Err(err) =>
+                    show_error!(
+                        "-Zmiri-track-pointer-tag requires a comma separated list of valid `u64` arguments: {}",
+                        err
+                    ),
+            };
+            for id in ids.into_iter().map(miri::SbTag::new) {
+                if let Some(id) = id {
+                    miri_config.tracked_pointer_tags.insert(id);
+                } else {
+                    show_error!("-Zmiri-track-pointer-tag requires nonzero arguments");
+                }
+            }
+        } else if let Some(param) = arg.strip_prefix("-Zmiri-track-call-id=") {
+            let ids: Vec<u64> = match parse_comma_list(param) {
+                Ok(ids) => ids,
+                Err(err) =>
+                    show_error!(
+                        "-Zmiri-track-call-id requires a comma separated list of valid `u64` arguments: {}",
+                        err
+                    ),
+            };
+            for id in ids.into_iter().map(miri::CallId::new) {
+                if let Some(id) = id {
+                    miri_config.tracked_call_ids.insert(id);
+                } else {
+                    show_error!("-Zmiri-track-call-id requires a nonzero argument");
+                }
+            }
+        } else if let Some(param) = arg.strip_prefix("-Zmiri-track-alloc-id=") {
+            let ids: Vec<miri::AllocId> = match parse_comma_list::<NonZeroU64>(param) {
+                Ok(ids) => ids.into_iter().map(miri::AllocId).collect(),
+                Err(err) =>
+                    show_error!(
+                        "-Zmiri-track-alloc-id requires a comma separated list of valid non-zero `u64` arguments: {}",
+                        err
+                    ),
+            };
+            miri_config.tracked_alloc_ids.extend(ids);
+        } else if let Some(param) = arg.strip_prefix("-Zmiri-compare-exchange-weak-failure-rate=") {
+            let rate = match param.parse::<f64>() {
+                Ok(rate) if rate >= 0.0 && rate <= 1.0 => rate,
+                Ok(_) =>
+                    show_error!(
+                        "-Zmiri-compare-exchange-weak-failure-rate must be between `0.0` and `1.0`"
+                    ),
+                Err(err) =>
+                    show_error!(
+                        "-Zmiri-compare-exchange-weak-failure-rate requires a `f64` between `0.0` and `1.0`: {}",
+                        err
+                    ),
+            };
+            miri_config.cmpxchg_weak_failure_rate = rate;
+        } else if let Some(param) = arg.strip_prefix("-Zmiri-preemption-rate=") {
+            let rate = match param.parse::<f64>() {
+                Ok(rate) if rate >= 0.0 && rate <= 1.0 => rate,
+                Ok(_) => show_error!("-Zmiri-preemption-rate must be between `0.0` and `1.0`"),
+                Err(err) =>
+                    show_error!(
+                        "-Zmiri-preemption-rate requires a `f64` between `0.0` and `1.0`: {}",
+                        err
+                    ),
+            };
+            miri_config.preemption_rate = rate;
+        } else if arg == "-Zmiri-report-progress" {
+            // This makes it take a few seconds between progress reports on my laptop.
+            miri_config.report_progress = Some(1_000_000);
+        } else if let Some(param) = arg.strip_prefix("-Zmiri-report-progress=") {
+            let interval = match param.parse::<u32>() {
+                Ok(i) => i,
+                Err(err) => show_error!("-Zmiri-report-progress requires a `u32`: {}", err),
+            };
+            miri_config.report_progress = Some(interval);
+        } else if let Some(param) = arg.strip_prefix("-Zmiri-tag-gc=") {
+            let interval = match param.parse::<u32>() {
+                Ok(i) => i,
+                Err(err) => show_error!("-Zmiri-tag-gc requires a `u32`: {}", err),
+            };
+            miri_config.gc_interval = interval;
+        } else if let Some(param) = arg.strip_prefix("-Zmiri-measureme=") {
+            miri_config.measureme_out = Some(param.to_string());
+        } else if let Some(param) = arg.strip_prefix("-Zmiri-backtrace=") {
+            miri_config.backtrace_style = match param {
+                "0" => BacktraceStyle::Off,
+                "1" => BacktraceStyle::Short,
+                "full" => BacktraceStyle::Full,
+                _ => show_error!("-Zmiri-backtrace may only be 0, 1, or full"),
+            };
+        } else if let Some(param) = arg.strip_prefix("-Zmiri-extern-so-file=") {
+            let filename = param.to_string();
+            if std::path::Path::new(&filename).exists() {
+                if let Some(other_filename) = miri_config.external_so_file {
+                    show_error!(
+                        "-Zmiri-extern-so-file is already set to {}",
+                        other_filename.display()
+                    );
+                }
+                miri_config.external_so_file = Some(filename.into());
+            } else {
+                show_error!("-Zmiri-extern-so-file `{}` does not exist", filename);
+            }
+        } else {
+            // Forward to rustc.
+            rustc_args.push(arg);
+        }
+    }
+
+    debug!("rustc arguments: {:?}", rustc_args);
+    debug!("crate arguments: {:?}", miri_config.args);
+    run_compiler(rustc_args, /* target_crate: */ true, &mut MiriCompilerCalls { miri_config })
+}
diff --git a/src/tools/miri/src/clock.rs b/src/tools/miri/src/clock.rs
new file mode 100644
index 0000000000000..3f33273e1e541
--- /dev/null
+++ b/src/tools/miri/src/clock.rs
@@ -0,0 +1,115 @@
+use std::sync::atomic::{AtomicU64, Ordering};
+use std::time::{Duration, Instant as StdInstant};
+
+/// When using a virtual clock, this defines how many nanoseconds we pretend are passing for each
+/// basic block.
+const NANOSECONDS_PER_BASIC_BLOCK: u64 = 10;
+
+#[derive(Debug)]
+pub struct Instant {
+    kind: InstantKind,
+}
+
+#[derive(Debug)]
+enum InstantKind {
+    Host(StdInstant),
+    Virtual { nanoseconds: u64 },
+}
+
+impl Instant {
+    pub fn checked_add(&self, duration: Duration) -> Option<Instant> {
+        match self.kind {
+            InstantKind::Host(instant) =>
+                instant.checked_add(duration).map(|i| Instant { kind: InstantKind::Host(i) }),
+            InstantKind::Virtual { nanoseconds } =>
+                u128::from(nanoseconds)
+                    .checked_add(duration.as_nanos())
+                    .and_then(|n| u64::try_from(n).ok())
+                    .map(|nanoseconds| Instant { kind: InstantKind::Virtual { nanoseconds } }),
+        }
+    }
+
+    pub fn duration_since(&self, earlier: Instant) -> Duration {
+        match (&self.kind, earlier.kind) {
+            (InstantKind::Host(instant), InstantKind::Host(earlier)) =>
+                instant.duration_since(earlier),
+            (
+                InstantKind::Virtual { nanoseconds },
+                InstantKind::Virtual { nanoseconds: earlier },
+            ) => Duration::from_nanos(nanoseconds.saturating_sub(earlier)),
+            _ => panic!("all `Instant` must be of the same kind"),
+        }
+    }
+}
+
+/// A monotone clock used for `Instant` simulation.
+#[derive(Debug)]
+pub struct Clock {
+    kind: ClockKind,
+}
+
+#[derive(Debug)]
+enum ClockKind {
+    Host {
+        /// The "time anchor" for this machine's monotone clock.
+        time_anchor: StdInstant,
+    },
+    Virtual {
+        /// The "current virtual time".
+        nanoseconds: AtomicU64,
+    },
+}
+
+impl Clock {
+    /// Create a new clock based on the availability of communication with the host.
+    pub fn new(communicate: bool) -> Self {
+        let kind = if communicate {
+            ClockKind::Host { time_anchor: StdInstant::now() }
+        } else {
+            ClockKind::Virtual { nanoseconds: 0.into() }
+        };
+
+        Self { kind }
+    }
+
+    /// Let the time pass for a small interval.
+    pub fn tick(&self) {
+        match &self.kind {
+            ClockKind::Host { .. } => {
+                // Time will pass without us doing anything.
+            }
+            ClockKind::Virtual { nanoseconds } => {
+                nanoseconds.fetch_add(NANOSECONDS_PER_BASIC_BLOCK, Ordering::SeqCst);
+            }
+        }
+    }
+
+    /// Sleep for the desired duration.
+    pub fn sleep(&self, duration: Duration) {
+        match &self.kind {
+            ClockKind::Host { .. } => std::thread::sleep(duration),
+            ClockKind::Virtual { nanoseconds } => {
+                // Just pretend that we have slept for some time.
+                nanoseconds.fetch_add(duration.as_nanos().try_into().unwrap(), Ordering::SeqCst);
+            }
+        }
+    }
+
+    /// Return the `anchor` instant, to convert between monotone instants and durations relative to the anchor.
+    pub fn anchor(&self) -> Instant {
+        match &self.kind {
+            ClockKind::Host { time_anchor } => Instant { kind: InstantKind::Host(*time_anchor) },
+            ClockKind::Virtual { .. } => Instant { kind: InstantKind::Virtual { nanoseconds: 0 } },
+        }
+    }
+
+    pub fn now(&self) -> Instant {
+        match &self.kind {
+            ClockKind::Host { .. } => Instant { kind: InstantKind::Host(StdInstant::now()) },
+            ClockKind::Virtual { nanoseconds } =>
+                Instant {
+                    kind: InstantKind::Virtual { nanoseconds: nanoseconds.load(Ordering::SeqCst) },
+                },
+        }
+    }
+}
diff --git a/src/tools/miri/src/concurrency/data_race.rs b/src/tools/miri/src/concurrency/data_race.rs
new file mode 100644
index 0000000000000..2e54ddaaba113
--- /dev/null
+++ b/src/tools/miri/src/concurrency/data_race.rs
@@ -0,0 +1,1596 @@
+//! Implementation of a data-race detector using Lamport Timestamps / Vector-clocks
+//! based on the Dynamic Race Detection for C++:
+//! <https://www.doc.ic.ac.uk/~afd/homepages/papers/pdfs/2017/POPL.pdf>
+//! which does not report false-positives when fences are used, and gives better
+//! accuracy in presence of read-modify-write operations.
+//!
+//! The implementation contains modifications to correctly model the changes to the memory model in C++20
+//! regarding the weakening of release sequences: <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0982r1.html>.
+//! Relaxed stores now unconditionally block all currently active release sequences and so per-thread tracking of release
+//! sequences is not needed.
+//!
+//! The implementation also models races with memory allocation and deallocation via treating allocation and
+//! deallocation as a type of write internally for detecting data-races.
+//!
+//! Weak memory orders are explored but not all weak behaviours are exhibited, so it can still miss data-races
+//! but should not report false-positives
+//!
+//! Data-race definition from(<https://en.cppreference.com/w/cpp/language/memory_model#Threads_and_data_races>):
+//! a data race occurs between two memory accesses if they are on different threads, at least one operation
+//! is non-atomic, at least one operation is a write and neither access happens-before the other. Read the link
+//! for full definition.
+//!
+//! This re-uses vector indexes for threads that are known to be unable to report data-races, this is valid
+//! because it only re-uses vector indexes once all currently-active (not-terminated) threads have an internal
+//! vector clock that happens-after the join operation of the candidate thread. Threads that have not been joined
+//! on are not considered. Since the thread's vector clock will only increase and a data-race implies that
+//! there is some index x where `clock[x] > thread_clock`, when this is true `clock[candidate-idx] > thread_clock`
+//! can never hold and hence a data-race can never be reported in that vector index again.
+//! This means that the thread-index can be safely re-used, starting on the next timestamp for the newly created
+//! thread.
+//!
+//! The timestamps used in the data-race detector assign each sequence of non-atomic operations
+//! followed by a single atomic or concurrent operation a single timestamp.
+//! Write, Read, Write, ThreadJoin will be represented by a single timestamp value on a thread.
+//! This is because extra increment operations between the operations in the sequence are not
+//! required for accurate reporting of data-race values.
+//!
+//! As per the paper a threads timestamp is only incremented after a release operation is performed
+//! so some atomic operations that only perform acquires do not increment the timestamp. Due to shared
+//! code some atomic operations may increment the timestamp when not necessary but this has no effect
+//! on the data-race detection code.
+
+use std::{
+    cell::{Cell, Ref, RefCell, RefMut},
+    fmt::Debug,
+    mem,
+};
+
+use rustc_ast::Mutability;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_index::vec::{Idx, IndexVec};
+use rustc_middle::{mir, ty::layout::TyAndLayout};
+use rustc_target::abi::{Align, Size};
+
+use crate::*;
+
+use super::{
+    vector_clock::{VClock, VTimestamp, VectorIdx},
+    weak_memory::EvalContextExt as _,
+};
+
+pub type AllocExtra = VClockAlloc;
+
+/// Valid atomic read-write orderings, alias of atomic::Ordering (not non-exhaustive).
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub enum AtomicRwOrd {
+    Relaxed,
+    Acquire,
+    Release,
+    AcqRel,
+    SeqCst,
+}
+
+/// Valid atomic read orderings, subset of atomic::Ordering.
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub enum AtomicReadOrd {
+    Relaxed,
+    Acquire,
+    SeqCst,
+}
+
+/// Valid atomic write orderings, subset of atomic::Ordering.
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub enum AtomicWriteOrd {
+    Relaxed,
+    Release,
+    SeqCst,
+}
+
+/// Valid atomic fence orderings, subset of atomic::Ordering.
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub enum AtomicFenceOrd {
+    Acquire,
+    Release,
+    AcqRel,
+    SeqCst,
+}
+
+/// The current set of vector clocks describing the state
+/// of a thread, contains the happens-before clock and
+/// additional metadata to model atomic fence operations.
+#[derive(Clone, Default, Debug)]
+pub(super) struct ThreadClockSet {
+    /// The increasing clock representing timestamps
+    /// that happen-before this thread.
+    pub(super) clock: VClock,
+
+    /// The set of timestamps that will happen-before this
+    /// thread once it performs an acquire fence.
+    fence_acquire: VClock,
+
+    /// The last timestamp of happens-before relations that
+    /// have been released by this thread by a fence.
+    fence_release: VClock,
+
+    /// Timestamps of the last SC fence performed by each
+    /// thread, updated when this thread performs an SC fence
+    pub(super) fence_seqcst: VClock,
+
+    /// Timestamps of the last SC write performed by each
+    /// thread, updated when this thread performs an SC fence
+    pub(super) write_seqcst: VClock,
+
+    /// Timestamps of the last SC fence performed by each
+    /// thread, updated when this thread performs an SC read
+    pub(super) read_seqcst: VClock,
+}
+
+impl ThreadClockSet {
+    /// Apply the effects of a release fence to this
+    /// set of thread vector clocks.
+    #[inline]
+    fn apply_release_fence(&mut self) {
+        self.fence_release.clone_from(&self.clock);
+    }
+
+    /// Apply the effects of an acquire fence to this
+    /// set of thread vector clocks.
+    #[inline]
+    fn apply_acquire_fence(&mut self) {
+        self.clock.join(&self.fence_acquire);
+    }
+
+    /// Increment the happens-before clock at a
+    /// known index.
+    #[inline]
+    fn increment_clock(&mut self, index: VectorIdx) {
+        self.clock.increment_index(index);
+    }
+
+    /// Join the happens-before clock with that of
+    /// another thread, used to model thread join
+    /// operations.
+    fn join_with(&mut self, other: &ThreadClockSet) {
+        self.clock.join(&other.clock);
+    }
+}
+
+/// Error returned by finding a data race
+/// should be elaborated upon.
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
+pub struct DataRace;
+
+/// Externally stored memory cell clocks
+/// explicitly to reduce memory usage for the
+/// common case where no atomic operations
+/// exists on the memory cell.
+#[derive(Clone, PartialEq, Eq, Default, Debug)]
+struct AtomicMemoryCellClocks {
+    /// The clock-vector of the timestamp of the last atomic
+    /// read operation performed by each thread.
+    /// This detects potential data-races between atomic read
+    /// and non-atomic write operations.
+    read_vector: VClock,
+
+    /// The clock-vector of the timestamp of the last atomic
+    /// write operation performed by each thread.
+    /// This detects potential data-races between atomic write
+    /// and non-atomic read or write operations.
+    write_vector: VClock,
+
+    /// Synchronization vector for acquire-release semantics
+    /// contains the vector of timestamps that will
+    /// happen-before a thread if an acquire-load is
+    /// performed on the data.
+    sync_vector: VClock,
+}
+
+/// Type of write operation: allocating memory
+/// non-atomic writes and deallocating memory
+/// are all treated as writes for the purpose
+/// of the data-race detector.
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+enum WriteType {
+    /// Allocate memory.
+    Allocate,
+
+    /// Standard unsynchronized write.
+    Write,
+
+    /// Deallocate memory.
+    /// Note that when memory is deallocated first, later non-atomic accesses
+    /// will be reported as use-after-free, not as data races.
+    /// (Same for `Allocate` above.)
+    Deallocate,
+}
+impl WriteType {
+    fn get_descriptor(self) -> &'static str {
+        match self {
+            WriteType::Allocate => "Allocate",
+            WriteType::Write => "Write",
+            WriteType::Deallocate => "Deallocate",
+        }
+    }
+}
+
+/// Memory Cell vector clock metadata
+/// for data-race detection.
+#[derive(Clone, PartialEq, Eq, Debug)]
+struct MemoryCellClocks {
+    /// The vector-clock timestamp of the last write
+    /// corresponding to the writing threads timestamp.
+    write: VTimestamp,
+
+    /// The identifier of the vector index, corresponding to a thread
+    /// that performed the last write operation.
+    write_index: VectorIdx,
+
+    /// The type of operation that the write index represents,
+    /// either newly allocated memory, a non-atomic write or
+    /// a deallocation of memory.
+    write_type: WriteType,
+
+    /// The vector-clock of the timestamp of the last read operation
+    /// performed by a thread since the last write operation occurred.
+    /// It is reset to zero on each write operation.
+    read: VClock,
+
+    /// Atomic acquire & release sequence tracking clocks.
+    /// For non-atomic memory in the common case this
+    /// value is set to None.
+    atomic_ops: Option<Box<AtomicMemoryCellClocks>>,
+}
+
+impl MemoryCellClocks {
+    /// Create a new set of clocks representing memory allocated
+    ///  at a given vector timestamp and index.
+    fn new(alloc: VTimestamp, alloc_index: VectorIdx) -> Self {
+        MemoryCellClocks {
+            read: VClock::default(),
+            write: alloc,
+            write_index: alloc_index,
+            write_type: WriteType::Allocate,
+            atomic_ops: None,
+        }
+    }
+
+    /// Load the internal atomic memory cells if they exist.
+    #[inline]
+    fn atomic(&self) -> Option<&AtomicMemoryCellClocks> {
+        self.atomic_ops.as_deref()
+    }
+
+    /// Load or create the internal atomic memory metadata
+    /// if it does not exist.
+    #[inline]
+    fn atomic_mut(&mut self) -> &mut AtomicMemoryCellClocks {
+        self.atomic_ops.get_or_insert_with(Default::default)
+    }
+
+    /// Update memory cell data-race tracking for atomic
+    /// load acquire semantics, is a no-op if this memory was
+    /// not used previously as atomic memory.
+    fn load_acquire(
+        &mut self,
+        clocks: &mut ThreadClockSet,
+        index: VectorIdx,
+    ) -> Result<(), DataRace> {
+        self.atomic_read_detect(clocks, index)?;
+        if let Some(atomic) = self.atomic() {
+            clocks.clock.join(&atomic.sync_vector);
+        }
+        Ok(())
+    }
+
+    /// Checks if the memory cell access is ordered with all prior atomic reads and writes
+    fn race_free_with_atomic(&self, clocks: &ThreadClockSet) -> bool {
+        if let Some(atomic) = self.atomic() {
+            atomic.read_vector <= clocks.clock && atomic.write_vector <= clocks.clock
+        } else {
+            true
+        }
+    }
+
+    /// Update memory cell data-race tracking for atomic
+    /// load relaxed semantics, is a no-op if this memory was
+    /// not used previously as atomic memory.
+    fn load_relaxed(
+        &mut self,
+        clocks: &mut ThreadClockSet,
+        index: VectorIdx,
+    ) -> Result<(), DataRace> {
+        self.atomic_read_detect(clocks, index)?;
+        if let Some(atomic) = self.atomic() {
+            clocks.fence_acquire.join(&atomic.sync_vector);
+        }
+        Ok(())
+    }
+
+    /// Update the memory cell data-race tracking for atomic
+    /// store release semantics.
+    fn store_release(&mut self, clocks: &ThreadClockSet, index: VectorIdx) -> Result<(), DataRace> {
+        self.atomic_write_detect(clocks, index)?;
+        let atomic = self.atomic_mut();
+        atomic.sync_vector.clone_from(&clocks.clock);
+        Ok(())
+    }
+
+    /// Update the memory cell data-race tracking for atomic
+    /// store relaxed semantics.
+    fn store_relaxed(&mut self, clocks: &ThreadClockSet, index: VectorIdx) -> Result<(), DataRace> {
+        self.atomic_write_detect(clocks, index)?;
+
+        // The handling of release sequences was changed in C++20 and so
+        // the code here is different to the paper since now all relaxed
+        // stores block release sequences. The exception for same-thread
+        // relaxed stores has been removed.
+        let atomic = self.atomic_mut();
+        atomic.sync_vector.clone_from(&clocks.fence_release);
+        Ok(())
+    }
+
+    /// Update the memory cell data-race tracking for atomic
+    /// store release semantics for RMW operations.
+    fn rmw_release(&mut self, clocks: &ThreadClockSet, index: VectorIdx) -> Result<(), DataRace> {
+        self.atomic_write_detect(clocks, index)?;
+        let atomic = self.atomic_mut();
+        atomic.sync_vector.join(&clocks.clock);
+        Ok(())
+    }
+
+    /// Update the memory cell data-race tracking for atomic
+    /// store relaxed semantics for RMW operations.
+    fn rmw_relaxed(&mut self, clocks: &ThreadClockSet, index: VectorIdx) -> Result<(), DataRace> {
+        self.atomic_write_detect(clocks, index)?;
+        let atomic = self.atomic_mut();
+        atomic.sync_vector.join(&clocks.fence_release);
+        Ok(())
+    }
+
+    /// Detect data-races with an atomic read, caused by a non-atomic write that does
+    /// not happen-before the atomic-read.
+    fn atomic_read_detect(
+        &mut self,
+        clocks: &ThreadClockSet,
+        index: VectorIdx,
+    ) -> Result<(), DataRace> {
+        log::trace!("Atomic read with vectors: {:#?} :: {:#?}", self, clocks);
+        if self.write <= clocks.clock[self.write_index] {
+            let atomic = self.atomic_mut();
+            atomic.read_vector.set_at_index(&clocks.clock, index);
+            Ok(())
+        } else {
+            Err(DataRace)
+        }
+    }
+
+    /// Detect data-races with an atomic write, either with a non-atomic read or with
+    /// a non-atomic write.
+    fn atomic_write_detect(
+        &mut self,
+        clocks: &ThreadClockSet,
+        index: VectorIdx,
+    ) -> Result<(), DataRace> {
+        log::trace!("Atomic write with vectors: {:#?} :: {:#?}", self, clocks);
+        if self.write <= clocks.clock[self.write_index] && self.read <= clocks.clock {
+            let atomic = self.atomic_mut();
+            atomic.write_vector.set_at_index(&clocks.clock, index);
+            Ok(())
+        } else {
+            Err(DataRace)
+        }
+    }
+
+    /// Detect races for non-atomic read operations at the current memory cell
+    /// returns true if a data-race is detected.
+    fn read_race_detect(
+        &mut self,
+        clocks: &ThreadClockSet,
+        index: VectorIdx,
+    ) -> Result<(), DataRace> {
+        log::trace!("Unsynchronized read with vectors: {:#?} :: {:#?}", self, clocks);
+        if self.write <= clocks.clock[self.write_index] {
+            let race_free = if let Some(atomic) = self.atomic() {
+                atomic.write_vector <= clocks.clock
+            } else {
+                true
+            };
+            if race_free {
+                self.read.set_at_index(&clocks.clock, index);
+                Ok(())
+            } else {
+                Err(DataRace)
+            }
+        } else {
+            Err(DataRace)
+        }
+    }
+
+    /// Detect races for non-atomic write operations at the current memory cell
+    /// returns true if a data-race is detected.
+    fn write_race_detect(
+        &mut self,
+        clocks: &ThreadClockSet,
+        index: VectorIdx,
+        write_type: WriteType,
+    ) -> Result<(), DataRace> {
+        log::trace!("Unsynchronized write with vectors: {:#?} :: {:#?}", self, clocks);
+        if self.write <= clocks.clock[self.write_index] && self.read <= clocks.clock {
+            let race_free = if let Some(atomic) = self.atomic() {
+                atomic.write_vector <= clocks.clock && atomic.read_vector <= clocks.clock
+            } else {
+                true
+            };
+            if race_free {
+                self.write = clocks.clock[index];
+                self.write_index = index;
+                self.write_type = write_type;
+                self.read.set_zero_vector();
+                Ok(())
+            } else {
+                Err(DataRace)
+            }
+        } else {
+            Err(DataRace)
+        }
+    }
+}
+
+/// Evaluation context extensions.
+impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for MiriInterpCx<'mir, 'tcx> {}
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
+    /// Atomic variant of read_scalar_at_offset.
+    fn read_scalar_at_offset_atomic(
+        &self,
+        op: &OpTy<'tcx, Provenance>,
+        offset: u64,
+        layout: TyAndLayout<'tcx>,
+        atomic: AtomicReadOrd,
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_ref();
+        let value_place = this.deref_operand_and_offset(op, offset, layout)?;
+        this.read_scalar_atomic(&value_place, atomic)
+    }
+
+    /// Atomic variant of write_scalar_at_offset.
+    fn write_scalar_at_offset_atomic(
+        &mut self,
+        op: &OpTy<'tcx, Provenance>,
+        offset: u64,
+        value: impl Into<Scalar<Provenance>>,
+        layout: TyAndLayout<'tcx>,
+        atomic: AtomicWriteOrd,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        let value_place = this.deref_operand_and_offset(op, offset, layout)?;
+        this.write_scalar_atomic(value.into(), &value_place, atomic)
+    }
+
+    /// Perform an atomic read operation at the memory location.
+    fn read_scalar_atomic(
+        &self,
+        place: &MPlaceTy<'tcx, Provenance>,
+        atomic: AtomicReadOrd,
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_ref();
+        this.atomic_access_check(place)?;
+        // This will read from the last store in the modification order of this location. In case
+        // weak memory emulation is enabled, this may not be the store we will pick to actually read from and return.
+        // This is fine with StackedBorrow and race checks because they don't concern metadata on
+        // the *value* (including the associated provenance if this is an AtomicPtr) at this location.
+        // Only metadata on the location itself is used.
+        let scalar = this.allow_data_races_ref(move |this| this.read_scalar(&place.into()))?;
+        this.validate_overlapping_atomic(place)?;
+        this.buffered_atomic_read(place, atomic, scalar, || {
+            this.validate_atomic_load(place, atomic)
+        })
+    }
+
+    /// Perform an atomic write operation at the memory location.
+    fn write_scalar_atomic(
+        &mut self,
+        val: Scalar<Provenance>,
+        dest: &MPlaceTy<'tcx, Provenance>,
+        atomic: AtomicWriteOrd,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        this.atomic_access_check(dest)?;
+
+        this.validate_overlapping_atomic(dest)?;
+        this.allow_data_races_mut(move |this| this.write_scalar(val, &dest.into()))?;
+        this.validate_atomic_store(dest, atomic)?;
+        // FIXME: it's not possible to get the value before write_scalar. A read_scalar will cause
+        // side effects from a read the program did not perform. So we have to initialise
+        // the store buffer with the value currently being written
+        // ONCE this is fixed please remove the hack in buffered_atomic_write() in weak_memory.rs
+        // https://github.com/rust-lang/miri/issues/2164
+        this.buffered_atomic_write(val, dest, atomic, val)
+    }
+
+    /// Perform an atomic operation on a memory location.
+    fn atomic_op_immediate(
+        &mut self,
+        place: &MPlaceTy<'tcx, Provenance>,
+        rhs: &ImmTy<'tcx, Provenance>,
+        op: mir::BinOp,
+        neg: bool,
+        atomic: AtomicRwOrd,
+    ) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> {
+        let this = self.eval_context_mut();
+        this.atomic_access_check(place)?;
+
+        this.validate_overlapping_atomic(place)?;
+        let old = this.allow_data_races_mut(|this| this.read_immediate(&place.into()))?;
+
+        // Atomics wrap around on overflow.
+        let val = this.binary_op(op, &old, rhs)?;
+        let val = if neg { this.unary_op(mir::UnOp::Not, &val)? } else { val };
+        this.allow_data_races_mut(|this| this.write_immediate(*val, &place.into()))?;
+
+        this.validate_atomic_rmw(place, atomic)?;
+
+        this.buffered_atomic_rmw(val.to_scalar(), place, atomic, old.to_scalar())?;
+        Ok(old)
+    }
+
+    /// Perform an atomic exchange with a memory place and a new
+    /// scalar value, the old value is returned.
+    fn atomic_exchange_scalar(
+        &mut self,
+        place: &MPlaceTy<'tcx, Provenance>,
+        new: Scalar<Provenance>,
+        atomic: AtomicRwOrd,
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_mut();
+        this.atomic_access_check(place)?;
+
+        this.validate_overlapping_atomic(place)?;
+        let old = this.allow_data_races_mut(|this| this.read_scalar(&place.into()))?;
+        this.allow_data_races_mut(|this| this.write_scalar(new, &place.into()))?;
+
+        this.validate_atomic_rmw(place, atomic)?;
+
+        this.buffered_atomic_rmw(new, place, atomic, old)?;
+        Ok(old)
+    }
+
+    /// Perform an conditional atomic exchange with a memory place and a new
+    /// scalar value, the old value is returned.
+    fn atomic_min_max_scalar(
+        &mut self,
+        place: &MPlaceTy<'tcx, Provenance>,
+        rhs: ImmTy<'tcx, Provenance>,
+        min: bool,
+        atomic: AtomicRwOrd,
+    ) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> {
+        let this = self.eval_context_mut();
+        this.atomic_access_check(place)?;
+
+        this.validate_overlapping_atomic(place)?;
+        let old = this.allow_data_races_mut(|this| this.read_immediate(&place.into()))?;
+        let lt = this.binary_op(mir::BinOp::Lt, &old, &rhs)?.to_scalar().to_bool()?;
+
+        let new_val = if min {
+            if lt { &old } else { &rhs }
+        } else {
+            if lt { &rhs } else { &old }
+        };
+
+        this.allow_data_races_mut(|this| this.write_immediate(**new_val, &place.into()))?;
+
+        this.validate_atomic_rmw(place, atomic)?;
+
+        this.buffered_atomic_rmw(new_val.to_scalar(), place, atomic, old.to_scalar())?;
+
+        // Return the old value.
+        Ok(old)
+    }
+
+    /// Perform an atomic compare and exchange at a given memory location.
+    /// On success an atomic RMW operation is performed and on failure
+    /// only an atomic read occurs. If `can_fail_spuriously` is true,
+    /// then we treat it as a "compare_exchange_weak" operation, and
+    /// some portion of the time fail even when the values are actually
+    /// identical.
+    fn atomic_compare_exchange_scalar(
+        &mut self,
+        place: &MPlaceTy<'tcx, Provenance>,
+        expect_old: &ImmTy<'tcx, Provenance>,
+        new: Scalar<Provenance>,
+        success: AtomicRwOrd,
+        fail: AtomicReadOrd,
+        can_fail_spuriously: bool,
+    ) -> InterpResult<'tcx, Immediate<Provenance>> {
+        use rand::Rng as _;
+        let this = self.eval_context_mut();
+        this.atomic_access_check(place)?;
+
+        this.validate_overlapping_atomic(place)?;
+        // Failure ordering cannot be stronger than success ordering, therefore first attempt
+        // to read with the failure ordering and if successful then try again with the success
+        // read ordering and write in the success case.
+        // Read as immediate for the sake of `binary_op()`
+        let old = this.allow_data_races_mut(|this| this.read_immediate(&(place.into())))?;
+        // `binary_op` will bail if either of them is not a scalar.
+        let eq = this.binary_op(mir::BinOp::Eq, &old, expect_old)?;
+        // If the operation would succeed, but is "weak", fail some portion
+        // of the time, based on `success_rate`.
+        let success_rate = 1.0 - this.machine.cmpxchg_weak_failure_rate;
+        let cmpxchg_success = eq.to_scalar().to_bool()?
+            && if can_fail_spuriously {
+                this.machine.rng.get_mut().gen_bool(success_rate)
+            } else {
+                true
+            };
+        let res = Immediate::ScalarPair(old.to_scalar(), Scalar::from_bool(cmpxchg_success));
+
+        // Update ptr depending on comparison.
+        // if successful, perform a full rw-atomic validation
+        // otherwise treat this as an atomic load with the fail ordering.
+        if cmpxchg_success {
+            this.allow_data_races_mut(|this| this.write_scalar(new, &place.into()))?;
+            this.validate_atomic_rmw(place, success)?;
+            this.buffered_atomic_rmw(new, place, success, old.to_scalar())?;
+        } else {
+            this.validate_atomic_load(place, fail)?;
+            // A failed compare exchange is equivalent to a load, reading from the latest store
+            // in the modification order.
+            // Since `old` is only a value and not the store element, we need to separately
+            // find it in our store buffer and perform load_impl on it.
+            this.perform_read_on_buffered_latest(place, fail, old.to_scalar())?;
+        }
+
+        // Return the old value.
+        Ok(res)
+    }
+
+    /// Update the data-race detector for an atomic fence on the current thread.
+    fn atomic_fence(&mut self, atomic: AtomicFenceOrd) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        if let Some(data_race) = &mut this.machine.data_race {
+            data_race.maybe_perform_sync_operation(&this.machine.threads, |index, mut clocks| {
+                log::trace!("Atomic fence on {:?} with ordering {:?}", index, atomic);
+
+                // Apply data-race detection for the current fences
+                // this treats AcqRel and SeqCst as the same as an acquire
+                // and release fence applied in the same timestamp.
+                if atomic != AtomicFenceOrd::Release {
+                    // Either Acquire | AcqRel | SeqCst
+                    clocks.apply_acquire_fence();
+                }
+                if atomic != AtomicFenceOrd::Acquire {
+                    // Either Release | AcqRel | SeqCst
+                    clocks.apply_release_fence();
+                }
+                if atomic == AtomicFenceOrd::SeqCst {
+                    data_race.last_sc_fence.borrow_mut().set_at_index(&clocks.clock, index);
+                    clocks.fence_seqcst.join(&data_race.last_sc_fence.borrow());
+                    clocks.write_seqcst.join(&data_race.last_sc_write.borrow());
+                }
+
+                // Increment timestamp in case of release semantics.
+                Ok(atomic != AtomicFenceOrd::Acquire)
+            })
+        } else {
+            Ok(())
+        }
+    }
+
+    /// After all threads are done running, this allows data races to occur for subsequent
+    /// 'administrative' machine accesses (that logically happen outside of the Abstract Machine).
+    fn allow_data_races_all_threads_done(&mut self) {
+        let this = self.eval_context_ref();
+        assert!(this.have_all_terminated());
+        if let Some(data_race) = &this.machine.data_race {
+            let old = data_race.ongoing_action_data_race_free.replace(true);
+            assert!(!old, "cannot nest allow_data_races");
+        }
+    }
+}
+
+/// Vector clock metadata for a logical memory allocation.
+#[derive(Debug, Clone)]
+pub struct VClockAlloc {
+    /// Assigning each byte a MemoryCellClocks.
+    alloc_ranges: RefCell<RangeMap<MemoryCellClocks>>,
+}
+
+impl VClockAlloc {
+    /// Create a new data-race detector for newly allocated memory.
+    pub fn new_allocation(
+        global: &GlobalState,
+        thread_mgr: &ThreadManager<'_, '_>,
+        len: Size,
+        kind: MemoryKind<MiriMemoryKind>,
+    ) -> VClockAlloc {
+        let (alloc_timestamp, alloc_index) = match kind {
+            // User allocated and stack memory should track allocation.
+            MemoryKind::Machine(
+                MiriMemoryKind::Rust | MiriMemoryKind::C | MiriMemoryKind::WinHeap,
+            )
+            | MemoryKind::Stack => {
+                let (alloc_index, clocks) = global.current_thread_state(thread_mgr);
+                let alloc_timestamp = clocks.clock[alloc_index];
+                (alloc_timestamp, alloc_index)
+            }
+            // Other global memory should trace races but be allocated at the 0 timestamp.
+            MemoryKind::Machine(
+                MiriMemoryKind::Global
+                | MiriMemoryKind::Machine
+                | MiriMemoryKind::Runtime
+                | MiriMemoryKind::ExternStatic
+                | MiriMemoryKind::Tls,
+            )
+            | MemoryKind::CallerLocation => (0, VectorIdx::MAX_INDEX),
+        };
+        VClockAlloc {
+            alloc_ranges: RefCell::new(RangeMap::new(
+                len,
+                MemoryCellClocks::new(alloc_timestamp, alloc_index),
+            )),
+        }
+    }
+
+    // Find an index, if one exists where the value
+    // in `l` is greater than the value in `r`.
+    fn find_gt_index(l: &VClock, r: &VClock) -> Option<VectorIdx> {
+        log::trace!("Find index where not {:?} <= {:?}", l, r);
+        let l_slice = l.as_slice();
+        let r_slice = r.as_slice();
+        l_slice
+            .iter()
+            .zip(r_slice.iter())
+            .enumerate()
+            .find_map(|(idx, (&l, &r))| if l > r { Some(idx) } else { None })
+            .or_else(|| {
+                if l_slice.len() > r_slice.len() {
+                    // By invariant, if l_slice is longer
+                    // then one element must be larger.
+                    // This just validates that this is true
+                    // and reports earlier elements first.
+                    let l_remainder_slice = &l_slice[r_slice.len()..];
+                    let idx = l_remainder_slice
+                        .iter()
+                        .enumerate()
+                        .find_map(|(idx, &r)| if r == 0 { None } else { Some(idx) })
+                        .expect("Invalid VClock Invariant");
+                    Some(idx + r_slice.len())
+                } else {
+                    None
+                }
+            })
+            .map(VectorIdx::new)
+    }
+
+    /// Report a data-race found in the program.
+    /// This finds the two racing threads and the type
+    /// of data-race that occurred. This will also
+    /// return info about the memory location the data-race
+    /// occurred in.
+    #[cold]
+    #[inline(never)]
+    fn report_data_race<'tcx>(
+        global: &GlobalState,
+        thread_mgr: &ThreadManager<'_, '_>,
+        range: &MemoryCellClocks,
+        action: &str,
+        is_atomic: bool,
+        ptr_dbg: Pointer<AllocId>,
+    ) -> InterpResult<'tcx> {
+        let (current_index, current_clocks) = global.current_thread_state(thread_mgr);
+        let write_clock;
+        let (other_action, other_thread, _other_clock) = if range.write
+            > current_clocks.clock[range.write_index]
+        {
+            // Convert the write action into the vector clock it
+            // represents for diagnostic purposes.
+            write_clock = VClock::new_with_index(range.write_index, range.write);
+            (range.write_type.get_descriptor(), range.write_index, &write_clock)
+        } else if let Some(idx) = Self::find_gt_index(&range.read, &current_clocks.clock) {
+            ("Read", idx, &range.read)
+        } else if !is_atomic {
+            if let Some(atomic) = range.atomic() {
+                if let Some(idx) = Self::find_gt_index(&atomic.write_vector, &current_clocks.clock)
+                {
+                    ("Atomic Store", idx, &atomic.write_vector)
+                } else if let Some(idx) =
+                    Self::find_gt_index(&atomic.read_vector, &current_clocks.clock)
+                {
+                    ("Atomic Load", idx, &atomic.read_vector)
+                } else {
+                    unreachable!(
+                        "Failed to report data-race for non-atomic operation: no race found"
+                    )
+                }
+            } else {
+                unreachable!(
+                    "Failed to report data-race for non-atomic operation: no atomic component"
+                )
+            }
+        } else {
+            unreachable!("Failed to report data-race for atomic operation")
+        };
+
+        // Load elaborated thread information about the racing thread actions.
+        let current_thread_info = global.print_thread_metadata(thread_mgr, current_index);
+        let other_thread_info = global.print_thread_metadata(thread_mgr, other_thread);
+
+        // Throw the data-race detection.
+        throw_ub_format!(
+            "Data race detected between {} on {} and {} on {} at {:?}",
+            action,
+            current_thread_info,
+            other_action,
+            other_thread_info,
+            ptr_dbg,
+        )
+    }
+
+    /// Detect racing atomic read and writes (not data races)
+    /// on every byte of the current access range
+    pub(super) fn race_free_with_atomic(
+        &self,
+        range: AllocRange,
+        global: &GlobalState,
+        thread_mgr: &ThreadManager<'_, '_>,
+    ) -> bool {
+        if global.race_detecting() {
+            let (_, clocks) = global.current_thread_state(thread_mgr);
+            let alloc_ranges = self.alloc_ranges.borrow();
+            for (_, range) in alloc_ranges.iter(range.start, range.size) {
+                if !range.race_free_with_atomic(&clocks) {
+                    return false;
+                }
+            }
+        }
+        true
+    }
+
+    /// Detect data-races for an unsynchronized read operation, will not perform
+    /// data-race detection if `race_detecting()` is false, either due to no threads
+    /// being created or if it is temporarily disabled during a racy read or write
+    /// operation for which data-race detection is handled separately, for example
+    /// atomic read operations.
+    pub fn read<'tcx>(
+        &self,
+        alloc_id: AllocId,
+        range: AllocRange,
+        global: &GlobalState,
+        thread_mgr: &ThreadManager<'_, '_>,
+    ) -> InterpResult<'tcx> {
+        if global.race_detecting() {
+            let (index, clocks) = global.current_thread_state(thread_mgr);
+            let mut alloc_ranges = self.alloc_ranges.borrow_mut();
+            for (offset, range) in alloc_ranges.iter_mut(range.start, range.size) {
+                if let Err(DataRace) = range.read_race_detect(&clocks, index) {
+                    // Report data-race.
+                    return Self::report_data_race(
+                        global,
+                        thread_mgr,
+                        range,
+                        "Read",
+                        false,
+                        Pointer::new(alloc_id, offset),
+                    );
+                }
+            }
+            Ok(())
+        } else {
+            Ok(())
+        }
+    }
+
+    // Shared code for detecting data-races on unique access to a section of memory
+    fn unique_access<'tcx>(
+        &mut self,
+        alloc_id: AllocId,
+        range: AllocRange,
+        write_type: WriteType,
+        global: &mut GlobalState,
+        thread_mgr: &ThreadManager<'_, '_>,
+    ) -> InterpResult<'tcx> {
+        if global.race_detecting() {
+            let (index, clocks) = global.current_thread_state(thread_mgr);
+            for (offset, range) in self.alloc_ranges.get_mut().iter_mut(range.start, range.size) {
+                if let Err(DataRace) = range.write_race_detect(&clocks, index, write_type) {
+                    // Report data-race
+                    return Self::report_data_race(
+                        global,
+                        thread_mgr,
+                        range,
+                        write_type.get_descriptor(),
+                        false,
+                        Pointer::new(alloc_id, offset),
+                    );
+                }
+            }
+            Ok(())
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Detect data-races for an unsynchronized write operation, will not perform
+    /// data-race threads if `race_detecting()` is false, either due to no threads
+    /// being created or if it is temporarily disabled during a racy read or write
+    /// operation
+    pub fn write<'tcx>(
+        &mut self,
+        alloc_id: AllocId,
+        range: AllocRange,
+        global: &mut GlobalState,
+        thread_mgr: &ThreadManager<'_, '_>,
+    ) -> InterpResult<'tcx> {
+        self.unique_access(alloc_id, range, WriteType::Write, global, thread_mgr)
+    }
+
+    /// Detect data-races for an unsynchronized deallocate operation, will not perform
+    /// data-race threads if `race_detecting()` is false, either due to no threads
+    /// being created or if it is temporarily disabled during a racy read or write
+    /// operation
+    pub fn deallocate<'tcx>(
+        &mut self,
+        alloc_id: AllocId,
+        range: AllocRange,
+        global: &mut GlobalState,
+        thread_mgr: &ThreadManager<'_, '_>,
+    ) -> InterpResult<'tcx> {
+        self.unique_access(alloc_id, range, WriteType::Deallocate, global, thread_mgr)
+    }
+}
+
+impl<'mir, 'tcx: 'mir> EvalContextPrivExt<'mir, 'tcx> for MiriInterpCx<'mir, 'tcx> {}
+trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
+    /// Temporarily allow data-races to occur. This should only be used in
+    /// one of these cases:
+    /// - One of the appropriate `validate_atomic` functions will be called to
+    /// to treat a memory access as atomic.
+    /// - The memory being accessed should be treated as internal state, that
+    /// cannot be accessed by the interpreted program.
+    /// - Execution of the interpreted program execution has halted.
+    #[inline]
+    fn allow_data_races_ref<R>(&self, op: impl FnOnce(&MiriInterpCx<'mir, 'tcx>) -> R) -> R {
+        let this = self.eval_context_ref();
+        if let Some(data_race) = &this.machine.data_race {
+            let old = data_race.ongoing_action_data_race_free.replace(true);
+            assert!(!old, "cannot nest allow_data_races");
+        }
+        let result = op(this);
+        if let Some(data_race) = &this.machine.data_race {
+            data_race.ongoing_action_data_race_free.set(false);
+        }
+        result
+    }
+
+    /// Same as `allow_data_races_ref`, this temporarily disables any data-race detection and
+    /// so should only be used for atomic operations or internal state that the program cannot
+    /// access.
+    #[inline]
+    fn allow_data_races_mut<R>(
+        &mut self,
+        op: impl FnOnce(&mut MiriInterpCx<'mir, 'tcx>) -> R,
+    ) -> R {
+        let this = self.eval_context_mut();
+        if let Some(data_race) = &this.machine.data_race {
+            let old = data_race.ongoing_action_data_race_free.replace(true);
+            assert!(!old, "cannot nest allow_data_races");
+        }
+        let result = op(this);
+        if let Some(data_race) = &this.machine.data_race {
+            data_race.ongoing_action_data_race_free.set(false);
+        }
+        result
+    }
+
+    /// Checks that an atomic access is legal at the given place.
+    fn atomic_access_check(&self, place: &MPlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
+        let this = self.eval_context_ref();
+        // Check alignment requirements. Atomics must always be aligned to their size,
+        // even if the type they wrap would be less aligned (e.g. AtomicU64 on 32bit must
+        // be 8-aligned).
+        let align = Align::from_bytes(place.layout.size.bytes()).unwrap();
+        this.check_ptr_access_align(
+            place.ptr,
+            place.layout.size,
+            align,
+            CheckInAllocMsg::MemoryAccessTest,
+        )?;
+        // Ensure the allocation is mutable. Even failing (read-only) compare_exchange need mutable
+        // memory on many targets (i.e., they segfault if taht memory is mapped read-only), and
+        // atomic loads can be implemented via compare_exchange on some targets. There could
+        // possibly be some very specific exceptions to this, see
+        // <https://github.com/rust-lang/miri/pull/2464#discussion_r939636130> for details.
+        // We avoid `get_ptr_alloc` since we do *not* want to run the access hooks -- the actual
+        // access will happen later.
+        let (alloc_id, _offset, _prov) =
+            this.ptr_try_get_alloc_id(place.ptr).expect("there are no zero-sized atomic accesses");
+        if this.get_alloc_mutability(alloc_id)? == Mutability::Not {
+            // FIXME: make this prettier, once these messages have separate title/span/help messages.
+            throw_ub_format!(
+                "atomic operations cannot be performed on read-only memory\n\
+                many platforms require atomic read-modify-write instructions to be performed on writeable memory, even if the operation fails \
+                (and is hence nominally read-only)\n\
+                some platforms implement (some) atomic loads via compare-exchange, which means they do not work on read-only memory; \
+                it is possible that we could have an exception permitting this for specific kinds of loads\n\
+                please report an issue at <https://github.com/rust-lang/miri/issues> if this is a problem for you"
+            );
+        }
+        Ok(())
+    }
+
+    /// Update the data-race detector for an atomic read occurring at the
+    /// associated memory-place and on the current thread.
+    fn validate_atomic_load(
+        &self,
+        place: &MPlaceTy<'tcx, Provenance>,
+        atomic: AtomicReadOrd,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_ref();
+        this.validate_overlapping_atomic(place)?;
+        this.validate_atomic_op(
+            place,
+            atomic,
+            "Atomic Load",
+            move |memory, clocks, index, atomic| {
+                if atomic == AtomicReadOrd::Relaxed {
+                    memory.load_relaxed(&mut *clocks, index)
+                } else {
+                    memory.load_acquire(&mut *clocks, index)
+                }
+            },
+        )
+    }
+
+    /// Update the data-race detector for an atomic write occurring at the
+    /// associated memory-place and on the current thread.
+    fn validate_atomic_store(
+        &mut self,
+        place: &MPlaceTy<'tcx, Provenance>,
+        atomic: AtomicWriteOrd,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        this.validate_overlapping_atomic(place)?;
+        this.validate_atomic_op(
+            place,
+            atomic,
+            "Atomic Store",
+            move |memory, clocks, index, atomic| {
+                if atomic == AtomicWriteOrd::Relaxed {
+                    memory.store_relaxed(clocks, index)
+                } else {
+                    memory.store_release(clocks, index)
+                }
+            },
+        )
+    }
+
+    /// Update the data-race detector for an atomic read-modify-write occurring
+    /// at the associated memory place and on the current thread.
+    fn validate_atomic_rmw(
+        &mut self,
+        place: &MPlaceTy<'tcx, Provenance>,
+        atomic: AtomicRwOrd,
+    ) -> InterpResult<'tcx> {
+        use AtomicRwOrd::*;
+        let acquire = matches!(atomic, Acquire | AcqRel | SeqCst);
+        let release = matches!(atomic, Release | AcqRel | SeqCst);
+        let this = self.eval_context_mut();
+        this.validate_overlapping_atomic(place)?;
+        this.validate_atomic_op(place, atomic, "Atomic RMW", move |memory, clocks, index, _| {
+            if acquire {
+                memory.load_acquire(clocks, index)?;
+            } else {
+                memory.load_relaxed(clocks, index)?;
+            }
+            if release {
+                memory.rmw_release(clocks, index)
+            } else {
+                memory.rmw_relaxed(clocks, index)
+            }
+        })
+    }
+
+    /// Generic atomic operation implementation
+    fn validate_atomic_op<A: Debug + Copy>(
+        &self,
+        place: &MPlaceTy<'tcx, Provenance>,
+        atomic: A,
+        description: &str,
+        mut op: impl FnMut(
+            &mut MemoryCellClocks,
+            &mut ThreadClockSet,
+            VectorIdx,
+            A,
+        ) -> Result<(), DataRace>,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_ref();
+        if let Some(data_race) = &this.machine.data_race {
+            if data_race.race_detecting() {
+                let size = place.layout.size;
+                let (alloc_id, base_offset, _prov) = this.ptr_get_alloc_id(place.ptr)?;
+                // Load and log the atomic operation.
+                // Note that atomic loads are possible even from read-only allocations, so `get_alloc_extra_mut` is not an option.
+                let alloc_meta = this.get_alloc_extra(alloc_id)?.data_race.as_ref().unwrap();
+                log::trace!(
+                    "Atomic op({}) with ordering {:?} on {:?} (size={})",
+                    description,
+                    &atomic,
+                    place.ptr,
+                    size.bytes()
+                );
+
+                // Perform the atomic operation.
+                data_race.maybe_perform_sync_operation(
+                    &this.machine.threads,
+                    |index, mut clocks| {
+                        for (offset, range) in
+                            alloc_meta.alloc_ranges.borrow_mut().iter_mut(base_offset, size)
+                        {
+                            if let Err(DataRace) = op(range, &mut clocks, index, atomic) {
+                                mem::drop(clocks);
+                                return VClockAlloc::report_data_race(
+                                    data_race,
+                                    &this.machine.threads,
+                                    range,
+                                    description,
+                                    true,
+                                    Pointer::new(alloc_id, offset),
+                                )
+                                .map(|_| true);
+                            }
+                        }
+
+                        // This conservatively assumes all operations have release semantics
+                        Ok(true)
+                    },
+                )?;
+
+                // Log changes to atomic memory.
+                if log::log_enabled!(log::Level::Trace) {
+                    for (_offset, range) in alloc_meta.alloc_ranges.borrow().iter(base_offset, size)
+                    {
+                        log::trace!(
+                            "Updated atomic memory({:?}, size={}) to {:#?}",
+                            place.ptr,
+                            size.bytes(),
+                            range.atomic_ops
+                        );
+                    }
+                }
+            }
+        }
+        Ok(())
+    }
+}
+
+/// Extra metadata associated with a thread.
+#[derive(Debug, Clone, Default)]
+struct ThreadExtraState {
+    /// The current vector index in use by the
+    /// thread currently, this is set to None
+    /// after the vector index has been re-used
+    /// and hence the value will never need to be
+    /// read during data-race reporting.
+    vector_index: Option<VectorIdx>,
+
+    /// Thread termination vector clock, this
+    /// is set on thread termination and is used
+    /// for joining on threads since the vector_index
+    /// may be re-used when the join operation occurs.
+    termination_vector_clock: Option<VClock>,
+}
+
+/// Global data-race detection state, contains the currently
+/// executing thread as well as the vector-clocks associated
+/// with each of the threads.
+// FIXME: it is probably better to have one large RefCell, than to have so many small ones.
+#[derive(Debug, Clone)]
+pub struct GlobalState {
+    /// Set to true once the first additional
+    /// thread has launched, due to the dependency
+    /// between before and after a thread launch.
+    /// Any data-races must be recorded after this
+    /// so concurrent execution can ignore recording
+    /// any data-races.
+    multi_threaded: Cell<bool>,
+
+    /// A flag to mark we are currently performing
+    /// a data race free action (such as atomic access)
+    /// to supress the race detector
+    ongoing_action_data_race_free: Cell<bool>,
+
+    /// Mapping of a vector index to a known set of thread
+    /// clocks, this is not directly mapping from a thread id
+    /// since it may refer to multiple threads.
+    vector_clocks: RefCell<IndexVec<VectorIdx, ThreadClockSet>>,
+
+    /// Mapping of a given vector index to the current thread
+    /// that the execution is representing, this may change
+    /// if a vector index is re-assigned to a new thread.
+    vector_info: RefCell<IndexVec<VectorIdx, ThreadId>>,
+
+    /// The mapping of a given thread to associated thread metadata.
+    thread_info: RefCell<IndexVec<ThreadId, ThreadExtraState>>,
+
+    /// Potential vector indices that could be re-used on thread creation
+    /// values are inserted here on after the thread has terminated and
+    /// been joined with, and hence may potentially become free
+    /// for use as the index for a new thread.
+    /// Elements in this set may still require the vector index to
+    /// report data-races, and can only be re-used after all
+    /// active vector-clocks catch up with the threads timestamp.
+    reuse_candidates: RefCell<FxHashSet<VectorIdx>>,
+
+    /// This contains threads that have terminated, but not yet joined
+    /// and so cannot become re-use candidates until a join operation
+    /// occurs.
+    /// The associated vector index will be moved into re-use candidates
+    /// after the join operation occurs.
+    terminated_threads: RefCell<FxHashMap<ThreadId, VectorIdx>>,
+
+    /// The timestamp of last SC fence performed by each thread
+    last_sc_fence: RefCell<VClock>,
+
+    /// The timestamp of last SC write performed by each thread
+    last_sc_write: RefCell<VClock>,
+
+    /// Track when an outdated (weak memory) load happens.
+    pub track_outdated_loads: bool,
+}
+
+impl GlobalState {
+    /// Create a new global state, setup with just thread-id=0
+    /// advanced to timestamp = 1.
+    pub fn new(config: &MiriConfig) -> Self {
+        let mut global_state = GlobalState {
+            multi_threaded: Cell::new(false),
+            ongoing_action_data_race_free: Cell::new(false),
+            vector_clocks: RefCell::new(IndexVec::new()),
+            vector_info: RefCell::new(IndexVec::new()),
+            thread_info: RefCell::new(IndexVec::new()),
+            reuse_candidates: RefCell::new(FxHashSet::default()),
+            terminated_threads: RefCell::new(FxHashMap::default()),
+            last_sc_fence: RefCell::new(VClock::default()),
+            last_sc_write: RefCell::new(VClock::default()),
+            track_outdated_loads: config.track_outdated_loads,
+        };
+
+        // Setup the main-thread since it is not explicitly created:
+        // uses vector index and thread-id 0.
+        let index = global_state.vector_clocks.get_mut().push(ThreadClockSet::default());
+        global_state.vector_info.get_mut().push(ThreadId::new(0));
+        global_state
+            .thread_info
+            .get_mut()
+            .push(ThreadExtraState { vector_index: Some(index), termination_vector_clock: None });
+
+        global_state
+    }
+
+    // We perform data race detection when there are more than 1 active thread
+    // and we have not temporarily disabled race detection to perform something
+    // data race free
+    fn race_detecting(&self) -> bool {
+        self.multi_threaded.get() && !self.ongoing_action_data_race_free.get()
+    }
+
+    pub fn ongoing_action_data_race_free(&self) -> bool {
+        self.ongoing_action_data_race_free.get()
+    }
+
+    // Try to find vector index values that can potentially be re-used
+    // by a new thread instead of a new vector index being created.
+    fn find_vector_index_reuse_candidate(&self) -> Option<VectorIdx> {
+        let mut reuse = self.reuse_candidates.borrow_mut();
+        let vector_clocks = self.vector_clocks.borrow();
+        let vector_info = self.vector_info.borrow();
+        let terminated_threads = self.terminated_threads.borrow();
+        for &candidate in reuse.iter() {
+            let target_timestamp = vector_clocks[candidate].clock[candidate];
+            if vector_clocks.iter_enumerated().all(|(clock_idx, clock)| {
+                // The thread happens before the clock, and hence cannot report
+                // a data-race with this the candidate index.
+                let no_data_race = clock.clock[candidate] >= target_timestamp;
+
+                // The vector represents a thread that has terminated and hence cannot
+                // report a data-race with the candidate index.
+                let thread_id = vector_info[clock_idx];
+                let vector_terminated =
+                    reuse.contains(&clock_idx) || terminated_threads.contains_key(&thread_id);
+
+                // The vector index cannot report a race with the candidate index
+                // and hence allows the candidate index to be re-used.
+                no_data_race || vector_terminated
+            }) {
+                // All vector clocks for each vector index are equal to
+                // the target timestamp, and the thread is known to have
+                // terminated, therefore this vector clock index cannot
+                // report any more data-races.
+                assert!(reuse.remove(&candidate));
+                return Some(candidate);
+            }
+        }
+        None
+    }
+
+    // Hook for thread creation, enabled multi-threaded execution and marks
+    // the current thread timestamp as happening-before the current thread.
+    #[inline]
+    pub fn thread_created(&mut self, thread_mgr: &ThreadManager<'_, '_>, thread: ThreadId) {
+        let current_index = self.current_index(thread_mgr);
+
+        // Enable multi-threaded execution, there are now at least two threads
+        // so data-races are now possible.
+        self.multi_threaded.set(true);
+
+        // Load and setup the associated thread metadata
+        let mut thread_info = self.thread_info.borrow_mut();
+        thread_info.ensure_contains_elem(thread, Default::default);
+
+        // Assign a vector index for the thread, attempting to re-use an old
+        // vector index that can no longer report any data-races if possible.
+        let created_index = if let Some(reuse_index) = self.find_vector_index_reuse_candidate() {
+            // Now re-configure the re-use candidate, increment the clock
+            // for the new sync use of the vector.
+            let vector_clocks = self.vector_clocks.get_mut();
+            vector_clocks[reuse_index].increment_clock(reuse_index);
+
+            // Locate the old thread the vector was associated with and update
+            // it to represent the new thread instead.
+            let vector_info = self.vector_info.get_mut();
+            let old_thread = vector_info[reuse_index];
+            vector_info[reuse_index] = thread;
+
+            // Mark the thread the vector index was associated with as no longer
+            // representing a thread index.
+            thread_info[old_thread].vector_index = None;
+
+            reuse_index
+        } else {
+            // No vector re-use candidates available, instead create
+            // a new vector index.
+            let vector_info = self.vector_info.get_mut();
+            vector_info.push(thread)
+        };
+
+        log::trace!("Creating thread = {:?} with vector index = {:?}", thread, created_index);
+
+        // Mark the chosen vector index as in use by the thread.
+        thread_info[thread].vector_index = Some(created_index);
+
+        // Create a thread clock set if applicable.
+        let vector_clocks = self.vector_clocks.get_mut();
+        if created_index == vector_clocks.next_index() {
+            vector_clocks.push(ThreadClockSet::default());
+        }
+
+        // Now load the two clocks and configure the initial state.
+        let (current, created) = vector_clocks.pick2_mut(current_index, created_index);
+
+        // Join the created with current, since the current threads
+        // previous actions happen-before the created thread.
+        created.join_with(current);
+
+        // Advance both threads after the synchronized operation.
+        // Both operations are considered to have release semantics.
+        current.increment_clock(current_index);
+        created.increment_clock(created_index);
+    }
+
+    /// Hook on a thread join to update the implicit happens-before relation between the joined
+    /// thread (the joinee, the thread that someone waited on) and the current thread (the joiner,
+    /// the thread who was waiting).
+    #[inline]
+    pub fn thread_joined(
+        &mut self,
+        thread_mgr: &ThreadManager<'_, '_>,
+        joiner: ThreadId,
+        joinee: ThreadId,
+    ) {
+        let clocks_vec = self.vector_clocks.get_mut();
+        let thread_info = self.thread_info.get_mut();
+
+        // Load the vector clock of the current thread.
+        let current_index = thread_info[joiner]
+            .vector_index
+            .expect("Performed thread join on thread with no assigned vector");
+        let current = &mut clocks_vec[current_index];
+
+        // Load the associated vector clock for the terminated thread.
+        let join_clock = thread_info[joinee]
+            .termination_vector_clock
+            .as_ref()
+            .expect("Joined with thread but thread has not terminated");
+
+        // The join thread happens-before the current thread
+        // so update the current vector clock.
+        // Is not a release operation so the clock is not incremented.
+        current.clock.join(join_clock);
+
+        // Check the number of live threads, if the value is 1
+        // then test for potentially disabling multi-threaded execution.
+        if thread_mgr.get_live_thread_count() == 1 {
+            // May potentially be able to disable multi-threaded execution.
+            let current_clock = &clocks_vec[current_index];
+            if clocks_vec
+                .iter_enumerated()
+                .all(|(idx, clocks)| clocks.clock[idx] <= current_clock.clock[idx])
+            {
+                // All thread terminations happen-before the current clock
+                // therefore no data-races can be reported until a new thread
+                // is created, so disable multi-threaded execution.
+                self.multi_threaded.set(false);
+            }
+        }
+
+        // If the thread is marked as terminated but not joined
+        // then move the thread to the re-use set.
+        let termination = self.terminated_threads.get_mut();
+        if let Some(index) = termination.remove(&joinee) {
+            let reuse = self.reuse_candidates.get_mut();
+            reuse.insert(index);
+        }
+    }
+
+    /// On thread termination, the vector-clock may re-used
+    /// in the future once all remaining thread-clocks catch
+    /// up with the time index of the terminated thread.
+    /// This assigns thread termination with a unique index
+    /// which will be used to join the thread
+    /// This should be called strictly before any calls to
+    /// `thread_joined`.
+    #[inline]
+    pub fn thread_terminated(&mut self, thread_mgr: &ThreadManager<'_, '_>) {
+        let current_index = self.current_index(thread_mgr);
+
+        // Increment the clock to a unique termination timestamp.
+        let vector_clocks = self.vector_clocks.get_mut();
+        let current_clocks = &mut vector_clocks[current_index];
+        current_clocks.increment_clock(current_index);
+
+        // Load the current thread id for the executing vector.
+        let vector_info = self.vector_info.get_mut();
+        let current_thread = vector_info[current_index];
+
+        // Load the current thread metadata, and move to a terminated
+        // vector state. Setting up the vector clock all join operations
+        // will use.
+        let thread_info = self.thread_info.get_mut();
+        let current = &mut thread_info[current_thread];
+        current.termination_vector_clock = Some(current_clocks.clock.clone());
+
+        // Add this thread as a candidate for re-use after a thread join
+        // occurs.
+        let termination = self.terminated_threads.get_mut();
+        termination.insert(current_thread, current_index);
+    }
+
+    /// Attempt to perform a synchronized operation, this
+    /// will perform no operation if multi-threading is
+    /// not currently enabled.
+    /// Otherwise it will increment the clock for the current
+    /// vector before and after the operation for data-race
+    /// detection between any happens-before edges the
+    /// operation may create.
+    fn maybe_perform_sync_operation<'tcx>(
+        &self,
+        thread_mgr: &ThreadManager<'_, '_>,
+        op: impl FnOnce(VectorIdx, RefMut<'_, ThreadClockSet>) -> InterpResult<'tcx, bool>,
+    ) -> InterpResult<'tcx> {
+        if self.multi_threaded.get() {
+            let (index, clocks) = self.current_thread_state_mut(thread_mgr);
+            if op(index, clocks)? {
+                let (_, mut clocks) = self.current_thread_state_mut(thread_mgr);
+                clocks.increment_clock(index);
+            }
+        }
+        Ok(())
+    }
+
+    /// Internal utility to identify a thread stored internally
+    /// returns the id and the name for better diagnostics.
+    fn print_thread_metadata(
+        &self,
+        thread_mgr: &ThreadManager<'_, '_>,
+        vector: VectorIdx,
+    ) -> String {
+        let thread = self.vector_info.borrow()[vector];
+        let thread_name = thread_mgr.get_thread_name(thread);
+        format!("thread `{}`", String::from_utf8_lossy(thread_name))
+    }
+
+    /// Acquire a lock, express that the previous call of
+    /// `validate_lock_release` must happen before this.
+    /// As this is an acquire operation, the thread timestamp is not
+    /// incremented.
+    pub fn validate_lock_acquire(&self, lock: &VClock, thread: ThreadId) {
+        let (_, mut clocks) = self.load_thread_state_mut(thread);
+        clocks.clock.join(lock);
+    }
+
+    /// Release a lock handle, express that this happens-before
+    /// any subsequent calls to `validate_lock_acquire`.
+    /// For normal locks this should be equivalent to `validate_lock_release_shared`
+    /// since an acquire operation should have occurred before, however
+    /// for futex & condvar operations this is not the case and this
+    /// operation must be used.
+    pub fn validate_lock_release(&self, lock: &mut VClock, thread: ThreadId) {
+        let (index, mut clocks) = self.load_thread_state_mut(thread);
+        lock.clone_from(&clocks.clock);
+        clocks.increment_clock(index);
+    }
+
+    /// Release a lock handle, express that this happens-before
+    /// any subsequent calls to `validate_lock_acquire` as well
+    /// as any previous calls to this function after any
+    /// `validate_lock_release` calls.
+    /// For normal locks this should be equivalent to `validate_lock_release`.
+    /// This function only exists for joining over the set of concurrent readers
+    /// in a read-write lock and should not be used for anything else.
+    pub fn validate_lock_release_shared(&self, lock: &mut VClock, thread: ThreadId) {
+        let (index, mut clocks) = self.load_thread_state_mut(thread);
+        lock.join(&clocks.clock);
+        clocks.increment_clock(index);
+    }
+
+    /// Load the vector index used by the given thread as well as the set of vector clocks
+    /// used by the thread.
+    #[inline]
+    fn load_thread_state_mut(&self, thread: ThreadId) -> (VectorIdx, RefMut<'_, ThreadClockSet>) {
+        let index = self.thread_info.borrow()[thread]
+            .vector_index
+            .expect("Loading thread state for thread with no assigned vector");
+        let ref_vector = self.vector_clocks.borrow_mut();
+        let clocks = RefMut::map(ref_vector, |vec| &mut vec[index]);
+        (index, clocks)
+    }
+
+    /// Load the current vector clock in use and the current set of thread clocks
+    /// in use for the vector.
+    #[inline]
+    pub(super) fn current_thread_state(
+        &self,
+        thread_mgr: &ThreadManager<'_, '_>,
+    ) -> (VectorIdx, Ref<'_, ThreadClockSet>) {
+        let index = self.current_index(thread_mgr);
+        let ref_vector = self.vector_clocks.borrow();
+        let clocks = Ref::map(ref_vector, |vec| &vec[index]);
+        (index, clocks)
+    }
+
+    /// Load the current vector clock in use and the current set of thread clocks
+    /// in use for the vector mutably for modification.
+    #[inline]
+    pub(super) fn current_thread_state_mut(
+        &self,
+        thread_mgr: &ThreadManager<'_, '_>,
+    ) -> (VectorIdx, RefMut<'_, ThreadClockSet>) {
+        let index = self.current_index(thread_mgr);
+        let ref_vector = self.vector_clocks.borrow_mut();
+        let clocks = RefMut::map(ref_vector, |vec| &mut vec[index]);
+        (index, clocks)
+    }
+
+    /// Return the current thread, should be the same
+    /// as the data-race active thread.
+    #[inline]
+    fn current_index(&self, thread_mgr: &ThreadManager<'_, '_>) -> VectorIdx {
+        let active_thread_id = thread_mgr.get_active_thread_id();
+        self.thread_info.borrow()[active_thread_id]
+            .vector_index
+            .expect("active thread has no assigned vector")
+    }
+
+    // SC ATOMIC STORE rule in the paper.
+    pub(super) fn sc_write(&self, thread_mgr: &ThreadManager<'_, '_>) {
+        let (index, clocks) = self.current_thread_state(thread_mgr);
+        self.last_sc_write.borrow_mut().set_at_index(&clocks.clock, index);
+    }
+
+    // SC ATOMIC READ rule in the paper.
+    pub(super) fn sc_read(&self, thread_mgr: &ThreadManager<'_, '_>) {
+        let (.., mut clocks) = self.current_thread_state_mut(thread_mgr);
+        clocks.read_seqcst.join(&self.last_sc_fence.borrow());
+    }
+}
diff --git a/src/tools/miri/src/concurrency/mod.rs b/src/tools/miri/src/concurrency/mod.rs
new file mode 100644
index 0000000000000..61ef3d5640e00
--- /dev/null
+++ b/src/tools/miri/src/concurrency/mod.rs
@@ -0,0 +1,6 @@
+pub mod data_race;
+mod range_object_map;
+pub mod sync;
+pub mod thread;
+mod vector_clock;
+pub mod weak_memory;
diff --git a/src/tools/miri/src/concurrency/range_object_map.rs b/src/tools/miri/src/concurrency/range_object_map.rs
new file mode 100644
index 0000000000000..50d3f8c9b20ea
--- /dev/null
+++ b/src/tools/miri/src/concurrency/range_object_map.rs
@@ -0,0 +1,277 @@
+//! Implements a map from allocation ranges to data. This is somewhat similar to RangeMap, but the
+//! ranges and data are discrete and non-splittable -- they represent distinct "objects". An
+//! allocation in the map will always have the same range until explicitly removed
+
+use rustc_target::abi::Size;
+use std::ops::{Index, IndexMut, Range};
+
+use rustc_const_eval::interpret::AllocRange;
+
+#[derive(Clone, Debug)]
+struct Elem<T> {
+    /// The range covered by this element; never empty.
+    range: AllocRange,
+    /// The data stored for this element.
+    data: T,
+}
+
+/// Index of an allocation within the map
+type Position = usize;
+
+#[derive(Clone, Debug)]
+pub struct RangeObjectMap<T> {
+    v: Vec<Elem<T>>,
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub enum AccessType {
+    /// The access perfectly overlaps (same offset and range) with the exsiting allocation
+    PerfectlyOverlapping(Position),
+    /// The access does not touch any exising allocation
+    Empty(Position),
+    /// The access overlaps with one or more existing allocations
+    ImperfectlyOverlapping(Range<Position>),
+}
+
+impl<T> RangeObjectMap<T> {
+    pub fn new() -> Self {
+        Self { v: Vec::new() }
+    }
+
+    /// Finds the position of the allocation containing the given offset. If the offset is not
+    /// in an existing allocation, then returns Err containing the position
+    /// where such allocation should be inserted
+    fn find_offset(&self, offset: Size) -> Result<Position, Position> {
+        // We do a binary search.
+        let mut left = 0usize; // inclusive
+        let mut right = self.v.len(); // exclusive
+        loop {
+            if left == right {
+                // No element contains the given offset. But the
+                // position is where such element should be placed at.
+                return Err(left);
+            }
+            let candidate = left.checked_add(right).unwrap() / 2;
+            let elem = &self.v[candidate];
+            if offset < elem.range.start {
+                // We are too far right (offset is further left).
+                debug_assert!(candidate < right); // we are making progress
+                right = candidate;
+            } else if offset >= elem.range.end() {
+                // We are too far left (offset is further right).
+                debug_assert!(candidate >= left); // we are making progress
+                left = candidate + 1;
+            } else {
+                // This is it!
+                return Ok(candidate);
+            }
+        }
+    }
+
+    /// Determines whether a given access on `range` overlaps with
+    /// an existing allocation
+    pub fn access_type(&self, range: AllocRange) -> AccessType {
+        match self.find_offset(range.start) {
+            Ok(pos) => {
+                // Start of the range belongs to an existing object, now let's check the overlapping situation
+                let elem = &self.v[pos];
+                // FIXME: derive Eq for AllocRange in rustc
+                if elem.range.start == range.start && elem.range.size == range.size {
+                    // Happy case: perfectly overlapping access
+                    AccessType::PerfectlyOverlapping(pos)
+                } else {
+                    // FIXME: add a last() method to AllocRange that returns the last inclusive offset (end() is exclusive)
+                    let end_pos = match self.find_offset(range.end() - Size::from_bytes(1)) {
+                        // If the end lands in an existing object, add one to get the exclusive position
+                        Ok(inclusive_pos) => inclusive_pos + 1,
+                        Err(exclusive_pos) => exclusive_pos,
+                    };
+
+                    AccessType::ImperfectlyOverlapping(pos..end_pos)
+                }
+            }
+            Err(pos) => {
+                // Start of the range doesn't belong to an existing object
+                match self.find_offset(range.end() - Size::from_bytes(1)) {
+                    // Neither does the end
+                    Err(end_pos) =>
+                        if pos == end_pos {
+                            // There's nothing between the start and the end, so the range thing is empty
+                            AccessType::Empty(pos)
+                        } else {
+                            // Otherwise we have entirely covered an existing object
+                            AccessType::ImperfectlyOverlapping(pos..end_pos)
+                        },
+                    // Otherwise at least part of it overlaps with something else
+                    Ok(end_pos) => AccessType::ImperfectlyOverlapping(pos..end_pos + 1),
+                }
+            }
+        }
+    }
+
+    /// Inserts an object and its occupied range at given position
+    // The Position can be calculated from AllocRange, but the only user of AllocationMap
+    // always calls access_type before calling insert/index/index_mut, and we don't
+    // want to repeat the binary search on each time, so we ask the caller to supply Position
+    pub fn insert_at_pos(&mut self, pos: Position, range: AllocRange, data: T) {
+        self.v.insert(pos, Elem { range, data });
+        // If we aren't the first element, then our start must be greater than the preivous element's end
+        if pos > 0 {
+            assert!(self.v[pos - 1].range.end() <= range.start);
+        }
+        // If we aren't the last element, then our end must be smaller than next element's start
+        if pos < self.v.len() - 1 {
+            assert!(range.end() <= self.v[pos + 1].range.start);
+        }
+    }
+
+    pub fn remove_pos_range(&mut self, pos_range: Range<Position>) {
+        self.v.drain(pos_range);
+    }
+
+    pub fn remove_from_pos(&mut self, pos: Position) {
+        self.v.remove(pos);
+    }
+}
+
+impl<T> Index<Position> for RangeObjectMap<T> {
+    type Output = T;
+
+    fn index(&self, pos: Position) -> &Self::Output {
+        &self.v[pos].data
+    }
+}
+
+impl<T> IndexMut<Position> for RangeObjectMap<T> {
+    fn index_mut(&mut self, pos: Position) -> &mut Self::Output {
+        &mut self.v[pos].data
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use rustc_const_eval::interpret::alloc_range;
+
+    use super::*;
+
+    #[test]
+    fn empty_map() {
+        // FIXME: make Size::from_bytes const
+        let four = Size::from_bytes(4);
+        let map = RangeObjectMap::<()>::new();
+
+        // Correctly tells where we should insert the first element (at position 0)
+        assert_eq!(map.find_offset(Size::from_bytes(3)), Err(0));
+
+        // Correctly tells the access type along with the supposed position
+        assert_eq!(map.access_type(alloc_range(Size::ZERO, four)), AccessType::Empty(0));
+    }
+
+    #[test]
+    #[should_panic]
+    fn no_overlapping_inserts() {
+        let four = Size::from_bytes(4);
+
+        let mut map = RangeObjectMap::<&str>::new();
+
+        // |_|_|_|_|#|#|#|#|_|_|_|_|...
+        //  0 1 2 3 4 5 6 7 8 9 a b c d
+        map.insert_at_pos(0, alloc_range(four, four), "#");
+        // |_|_|_|_|#|#|#|#|_|_|_|_|...
+        //  0 ^ ^ ^ ^ 5 6 7 8 9 a b c d
+        map.insert_at_pos(0, alloc_range(Size::from_bytes(1), four), "@");
+    }
+
+    #[test]
+    fn boundaries() {
+        let four = Size::from_bytes(4);
+
+        let mut map = RangeObjectMap::<&str>::new();
+
+        // |#|#|#|#|_|_|...
+        //  0 1 2 3 4 5
+        map.insert_at_pos(0, alloc_range(Size::ZERO, four), "#");
+        // |#|#|#|#|_|_|...
+        //  0 1 2 3 ^ 5
+        assert_eq!(map.find_offset(four), Err(1));
+        // |#|#|#|#|_|_|_|_|_|...
+        //  0 1 2 3 ^ ^ ^ ^ 8
+        assert_eq!(map.access_type(alloc_range(four, four)), AccessType::Empty(1));
+
+        let eight = Size::from_bytes(8);
+        // |#|#|#|#|_|_|_|_|@|@|@|@|_|_|...
+        //  0 1 2 3 4 5 6 7 8 9 a b c d
+        map.insert_at_pos(1, alloc_range(eight, four), "@");
+        // |#|#|#|#|_|_|_|_|@|@|@|@|_|_|...
+        //  0 1 2 3 4 5 6 ^ 8 9 a b c d
+        assert_eq!(map.find_offset(Size::from_bytes(7)), Err(1));
+        // |#|#|#|#|_|_|_|_|@|@|@|@|_|_|...
+        //  0 1 2 3 ^ ^ ^ ^ 8 9 a b c d
+        assert_eq!(map.access_type(alloc_range(four, four)), AccessType::Empty(1));
+    }
+
+    #[test]
+    fn perfectly_overlapping() {
+        let four = Size::from_bytes(4);
+
+        let mut map = RangeObjectMap::<&str>::new();
+
+        // |#|#|#|#|_|_|...
+        //  0 1 2 3 4 5
+        map.insert_at_pos(0, alloc_range(Size::ZERO, four), "#");
+        // |#|#|#|#|_|_|...
+        //  ^ ^ ^ ^ 4 5
+        assert_eq!(map.find_offset(Size::ZERO), Ok(0));
+        assert_eq!(
+            map.access_type(alloc_range(Size::ZERO, four)),
+            AccessType::PerfectlyOverlapping(0)
+        );
+
+        // |#|#|#|#|@|@|@|@|_|...
+        //  0 1 2 3 4 5 6 7 8
+        map.insert_at_pos(1, alloc_range(four, four), "@");
+        // |#|#|#|#|@|@|@|@|_|...
+        //  0 1 2 3 ^ ^ ^ ^ 8
+        assert_eq!(map.find_offset(four), Ok(1));
+        assert_eq!(map.access_type(alloc_range(four, four)), AccessType::PerfectlyOverlapping(1));
+    }
+
+    #[test]
+    fn straddling() {
+        let four = Size::from_bytes(4);
+
+        let mut map = RangeObjectMap::<&str>::new();
+
+        // |_|_|_|_|#|#|#|#|_|_|_|_|...
+        //  0 1 2 3 4 5 6 7 8 9 a b c d
+        map.insert_at_pos(0, alloc_range(four, four), "#");
+        // |_|_|_|_|#|#|#|#|_|_|_|_|...
+        //  0 1 ^ ^ ^ ^ 6 7 8 9 a b c d
+        assert_eq!(
+            map.access_type(alloc_range(Size::from_bytes(2), four)),
+            AccessType::ImperfectlyOverlapping(0..1)
+        );
+        // |_|_|_|_|#|#|#|#|_|_|_|_|...
+        //  0 1 2 3 4 5 ^ ^ ^ ^ a b c d
+        assert_eq!(
+            map.access_type(alloc_range(Size::from_bytes(6), four)),
+            AccessType::ImperfectlyOverlapping(0..1)
+        );
+        // |_|_|_|_|#|#|#|#|_|_|_|_|...
+        //  0 1 ^ ^ ^ ^ ^ ^ ^ ^ a b c d
+        assert_eq!(
+            map.access_type(alloc_range(Size::from_bytes(2), Size::from_bytes(8))),
+            AccessType::ImperfectlyOverlapping(0..1)
+        );
+
+        // |_|_|_|_|#|#|#|#|_|_|@|@|_|_|...
+        //  0 1 2 3 4 5 6 7 8 9 a b c d
+        map.insert_at_pos(1, alloc_range(Size::from_bytes(10), Size::from_bytes(2)), "@");
+        // |_|_|_|_|#|#|#|#|_|_|@|@|_|_|...
+        //  0 1 2 3 4 5 ^ ^ ^ ^ ^ ^ ^ ^
+        assert_eq!(
+            map.access_type(alloc_range(Size::from_bytes(6), Size::from_bytes(8))),
+            AccessType::ImperfectlyOverlapping(0..2)
+        );
+    }
+}
diff --git a/src/tools/miri/src/concurrency/sync.rs b/src/tools/miri/src/concurrency/sync.rs
new file mode 100644
index 0000000000000..464f452ca7696
--- /dev/null
+++ b/src/tools/miri/src/concurrency/sync.rs
@@ -0,0 +1,584 @@
+use std::collections::{hash_map::Entry, VecDeque};
+use std::num::NonZeroU32;
+use std::ops::Not;
+
+use log::trace;
+
+use rustc_data_structures::fx::FxHashMap;
+use rustc_index::vec::{Idx, IndexVec};
+
+use super::vector_clock::VClock;
+use crate::*;
+
+/// We cannot use the `newtype_index!` macro because we have to use 0 as a
+/// sentinel value meaning that the identifier is not assigned. This is because
+/// the pthreads static initializers initialize memory with zeros (see the
+/// `src/shims/sync.rs` file).
+macro_rules! declare_id {
+    ($name: ident) => {
+        /// 0 is used to indicate that the id was not yet assigned and,
+        /// therefore, is not a valid identifier.
+        #[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
+        pub struct $name(NonZeroU32);
+
+        impl $name {
+            // Panics if `id == 0`.
+            pub fn from_u32(id: u32) -> Self {
+                Self(NonZeroU32::new(id).unwrap())
+            }
+        }
+
+        impl Idx for $name {
+            fn new(idx: usize) -> Self {
+                // We use 0 as a sentinel value (see the comment above) and,
+                // therefore, need to shift by one when converting from an index
+                // into a vector.
+                let shifted_idx = u32::try_from(idx).unwrap().checked_add(1).unwrap();
+                $name(NonZeroU32::new(shifted_idx).unwrap())
+            }
+            fn index(self) -> usize {
+                // See the comment in `Self::new`.
+                // (This cannot underflow because self is NonZeroU32.)
+                usize::try_from(self.0.get() - 1).unwrap()
+            }
+        }
+
+        impl $name {
+            pub fn to_u32_scalar(&self) -> Scalar<Provenance> {
+                Scalar::from_u32(self.0.get())
+            }
+        }
+    };
+}
+
+declare_id!(MutexId);
+
+/// The mutex state.
+#[derive(Default, Debug)]
+struct Mutex {
+    /// The thread that currently owns the lock.
+    owner: Option<ThreadId>,
+    /// How many times the mutex was locked by the owner.
+    lock_count: usize,
+    /// The queue of threads waiting for this mutex.
+    queue: VecDeque<ThreadId>,
+    /// Data race handle, this tracks the happens-before
+    /// relationship between each mutex access. It is
+    /// released to during unlock and acquired from during
+    /// locking, and therefore stores the clock of the last
+    /// thread to release this mutex.
+    data_race: VClock,
+}
+
+declare_id!(RwLockId);
+
+/// The read-write lock state.
+#[derive(Default, Debug)]
+struct RwLock {
+    /// The writer thread that currently owns the lock.
+    writer: Option<ThreadId>,
+    /// The readers that currently own the lock and how many times they acquired
+    /// the lock.
+    readers: FxHashMap<ThreadId, usize>,
+    /// The queue of writer threads waiting for this lock.
+    writer_queue: VecDeque<ThreadId>,
+    /// The queue of reader threads waiting for this lock.
+    reader_queue: VecDeque<ThreadId>,
+    /// Data race handle for writers, tracks the happens-before
+    /// ordering between each write access to a rwlock and is updated
+    /// after a sequence of concurrent readers to track the happens-
+    /// before ordering between the set of previous readers and
+    /// the current writer.
+    /// Contains the clock of the last thread to release a writer
+    /// lock or the joined clock of the set of last threads to release
+    /// shared reader locks.
+    data_race: VClock,
+    /// Data race handle for readers, this is temporary storage
+    /// for the combined happens-before ordering for between all
+    /// concurrent readers and the next writer, and the value
+    /// is stored to the main data_race variable once all
+    /// readers are finished.
+    /// Has to be stored separately since reader lock acquires
+    /// must load the clock of the last write and must not
+    /// add happens-before orderings between shared reader
+    /// locks.
+    data_race_reader: VClock,
+}
+
+declare_id!(CondvarId);
+
+/// A thread waiting on a conditional variable.
+#[derive(Debug)]
+struct CondvarWaiter {
+    /// The thread that is waiting on this variable.
+    thread: ThreadId,
+    /// The mutex on which the thread is waiting.
+    mutex: MutexId,
+}
+
+/// The conditional variable state.
+#[derive(Default, Debug)]
+struct Condvar {
+    waiters: VecDeque<CondvarWaiter>,
+    /// Tracks the happens-before relationship
+    /// between a cond-var signal and a cond-var
+    /// wait during a non-suprious signal event.
+    /// Contains the clock of the last thread to
+    /// perform a futex-signal.
+    data_race: VClock,
+}
+
+/// The futex state.
+#[derive(Default, Debug)]
+struct Futex {
+    waiters: VecDeque<FutexWaiter>,
+    /// Tracks the happens-before relationship
+    /// between a futex-wake and a futex-wait
+    /// during a non-spurious wake event.
+    /// Contains the clock of the last thread to
+    /// perform a futex-wake.
+    data_race: VClock,
+}
+
+/// A thread waiting on a futex.
+#[derive(Debug)]
+struct FutexWaiter {
+    /// The thread that is waiting on this futex.
+    thread: ThreadId,
+    /// The bitset used by FUTEX_*_BITSET, or u32::MAX for other operations.
+    bitset: u32,
+}
+
+/// The state of all synchronization variables.
+#[derive(Default, Debug)]
+pub(crate) struct SynchronizationState {
+    mutexes: IndexVec<MutexId, Mutex>,
+    rwlocks: IndexVec<RwLockId, RwLock>,
+    condvars: IndexVec<CondvarId, Condvar>,
+    futexes: FxHashMap<u64, Futex>,
+}
+
+// Private extension trait for local helper methods
+impl<'mir, 'tcx: 'mir> EvalContextExtPriv<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    /// Take a reader out of the queue waiting for the lock.
+    /// Returns `true` if some thread got the rwlock.
+    #[inline]
+    fn rwlock_dequeue_and_lock_reader(&mut self, id: RwLockId) -> bool {
+        let this = self.eval_context_mut();
+        if let Some(reader) = this.machine.threads.sync.rwlocks[id].reader_queue.pop_front() {
+            this.unblock_thread(reader);
+            this.rwlock_reader_lock(id, reader);
+            true
+        } else {
+            false
+        }
+    }
+
+    /// Take the writer out of the queue waiting for the lock.
+    /// Returns `true` if some thread got the rwlock.
+    #[inline]
+    fn rwlock_dequeue_and_lock_writer(&mut self, id: RwLockId) -> bool {
+        let this = self.eval_context_mut();
+        if let Some(writer) = this.machine.threads.sync.rwlocks[id].writer_queue.pop_front() {
+            this.unblock_thread(writer);
+            this.rwlock_writer_lock(id, writer);
+            true
+        } else {
+            false
+        }
+    }
+
+    /// Take a thread out of the queue waiting for the mutex, and lock
+    /// the mutex for it. Returns `true` if some thread has the mutex now.
+    #[inline]
+    fn mutex_dequeue_and_lock(&mut self, id: MutexId) -> bool {
+        let this = self.eval_context_mut();
+        if let Some(thread) = this.machine.threads.sync.mutexes[id].queue.pop_front() {
+            this.unblock_thread(thread);
+            this.mutex_lock(id, thread);
+            true
+        } else {
+            false
+        }
+    }
+}
+
+// Public interface to synchronization primitives. Please note that in most
+// cases, the function calls are infallible and it is the client's (shim
+// implementation's) responsibility to detect and deal with erroneous
+// situations.
+impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    #[inline]
+    /// Create state for a new mutex.
+    fn mutex_create(&mut self) -> MutexId {
+        let this = self.eval_context_mut();
+        this.machine.threads.sync.mutexes.push(Default::default())
+    }
+
+    #[inline]
+    /// Provides the closure with the next MutexId. Creates that mutex if the closure returns None,
+    /// otherwise returns the value from the closure
+    fn mutex_get_or_create<F>(&mut self, existing: F) -> InterpResult<'tcx, MutexId>
+    where
+        F: FnOnce(&mut MiriInterpCx<'mir, 'tcx>, MutexId) -> InterpResult<'tcx, Option<MutexId>>,
+    {
+        let this = self.eval_context_mut();
+        let next_index = this.machine.threads.sync.mutexes.next_index();
+        if let Some(old) = existing(this, next_index)? {
+            Ok(old)
+        } else {
+            let new_index = this.machine.threads.sync.mutexes.push(Default::default());
+            assert_eq!(next_index, new_index);
+            Ok(new_index)
+        }
+    }
+
+    #[inline]
+    /// Get the id of the thread that currently owns this lock.
+    fn mutex_get_owner(&mut self, id: MutexId) -> ThreadId {
+        let this = self.eval_context_ref();
+        this.machine.threads.sync.mutexes[id].owner.unwrap()
+    }
+
+    #[inline]
+    /// Check if locked.
+    fn mutex_is_locked(&self, id: MutexId) -> bool {
+        let this = self.eval_context_ref();
+        this.machine.threads.sync.mutexes[id].owner.is_some()
+    }
+
+    /// Lock by setting the mutex owner and increasing the lock count.
+    fn mutex_lock(&mut self, id: MutexId, thread: ThreadId) {
+        let this = self.eval_context_mut();
+        let mutex = &mut this.machine.threads.sync.mutexes[id];
+        if let Some(current_owner) = mutex.owner {
+            assert_eq!(thread, current_owner, "mutex already locked by another thread");
+            assert!(
+                mutex.lock_count > 0,
+                "invariant violation: lock_count == 0 iff the thread is unlocked"
+            );
+        } else {
+            mutex.owner = Some(thread);
+        }
+        mutex.lock_count = mutex.lock_count.checked_add(1).unwrap();
+        if let Some(data_race) = &this.machine.data_race {
+            data_race.validate_lock_acquire(&mutex.data_race, thread);
+        }
+    }
+
+    /// Try unlocking by decreasing the lock count and returning the old lock
+    /// count. If the lock count reaches 0, release the lock and potentially
+    /// give to a new owner. If the lock was not locked by `expected_owner`,
+    /// return `None`.
+    fn mutex_unlock(&mut self, id: MutexId, expected_owner: ThreadId) -> Option<usize> {
+        let this = self.eval_context_mut();
+        let mutex = &mut this.machine.threads.sync.mutexes[id];
+        if let Some(current_owner) = mutex.owner {
+            // Mutex is locked.
+            if current_owner != expected_owner {
+                // Only the owner can unlock the mutex.
+                return None;
+            }
+            let old_lock_count = mutex.lock_count;
+            mutex.lock_count = old_lock_count
+                .checked_sub(1)
+                .expect("invariant violation: lock_count == 0 iff the thread is unlocked");
+            if mutex.lock_count == 0 {
+                mutex.owner = None;
+                // The mutex is completely unlocked. Try transfering ownership
+                // to another thread.
+                if let Some(data_race) = &this.machine.data_race {
+                    data_race.validate_lock_release(&mut mutex.data_race, current_owner);
+                }
+                this.mutex_dequeue_and_lock(id);
+            }
+            Some(old_lock_count)
+        } else {
+            // Mutex is not locked.
+            None
+        }
+    }
+
+    #[inline]
+    /// Put the thread into the queue waiting for the mutex.
+    fn mutex_enqueue_and_block(&mut self, id: MutexId, thread: ThreadId) {
+        let this = self.eval_context_mut();
+        assert!(this.mutex_is_locked(id), "queing on unlocked mutex");
+        this.machine.threads.sync.mutexes[id].queue.push_back(thread);
+        this.block_thread(thread);
+    }
+
+    #[inline]
+    /// Create state for a new read write lock.
+    fn rwlock_create(&mut self) -> RwLockId {
+        let this = self.eval_context_mut();
+        this.machine.threads.sync.rwlocks.push(Default::default())
+    }
+
+    #[inline]
+    /// Provides the closure with the next RwLockId. Creates that RwLock if the closure returns None,
+    /// otherwise returns the value from the closure
+    fn rwlock_get_or_create<F>(&mut self, existing: F) -> InterpResult<'tcx, RwLockId>
+    where
+        F: FnOnce(&mut MiriInterpCx<'mir, 'tcx>, RwLockId) -> InterpResult<'tcx, Option<RwLockId>>,
+    {
+        let this = self.eval_context_mut();
+        let next_index = this.machine.threads.sync.rwlocks.next_index();
+        if let Some(old) = existing(this, next_index)? {
+            Ok(old)
+        } else {
+            let new_index = this.machine.threads.sync.rwlocks.push(Default::default());
+            assert_eq!(next_index, new_index);
+            Ok(new_index)
+        }
+    }
+
+    #[inline]
+    /// Check if locked.
+    fn rwlock_is_locked(&self, id: RwLockId) -> bool {
+        let this = self.eval_context_ref();
+        let rwlock = &this.machine.threads.sync.rwlocks[id];
+        trace!(
+            "rwlock_is_locked: {:?} writer is {:?} and there are {} reader threads (some of which could hold multiple read locks)",
+            id,
+            rwlock.writer,
+            rwlock.readers.len(),
+        );
+        rwlock.writer.is_some() || rwlock.readers.is_empty().not()
+    }
+
+    #[inline]
+    /// Check if write locked.
+    fn rwlock_is_write_locked(&self, id: RwLockId) -> bool {
+        let this = self.eval_context_ref();
+        let rwlock = &this.machine.threads.sync.rwlocks[id];
+        trace!("rwlock_is_write_locked: {:?} writer is {:?}", id, rwlock.writer);
+        rwlock.writer.is_some()
+    }
+
+    /// Read-lock the lock by adding the `reader` the list of threads that own
+    /// this lock.
+    fn rwlock_reader_lock(&mut self, id: RwLockId, reader: ThreadId) {
+        let this = self.eval_context_mut();
+        assert!(!this.rwlock_is_write_locked(id), "the lock is write locked");
+        trace!("rwlock_reader_lock: {:?} now also held (one more time) by {:?}", id, reader);
+        let rwlock = &mut this.machine.threads.sync.rwlocks[id];
+        let count = rwlock.readers.entry(reader).or_insert(0);
+        *count = count.checked_add(1).expect("the reader counter overflowed");
+        if let Some(data_race) = &this.machine.data_race {
+            data_race.validate_lock_acquire(&rwlock.data_race, reader);
+        }
+    }
+
+    /// Try read-unlock the lock for `reader` and potentially give the lock to a new owner.
+    /// Returns `true` if succeeded, `false` if this `reader` did not hold the lock.
+    fn rwlock_reader_unlock(&mut self, id: RwLockId, reader: ThreadId) -> bool {
+        let this = self.eval_context_mut();
+        let rwlock = &mut this.machine.threads.sync.rwlocks[id];
+        match rwlock.readers.entry(reader) {
+            Entry::Occupied(mut entry) => {
+                let count = entry.get_mut();
+                assert!(*count > 0, "rwlock locked with count == 0");
+                *count -= 1;
+                if *count == 0 {
+                    trace!("rwlock_reader_unlock: {:?} no longer held by {:?}", id, reader);
+                    entry.remove();
+                } else {
+                    trace!("rwlock_reader_unlock: {:?} held one less time by {:?}", id, reader);
+                }
+            }
+            Entry::Vacant(_) => return false, // we did not even own this lock
+        }
+        if let Some(data_race) = &this.machine.data_race {
+            data_race.validate_lock_release_shared(&mut rwlock.data_race_reader, reader);
+        }
+
+        // The thread was a reader. If the lock is not held any more, give it to a writer.
+        if this.rwlock_is_locked(id).not() {
+            // All the readers are finished, so set the writer data-race handle to the value
+            //  of the union of all reader data race handles, since the set of readers
+            //  happen-before the writers
+            let rwlock = &mut this.machine.threads.sync.rwlocks[id];
+            rwlock.data_race.clone_from(&rwlock.data_race_reader);
+            this.rwlock_dequeue_and_lock_writer(id);
+        }
+        true
+    }
+
+    #[inline]
+    /// Put the reader in the queue waiting for the lock and block it.
+    fn rwlock_enqueue_and_block_reader(&mut self, id: RwLockId, reader: ThreadId) {
+        let this = self.eval_context_mut();
+        assert!(this.rwlock_is_write_locked(id), "read-queueing on not write locked rwlock");
+        this.machine.threads.sync.rwlocks[id].reader_queue.push_back(reader);
+        this.block_thread(reader);
+    }
+
+    #[inline]
+    /// Lock by setting the writer that owns the lock.
+    fn rwlock_writer_lock(&mut self, id: RwLockId, writer: ThreadId) {
+        let this = self.eval_context_mut();
+        assert!(!this.rwlock_is_locked(id), "the rwlock is already locked");
+        trace!("rwlock_writer_lock: {:?} now held by {:?}", id, writer);
+        let rwlock = &mut this.machine.threads.sync.rwlocks[id];
+        rwlock.writer = Some(writer);
+        if let Some(data_race) = &this.machine.data_race {
+            data_race.validate_lock_acquire(&rwlock.data_race, writer);
+        }
+    }
+
+    #[inline]
+    /// Try to unlock by removing the writer.
+    fn rwlock_writer_unlock(&mut self, id: RwLockId, expected_writer: ThreadId) -> bool {
+        let this = self.eval_context_mut();
+        let rwlock = &mut this.machine.threads.sync.rwlocks[id];
+        if let Some(current_writer) = rwlock.writer {
+            if current_writer != expected_writer {
+                // Only the owner can unlock the rwlock.
+                return false;
+            }
+            rwlock.writer = None;
+            trace!("rwlock_writer_unlock: {:?} unlocked by {:?}", id, expected_writer);
+            // Release memory to both reader and writer vector clocks
+            //  since this writer happens-before both the union of readers once they are finished
+            //  and the next writer
+            if let Some(data_race) = &this.machine.data_race {
+                data_race.validate_lock_release(&mut rwlock.data_race, current_writer);
+                data_race.validate_lock_release(&mut rwlock.data_race_reader, current_writer);
+            }
+            // The thread was a writer.
+            //
+            // We are prioritizing writers here against the readers. As a
+            // result, not only readers can starve writers, but also writers can
+            // starve readers.
+            if this.rwlock_dequeue_and_lock_writer(id) {
+                // Someone got the write lock, nice.
+            } else {
+                // Give the lock to all readers.
+                while this.rwlock_dequeue_and_lock_reader(id) {
+                    // Rinse and repeat.
+                }
+            }
+            true
+        } else {
+            false
+        }
+    }
+
+    #[inline]
+    /// Put the writer in the queue waiting for the lock.
+    fn rwlock_enqueue_and_block_writer(&mut self, id: RwLockId, writer: ThreadId) {
+        let this = self.eval_context_mut();
+        assert!(this.rwlock_is_locked(id), "write-queueing on unlocked rwlock");
+        this.machine.threads.sync.rwlocks[id].writer_queue.push_back(writer);
+        this.block_thread(writer);
+    }
+
+    #[inline]
+    /// Create state for a new conditional variable.
+    fn condvar_create(&mut self) -> CondvarId {
+        let this = self.eval_context_mut();
+        this.machine.threads.sync.condvars.push(Default::default())
+    }
+
+    #[inline]
+    /// Provides the closure with the next CondvarId. Creates that Condvar if the closure returns None,
+    /// otherwise returns the value from the closure
+    fn condvar_get_or_create<F>(&mut self, existing: F) -> InterpResult<'tcx, CondvarId>
+    where
+        F: FnOnce(
+            &mut MiriInterpCx<'mir, 'tcx>,
+            CondvarId,
+        ) -> InterpResult<'tcx, Option<CondvarId>>,
+    {
+        let this = self.eval_context_mut();
+        let next_index = this.machine.threads.sync.condvars.next_index();
+        if let Some(old) = existing(this, next_index)? {
+            Ok(old)
+        } else {
+            let new_index = this.machine.threads.sync.condvars.push(Default::default());
+            assert_eq!(next_index, new_index);
+            Ok(new_index)
+        }
+    }
+
+    #[inline]
+    /// Is the conditional variable awaited?
+    fn condvar_is_awaited(&mut self, id: CondvarId) -> bool {
+        let this = self.eval_context_mut();
+        !this.machine.threads.sync.condvars[id].waiters.is_empty()
+    }
+
+    /// Mark that the thread is waiting on the conditional variable.
+    fn condvar_wait(&mut self, id: CondvarId, thread: ThreadId, mutex: MutexId) {
+        let this = self.eval_context_mut();
+        let waiters = &mut this.machine.threads.sync.condvars[id].waiters;
+        assert!(waiters.iter().all(|waiter| waiter.thread != thread), "thread is already waiting");
+        waiters.push_back(CondvarWaiter { thread, mutex });
+    }
+
+    /// Wake up some thread (if there is any) sleeping on the conditional
+    /// variable.
+    fn condvar_signal(&mut self, id: CondvarId) -> Option<(ThreadId, MutexId)> {
+        let this = self.eval_context_mut();
+        let current_thread = this.get_active_thread();
+        let condvar = &mut this.machine.threads.sync.condvars[id];
+        let data_race = &this.machine.data_race;
+
+        // Each condvar signal happens-before the end of the condvar wake
+        if let Some(data_race) = data_race {
+            data_race.validate_lock_release(&mut condvar.data_race, current_thread);
+        }
+        condvar.waiters.pop_front().map(|waiter| {
+            if let Some(data_race) = data_race {
+                data_race.validate_lock_acquire(&condvar.data_race, waiter.thread);
+            }
+            (waiter.thread, waiter.mutex)
+        })
+    }
+
+    #[inline]
+    /// Remove the thread from the queue of threads waiting on this conditional variable.
+    fn condvar_remove_waiter(&mut self, id: CondvarId, thread: ThreadId) {
+        let this = self.eval_context_mut();
+        this.machine.threads.sync.condvars[id].waiters.retain(|waiter| waiter.thread != thread);
+    }
+
+    fn futex_wait(&mut self, addr: u64, thread: ThreadId, bitset: u32) {
+        let this = self.eval_context_mut();
+        let futex = &mut this.machine.threads.sync.futexes.entry(addr).or_default();
+        let waiters = &mut futex.waiters;
+        assert!(waiters.iter().all(|waiter| waiter.thread != thread), "thread is already waiting");
+        waiters.push_back(FutexWaiter { thread, bitset });
+    }
+
+    fn futex_wake(&mut self, addr: u64, bitset: u32) -> Option<ThreadId> {
+        let this = self.eval_context_mut();
+        let current_thread = this.get_active_thread();
+        let futex = &mut this.machine.threads.sync.futexes.get_mut(&addr)?;
+        let data_race = &this.machine.data_race;
+
+        // Each futex-wake happens-before the end of the futex wait
+        if let Some(data_race) = data_race {
+            data_race.validate_lock_release(&mut futex.data_race, current_thread);
+        }
+
+        // Wake up the first thread in the queue that matches any of the bits in the bitset.
+        futex.waiters.iter().position(|w| w.bitset & bitset != 0).map(|i| {
+            let waiter = futex.waiters.remove(i).unwrap();
+            if let Some(data_race) = data_race {
+                data_race.validate_lock_acquire(&futex.data_race, waiter.thread);
+            }
+            waiter.thread
+        })
+    }
+
+    fn futex_remove_waiter(&mut self, addr: u64, thread: ThreadId) {
+        let this = self.eval_context_mut();
+        if let Some(futex) = this.machine.threads.sync.futexes.get_mut(&addr) {
+            futex.waiters.retain(|waiter| waiter.thread != thread);
+        }
+    }
+}
diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs
new file mode 100644
index 0000000000000..f1a3d19fb4cbc
--- /dev/null
+++ b/src/tools/miri/src/concurrency/thread.rs
@@ -0,0 +1,933 @@
+//! Implements threads.
+
+use std::cell::RefCell;
+use std::collections::hash_map::Entry;
+use std::num::TryFromIntError;
+use std::time::{Duration, SystemTime};
+
+use log::trace;
+
+use rustc_data_structures::fx::FxHashMap;
+use rustc_hir::def_id::DefId;
+use rustc_index::vec::{Idx, IndexVec};
+use rustc_middle::mir::Mutability;
+use rustc_middle::ty::layout::TyAndLayout;
+use rustc_target::spec::abi::Abi;
+
+use crate::concurrency::data_race;
+use crate::concurrency::sync::SynchronizationState;
+use crate::*;
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum SchedulingAction {
+    /// Execute step on the active thread.
+    ExecuteStep,
+    /// Execute a timeout callback.
+    ExecuteTimeoutCallback,
+    /// Execute destructors of the active thread.
+    ExecuteDtors,
+    /// Stop the program.
+    Stop,
+}
+
+/// Timeout callbacks can be created by synchronization primitives to tell the
+/// scheduler that they should be called once some period of time passes.
+type TimeoutCallback<'mir, 'tcx> = Box<
+    dyn FnOnce(&mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>) -> InterpResult<'tcx> + 'tcx,
+>;
+
+/// A thread identifier.
+#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
+pub struct ThreadId(u32);
+
+/// The main thread. When it terminates, the whole application terminates.
+const MAIN_THREAD: ThreadId = ThreadId(0);
+
+impl ThreadId {
+    pub fn to_u32(self) -> u32 {
+        self.0
+    }
+}
+
+impl Idx for ThreadId {
+    fn new(idx: usize) -> Self {
+        ThreadId(u32::try_from(idx).unwrap())
+    }
+
+    fn index(self) -> usize {
+        usize::try_from(self.0).unwrap()
+    }
+}
+
+impl TryFrom<u64> for ThreadId {
+    type Error = TryFromIntError;
+    fn try_from(id: u64) -> Result<Self, Self::Error> {
+        u32::try_from(id).map(Self)
+    }
+}
+
+impl From<u32> for ThreadId {
+    fn from(id: u32) -> Self {
+        Self(id)
+    }
+}
+
+impl From<ThreadId> for u64 {
+    fn from(t: ThreadId) -> Self {
+        t.0.into()
+    }
+}
+
+/// The state of a thread.
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum ThreadState {
+    /// The thread is enabled and can be executed.
+    Enabled,
+    /// The thread tried to join the specified thread and is blocked until that
+    /// thread terminates.
+    BlockedOnJoin(ThreadId),
+    /// The thread is blocked on some synchronization primitive. It is the
+    /// responsibility of the synchronization primitives to track threads that
+    /// are blocked by them.
+    BlockedOnSync,
+    /// The thread has terminated its execution. We do not delete terminated
+    /// threads (FIXME: why?).
+    Terminated,
+}
+
+/// The join status of a thread.
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+enum ThreadJoinStatus {
+    /// The thread can be joined.
+    Joinable,
+    /// A thread is detached if its join handle was destroyed and no other
+    /// thread can join it.
+    Detached,
+    /// The thread was already joined by some thread and cannot be joined again.
+    Joined,
+}
+
+/// A thread.
+pub struct Thread<'mir, 'tcx> {
+    state: ThreadState,
+
+    /// Name of the thread.
+    thread_name: Option<Vec<u8>>,
+
+    /// The virtual call stack.
+    stack: Vec<Frame<'mir, 'tcx, Provenance, FrameData<'tcx>>>,
+
+    /// The join status.
+    join_status: ThreadJoinStatus,
+
+    /// The temporary used for storing the argument of
+    /// the call to `miri_start_panic` (the panic payload) when unwinding.
+    /// This is pointer-sized, and matches the `Payload` type in `src/libpanic_unwind/miri.rs`.
+    pub(crate) panic_payload: Option<Scalar<Provenance>>,
+
+    /// Last OS error location in memory. It is a 32-bit integer.
+    pub(crate) last_error: Option<MPlaceTy<'tcx, Provenance>>,
+}
+
+impl<'mir, 'tcx> Thread<'mir, 'tcx> {
+    /// Check if the thread is done executing (no more stack frames). If yes,
+    /// change the state to terminated and return `true`.
+    fn check_terminated(&mut self) -> bool {
+        if self.state == ThreadState::Enabled {
+            if self.stack.is_empty() {
+                self.state = ThreadState::Terminated;
+                return true;
+            }
+        }
+        false
+    }
+
+    /// Get the name of the current thread, or `<unnamed>` if it was not set.
+    fn thread_name(&self) -> &[u8] {
+        if let Some(ref thread_name) = self.thread_name { thread_name } else { b"<unnamed>" }
+    }
+}
+
+impl<'mir, 'tcx> std::fmt::Debug for Thread<'mir, 'tcx> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "{}({:?}, {:?})",
+            String::from_utf8_lossy(self.thread_name()),
+            self.state,
+            self.join_status
+        )
+    }
+}
+
+impl<'mir, 'tcx> Default for Thread<'mir, 'tcx> {
+    fn default() -> Self {
+        Self {
+            state: ThreadState::Enabled,
+            thread_name: None,
+            stack: Vec::new(),
+            join_status: ThreadJoinStatus::Joinable,
+            panic_payload: None,
+            last_error: None,
+        }
+    }
+}
+
+impl<'mir, 'tcx> Thread<'mir, 'tcx> {
+    fn new(name: &str) -> Self {
+        let mut thread = Thread::default();
+        thread.thread_name = Some(Vec::from(name.as_bytes()));
+        thread
+    }
+}
+
+/// A specific moment in time.
+#[derive(Debug)]
+pub enum Time {
+    Monotonic(Instant),
+    RealTime(SystemTime),
+}
+
+impl Time {
+    /// How long do we have to wait from now until the specified time?
+    fn get_wait_time(&self, clock: &Clock) -> Duration {
+        match self {
+            Time::Monotonic(instant) => instant.duration_since(clock.now()),
+            Time::RealTime(time) =>
+                time.duration_since(SystemTime::now()).unwrap_or(Duration::new(0, 0)),
+        }
+    }
+}
+
+/// Callbacks are used to implement timeouts. For example, waiting on a
+/// conditional variable with a timeout creates a callback that is called after
+/// the specified time and unblocks the thread. If another thread signals on the
+/// conditional variable, the signal handler deletes the callback.
+struct TimeoutCallbackInfo<'mir, 'tcx> {
+    /// The callback should be called no earlier than this time.
+    call_time: Time,
+    /// The called function.
+    callback: TimeoutCallback<'mir, 'tcx>,
+}
+
+impl<'mir, 'tcx> std::fmt::Debug for TimeoutCallbackInfo<'mir, 'tcx> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "TimeoutCallback({:?})", self.call_time)
+    }
+}
+
+/// A set of threads.
+#[derive(Debug)]
+pub struct ThreadManager<'mir, 'tcx> {
+    /// Identifier of the currently active thread.
+    active_thread: ThreadId,
+    /// Threads used in the program.
+    ///
+    /// Note that this vector also contains terminated threads.
+    threads: IndexVec<ThreadId, Thread<'mir, 'tcx>>,
+    /// This field is pub(crate) because the synchronization primitives
+    /// (`crate::sync`) need a way to access it.
+    pub(crate) sync: SynchronizationState,
+    /// A mapping from a thread-local static to an allocation id of a thread
+    /// specific allocation.
+    thread_local_alloc_ids: RefCell<FxHashMap<(DefId, ThreadId), Pointer<Provenance>>>,
+    /// A flag that indicates that we should change the active thread.
+    yield_active_thread: bool,
+    /// Callbacks that are called once the specified time passes.
+    timeout_callbacks: FxHashMap<ThreadId, TimeoutCallbackInfo<'mir, 'tcx>>,
+}
+
+impl<'mir, 'tcx> Default for ThreadManager<'mir, 'tcx> {
+    fn default() -> Self {
+        let mut threads = IndexVec::new();
+        // Create the main thread and add it to the list of threads.
+        threads.push(Thread::new("main"));
+        Self {
+            active_thread: ThreadId::new(0),
+            threads,
+            sync: SynchronizationState::default(),
+            thread_local_alloc_ids: Default::default(),
+            yield_active_thread: false,
+            timeout_callbacks: FxHashMap::default(),
+        }
+    }
+}
+
+impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
+    pub(crate) fn init(ecx: &mut MiriInterpCx<'mir, 'tcx>) {
+        if ecx.tcx.sess.target.os.as_ref() != "windows" {
+            // The main thread can *not* be joined on except on windows.
+            ecx.machine.threads.threads[ThreadId::new(0)].join_status = ThreadJoinStatus::Detached;
+        }
+    }
+
+    /// Check if we have an allocation for the given thread local static for the
+    /// active thread.
+    fn get_thread_local_alloc_id(&self, def_id: DefId) -> Option<Pointer<Provenance>> {
+        self.thread_local_alloc_ids.borrow().get(&(def_id, self.active_thread)).cloned()
+    }
+
+    /// Set the pointer for the allocation of the given thread local
+    /// static for the active thread.
+    ///
+    /// Panics if a thread local is initialized twice for the same thread.
+    fn set_thread_local_alloc(&self, def_id: DefId, ptr: Pointer<Provenance>) {
+        self.thread_local_alloc_ids
+            .borrow_mut()
+            .try_insert((def_id, self.active_thread), ptr)
+            .unwrap();
+    }
+
+    /// Borrow the stack of the active thread.
+    pub fn active_thread_stack(&self) -> &[Frame<'mir, 'tcx, Provenance, FrameData<'tcx>>] {
+        &self.threads[self.active_thread].stack
+    }
+
+    /// Mutably borrow the stack of the active thread.
+    fn active_thread_stack_mut(
+        &mut self,
+    ) -> &mut Vec<Frame<'mir, 'tcx, Provenance, FrameData<'tcx>>> {
+        &mut self.threads[self.active_thread].stack
+    }
+
+    pub fn iter(&self) -> impl Iterator<Item = &Thread<'mir, 'tcx>> {
+        self.threads.iter()
+    }
+
+    pub fn all_stacks(
+        &self,
+    ) -> impl Iterator<Item = &[Frame<'mir, 'tcx, Provenance, FrameData<'tcx>>]> {
+        self.threads.iter().map(|t| &t.stack[..])
+    }
+
+    /// Create a new thread and returns its id.
+    fn create_thread(&mut self) -> ThreadId {
+        let new_thread_id = ThreadId::new(self.threads.len());
+        self.threads.push(Default::default());
+        new_thread_id
+    }
+
+    /// Set an active thread and return the id of the thread that was active before.
+    fn set_active_thread_id(&mut self, id: ThreadId) -> ThreadId {
+        let active_thread_id = self.active_thread;
+        self.active_thread = id;
+        assert!(self.active_thread.index() < self.threads.len());
+        active_thread_id
+    }
+
+    /// Get the id of the currently active thread.
+    pub fn get_active_thread_id(&self) -> ThreadId {
+        self.active_thread
+    }
+
+    /// Get the total number of threads that were ever spawn by this program.
+    pub fn get_total_thread_count(&self) -> usize {
+        self.threads.len()
+    }
+
+    /// Get the total of threads that are currently live, i.e., not yet terminated.
+    /// (They might be blocked.)
+    pub fn get_live_thread_count(&self) -> usize {
+        self.threads.iter().filter(|t| !matches!(t.state, ThreadState::Terminated)).count()
+    }
+
+    /// Has the given thread terminated?
+    fn has_terminated(&self, thread_id: ThreadId) -> bool {
+        self.threads[thread_id].state == ThreadState::Terminated
+    }
+
+    /// Have all threads terminated?
+    fn have_all_terminated(&self) -> bool {
+        self.threads.iter().all(|thread| thread.state == ThreadState::Terminated)
+    }
+
+    /// Enable the thread for execution. The thread must be terminated.
+    fn enable_thread(&mut self, thread_id: ThreadId) {
+        assert!(self.has_terminated(thread_id));
+        self.threads[thread_id].state = ThreadState::Enabled;
+    }
+
+    /// Get a mutable borrow of the currently active thread.
+    fn active_thread_mut(&mut self) -> &mut Thread<'mir, 'tcx> {
+        &mut self.threads[self.active_thread]
+    }
+
+    /// Get a shared borrow of the currently active thread.
+    fn active_thread_ref(&self) -> &Thread<'mir, 'tcx> {
+        &self.threads[self.active_thread]
+    }
+
+    /// Mark the thread as detached, which means that no other thread will try
+    /// to join it and the thread is responsible for cleaning up.
+    ///
+    /// `allow_terminated_joined` allows detaching joined threads that have already terminated.
+    /// This matches Windows's behavior for `CloseHandle`.
+    ///
+    /// See <https://docs.microsoft.com/en-us/windows/win32/procthread/thread-handles-and-identifiers>:
+    /// > The handle is valid until closed, even after the thread it represents has been terminated.
+    fn detach_thread(&mut self, id: ThreadId, allow_terminated_joined: bool) -> InterpResult<'tcx> {
+        trace!("detaching {:?}", id);
+
+        let is_ub = if allow_terminated_joined && self.threads[id].state == ThreadState::Terminated
+        {
+            // "Detached" in particular means "not yet joined". Redundant detaching is still UB.
+            self.threads[id].join_status == ThreadJoinStatus::Detached
+        } else {
+            self.threads[id].join_status != ThreadJoinStatus::Joinable
+        };
+        if is_ub {
+            throw_ub_format!("trying to detach thread that was already detached or joined");
+        }
+
+        self.threads[id].join_status = ThreadJoinStatus::Detached;
+        Ok(())
+    }
+
+    /// Mark that the active thread tries to join the thread with `joined_thread_id`.
+    fn join_thread(
+        &mut self,
+        joined_thread_id: ThreadId,
+        data_race: Option<&mut data_race::GlobalState>,
+    ) -> InterpResult<'tcx> {
+        if self.threads[joined_thread_id].join_status == ThreadJoinStatus::Detached {
+            throw_ub_format!("trying to join a detached thread");
+        }
+
+        // Mark the joined thread as being joined so that we detect if other
+        // threads try to join it.
+        self.threads[joined_thread_id].join_status = ThreadJoinStatus::Joined;
+        if self.threads[joined_thread_id].state != ThreadState::Terminated {
+            // The joined thread is still running, we need to wait for it.
+            self.active_thread_mut().state = ThreadState::BlockedOnJoin(joined_thread_id);
+            trace!(
+                "{:?} blocked on {:?} when trying to join",
+                self.active_thread,
+                joined_thread_id
+            );
+        } else {
+            // The thread has already terminated - mark join happens-before
+            if let Some(data_race) = data_race {
+                data_race.thread_joined(self, self.active_thread, joined_thread_id);
+            }
+        }
+        Ok(())
+    }
+
+    /// Mark that the active thread tries to exclusively join the thread with `joined_thread_id`.
+    /// If the thread is already joined by another thread, it will throw UB
+    fn join_thread_exclusive(
+        &mut self,
+        joined_thread_id: ThreadId,
+        data_race: Option<&mut data_race::GlobalState>,
+    ) -> InterpResult<'tcx> {
+        if self.threads[joined_thread_id].join_status == ThreadJoinStatus::Joined {
+            throw_ub_format!("trying to join an already joined thread");
+        }
+
+        if joined_thread_id == self.active_thread {
+            throw_ub_format!("trying to join itself");
+        }
+
+        assert!(
+            self.threads
+                .iter()
+                .all(|thread| thread.state != ThreadState::BlockedOnJoin(joined_thread_id)),
+            "this thread already has threads waiting for its termination"
+        );
+
+        self.join_thread(joined_thread_id, data_race)
+    }
+
+    /// Set the name of the given thread.
+    pub fn set_thread_name(&mut self, thread: ThreadId, new_thread_name: Vec<u8>) {
+        self.threads[thread].thread_name = Some(new_thread_name);
+    }
+
+    /// Get the name of the given thread.
+    pub fn get_thread_name(&self, thread: ThreadId) -> &[u8] {
+        self.threads[thread].thread_name()
+    }
+
+    /// Put the thread into the blocked state.
+    fn block_thread(&mut self, thread: ThreadId) {
+        let state = &mut self.threads[thread].state;
+        assert_eq!(*state, ThreadState::Enabled);
+        *state = ThreadState::BlockedOnSync;
+    }
+
+    /// Put the blocked thread into the enabled state.
+    fn unblock_thread(&mut self, thread: ThreadId) {
+        let state = &mut self.threads[thread].state;
+        assert_eq!(*state, ThreadState::BlockedOnSync);
+        *state = ThreadState::Enabled;
+    }
+
+    /// Change the active thread to some enabled thread.
+    fn yield_active_thread(&mut self) {
+        // We do not yield immediately, as swapping out the current stack while executing a MIR statement
+        // could lead to all sorts of confusion.
+        // We should only switch stacks between steps.
+        self.yield_active_thread = true;
+    }
+
+    /// Register the given `callback` to be called once the `call_time` passes.
+    ///
+    /// The callback will be called with `thread` being the active thread, and
+    /// the callback may not change the active thread.
+    fn register_timeout_callback(
+        &mut self,
+        thread: ThreadId,
+        call_time: Time,
+        callback: TimeoutCallback<'mir, 'tcx>,
+    ) {
+        self.timeout_callbacks
+            .try_insert(thread, TimeoutCallbackInfo { call_time, callback })
+            .unwrap();
+    }
+
+    /// Unregister the callback for the `thread`.
+    fn unregister_timeout_callback_if_exists(&mut self, thread: ThreadId) {
+        self.timeout_callbacks.remove(&thread);
+    }
+
+    /// Get a callback that is ready to be called.
+    fn get_ready_callback(
+        &mut self,
+        clock: &Clock,
+    ) -> Option<(ThreadId, TimeoutCallback<'mir, 'tcx>)> {
+        // We iterate over all threads in the order of their indices because
+        // this allows us to have a deterministic scheduler.
+        for thread in self.threads.indices() {
+            match self.timeout_callbacks.entry(thread) {
+                Entry::Occupied(entry) =>
+                    if entry.get().call_time.get_wait_time(clock) == Duration::new(0, 0) {
+                        return Some((thread, entry.remove().callback));
+                    },
+                Entry::Vacant(_) => {}
+            }
+        }
+        None
+    }
+
+    /// Wakes up threads joining on the active one and deallocates thread-local statics.
+    /// The `AllocId` that can now be freed are returned.
+    fn thread_terminated(
+        &mut self,
+        mut data_race: Option<&mut data_race::GlobalState>,
+    ) -> Vec<Pointer<Provenance>> {
+        let mut free_tls_statics = Vec::new();
+        {
+            let mut thread_local_statics = self.thread_local_alloc_ids.borrow_mut();
+            thread_local_statics.retain(|&(_def_id, thread), &mut alloc_id| {
+                if thread != self.active_thread {
+                    // Keep this static around.
+                    return true;
+                }
+                // Delete this static from the map and from memory.
+                // We cannot free directly here as we cannot use `?` in this context.
+                free_tls_statics.push(alloc_id);
+                false
+            });
+        }
+        // Set the thread into a terminated state in the data-race detector.
+        if let Some(ref mut data_race) = data_race {
+            data_race.thread_terminated(self);
+        }
+        // Check if we need to unblock any threads.
+        let mut joined_threads = vec![]; // store which threads joined, we'll need it
+        for (i, thread) in self.threads.iter_enumerated_mut() {
+            if thread.state == ThreadState::BlockedOnJoin(self.active_thread) {
+                // The thread has terminated, mark happens-before edge to joining thread
+                if data_race.is_some() {
+                    joined_threads.push(i);
+                }
+                trace!("unblocking {:?} because {:?} terminated", i, self.active_thread);
+                thread.state = ThreadState::Enabled;
+            }
+        }
+        for &i in &joined_threads {
+            data_race.as_mut().unwrap().thread_joined(self, i, self.active_thread);
+        }
+        free_tls_statics
+    }
+
+    /// Decide which action to take next and on which thread.
+    ///
+    /// The currently implemented scheduling policy is the one that is commonly
+    /// used in stateless model checkers such as Loom: run the active thread as
+    /// long as we can and switch only when we have to (the active thread was
+    /// blocked, terminated, or has explicitly asked to be preempted).
+    fn schedule(&mut self, clock: &Clock) -> InterpResult<'tcx, SchedulingAction> {
+        // Check whether the thread has **just** terminated (`check_terminated`
+        // checks whether the thread has popped all its stack and if yes, sets
+        // the thread state to terminated).
+        if self.threads[self.active_thread].check_terminated() {
+            return Ok(SchedulingAction::ExecuteDtors);
+        }
+        // If we get here again and the thread is *still* terminated, there are no more dtors to run.
+        if self.threads[MAIN_THREAD].state == ThreadState::Terminated {
+            // The main thread terminated; stop the program.
+            // We do *not* run TLS dtors of remaining threads, which seems to match rustc behavior.
+            return Ok(SchedulingAction::Stop);
+        }
+        // This thread and the program can keep going.
+        if self.threads[self.active_thread].state == ThreadState::Enabled
+            && !self.yield_active_thread
+        {
+            // The currently active thread is still enabled, just continue with it.
+            return Ok(SchedulingAction::ExecuteStep);
+        }
+        // The active thread yielded. Let's see if there are any timeouts to take care of. We do
+        // this *before* running any other thread, to ensure that timeouts "in the past" fire before
+        // any other thread can take an action. This ensures that for `pthread_cond_timedwait`, "an
+        // error is returned if [...] the absolute time specified by abstime has already been passed
+        // at the time of the call".
+        // <https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_cond_timedwait.html>
+        let potential_sleep_time =
+            self.timeout_callbacks.values().map(|info| info.call_time.get_wait_time(clock)).min();
+        if potential_sleep_time == Some(Duration::new(0, 0)) {
+            return Ok(SchedulingAction::ExecuteTimeoutCallback);
+        }
+        // No callbacks scheduled, pick a regular thread to execute.
+        // The active thread blocked or yielded. So we go search for another enabled thread.
+        // Crucially, we start searching at the current active thread ID, rather than at 0, since we
+        // want to avoid always scheduling threads 0 and 1 without ever making progress in thread 2.
+        //
+        // `skip(N)` means we start iterating at thread N, so we skip 1 more to start just *after*
+        // the active thread. Then after that we look at `take(N)`, i.e., the threads *before* the
+        // active thread.
+        let threads = self
+            .threads
+            .iter_enumerated()
+            .skip(self.active_thread.index() + 1)
+            .chain(self.threads.iter_enumerated().take(self.active_thread.index()));
+        for (id, thread) in threads {
+            debug_assert_ne!(self.active_thread, id);
+            if thread.state == ThreadState::Enabled {
+                self.active_thread = id;
+                break;
+            }
+        }
+        self.yield_active_thread = false;
+        if self.threads[self.active_thread].state == ThreadState::Enabled {
+            return Ok(SchedulingAction::ExecuteStep);
+        }
+        // We have not found a thread to execute.
+        if self.threads.iter().all(|thread| thread.state == ThreadState::Terminated) {
+            unreachable!("all threads terminated without the main thread terminating?!");
+        } else if let Some(sleep_time) = potential_sleep_time {
+            // All threads are currently blocked, but we have unexecuted
+            // timeout_callbacks, which may unblock some of the threads. Hence,
+            // sleep until the first callback.
+
+            clock.sleep(sleep_time);
+            Ok(SchedulingAction::ExecuteTimeoutCallback)
+        } else {
+            throw_machine_stop!(TerminationInfo::Deadlock);
+        }
+    }
+}
+
+// Public interface to thread management.
+impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    /// Get a thread-specific allocation id for the given thread-local static.
+    /// If needed, allocate a new one.
+    fn get_or_create_thread_local_alloc(
+        &mut self,
+        def_id: DefId,
+    ) -> InterpResult<'tcx, Pointer<Provenance>> {
+        let this = self.eval_context_mut();
+        let tcx = this.tcx;
+        if let Some(old_alloc) = this.machine.threads.get_thread_local_alloc_id(def_id) {
+            // We already have a thread-specific allocation id for this
+            // thread-local static.
+            Ok(old_alloc)
+        } else {
+            // We need to allocate a thread-specific allocation id for this
+            // thread-local static.
+            // First, we compute the initial value for this static.
+            if tcx.is_foreign_item(def_id) {
+                throw_unsup_format!("foreign thread-local statics are not supported");
+            }
+            let allocation = tcx.eval_static_initializer(def_id)?;
+            let mut allocation = allocation.inner().clone();
+            // This allocation will be deallocated when the thread dies, so it is not in read-only memory.
+            allocation.mutability = Mutability::Mut;
+            // Create a fresh allocation with this content.
+            let new_alloc = this.allocate_raw_ptr(allocation, MiriMemoryKind::Tls.into())?;
+            this.machine.threads.set_thread_local_alloc(def_id, new_alloc);
+            Ok(new_alloc)
+        }
+    }
+
+    #[inline]
+    fn create_thread(&mut self) -> ThreadId {
+        let this = self.eval_context_mut();
+        let id = this.machine.threads.create_thread();
+        if let Some(data_race) = &mut this.machine.data_race {
+            data_race.thread_created(&this.machine.threads, id);
+        }
+        id
+    }
+
+    #[inline]
+    fn start_thread(
+        &mut self,
+        thread: Option<MPlaceTy<'tcx, Provenance>>,
+        start_routine: Pointer<Option<Provenance>>,
+        start_abi: Abi,
+        func_arg: ImmTy<'tcx, Provenance>,
+        ret_layout: TyAndLayout<'tcx>,
+    ) -> InterpResult<'tcx, ThreadId> {
+        let this = self.eval_context_mut();
+
+        // Create the new thread
+        let new_thread_id = this.create_thread();
+
+        // Write the current thread-id, switch to the next thread later
+        // to treat this write operation as occuring on the current thread.
+        if let Some(thread_info_place) = thread {
+            this.write_scalar(
+                Scalar::from_uint(new_thread_id.to_u32(), thread_info_place.layout.size),
+                &thread_info_place.into(),
+            )?;
+        }
+
+        // Finally switch to new thread so that we can push the first stackframe.
+        // After this all accesses will be treated as occuring in the new thread.
+        let old_thread_id = this.set_active_thread(new_thread_id);
+
+        // Perform the function pointer load in the new thread frame.
+        let instance = this.get_ptr_fn(start_routine)?.as_instance()?;
+
+        // Note: the returned value is currently ignored (see the FIXME in
+        // pthread_join in shims/unix/thread.rs) because the Rust standard library does not use
+        // it.
+        let ret_place = this.allocate(ret_layout, MiriMemoryKind::Machine.into())?;
+
+        this.call_function(
+            instance,
+            start_abi,
+            &[*func_arg],
+            Some(&ret_place.into()),
+            StackPopCleanup::Root { cleanup: true },
+        )?;
+
+        // Restore the old active thread frame.
+        this.set_active_thread(old_thread_id);
+
+        Ok(new_thread_id)
+    }
+
+    #[inline]
+    fn detach_thread(
+        &mut self,
+        thread_id: ThreadId,
+        allow_terminated_joined: bool,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        this.machine.threads.detach_thread(thread_id, allow_terminated_joined)
+    }
+
+    #[inline]
+    fn join_thread(&mut self, joined_thread_id: ThreadId) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        this.machine.threads.join_thread(joined_thread_id, this.machine.data_race.as_mut())?;
+        Ok(())
+    }
+
+    #[inline]
+    fn join_thread_exclusive(&mut self, joined_thread_id: ThreadId) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        this.machine
+            .threads
+            .join_thread_exclusive(joined_thread_id, this.machine.data_race.as_mut())?;
+        Ok(())
+    }
+
+    #[inline]
+    fn set_active_thread(&mut self, thread_id: ThreadId) -> ThreadId {
+        let this = self.eval_context_mut();
+        this.machine.threads.set_active_thread_id(thread_id)
+    }
+
+    #[inline]
+    fn get_active_thread(&self) -> ThreadId {
+        let this = self.eval_context_ref();
+        this.machine.threads.get_active_thread_id()
+    }
+
+    #[inline]
+    fn active_thread_mut(&mut self) -> &mut Thread<'mir, 'tcx> {
+        let this = self.eval_context_mut();
+        this.machine.threads.active_thread_mut()
+    }
+
+    #[inline]
+    fn active_thread_ref(&self) -> &Thread<'mir, 'tcx> {
+        let this = self.eval_context_ref();
+        this.machine.threads.active_thread_ref()
+    }
+
+    #[inline]
+    fn get_total_thread_count(&self) -> usize {
+        let this = self.eval_context_ref();
+        this.machine.threads.get_total_thread_count()
+    }
+
+    #[inline]
+    fn has_terminated(&self, thread_id: ThreadId) -> bool {
+        let this = self.eval_context_ref();
+        this.machine.threads.has_terminated(thread_id)
+    }
+
+    #[inline]
+    fn have_all_terminated(&self) -> bool {
+        let this = self.eval_context_ref();
+        this.machine.threads.have_all_terminated()
+    }
+
+    #[inline]
+    fn enable_thread(&mut self, thread_id: ThreadId) {
+        let this = self.eval_context_mut();
+        this.machine.threads.enable_thread(thread_id);
+    }
+
+    #[inline]
+    fn active_thread_stack(&self) -> &[Frame<'mir, 'tcx, Provenance, FrameData<'tcx>>] {
+        let this = self.eval_context_ref();
+        this.machine.threads.active_thread_stack()
+    }
+
+    #[inline]
+    fn active_thread_stack_mut(
+        &mut self,
+    ) -> &mut Vec<Frame<'mir, 'tcx, Provenance, FrameData<'tcx>>> {
+        let this = self.eval_context_mut();
+        this.machine.threads.active_thread_stack_mut()
+    }
+
+    #[inline]
+    fn set_thread_name(&mut self, thread: ThreadId, new_thread_name: Vec<u8>) {
+        let this = self.eval_context_mut();
+        this.machine.threads.set_thread_name(thread, new_thread_name);
+    }
+
+    #[inline]
+    fn set_thread_name_wide(&mut self, thread: ThreadId, new_thread_name: &[u16]) {
+        let this = self.eval_context_mut();
+
+        // The Windows `GetThreadDescription` shim to get the thread name isn't implemented, so being lossy is okay.
+        // This is only read by diagnostics, which already use `from_utf8_lossy`.
+        this.machine
+            .threads
+            .set_thread_name(thread, String::from_utf16_lossy(new_thread_name).into_bytes());
+    }
+
+    #[inline]
+    fn get_thread_name<'c>(&'c self, thread: ThreadId) -> &'c [u8]
+    where
+        'mir: 'c,
+    {
+        let this = self.eval_context_ref();
+        this.machine.threads.get_thread_name(thread)
+    }
+
+    #[inline]
+    fn block_thread(&mut self, thread: ThreadId) {
+        let this = self.eval_context_mut();
+        this.machine.threads.block_thread(thread);
+    }
+
+    #[inline]
+    fn unblock_thread(&mut self, thread: ThreadId) {
+        let this = self.eval_context_mut();
+        this.machine.threads.unblock_thread(thread);
+    }
+
+    #[inline]
+    fn yield_active_thread(&mut self) {
+        let this = self.eval_context_mut();
+        this.machine.threads.yield_active_thread();
+    }
+
+    #[inline]
+    fn maybe_preempt_active_thread(&mut self) {
+        use rand::Rng as _;
+
+        let this = self.eval_context_mut();
+        if this.machine.rng.get_mut().gen_bool(this.machine.preemption_rate) {
+            this.yield_active_thread();
+        }
+    }
+
+    #[inline]
+    fn register_timeout_callback(
+        &mut self,
+        thread: ThreadId,
+        call_time: Time,
+        callback: TimeoutCallback<'mir, 'tcx>,
+    ) {
+        let this = self.eval_context_mut();
+        if !this.machine.communicate() && matches!(call_time, Time::RealTime(..)) {
+            panic!("cannot have `RealTime` callback with isolation enabled!")
+        }
+        this.machine.threads.register_timeout_callback(thread, call_time, callback);
+    }
+
+    #[inline]
+    fn unregister_timeout_callback_if_exists(&mut self, thread: ThreadId) {
+        let this = self.eval_context_mut();
+        this.machine.threads.unregister_timeout_callback_if_exists(thread);
+    }
+
+    /// Execute a timeout callback on the callback's thread.
+    #[inline]
+    fn run_timeout_callback(&mut self) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        let (thread, callback) = if let Some((thread, callback)) =
+            this.machine.threads.get_ready_callback(&this.machine.clock)
+        {
+            (thread, callback)
+        } else {
+            // get_ready_callback can return None if the computer's clock
+            // was shifted after calling the scheduler and before the call
+            // to get_ready_callback (see issue
+            // https://github.com/rust-lang/miri/issues/1763). In this case,
+            // just do nothing, which effectively just returns to the
+            // scheduler.
+            return Ok(());
+        };
+        // This back-and-forth with `set_active_thread` is here because of two
+        // design decisions:
+        // 1. Make the caller and not the callback responsible for changing
+        //    thread.
+        // 2. Make the scheduler the only place that can change the active
+        //    thread.
+        let old_thread = this.set_active_thread(thread);
+        callback(this)?;
+        this.set_active_thread(old_thread);
+        Ok(())
+    }
+
+    /// Decide which action to take next and on which thread.
+    #[inline]
+    fn schedule(&mut self) -> InterpResult<'tcx, SchedulingAction> {
+        let this = self.eval_context_mut();
+        this.machine.threads.schedule(&this.machine.clock)
+    }
+
+    /// Handles thread termination of the active thread: wakes up threads joining on this one,
+    /// and deallocated thread-local statics.
+    ///
+    /// This is called from `tls.rs` after handling the TLS dtors.
+    #[inline]
+    fn thread_terminated(&mut self) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        for ptr in this.machine.threads.thread_terminated(this.machine.data_race.as_mut()) {
+            this.deallocate_ptr(ptr.into(), None, MiriMemoryKind::Tls.into())?;
+        }
+        Ok(())
+    }
+}
diff --git a/src/tools/miri/src/concurrency/vector_clock.rs b/src/tools/miri/src/concurrency/vector_clock.rs
new file mode 100644
index 0000000000000..32449f8eb1884
--- /dev/null
+++ b/src/tools/miri/src/concurrency/vector_clock.rs
@@ -0,0 +1,470 @@
+use rustc_index::vec::Idx;
+use smallvec::SmallVec;
+use std::{cmp::Ordering, fmt::Debug, ops::Index};
+
+/// A vector clock index, this is associated with a thread id
+/// but in some cases one vector index may be shared with
+/// multiple thread ids if it safe to do so.
+#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
+pub struct VectorIdx(u32);
+
+impl VectorIdx {
+    #[inline(always)]
+    pub fn to_u32(self) -> u32 {
+        self.0
+    }
+
+    pub const MAX_INDEX: VectorIdx = VectorIdx(u32::MAX);
+}
+
+impl Idx for VectorIdx {
+    #[inline]
+    fn new(idx: usize) -> Self {
+        VectorIdx(u32::try_from(idx).unwrap())
+    }
+
+    #[inline]
+    fn index(self) -> usize {
+        usize::try_from(self.0).unwrap()
+    }
+}
+
+impl From<u32> for VectorIdx {
+    #[inline]
+    fn from(id: u32) -> Self {
+        Self(id)
+    }
+}
+
+/// The size of the vector-clock to store inline
+/// clock vectors larger than this will be stored on the heap
+const SMALL_VECTOR: usize = 4;
+
+/// The type of the time-stamps recorded in the data-race detector
+/// set to a type of unsigned integer
+pub type VTimestamp = u32;
+
+/// A vector clock for detecting data-races, this is conceptually
+/// a map from a vector index (and thus a thread id) to a timestamp.
+/// The compare operations require that the invariant that the last
+/// element in the internal timestamp slice must not be a 0, hence
+/// all zero vector clocks are always represented by the empty slice;
+/// and allows for the implementation of compare operations to short
+/// circuit the calculation and return the correct result faster,
+/// also this means that there is only one unique valid length
+/// for each set of vector clock values and hence the PartialEq
+/// and Eq derivations are correct.
+#[derive(PartialEq, Eq, Default, Debug)]
+pub struct VClock(SmallVec<[VTimestamp; SMALL_VECTOR]>);
+
+impl VClock {
+    /// Create a new vector-clock containing all zeros except
+    /// for a value at the given index
+    pub fn new_with_index(index: VectorIdx, timestamp: VTimestamp) -> VClock {
+        let len = index.index() + 1;
+        let mut vec = smallvec::smallvec![0; len];
+        vec[index.index()] = timestamp;
+        VClock(vec)
+    }
+
+    /// Load the internal timestamp slice in the vector clock
+    #[inline]
+    pub fn as_slice(&self) -> &[VTimestamp] {
+        self.0.as_slice()
+    }
+
+    /// Get a mutable slice to the internal vector with minimum `min_len`
+    /// elements, to preserve invariants this vector must modify
+    /// the `min_len`-1 nth element to a non-zero value
+    #[inline]
+    fn get_mut_with_min_len(&mut self, min_len: usize) -> &mut [VTimestamp] {
+        if self.0.len() < min_len {
+            self.0.resize(min_len, 0);
+        }
+        assert!(self.0.len() >= min_len);
+        self.0.as_mut_slice()
+    }
+
+    /// Increment the vector clock at a known index
+    /// this will panic if the vector index overflows
+    #[inline]
+    pub fn increment_index(&mut self, idx: VectorIdx) {
+        let idx = idx.index();
+        let mut_slice = self.get_mut_with_min_len(idx + 1);
+        let idx_ref = &mut mut_slice[idx];
+        *idx_ref = idx_ref.checked_add(1).expect("Vector clock overflow")
+    }
+
+    // Join the two vector-clocks together, this
+    // sets each vector-element to the maximum value
+    // of that element in either of the two source elements.
+    pub fn join(&mut self, other: &Self) {
+        let rhs_slice = other.as_slice();
+        let lhs_slice = self.get_mut_with_min_len(rhs_slice.len());
+        for (l, &r) in lhs_slice.iter_mut().zip(rhs_slice.iter()) {
+            *l = r.max(*l);
+        }
+    }
+
+    /// Set the element at the current index of the vector
+    pub fn set_at_index(&mut self, other: &Self, idx: VectorIdx) {
+        let mut_slice = self.get_mut_with_min_len(idx.index() + 1);
+        mut_slice[idx.index()] = other[idx];
+    }
+
+    /// Set the vector to the all-zero vector
+    #[inline]
+    pub fn set_zero_vector(&mut self) {
+        self.0.clear();
+    }
+
+    /// Return if this vector is the all-zero vector
+    pub fn is_zero_vector(&self) -> bool {
+        self.0.is_empty()
+    }
+}
+
+impl Clone for VClock {
+    fn clone(&self) -> Self {
+        VClock(self.0.clone())
+    }
+
+    // Optimized clone-from, can be removed
+    // and replaced with a derive once a similar
+    // optimization is inserted into SmallVec's
+    // clone implementation.
+    fn clone_from(&mut self, source: &Self) {
+        let source_slice = source.as_slice();
+        self.0.clear();
+        self.0.extend_from_slice(source_slice);
+    }
+}
+
+impl PartialOrd for VClock {
+    fn partial_cmp(&self, other: &VClock) -> Option<Ordering> {
+        // Load the values as slices
+        let lhs_slice = self.as_slice();
+        let rhs_slice = other.as_slice();
+
+        // Iterate through the combined vector slice continuously updating
+        // the value of `order` to the current comparison of the vector from
+        // index 0 to the currently checked index.
+        // An Equal ordering can be converted into Less or Greater ordering
+        // on finding an element that is less than or greater than the other
+        // but if one Greater and one Less element-wise comparison is found
+        // then no ordering is possible and so directly return an ordering
+        // of None.
+        let mut iter = lhs_slice.iter().zip(rhs_slice.iter());
+        let mut order = match iter.next() {
+            Some((lhs, rhs)) => lhs.cmp(rhs),
+            None => Ordering::Equal,
+        };
+        for (l, r) in iter {
+            match order {
+                Ordering::Equal => order = l.cmp(r),
+                Ordering::Less =>
+                    if l > r {
+                        return None;
+                    },
+                Ordering::Greater =>
+                    if l < r {
+                        return None;
+                    },
+            }
+        }
+
+        // Now test if either left or right have trailing elements,
+        // by the invariant the trailing elements have at least 1
+        // non zero value, so no additional calculation is required
+        // to determine the result of the PartialOrder.
+        let l_len = lhs_slice.len();
+        let r_len = rhs_slice.len();
+        match l_len.cmp(&r_len) {
+            // Equal means no additional elements: return current order
+            Ordering::Equal => Some(order),
+            // Right has at least 1 element > than the implicit 0,
+            // so the only valid values are Ordering::Less or None.
+            Ordering::Less =>
+                match order {
+                    Ordering::Less | Ordering::Equal => Some(Ordering::Less),
+                    Ordering::Greater => None,
+                },
+            // Left has at least 1 element > than the implicit 0,
+            // so the only valid values are Ordering::Greater or None.
+            Ordering::Greater =>
+                match order {
+                    Ordering::Greater | Ordering::Equal => Some(Ordering::Greater),
+                    Ordering::Less => None,
+                },
+        }
+    }
+
+    fn lt(&self, other: &VClock) -> bool {
+        // Load the values as slices
+        let lhs_slice = self.as_slice();
+        let rhs_slice = other.as_slice();
+
+        // If l_len > r_len then at least one element
+        // in l_len is > than r_len, therefore the result
+        // is either Some(Greater) or None, so return false
+        // early.
+        let l_len = lhs_slice.len();
+        let r_len = rhs_slice.len();
+        if l_len <= r_len {
+            // If any elements on the left are greater than the right
+            // then the result is None or Some(Greater), both of which
+            // return false, the earlier test asserts that no elements in the
+            // extended tail violate this assumption. Otherwise l <= r, finally
+            // the case where the values are potentially equal needs to be considered
+            // and false returned as well
+            let mut equal = l_len == r_len;
+            for (&l, &r) in lhs_slice.iter().zip(rhs_slice.iter()) {
+                if l > r {
+                    return false;
+                } else if l < r {
+                    equal = false;
+                }
+            }
+            !equal
+        } else {
+            false
+        }
+    }
+
+    fn le(&self, other: &VClock) -> bool {
+        // Load the values as slices
+        let lhs_slice = self.as_slice();
+        let rhs_slice = other.as_slice();
+
+        // If l_len > r_len then at least one element
+        // in l_len is > than r_len, therefore the result
+        // is either Some(Greater) or None, so return false
+        // early.
+        let l_len = lhs_slice.len();
+        let r_len = rhs_slice.len();
+        if l_len <= r_len {
+            // If any elements on the left are greater than the right
+            // then the result is None or Some(Greater), both of which
+            // return false, the earlier test asserts that no elements in the
+            // extended tail violate this assumption. Otherwise l <= r
+            !lhs_slice.iter().zip(rhs_slice.iter()).any(|(&l, &r)| l > r)
+        } else {
+            false
+        }
+    }
+
+    fn gt(&self, other: &VClock) -> bool {
+        // Load the values as slices
+        let lhs_slice = self.as_slice();
+        let rhs_slice = other.as_slice();
+
+        // If r_len > l_len then at least one element
+        // in r_len is > than l_len, therefore the result
+        // is either Some(Less) or None, so return false
+        // early.
+        let l_len = lhs_slice.len();
+        let r_len = rhs_slice.len();
+        if l_len >= r_len {
+            // If any elements on the left are less than the right
+            // then the result is None or Some(Less), both of which
+            // return false, the earlier test asserts that no elements in the
+            // extended tail violate this assumption. Otherwise l >=, finally
+            // the case where the values are potentially equal needs to be considered
+            // and false returned as well
+            let mut equal = l_len == r_len;
+            for (&l, &r) in lhs_slice.iter().zip(rhs_slice.iter()) {
+                if l < r {
+                    return false;
+                } else if l > r {
+                    equal = false;
+                }
+            }
+            !equal
+        } else {
+            false
+        }
+    }
+
+    fn ge(&self, other: &VClock) -> bool {
+        // Load the values as slices
+        let lhs_slice = self.as_slice();
+        let rhs_slice = other.as_slice();
+
+        // If r_len > l_len then at least one element
+        // in r_len is > than l_len, therefore the result
+        // is either Some(Less) or None, so return false
+        // early.
+        let l_len = lhs_slice.len();
+        let r_len = rhs_slice.len();
+        if l_len >= r_len {
+            // If any elements on the left are less than the right
+            // then the result is None or Some(Less), both of which
+            // return false, the earlier test asserts that no elements in the
+            // extended tail violate this assumption. Otherwise l >= r
+            !lhs_slice.iter().zip(rhs_slice.iter()).any(|(&l, &r)| l < r)
+        } else {
+            false
+        }
+    }
+}
+
+impl Index<VectorIdx> for VClock {
+    type Output = VTimestamp;
+
+    #[inline]
+    fn index(&self, index: VectorIdx) -> &VTimestamp {
+        self.as_slice().get(index.to_u32() as usize).unwrap_or(&0)
+    }
+}
+
+/// Test vector clock ordering operations
+///  data-race detection is tested in the external
+///  test suite
+#[cfg(test)]
+mod tests {
+
+    use super::{VClock, VTimestamp, VectorIdx};
+    use std::cmp::Ordering;
+
+    #[test]
+    fn test_equal() {
+        let mut c1 = VClock::default();
+        let mut c2 = VClock::default();
+        assert_eq!(c1, c2);
+        c1.increment_index(VectorIdx(5));
+        assert_ne!(c1, c2);
+        c2.increment_index(VectorIdx(53));
+        assert_ne!(c1, c2);
+        c1.increment_index(VectorIdx(53));
+        assert_ne!(c1, c2);
+        c2.increment_index(VectorIdx(5));
+        assert_eq!(c1, c2);
+    }
+
+    #[test]
+    fn test_partial_order() {
+        // Small test
+        assert_order(&[1], &[1], Some(Ordering::Equal));
+        assert_order(&[1], &[2], Some(Ordering::Less));
+        assert_order(&[2], &[1], Some(Ordering::Greater));
+        assert_order(&[1], &[1, 2], Some(Ordering::Less));
+        assert_order(&[2], &[1, 2], None);
+
+        // Misc tests
+        assert_order(&[400], &[0, 1], None);
+
+        // Large test
+        assert_order(
+            &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
+            &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0],
+            Some(Ordering::Equal),
+        );
+        assert_order(
+            &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
+            &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 0],
+            Some(Ordering::Less),
+        );
+        assert_order(
+            &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11],
+            &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0],
+            Some(Ordering::Greater),
+        );
+        assert_order(
+            &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11],
+            &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 0],
+            None,
+        );
+        assert_order(
+            &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 9],
+            &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0],
+            Some(Ordering::Less),
+        );
+        assert_order(
+            &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 9],
+            &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 0],
+            Some(Ordering::Less),
+        );
+    }
+
+    fn from_slice(mut slice: &[VTimestamp]) -> VClock {
+        while let Some(0) = slice.last() {
+            slice = &slice[..slice.len() - 1]
+        }
+        VClock(smallvec::SmallVec::from_slice(slice))
+    }
+
+    fn assert_order(l: &[VTimestamp], r: &[VTimestamp], o: Option<Ordering>) {
+        let l = from_slice(l);
+        let r = from_slice(r);
+
+        //Test partial_cmp
+        let compare = l.partial_cmp(&r);
+        assert_eq!(compare, o, "Invalid comparison\n l: {:?}\n r: {:?}", l, r);
+        let alt_compare = r.partial_cmp(&l);
+        assert_eq!(
+            alt_compare,
+            o.map(Ordering::reverse),
+            "Invalid alt comparison\n l: {:?}\n r: {:?}",
+            l,
+            r
+        );
+
+        //Test operators with faster implementations
+        assert_eq!(
+            matches!(compare, Some(Ordering::Less)),
+            l < r,
+            "Invalid (<):\n l: {:?}\n r: {:?}",
+            l,
+            r
+        );
+        assert_eq!(
+            matches!(compare, Some(Ordering::Less) | Some(Ordering::Equal)),
+            l <= r,
+            "Invalid (<=):\n l: {:?}\n r: {:?}",
+            l,
+            r
+        );
+        assert_eq!(
+            matches!(compare, Some(Ordering::Greater)),
+            l > r,
+            "Invalid (>):\n l: {:?}\n r: {:?}",
+            l,
+            r
+        );
+        assert_eq!(
+            matches!(compare, Some(Ordering::Greater) | Some(Ordering::Equal)),
+            l >= r,
+            "Invalid (>=):\n l: {:?}\n r: {:?}",
+            l,
+            r
+        );
+        assert_eq!(
+            matches!(alt_compare, Some(Ordering::Less)),
+            r < l,
+            "Invalid alt (<):\n l: {:?}\n r: {:?}",
+            l,
+            r
+        );
+        assert_eq!(
+            matches!(alt_compare, Some(Ordering::Less) | Some(Ordering::Equal)),
+            r <= l,
+            "Invalid alt (<=):\n l: {:?}\n r: {:?}",
+            l,
+            r
+        );
+        assert_eq!(
+            matches!(alt_compare, Some(Ordering::Greater)),
+            r > l,
+            "Invalid alt (>):\n l: {:?}\n r: {:?}",
+            l,
+            r
+        );
+        assert_eq!(
+            matches!(alt_compare, Some(Ordering::Greater) | Some(Ordering::Equal)),
+            r >= l,
+            "Invalid alt (>=):\n l: {:?}\n r: {:?}",
+            l,
+            r
+        );
+    }
+}
diff --git a/src/tools/miri/src/concurrency/weak_memory.rs b/src/tools/miri/src/concurrency/weak_memory.rs
new file mode 100644
index 0000000000000..bac403e9ec7b1
--- /dev/null
+++ b/src/tools/miri/src/concurrency/weak_memory.rs
@@ -0,0 +1,630 @@
+//! Implementation of C++11-consistent weak memory emulation using store buffers
+//! based on Dynamic Race Detection for C++ ("the paper"):
+//! <https://www.doc.ic.ac.uk/~afd/homepages/papers/pdfs/2017/POPL.pdf>
+//!
+//! This implementation will never generate weak memory behaviours forbidden by the C++11 model,
+//! but it is incapable of producing all possible weak behaviours allowed by the model. There are
+//! certain weak behaviours observable on real hardware but not while using this.
+//!
+//! Note that this implementation does not fully take into account of C++20's memory model revision to SC accesses
+//! and fences introduced by P0668 (<https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0668r5.html>).
+//! This implementation is not fully correct under the revised C++20 model and may generate behaviours C++20
+//! disallows (<https://github.com/rust-lang/miri/issues/2301>).
+//!
+//! A modification is made to the paper's model to partially address C++20 changes.
+//! Specifically, if an SC load reads from an atomic store of any ordering, then a later SC load cannot read from
+//! an earlier store in the location's modification order. This is to prevent creating a backwards S edge from the second
+//! load to the first, as a result of C++20's coherence-ordered before rules.
+//!
+//! Rust follows the C++20 memory model (except for the Consume ordering and some operations not performable through C++'s
+//! std::atomic<T> API). It is therefore possible for this implementation to generate behaviours never observable when the
+//! same program is compiled and run natively. Unfortunately, no literature exists at the time of writing which proposes
+//! an implementable and C++20-compatible relaxed memory model that supports all atomic operation existing in Rust. The closest one is
+//! A Promising Semantics for Relaxed-Memory Concurrency by Jeehoon Kang et al. (<https://www.cs.tau.ac.il/~orilahav/papers/popl17.pdf>)
+//! However, this model lacks SC accesses and is therefore unusable by Miri (SC accesses are everywhere in library code).
+//!
+//! If you find anything that proposes a relaxed memory model that is C++20-consistent, supports all orderings Rust's atomic accesses
+//! and fences accept, and is implementable (with operational semanitcs), please open a GitHub issue!
+//!
+//! One characteristic of this implementation, in contrast to some other notable operational models such as ones proposed in
+//! Taming Release-Acquire Consistency by Ori Lahav et al. (<https://plv.mpi-sws.org/sra/paper.pdf>) or Promising Semantics noted above,
+//! is that this implementation does not require each thread to hold an isolated view of the entire memory. Here, store buffers are per-location
+//! and shared across all threads. This is more memory efficient but does require store elements (representing writes to a location) to record
+//! information about reads, whereas in the other two models it is the other way round: reads points to the write it got its value from.
+//! Additionally, writes in our implementation do not have globally unique timestamps attached. In the other two models this timestamp is
+//! used to make sure a value in a thread's view is not overwritten by a write that occured earlier than the one in the existing view.
+//! In our implementation, this is detected using read information attached to store elements, as there is no data strucutre representing reads.
+//!
+//! The C++ memory model is built around the notion of an 'atomic object', so it would be natural
+//! to attach store buffers to atomic objects. However, Rust follows LLVM in that it only has
+//! 'atomic accesses'. Therefore Miri cannot know when and where atomic 'objects' are being
+//! created or destroyed, to manage its store buffers. Instead, we hence lazily create an
+//! atomic object on the first atomic access to a given region, and we destroy that object
+//! on the next non-atomic or imperfectly overlapping atomic access to that region.
+//! These lazy (de)allocations happen in memory_accessed() on non-atomic accesses, and
+//! get_or_create_store_buffer() on atomic accesses. This mostly works well, but it does
+//! lead to some issues (<https://github.com/rust-lang/miri/issues/2164>).
+//!
+//! One consequence of this difference is that safe/sound Rust allows for more operations on atomic locations
+//! than the C++20 atomic API was intended to allow, such as non-atomically accessing
+//! a previously atomically accessed location, or accessing previously atomically accessed locations with a differently sized operation
+//! (such as accessing the top 16 bits of an AtomicU32). These senarios are generally undiscussed in formalisations of C++ memory model.
+//! In Rust, these operations can only be done through a `&mut AtomicFoo` reference or one derived from it, therefore these operations
+//! can only happen after all previous accesses on the same locations. This implementation is adapted to allow these operations.
+//! A mixed atomicity read that races with writes, or a write that races with reads or writes will still cause UBs to be thrown.
+//! Mixed size atomic accesses must not race with any other atomic access, whether read or write, or a UB will be thrown.
+//! You can refer to test cases in weak_memory/extra_cpp.rs and weak_memory/extra_cpp_unsafe.rs for examples of these operations.
+
+// Our and the author's own implementation (tsan11) of the paper have some deviations from the provided operational semantics in §5.3:
+// 1. In the operational semantics, store elements keep a copy of the atomic object's vector clock (AtomicCellClocks::sync_vector in miri),
+// but this is not used anywhere so it's omitted here.
+//
+// 2. In the operational semantics, each store element keeps the timestamp of a thread when it loads from the store.
+// If the same thread loads from the same store element multiple times, then the timestamps at all loads are saved in a list of load elements.
+// This is not necessary as later loads by the same thread will always have greater timetstamp values, so we only need to record the timestamp of the first
+// load by each thread. This optimisation is done in tsan11
+// (https://github.com/ChrisLidbury/tsan11/blob/ecbd6b81e9b9454e01cba78eb9d88684168132c7/lib/tsan/rtl/tsan_relaxed.h#L35-L37)
+// and here.
+//
+// 3. §4.5 of the paper wants an SC store to mark all existing stores in the buffer that happens before it
+// as SC. This is not done in the operational semantics but implemented correctly in tsan11
+// (https://github.com/ChrisLidbury/tsan11/blob/ecbd6b81e9b9454e01cba78eb9d88684168132c7/lib/tsan/rtl/tsan_relaxed.cc#L160-L167)
+// and here.
+//
+// 4. W_SC ; R_SC case requires the SC load to ignore all but last store maked SC (stores not marked SC are not
+// affected). But this rule is applied to all loads in ReadsFromSet from the paper (last two lines of code), not just SC load.
+// This is implemented correctly in tsan11
+// (https://github.com/ChrisLidbury/tsan11/blob/ecbd6b81e9b9454e01cba78eb9d88684168132c7/lib/tsan/rtl/tsan_relaxed.cc#L295)
+// and here.
+
+use std::{
+    cell::{Ref, RefCell},
+    collections::VecDeque,
+};
+
+use rustc_const_eval::interpret::{alloc_range, AllocRange, InterpResult, MPlaceTy, Scalar};
+use rustc_data_structures::fx::FxHashMap;
+
+use crate::*;
+
+use super::{
+    data_race::{GlobalState as DataRaceState, ThreadClockSet},
+    range_object_map::{AccessType, RangeObjectMap},
+    vector_clock::{VClock, VTimestamp, VectorIdx},
+};
+
+pub type AllocExtra = StoreBufferAlloc;
+
+// Each store buffer must be bounded otherwise it will grow indefinitely.
+// However, bounding the store buffer means restricting the amount of weak
+// behaviours observable. The author picked 128 as a good tradeoff
+// so we follow them here.
+const STORE_BUFFER_LIMIT: usize = 128;
+
+#[derive(Debug, Clone)]
+pub struct StoreBufferAlloc {
+    /// Store buffer of each atomic object in this allocation
+    // Behind a RefCell because we need to allocate/remove on read access
+    store_buffers: RefCell<RangeObjectMap<StoreBuffer>>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub(super) struct StoreBuffer {
+    // Stores to this location in modification order
+    buffer: VecDeque<StoreElement>,
+}
+
+/// Whether a load returned the latest value or not.
+#[derive(PartialEq, Eq)]
+enum LoadRecency {
+    Latest,
+    Outdated,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+struct StoreElement {
+    /// The identifier of the vector index, corresponding to a thread
+    /// that performed the store.
+    store_index: VectorIdx,
+
+    /// Whether this store is SC.
+    is_seqcst: bool,
+
+    /// The timestamp of the storing thread when it performed the store
+    timestamp: VTimestamp,
+    /// The value of this store
+    // FIXME: this means the store must be fully initialized;
+    // we will have to change this if we want to support atomics on
+    // (partially) uninitialized data.
+    val: Scalar<Provenance>,
+
+    /// Metadata about loads from this store element,
+    /// behind a RefCell to keep load op take &self
+    load_info: RefCell<LoadInfo>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Default)]
+struct LoadInfo {
+    /// Timestamp of first loads from this store element by each thread
+    timestamps: FxHashMap<VectorIdx, VTimestamp>,
+    /// Whether this store element has been read by an SC load
+    sc_loaded: bool,
+}
+
+impl StoreBufferAlloc {
+    pub fn new_allocation() -> Self {
+        Self { store_buffers: RefCell::new(RangeObjectMap::new()) }
+    }
+
+    /// Checks if the range imperfectly overlaps with existing buffers
+    /// Used to determine if mixed-size atomic accesses
+    fn is_overlapping(&self, range: AllocRange) -> bool {
+        let buffers = self.store_buffers.borrow();
+        let access_type = buffers.access_type(range);
+        matches!(access_type, AccessType::ImperfectlyOverlapping(_))
+    }
+
+    /// When a non-atomic access happens on a location that has been atomically accessed
+    /// before without data race, we can determine that the non-atomic access fully happens
+    /// after all the prior atomic accesses so the location no longer needs to exhibit
+    /// any weak memory behaviours until further atomic accesses.
+    pub fn memory_accessed(&self, range: AllocRange, global: &DataRaceState) {
+        if !global.ongoing_action_data_race_free() {
+            let mut buffers = self.store_buffers.borrow_mut();
+            let access_type = buffers.access_type(range);
+            match access_type {
+                AccessType::PerfectlyOverlapping(pos) => {
+                    buffers.remove_from_pos(pos);
+                }
+                AccessType::ImperfectlyOverlapping(pos_range) => {
+                    buffers.remove_pos_range(pos_range);
+                }
+                AccessType::Empty(_) => {
+                    // The range had no weak behaivours attached, do nothing
+                }
+            }
+        }
+    }
+
+    /// Gets a store buffer associated with an atomic object in this allocation,
+    /// or creates one with the specified initial value if no atomic object exists yet.
+    fn get_or_create_store_buffer<'tcx>(
+        &self,
+        range: AllocRange,
+        init: Scalar<Provenance>,
+    ) -> InterpResult<'tcx, Ref<'_, StoreBuffer>> {
+        let access_type = self.store_buffers.borrow().access_type(range);
+        let pos = match access_type {
+            AccessType::PerfectlyOverlapping(pos) => pos,
+            AccessType::Empty(pos) => {
+                let mut buffers = self.store_buffers.borrow_mut();
+                buffers.insert_at_pos(pos, range, StoreBuffer::new(init));
+                pos
+            }
+            AccessType::ImperfectlyOverlapping(pos_range) => {
+                // Once we reach here we would've already checked that this access is not racy
+                let mut buffers = self.store_buffers.borrow_mut();
+                buffers.remove_pos_range(pos_range.clone());
+                buffers.insert_at_pos(pos_range.start, range, StoreBuffer::new(init));
+                pos_range.start
+            }
+        };
+        Ok(Ref::map(self.store_buffers.borrow(), |buffer| &buffer[pos]))
+    }
+
+    /// Gets a mutable store buffer associated with an atomic object in this allocation
+    fn get_or_create_store_buffer_mut<'tcx>(
+        &mut self,
+        range: AllocRange,
+        init: Scalar<Provenance>,
+    ) -> InterpResult<'tcx, &mut StoreBuffer> {
+        let buffers = self.store_buffers.get_mut();
+        let access_type = buffers.access_type(range);
+        let pos = match access_type {
+            AccessType::PerfectlyOverlapping(pos) => pos,
+            AccessType::Empty(pos) => {
+                buffers.insert_at_pos(pos, range, StoreBuffer::new(init));
+                pos
+            }
+            AccessType::ImperfectlyOverlapping(pos_range) => {
+                buffers.remove_pos_range(pos_range.clone());
+                buffers.insert_at_pos(pos_range.start, range, StoreBuffer::new(init));
+                pos_range.start
+            }
+        };
+        Ok(&mut buffers[pos])
+    }
+}
+
+impl<'mir, 'tcx: 'mir> StoreBuffer {
+    fn new(init: Scalar<Provenance>) -> Self {
+        let mut buffer = VecDeque::new();
+        buffer.reserve(STORE_BUFFER_LIMIT);
+        let mut ret = Self { buffer };
+        let store_elem = StoreElement {
+            // The thread index and timestamp of the initialisation write
+            // are never meaningfully used, so it's fine to leave them as 0
+            store_index: VectorIdx::from(0),
+            timestamp: 0,
+            val: init,
+            is_seqcst: false,
+            load_info: RefCell::new(LoadInfo::default()),
+        };
+        ret.buffer.push_back(store_elem);
+        ret
+    }
+
+    /// Reads from the last store in modification order
+    fn read_from_last_store(
+        &self,
+        global: &DataRaceState,
+        thread_mgr: &ThreadManager<'_, '_>,
+        is_seqcst: bool,
+    ) {
+        let store_elem = self.buffer.back();
+        if let Some(store_elem) = store_elem {
+            let (index, clocks) = global.current_thread_state(thread_mgr);
+            store_elem.load_impl(index, &clocks, is_seqcst);
+        }
+    }
+
+    fn buffered_read(
+        &self,
+        global: &DataRaceState,
+        thread_mgr: &ThreadManager<'_, '_>,
+        is_seqcst: bool,
+        rng: &mut (impl rand::Rng + ?Sized),
+        validate: impl FnOnce() -> InterpResult<'tcx>,
+    ) -> InterpResult<'tcx, (Scalar<Provenance>, LoadRecency)> {
+        // Having a live borrow to store_buffer while calling validate_atomic_load is fine
+        // because the race detector doesn't touch store_buffer
+
+        let (store_elem, recency) = {
+            // The `clocks` we got here must be dropped before calling validate_atomic_load
+            // as the race detector will update it
+            let (.., clocks) = global.current_thread_state(thread_mgr);
+            // Load from a valid entry in the store buffer
+            self.fetch_store(is_seqcst, &clocks, &mut *rng)
+        };
+
+        // Unlike in buffered_atomic_write, thread clock updates have to be done
+        // after we've picked a store element from the store buffer, as presented
+        // in ATOMIC LOAD rule of the paper. This is because fetch_store
+        // requires access to ThreadClockSet.clock, which is updated by the race detector
+        validate()?;
+
+        let (index, clocks) = global.current_thread_state(thread_mgr);
+        let loaded = store_elem.load_impl(index, &clocks, is_seqcst);
+        Ok((loaded, recency))
+    }
+
+    fn buffered_write(
+        &mut self,
+        val: Scalar<Provenance>,
+        global: &DataRaceState,
+        thread_mgr: &ThreadManager<'_, '_>,
+        is_seqcst: bool,
+    ) -> InterpResult<'tcx> {
+        let (index, clocks) = global.current_thread_state(thread_mgr);
+
+        self.store_impl(val, index, &clocks.clock, is_seqcst);
+        Ok(())
+    }
+
+    #[allow(clippy::if_same_then_else, clippy::needless_bool)]
+    /// Selects a valid store element in the buffer.
+    fn fetch_store<R: rand::Rng + ?Sized>(
+        &self,
+        is_seqcst: bool,
+        clocks: &ThreadClockSet,
+        rng: &mut R,
+    ) -> (&StoreElement, LoadRecency) {
+        use rand::seq::IteratorRandom;
+        let mut found_sc = false;
+        // FIXME: we want an inclusive take_while (stops after a false predicate, but
+        // includes the element that gave the false), but such function doesn't yet
+        // exist in the standard libary https://github.com/rust-lang/rust/issues/62208
+        // so we have to hack around it with keep_searching
+        let mut keep_searching = true;
+        let candidates = self
+            .buffer
+            .iter()
+            .rev()
+            .take_while(move |&store_elem| {
+                if !keep_searching {
+                    return false;
+                }
+
+                keep_searching = if store_elem.timestamp <= clocks.clock[store_elem.store_index] {
+                    // CoWR: if a store happens-before the current load,
+                    // then we can't read-from anything earlier in modification order.
+                    // C++20 §6.9.2.2 [intro.races] paragraph 18
+                    false
+                } else if store_elem.load_info.borrow().timestamps.iter().any(
+                    |(&load_index, &load_timestamp)| load_timestamp <= clocks.clock[load_index],
+                ) {
+                    // CoRR: if there was a load from this store which happened-before the current load,
+                    // then we cannot read-from anything earlier in modification order.
+                    // C++20 §6.9.2.2 [intro.races] paragraph 16
+                    false
+                } else if store_elem.timestamp <= clocks.fence_seqcst[store_elem.store_index] {
+                    // The current load, which may be sequenced-after an SC fence, cannot read-before
+                    // the last store sequenced-before an SC fence in another thread.
+                    // C++17 §32.4 [atomics.order] paragraph 6
+                    false
+                } else if store_elem.timestamp <= clocks.write_seqcst[store_elem.store_index]
+                    && store_elem.is_seqcst
+                {
+                    // The current non-SC load, which may be sequenced-after an SC fence,
+                    // cannot read-before the last SC store executed before the fence.
+                    // C++17 §32.4 [atomics.order] paragraph 4
+                    false
+                } else if is_seqcst
+                    && store_elem.timestamp <= clocks.read_seqcst[store_elem.store_index]
+                {
+                    // The current SC load cannot read-before the last store sequenced-before
+                    // the last SC fence.
+                    // C++17 §32.4 [atomics.order] paragraph 5
+                    false
+                } else if is_seqcst && store_elem.load_info.borrow().sc_loaded {
+                    // The current SC load cannot read-before a store that an earlier SC load has observed.
+                    // See https://github.com/rust-lang/miri/issues/2301#issuecomment-1222720427
+                    // Consequences of C++20 §31.4 [atomics.order] paragraph 3.1, 3.3 (coherence-ordered before)
+                    // and 4.1 (coherence-ordered before between SC makes global total order S)
+                    false
+                } else {
+                    true
+                };
+
+                true
+            })
+            .filter(|&store_elem| {
+                if is_seqcst && store_elem.is_seqcst {
+                    // An SC load needs to ignore all but last store maked SC (stores not marked SC are not
+                    // affected)
+                    let include = !found_sc;
+                    found_sc = true;
+                    include
+                } else {
+                    true
+                }
+            });
+
+        let chosen = candidates.choose(rng).expect("store buffer cannot be empty");
+        if std::ptr::eq(chosen, self.buffer.back().expect("store buffer cannot be empty")) {
+            (chosen, LoadRecency::Latest)
+        } else {
+            (chosen, LoadRecency::Outdated)
+        }
+    }
+
+    /// ATOMIC STORE IMPL in the paper (except we don't need the location's vector clock)
+    fn store_impl(
+        &mut self,
+        val: Scalar<Provenance>,
+        index: VectorIdx,
+        thread_clock: &VClock,
+        is_seqcst: bool,
+    ) {
+        let store_elem = StoreElement {
+            store_index: index,
+            timestamp: thread_clock[index],
+            // In the language provided in the paper, an atomic store takes the value from a
+            // non-atomic memory location.
+            // But we already have the immediate value here so we don't need to do the memory
+            // access
+            val,
+            is_seqcst,
+            load_info: RefCell::new(LoadInfo::default()),
+        };
+        self.buffer.push_back(store_elem);
+        if self.buffer.len() > STORE_BUFFER_LIMIT {
+            self.buffer.pop_front();
+        }
+        if is_seqcst {
+            // Every store that happens before this needs to be marked as SC
+            // so that in a later SC load, only the last SC store (i.e. this one) or stores that
+            // aren't ordered by hb with the last SC is picked.
+            self.buffer.iter_mut().rev().for_each(|elem| {
+                if elem.timestamp <= thread_clock[elem.store_index] {
+                    elem.is_seqcst = true;
+                }
+            })
+        }
+    }
+}
+
+impl StoreElement {
+    /// ATOMIC LOAD IMPL in the paper
+    /// Unlike the operational semantics in the paper, we don't need to keep track
+    /// of the thread timestamp for every single load. Keeping track of the first (smallest)
+    /// timestamp of each thread that has loaded from a store is sufficient: if the earliest
+    /// load of another thread happens before the current one, then we must stop searching the store
+    /// buffer regardless of subsequent loads by the same thread; if the earliest load of another
+    /// thread doesn't happen before the current one, then no subsequent load by the other thread
+    /// can happen before the current one.
+    fn load_impl(
+        &self,
+        index: VectorIdx,
+        clocks: &ThreadClockSet,
+        is_seqcst: bool,
+    ) -> Scalar<Provenance> {
+        let mut load_info = self.load_info.borrow_mut();
+        load_info.sc_loaded |= is_seqcst;
+        let _ = load_info.timestamps.try_insert(index, clocks.clock[index]);
+        self.val
+    }
+}
+
+impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
+    crate::MiriInterpCxExt<'mir, 'tcx>
+{
+    // If weak memory emulation is enabled, check if this atomic op imperfectly overlaps with a previous
+    // atomic read or write. If it does, then we require it to be ordered (non-racy) with all previous atomic
+    // accesses on all the bytes in range
+    fn validate_overlapping_atomic(
+        &self,
+        place: &MPlaceTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_ref();
+        let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(place.ptr)?;
+        if let crate::AllocExtra {
+            weak_memory: Some(alloc_buffers),
+            data_race: Some(alloc_clocks),
+            ..
+        } = this.get_alloc_extra(alloc_id)?
+        {
+            let range = alloc_range(base_offset, place.layout.size);
+            if alloc_buffers.is_overlapping(range)
+                && !alloc_clocks.race_free_with_atomic(
+                    range,
+                    this.machine.data_race.as_ref().unwrap(),
+                    &this.machine.threads,
+                )
+            {
+                throw_unsup_format!(
+                    "racy imperfectly overlapping atomic access is not possible in the C++20 memory model, and not supported by Miri's weak memory emulation"
+                );
+            }
+        }
+        Ok(())
+    }
+
+    fn buffered_atomic_rmw(
+        &mut self,
+        new_val: Scalar<Provenance>,
+        place: &MPlaceTy<'tcx, Provenance>,
+        atomic: AtomicRwOrd,
+        init: Scalar<Provenance>,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(place.ptr)?;
+        if let (
+            crate::AllocExtra { weak_memory: Some(alloc_buffers), .. },
+            crate::MiriMachine { data_race: Some(global), threads, .. },
+        ) = this.get_alloc_extra_mut(alloc_id)?
+        {
+            if atomic == AtomicRwOrd::SeqCst {
+                global.sc_read(threads);
+                global.sc_write(threads);
+            }
+            let range = alloc_range(base_offset, place.layout.size);
+            let buffer = alloc_buffers.get_or_create_store_buffer_mut(range, init)?;
+            buffer.read_from_last_store(global, threads, atomic == AtomicRwOrd::SeqCst);
+            buffer.buffered_write(new_val, global, threads, atomic == AtomicRwOrd::SeqCst)?;
+        }
+        Ok(())
+    }
+
+    fn buffered_atomic_read(
+        &self,
+        place: &MPlaceTy<'tcx, Provenance>,
+        atomic: AtomicReadOrd,
+        latest_in_mo: Scalar<Provenance>,
+        validate: impl FnOnce() -> InterpResult<'tcx>,
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_ref();
+        if let Some(global) = &this.machine.data_race {
+            let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(place.ptr)?;
+            if let Some(alloc_buffers) = this.get_alloc_extra(alloc_id)?.weak_memory.as_ref() {
+                if atomic == AtomicReadOrd::SeqCst {
+                    global.sc_read(&this.machine.threads);
+                }
+                let mut rng = this.machine.rng.borrow_mut();
+                let buffer = alloc_buffers.get_or_create_store_buffer(
+                    alloc_range(base_offset, place.layout.size),
+                    latest_in_mo,
+                )?;
+                let (loaded, recency) = buffer.buffered_read(
+                    global,
+                    &this.machine.threads,
+                    atomic == AtomicReadOrd::SeqCst,
+                    &mut *rng,
+                    validate,
+                )?;
+                if global.track_outdated_loads && recency == LoadRecency::Outdated {
+                    this.emit_diagnostic(NonHaltingDiagnostic::WeakMemoryOutdatedLoad);
+                }
+
+                return Ok(loaded);
+            }
+        }
+
+        // Race detector or weak memory disabled, simply read the latest value
+        validate()?;
+        Ok(latest_in_mo)
+    }
+
+    fn buffered_atomic_write(
+        &mut self,
+        val: Scalar<Provenance>,
+        dest: &MPlaceTy<'tcx, Provenance>,
+        atomic: AtomicWriteOrd,
+        init: Scalar<Provenance>,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(dest.ptr)?;
+        if let (
+            crate::AllocExtra { weak_memory: Some(alloc_buffers), .. },
+            crate::MiriMachine { data_race: Some(global), threads, .. },
+        ) = this.get_alloc_extra_mut(alloc_id)?
+        {
+            if atomic == AtomicWriteOrd::SeqCst {
+                global.sc_write(threads);
+            }
+
+            // UGLY HACK: in write_scalar_atomic() we don't know the value before our write,
+            // so init == val always. If the buffer is fresh then we would've duplicated an entry,
+            // so we need to remove it.
+            // See https://github.com/rust-lang/miri/issues/2164
+            let was_empty = matches!(
+                alloc_buffers
+                    .store_buffers
+                    .borrow()
+                    .access_type(alloc_range(base_offset, dest.layout.size)),
+                AccessType::Empty(_)
+            );
+            let buffer = alloc_buffers
+                .get_or_create_store_buffer_mut(alloc_range(base_offset, dest.layout.size), init)?;
+            if was_empty {
+                buffer.buffer.pop_front();
+            }
+
+            buffer.buffered_write(val, global, threads, atomic == AtomicWriteOrd::SeqCst)?;
+        }
+
+        // Caller should've written to dest with the vanilla scalar write, we do nothing here
+        Ok(())
+    }
+
+    /// Caller should never need to consult the store buffer for the latest value.
+    /// This function is used exclusively for failed atomic_compare_exchange_scalar
+    /// to perform load_impl on the latest store element
+    fn perform_read_on_buffered_latest(
+        &self,
+        place: &MPlaceTy<'tcx, Provenance>,
+        atomic: AtomicReadOrd,
+        init: Scalar<Provenance>,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_ref();
+
+        if let Some(global) = &this.machine.data_race {
+            if atomic == AtomicReadOrd::SeqCst {
+                global.sc_read(&this.machine.threads);
+            }
+            let size = place.layout.size;
+            let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(place.ptr)?;
+            if let Some(alloc_buffers) = this.get_alloc_extra(alloc_id)?.weak_memory.as_ref() {
+                let buffer = alloc_buffers
+                    .get_or_create_store_buffer(alloc_range(base_offset, size), init)?;
+                buffer.read_from_last_store(
+                    global,
+                    &this.machine.threads,
+                    atomic == AtomicReadOrd::SeqCst,
+                );
+            }
+        }
+        Ok(())
+    }
+}
diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs
new file mode 100644
index 0000000000000..868c85c04a88d
--- /dev/null
+++ b/src/tools/miri/src/diagnostics.rs
@@ -0,0 +1,500 @@
+use std::fmt;
+use std::num::NonZeroU64;
+
+use log::trace;
+
+use rustc_span::{source_map::DUMMY_SP, SpanData, Symbol};
+use rustc_target::abi::{Align, Size};
+
+use crate::stacked_borrows::{diagnostics::TagHistory, AccessKind};
+use crate::*;
+
+/// Details of premature program termination.
+pub enum TerminationInfo {
+    Exit(i64),
+    Abort(String),
+    UnsupportedInIsolation(String),
+    StackedBorrowsUb {
+        msg: String,
+        help: Option<String>,
+        history: Option<TagHistory>,
+    },
+    Int2PtrWithStrictProvenance,
+    Deadlock,
+    MultipleSymbolDefinitions {
+        link_name: Symbol,
+        first: SpanData,
+        first_crate: Symbol,
+        second: SpanData,
+        second_crate: Symbol,
+    },
+    SymbolShimClashing {
+        link_name: Symbol,
+        span: SpanData,
+    },
+}
+
+impl fmt::Display for TerminationInfo {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        use TerminationInfo::*;
+        match self {
+            Exit(code) => write!(f, "the evaluated program completed with exit code {code}"),
+            Abort(msg) => write!(f, "{msg}"),
+            UnsupportedInIsolation(msg) => write!(f, "{msg}"),
+            Int2PtrWithStrictProvenance =>
+                write!(
+                    f,
+                    "integer-to-pointer casts and `ptr::from_exposed_addr` are not supported with `-Zmiri-strict-provenance`"
+                ),
+            StackedBorrowsUb { msg, .. } => write!(f, "{msg}"),
+            Deadlock => write!(f, "the evaluated program deadlocked"),
+            MultipleSymbolDefinitions { link_name, .. } =>
+                write!(f, "multiple definitions of symbol `{link_name}`"),
+            SymbolShimClashing { link_name, .. } =>
+                write!(f, "found `{link_name}` symbol definition that clashes with a built-in shim",),
+        }
+    }
+}
+
+impl MachineStopType for TerminationInfo {}
+
+/// Miri specific diagnostics
+pub enum NonHaltingDiagnostic {
+    CreatedPointerTag(NonZeroU64, Option<(AllocId, AllocRange)>),
+    /// This `Item` was popped from the borrow stack, either due to an access with the given tag or
+    /// a deallocation when the second argument is `None`.
+    PoppedPointerTag(Item, Option<(ProvenanceExtra, AccessKind)>),
+    CreatedCallId(CallId),
+    CreatedAlloc(AllocId, Size, Align, MemoryKind<MiriMemoryKind>),
+    FreedAlloc(AllocId),
+    RejectedIsolatedOp(String),
+    ProgressReport {
+        block_count: u64, // how many basic blocks have been run so far
+    },
+    Int2Ptr {
+        details: bool,
+    },
+    WeakMemoryOutdatedLoad,
+}
+
+/// Level of Miri specific diagnostics
+enum DiagLevel {
+    Error,
+    Warning,
+    Note,
+}
+
+/// Attempts to prune a stacktrace to omit the Rust runtime, and returns a bool indicating if any
+/// frames were pruned. If the stacktrace does not have any local frames, we conclude that it must
+/// be pointing to a problem in the Rust runtime itself, and do not prune it at all.
+fn prune_stacktrace<'tcx>(
+    mut stacktrace: Vec<FrameInfo<'tcx>>,
+    machine: &MiriMachine<'_, 'tcx>,
+) -> (Vec<FrameInfo<'tcx>>, bool) {
+    match machine.backtrace_style {
+        BacktraceStyle::Off => {
+            // Remove all frames marked with `caller_location` -- that attribute indicates we
+            // usually want to point at the caller, not them.
+            stacktrace.retain(|frame| !frame.instance.def.requires_caller_location(machine.tcx));
+            // Retain one frame so that we can print a span for the error itself
+            stacktrace.truncate(1);
+            (stacktrace, false)
+        }
+        BacktraceStyle::Short => {
+            let original_len = stacktrace.len();
+            // Only prune frames if there is at least one local frame. This check ensures that if
+            // we get a backtrace that never makes it to the user code because it has detected a
+            // bug in the Rust runtime, we don't prune away every frame.
+            let has_local_frame = stacktrace.iter().any(|frame| machine.is_local(frame));
+            if has_local_frame {
+                // Remove all frames marked with `caller_location` -- that attribute indicates we
+                // usually want to point at the caller, not them.
+                stacktrace
+                    .retain(|frame| !frame.instance.def.requires_caller_location(machine.tcx));
+
+                // This is part of the logic that `std` uses to select the relevant part of a
+                // backtrace. But here, we only look for __rust_begin_short_backtrace, not
+                // __rust_end_short_backtrace because the end symbol comes from a call to the default
+                // panic handler.
+                stacktrace = stacktrace
+                    .into_iter()
+                    .take_while(|frame| {
+                        let def_id = frame.instance.def_id();
+                        let path = machine.tcx.def_path_str(def_id);
+                        !path.contains("__rust_begin_short_backtrace")
+                    })
+                    .collect::<Vec<_>>();
+
+                // After we prune frames from the bottom, there are a few left that are part of the
+                // Rust runtime. So we remove frames until we get to a local symbol, which should be
+                // main or a test.
+                // This len check ensures that we don't somehow remove every frame, as doing so breaks
+                // the primary error message.
+                while stacktrace.len() > 1
+                    && stacktrace.last().map_or(false, |frame| !machine.is_local(frame))
+                {
+                    stacktrace.pop();
+                }
+            }
+            let was_pruned = stacktrace.len() != original_len;
+            (stacktrace, was_pruned)
+        }
+        BacktraceStyle::Full => (stacktrace, false),
+    }
+}
+
+/// Emit a custom diagnostic without going through the miri-engine machinery
+pub fn report_error<'tcx, 'mir>(
+    ecx: &InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
+    e: InterpErrorInfo<'tcx>,
+) -> Option<i64> {
+    use InterpError::*;
+
+    let mut msg = vec![];
+
+    let (title, helps) = match &e.kind() {
+        MachineStop(info) => {
+            let info = info.downcast_ref::<TerminationInfo>().expect("invalid MachineStop payload");
+            use TerminationInfo::*;
+            let title = match info {
+                Exit(code) => return Some(*code),
+                Abort(_) => Some("abnormal termination"),
+                UnsupportedInIsolation(_) | Int2PtrWithStrictProvenance =>
+                    Some("unsupported operation"),
+                StackedBorrowsUb { .. } => Some("Undefined Behavior"),
+                Deadlock => Some("deadlock"),
+                MultipleSymbolDefinitions { .. } | SymbolShimClashing { .. } => None,
+            };
+            #[rustfmt::skip]
+            let helps = match info {
+                UnsupportedInIsolation(_) =>
+                    vec![
+                        (None, format!("pass the flag `-Zmiri-disable-isolation` to disable isolation;")),
+                        (None, format!("or pass `-Zmiri-isolation-error=warn` to configure Miri to return an error code from isolated operations (if supported for that operation) and continue with a warning")),
+                    ],
+                StackedBorrowsUb { help, history, .. } => {
+                    let url = "https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md";
+                    msg.extend(help.clone());
+                    let mut helps = vec![
+                        (None, format!("this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental")),
+                        (None, format!("see {url} for further information")),
+                    ];
+                    if let Some(TagHistory {created, invalidated, protected}) = history.clone() {
+                        helps.push((Some(created.1), created.0));
+                        if let Some((msg, span)) = invalidated {
+                            helps.push((Some(span), msg));
+                        }
+                        if let Some((protector_msg, protector_span)) = protected {
+                            helps.push((Some(protector_span), protector_msg));
+                        }
+                    }
+                    helps
+                }
+                MultipleSymbolDefinitions { first, first_crate, second, second_crate, .. } =>
+                    vec![
+                        (Some(*first), format!("it's first defined here, in crate `{first_crate}`")),
+                        (Some(*second), format!("then it's defined here again, in crate `{second_crate}`")),
+                    ],
+                SymbolShimClashing { link_name, span } =>
+                    vec![(Some(*span), format!("the `{link_name}` symbol is defined here"))],
+                Int2PtrWithStrictProvenance =>
+                    vec![(None, format!("use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead"))],
+                _ => vec![],
+            };
+            (title, helps)
+        }
+        _ => {
+            #[rustfmt::skip]
+            let title = match e.kind() {
+                Unsupported(_) =>
+                    "unsupported operation",
+                UndefinedBehavior(_) =>
+                    "Undefined Behavior",
+                ResourceExhaustion(_) =>
+                    "resource exhaustion",
+                InvalidProgram(
+                    InvalidProgramInfo::AlreadyReported(_) |
+                    InvalidProgramInfo::Layout(..) |
+                    InvalidProgramInfo::ReferencedConstant
+                ) =>
+                    "post-monomorphization error",
+                kind =>
+                    bug!("This error should be impossible in Miri: {kind:?}"),
+            };
+            #[rustfmt::skip]
+            let helps = match e.kind() {
+                Unsupported(
+                    UnsupportedOpInfo::ThreadLocalStatic(_) |
+                    UnsupportedOpInfo::ReadExternStatic(_) |
+                    UnsupportedOpInfo::PartialPointerOverwrite(_) | // we make memory uninit instead
+                    UnsupportedOpInfo::ReadPointerAsBytes
+                ) =>
+                    panic!("Error should never be raised by Miri: {kind:?}", kind = e.kind()),
+                Unsupported(
+                    UnsupportedOpInfo::Unsupported(_) |
+                    UnsupportedOpInfo::PartialPointerCopy(_)
+                ) =>
+                    vec![(None, format!("this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support"))],
+                UndefinedBehavior(UndefinedBehaviorInfo::AlignmentCheckFailed { .. })
+                    if ecx.machine.check_alignment == AlignmentCheck::Symbolic
+                =>
+                    vec![
+                        (None, format!("this usually indicates that your program performed an invalid operation and caused Undefined Behavior")),
+                        (None, format!("but due to `-Zmiri-symbolic-alignment-check`, alignment errors can also be false positives")),
+                    ],
+                UndefinedBehavior(_) =>
+                    vec![
+                        (None, format!("this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior")),
+                        (None, format!("see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information")),
+                    ],
+                InvalidProgram(_) | ResourceExhaustion(_) | MachineStop(_) =>
+                    vec![],
+            };
+            (Some(title), helps)
+        }
+    };
+
+    let stacktrace = ecx.generate_stacktrace();
+    let (stacktrace, was_pruned) = prune_stacktrace(stacktrace, &ecx.machine);
+    e.print_backtrace();
+    msg.insert(0, e.to_string());
+    report_msg(
+        DiagLevel::Error,
+        &if let Some(title) = title { format!("{}: {}", title, msg[0]) } else { msg[0].clone() },
+        msg,
+        vec![],
+        helps,
+        &stacktrace,
+        &ecx.machine,
+    );
+
+    // Include a note like `std` does when we omit frames from a backtrace
+    if was_pruned {
+        ecx.tcx.sess.diagnostic().note_without_error(
+            "some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace",
+        );
+    }
+
+    // Debug-dump all locals.
+    for (i, frame) in ecx.active_thread_stack().iter().enumerate() {
+        trace!("-------------------");
+        trace!("Frame {}", i);
+        trace!("    return: {:?}", *frame.return_place);
+        for (i, local) in frame.locals.iter().enumerate() {
+            trace!("    local {}: {:?}", i, local.value);
+        }
+    }
+
+    // Extra output to help debug specific issues.
+    match e.kind() {
+        UndefinedBehavior(UndefinedBehaviorInfo::InvalidUninitBytes(Some((alloc_id, access)))) => {
+            eprintln!(
+                "Uninitialized memory occurred at {alloc_id:?}{range:?}, in this allocation:",
+                range = access.uninit,
+            );
+            eprintln!("{:?}", ecx.dump_alloc(*alloc_id));
+        }
+        _ => {}
+    }
+
+    None
+}
+
+/// Report an error or note (depending on the `error` argument) with the given stacktrace.
+/// Also emits a full stacktrace of the interpreter stack.
+/// We want to present a multi-line span message for some errors. Diagnostics do not support this
+/// directly, so we pass the lines as a `Vec<String>` and display each line after the first with an
+/// additional `span_label` or `note` call.
+fn report_msg<'tcx>(
+    diag_level: DiagLevel,
+    title: &str,
+    span_msg: Vec<String>,
+    notes: Vec<(Option<SpanData>, String)>,
+    helps: Vec<(Option<SpanData>, String)>,
+    stacktrace: &[FrameInfo<'tcx>],
+    machine: &MiriMachine<'_, 'tcx>,
+) {
+    let span = stacktrace.first().map_or(DUMMY_SP, |fi| fi.span);
+    let sess = machine.tcx.sess;
+    let mut err = match diag_level {
+        DiagLevel::Error => sess.struct_span_err(span, title).forget_guarantee(),
+        DiagLevel::Warning => sess.struct_span_warn(span, title),
+        DiagLevel::Note => sess.diagnostic().span_note_diag(span, title),
+    };
+
+    // Show main message.
+    if span != DUMMY_SP {
+        for line in span_msg {
+            err.span_label(span, line);
+        }
+    } else {
+        // Make sure we show the message even when it is a dummy span.
+        for line in span_msg {
+            err.note(&line);
+        }
+        err.note("(no span available)");
+    }
+
+    // Show note and help messages.
+    for (span_data, note) in &notes {
+        if let Some(span_data) = span_data {
+            err.span_note(span_data.span(), note);
+        } else {
+            err.note(note);
+        }
+    }
+    for (span_data, help) in &helps {
+        if let Some(span_data) = span_data {
+            err.span_help(span_data.span(), help);
+        } else {
+            err.help(help);
+        }
+    }
+    if notes.len() + helps.len() > 0 {
+        // Add visual separator before backtrace.
+        err.note("BACKTRACE:");
+    }
+    // Add backtrace
+    for (idx, frame_info) in stacktrace.iter().enumerate() {
+        let is_local = machine.is_local(frame_info);
+        // No span for non-local frames and the first frame (which is the error site).
+        if is_local && idx > 0 {
+            err.span_note(frame_info.span, &frame_info.to_string());
+        } else {
+            err.note(&frame_info.to_string());
+        }
+    }
+
+    err.emit();
+}
+
+impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
+    pub fn emit_diagnostic(&self, e: NonHaltingDiagnostic) {
+        use NonHaltingDiagnostic::*;
+
+        let stacktrace =
+            MiriInterpCx::generate_stacktrace_from_stack(self.threads.active_thread_stack());
+        let (stacktrace, _was_pruned) = prune_stacktrace(stacktrace, self);
+
+        let (title, diag_level) = match e {
+            RejectedIsolatedOp(_) => ("operation rejected by isolation", DiagLevel::Warning),
+            Int2Ptr { .. } => ("integer-to-pointer cast", DiagLevel::Warning),
+            CreatedPointerTag(..)
+            | PoppedPointerTag(..)
+            | CreatedCallId(..)
+            | CreatedAlloc(..)
+            | FreedAlloc(..)
+            | ProgressReport { .. }
+            | WeakMemoryOutdatedLoad => ("tracking was triggered", DiagLevel::Note),
+        };
+
+        let msg = match e {
+            CreatedPointerTag(tag, None) => format!("created tag {tag:?}"),
+            CreatedPointerTag(tag, Some((alloc_id, range))) =>
+                format!("created tag {tag:?} at {alloc_id:?}{range:?}"),
+            PoppedPointerTag(item, tag) =>
+                match tag {
+                    None => format!("popped tracked tag for item {item:?} due to deallocation",),
+                    Some((tag, access)) => {
+                        format!(
+                            "popped tracked tag for item {item:?} due to {access:?} access for {tag:?}",
+                        )
+                    }
+                },
+            CreatedCallId(id) => format!("function call with id {id}"),
+            CreatedAlloc(AllocId(id), size, align, kind) =>
+                format!(
+                    "created {kind} allocation of {size} bytes (alignment {align} bytes) with id {id}",
+                    size = size.bytes(),
+                    align = align.bytes(),
+                ),
+            FreedAlloc(AllocId(id)) => format!("freed allocation with id {id}"),
+            RejectedIsolatedOp(ref op) =>
+                format!("{op} was made to return an error due to isolation"),
+            ProgressReport { .. } =>
+                format!("progress report: current operation being executed is here"),
+            Int2Ptr { .. } => format!("integer-to-pointer cast"),
+            WeakMemoryOutdatedLoad =>
+                format!("weak memory emulation: outdated value returned from load"),
+        };
+
+        let notes = match e {
+            ProgressReport { block_count } => {
+                // It is important that each progress report is slightly different, since
+                // identical diagnostics are being deduplicated.
+                vec![(None, format!("so far, {block_count} basic blocks have been executed"))]
+            }
+            _ => vec![],
+        };
+
+        let helps = match e {
+            Int2Ptr { details: true } =>
+                vec![
+                    (
+                        None,
+                        format!(
+                            "This program is using integer-to-pointer casts or (equivalently) `ptr::from_exposed_addr`,"
+                        ),
+                    ),
+                    (
+                        None,
+                        format!("which means that Miri might miss pointer bugs in this program."),
+                    ),
+                    (
+                        None,
+                        format!(
+                            "See https://doc.rust-lang.org/nightly/std/ptr/fn.from_exposed_addr.html for more details on that operation."
+                        ),
+                    ),
+                    (
+                        None,
+                        format!(
+                            "To ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead."
+                        ),
+                    ),
+                    (
+                        None,
+                        format!(
+                            "You can then pass the `-Zmiri-strict-provenance` flag to Miri, to ensure you are not relying on `from_exposed_addr` semantics."
+                        ),
+                    ),
+                    (
+                        None,
+                        format!(
+                            "Alternatively, the `-Zmiri-permissive-provenance` flag disables this warning."
+                        ),
+                    ),
+                ],
+            _ => vec![],
+        };
+
+        report_msg(diag_level, title, vec![msg], notes, helps, &stacktrace, self);
+    }
+}
+
+impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    fn emit_diagnostic(&self, e: NonHaltingDiagnostic) {
+        let this = self.eval_context_ref();
+        this.machine.emit_diagnostic(e);
+    }
+
+    /// We had a panic in Miri itself, try to print something useful.
+    fn handle_ice(&self) {
+        eprintln!();
+        eprintln!(
+            "Miri caused an ICE during evaluation. Here's the interpreter backtrace at the time of the panic:"
+        );
+        let this = self.eval_context_ref();
+        let stacktrace = this.generate_stacktrace();
+        report_msg(
+            DiagLevel::Note,
+            "the place in the program where the ICE was triggered",
+            vec![],
+            vec![],
+            vec![],
+            &stacktrace,
+            &this.machine,
+        );
+    }
+}
diff --git a/src/tools/miri/src/eval.rs b/src/tools/miri/src/eval.rs
new file mode 100644
index 0000000000000..91a2ac13b1b19
--- /dev/null
+++ b/src/tools/miri/src/eval.rs
@@ -0,0 +1,514 @@
+//! Main evaluator loop and setting up the initial stack frame.
+
+use std::ffi::{OsStr, OsString};
+use std::iter;
+use std::panic::{self, AssertUnwindSafe};
+use std::path::PathBuf;
+use std::thread;
+
+use log::info;
+
+use rustc_data_structures::fx::FxHashSet;
+use rustc_hir::def_id::DefId;
+use rustc_middle::ty::{
+    self,
+    layout::{LayoutCx, LayoutOf},
+    TyCtxt,
+};
+use rustc_target::spec::abi::Abi;
+
+use rustc_session::config::EntryFnType;
+
+use crate::*;
+
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum AlignmentCheck {
+    /// Do not check alignment.
+    None,
+    /// Check alignment "symbolically", i.e., using only the requested alignment for an allocation and not its real base address.
+    Symbolic,
+    /// Check alignment on the actual physical integer address.
+    Int,
+}
+
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum RejectOpWith {
+    /// Isolated op is rejected with an abort of the machine.
+    Abort,
+
+    /// If not Abort, miri returns an error for an isolated op.
+    /// Following options determine if user should be warned about such error.
+    /// Do not print warning about rejected isolated op.
+    NoWarning,
+
+    /// Print a warning about rejected isolated op, with backtrace.
+    Warning,
+
+    /// Print a warning about rejected isolated op, without backtrace.
+    WarningWithoutBacktrace,
+}
+
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum IsolatedOp {
+    /// Reject an op requiring communication with the host. By
+    /// default, miri rejects the op with an abort. If not, it returns
+    /// an error code, and prints a warning about it. Warning levels
+    /// are controlled by `RejectOpWith` enum.
+    Reject(RejectOpWith),
+
+    /// Execute op requiring communication with the host, i.e. disable isolation.
+    Allow,
+}
+
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub enum BacktraceStyle {
+    /// Prints a terser backtrace which ideally only contains relevant information.
+    Short,
+    /// Prints a backtrace with all possible information.
+    Full,
+    /// Prints only the frame that the error occurs in.
+    Off,
+}
+
+/// Configuration needed to spawn a Miri instance.
+#[derive(Clone)]
+pub struct MiriConfig {
+    /// The host environment snapshot to use as basis for what is provided to the interpreted program.
+    /// (This is still subject to isolation as well as `forwarded_env_vars`.)
+    pub env: Vec<(OsString, OsString)>,
+    /// Determine if validity checking is enabled.
+    pub validate: bool,
+    /// Determines if Stacked Borrows is enabled.
+    pub stacked_borrows: bool,
+    /// Controls alignment checking.
+    pub check_alignment: AlignmentCheck,
+    /// Controls function [ABI](Abi) checking.
+    pub check_abi: bool,
+    /// Action for an op requiring communication with the host.
+    pub isolated_op: IsolatedOp,
+    /// Determines if memory leaks should be ignored.
+    pub ignore_leaks: bool,
+    /// Environment variables that should always be forwarded from the host.
+    pub forwarded_env_vars: Vec<String>,
+    /// Command-line arguments passed to the interpreted program.
+    pub args: Vec<String>,
+    /// The seed to use when non-determinism or randomness are required (e.g. ptr-to-int cast, `getrandom()`).
+    pub seed: Option<u64>,
+    /// The stacked borrows pointer ids to report about
+    pub tracked_pointer_tags: FxHashSet<SbTag>,
+    /// The stacked borrows call IDs to report about
+    pub tracked_call_ids: FxHashSet<CallId>,
+    /// The allocation ids to report about.
+    pub tracked_alloc_ids: FxHashSet<AllocId>,
+    /// Determine if data race detection should be enabled
+    pub data_race_detector: bool,
+    /// Determine if weak memory emulation should be enabled. Requires data race detection to be enabled
+    pub weak_memory_emulation: bool,
+    /// Track when an outdated (weak memory) load happens.
+    pub track_outdated_loads: bool,
+    /// Rate of spurious failures for compare_exchange_weak atomic operations,
+    /// between 0.0 and 1.0, defaulting to 0.8 (80% chance of failure).
+    pub cmpxchg_weak_failure_rate: f64,
+    /// If `Some`, enable the `measureme` profiler, writing results to a file
+    /// with the specified prefix.
+    pub measureme_out: Option<String>,
+    /// Panic when unsupported functionality is encountered.
+    pub panic_on_unsupported: bool,
+    /// Which style to use for printing backtraces.
+    pub backtrace_style: BacktraceStyle,
+    /// Which provenance to use for int2ptr casts
+    pub provenance_mode: ProvenanceMode,
+    /// Whether to ignore any output by the program. This is helpful when debugging miri
+    /// as its messages don't get intermingled with the program messages.
+    pub mute_stdout_stderr: bool,
+    /// The probability of the active thread being preempted at the end of each basic block.
+    pub preemption_rate: f64,
+    /// Report the current instruction being executed every N basic blocks.
+    pub report_progress: Option<u32>,
+    /// Whether Stacked Borrows retagging should recurse into fields of datatypes.
+    pub retag_fields: bool,
+    /// The location of a shared object file to load when calling external functions
+    /// FIXME! consider allowing users to specify paths to multiple SO files, or to a directory
+    pub external_so_file: Option<PathBuf>,
+    /// Run a garbage collector for SbTags every N basic blocks.
+    pub gc_interval: u32,
+}
+
+impl Default for MiriConfig {
+    fn default() -> MiriConfig {
+        MiriConfig {
+            env: vec![],
+            validate: true,
+            stacked_borrows: true,
+            check_alignment: AlignmentCheck::Int,
+            check_abi: true,
+            isolated_op: IsolatedOp::Reject(RejectOpWith::Abort),
+            ignore_leaks: false,
+            forwarded_env_vars: vec![],
+            args: vec![],
+            seed: None,
+            tracked_pointer_tags: FxHashSet::default(),
+            tracked_call_ids: FxHashSet::default(),
+            tracked_alloc_ids: FxHashSet::default(),
+            data_race_detector: true,
+            weak_memory_emulation: true,
+            track_outdated_loads: false,
+            cmpxchg_weak_failure_rate: 0.8, // 80%
+            measureme_out: None,
+            panic_on_unsupported: false,
+            backtrace_style: BacktraceStyle::Short,
+            provenance_mode: ProvenanceMode::Default,
+            mute_stdout_stderr: false,
+            preemption_rate: 0.01, // 1%
+            report_progress: None,
+            retag_fields: false,
+            external_so_file: None,
+            gc_interval: 10_000,
+        }
+    }
+}
+
+/// Returns a freshly created `InterpCx`, along with an `MPlaceTy` representing
+/// the location where the return value of the `start` function will be
+/// written to.
+/// Public because this is also used by `priroda`.
+pub fn create_ecx<'mir, 'tcx: 'mir>(
+    tcx: TyCtxt<'tcx>,
+    entry_id: DefId,
+    entry_type: EntryFnType,
+    config: &MiriConfig,
+) -> InterpResult<'tcx, (InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, MPlaceTy<'tcx, Provenance>)>
+{
+    let param_env = ty::ParamEnv::reveal_all();
+    let layout_cx = LayoutCx { tcx, param_env };
+    let mut ecx = InterpCx::new(
+        tcx,
+        rustc_span::source_map::DUMMY_SP,
+        param_env,
+        MiriMachine::new(config, layout_cx),
+    );
+
+    // Some parts of initialization require a full `InterpCx`.
+    MiriMachine::late_init(&mut ecx, config)?;
+
+    // Make sure we have MIR. We check MIR for some stable monomorphic function in libcore.
+    let sentinel = ecx.try_resolve_path(&["core", "ascii", "escape_default"]);
+    if !matches!(sentinel, Some(s) if tcx.is_mir_available(s.def.def_id())) {
+        tcx.sess.fatal(
+            "the current sysroot was built without `-Zalways-encode-mir`, or libcore seems missing. \
+            Use `cargo miri setup` to prepare a sysroot that is suitable for Miri."
+        );
+    }
+
+    // Setup first stack frame.
+    let entry_instance = ty::Instance::mono(tcx, entry_id);
+
+    // First argument is constructed later, because it's skipped if the entry function uses #[start].
+
+    // Second argument (argc): length of `config.args`.
+    let argc = Scalar::from_machine_usize(u64::try_from(config.args.len()).unwrap(), &ecx);
+    // Third argument (`argv`): created from `config.args`.
+    let argv = {
+        // Put each argument in memory, collect pointers.
+        let mut argvs = Vec::<Immediate<Provenance>>::new();
+        for arg in config.args.iter() {
+            // Make space for `0` terminator.
+            let size = u64::try_from(arg.len()).unwrap().checked_add(1).unwrap();
+            let arg_type = tcx.mk_array(tcx.types.u8, size);
+            let arg_place =
+                ecx.allocate(ecx.layout_of(arg_type)?, MiriMemoryKind::Machine.into())?;
+            ecx.write_os_str_to_c_str(OsStr::new(arg), arg_place.ptr, size)?;
+            ecx.mark_immutable(&arg_place);
+            argvs.push(arg_place.to_ref(&ecx));
+        }
+        // Make an array with all these pointers, in the Miri memory.
+        let argvs_layout = ecx.layout_of(
+            tcx.mk_array(tcx.mk_imm_ptr(tcx.types.u8), u64::try_from(argvs.len()).unwrap()),
+        )?;
+        let argvs_place = ecx.allocate(argvs_layout, MiriMemoryKind::Machine.into())?;
+        for (idx, arg) in argvs.into_iter().enumerate() {
+            let place = ecx.mplace_field(&argvs_place, idx)?;
+            ecx.write_immediate(arg, &place.into())?;
+        }
+        ecx.mark_immutable(&argvs_place);
+        // A pointer to that place is the 3rd argument for main.
+        let argv = argvs_place.to_ref(&ecx);
+        // Store `argc` and `argv` for macOS `_NSGetArg{c,v}`.
+        {
+            let argc_place =
+                ecx.allocate(ecx.machine.layouts.isize, MiriMemoryKind::Machine.into())?;
+            ecx.write_scalar(argc, &argc_place.into())?;
+            ecx.mark_immutable(&argc_place);
+            ecx.machine.argc = Some(*argc_place);
+
+            let argv_place = ecx.allocate(
+                ecx.layout_of(tcx.mk_imm_ptr(tcx.types.unit))?,
+                MiriMemoryKind::Machine.into(),
+            )?;
+            ecx.write_immediate(argv, &argv_place.into())?;
+            ecx.mark_immutable(&argv_place);
+            ecx.machine.argv = Some(*argv_place);
+        }
+        // Store command line as UTF-16 for Windows `GetCommandLineW`.
+        {
+            // Construct a command string with all the arguments.
+            let cmd_utf16: Vec<u16> = args_to_utf16_command_string(config.args.iter());
+
+            let cmd_type = tcx.mk_array(tcx.types.u16, u64::try_from(cmd_utf16.len()).unwrap());
+            let cmd_place =
+                ecx.allocate(ecx.layout_of(cmd_type)?, MiriMemoryKind::Machine.into())?;
+            ecx.machine.cmd_line = Some(*cmd_place);
+            // Store the UTF-16 string. We just allocated so we know the bounds are fine.
+            for (idx, &c) in cmd_utf16.iter().enumerate() {
+                let place = ecx.mplace_field(&cmd_place, idx)?;
+                ecx.write_scalar(Scalar::from_u16(c), &place.into())?;
+            }
+            ecx.mark_immutable(&cmd_place);
+        }
+        argv
+    };
+
+    // Return place (in static memory so that it does not count as leak).
+    let ret_place = ecx.allocate(ecx.machine.layouts.isize, MiriMemoryKind::Machine.into())?;
+    // Call start function.
+
+    match entry_type {
+        EntryFnType::Main { .. } => {
+            let start_id = tcx.lang_items().start_fn().unwrap();
+            let main_ret_ty = tcx.fn_sig(entry_id).output();
+            let main_ret_ty = main_ret_ty.no_bound_vars().unwrap();
+            let start_instance = ty::Instance::resolve(
+                tcx,
+                ty::ParamEnv::reveal_all(),
+                start_id,
+                tcx.mk_substs(::std::iter::once(ty::subst::GenericArg::from(main_ret_ty))),
+            )
+            .unwrap()
+            .unwrap();
+
+            let main_ptr = ecx.create_fn_alloc_ptr(FnVal::Instance(entry_instance));
+
+            // Inlining of `DEFAULT` from
+            // https://github.com/rust-lang/rust/blob/master/compiler/rustc_session/src/config/sigpipe.rs.
+            // Alaways using DEFAULT is okay since we don't support signals in Miri anyway.
+            let sigpipe = 2;
+
+            ecx.call_function(
+                start_instance,
+                Abi::Rust,
+                &[
+                    Scalar::from_pointer(main_ptr, &ecx).into(),
+                    argc.into(),
+                    argv,
+                    Scalar::from_u8(sigpipe).into(),
+                ],
+                Some(&ret_place.into()),
+                StackPopCleanup::Root { cleanup: true },
+            )?;
+        }
+        EntryFnType::Start => {
+            ecx.call_function(
+                entry_instance,
+                Abi::Rust,
+                &[argc.into(), argv],
+                Some(&ret_place.into()),
+                StackPopCleanup::Root { cleanup: true },
+            )?;
+        }
+    }
+
+    Ok((ecx, ret_place))
+}
+
+/// Evaluates the entry function specified by `entry_id`.
+/// Returns `Some(return_code)` if program executed completed.
+/// Returns `None` if an evaluation error occurred.
+#[allow(clippy::needless_lifetimes)]
+pub fn eval_entry<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    entry_id: DefId,
+    entry_type: EntryFnType,
+    config: MiriConfig,
+) -> Option<i64> {
+    // Copy setting before we move `config`.
+    let ignore_leaks = config.ignore_leaks;
+
+    let (mut ecx, ret_place) = match create_ecx(tcx, entry_id, entry_type, &config) {
+        Ok(v) => v,
+        Err(err) => {
+            err.print_backtrace();
+            panic!("Miri initialization error: {}", err.kind())
+        }
+    };
+
+    // Perform the main execution.
+    let res: thread::Result<InterpResult<'_, i64>> = panic::catch_unwind(AssertUnwindSafe(|| {
+        // Main loop.
+        loop {
+            match ecx.schedule()? {
+                SchedulingAction::ExecuteStep => {
+                    assert!(ecx.step()?, "a terminated thread was scheduled for execution");
+                }
+                SchedulingAction::ExecuteTimeoutCallback => {
+                    ecx.run_timeout_callback()?;
+                }
+                SchedulingAction::ExecuteDtors => {
+                    // This will either enable the thread again (so we go back
+                    // to `ExecuteStep`), or determine that this thread is done
+                    // for good.
+                    ecx.schedule_next_tls_dtor_for_active_thread()?;
+                }
+                SchedulingAction::Stop => {
+                    break;
+                }
+            }
+        }
+        let return_code = ecx.read_scalar(&ret_place.into())?.to_machine_isize(&ecx)?;
+        Ok(return_code)
+    }));
+    let res = res.unwrap_or_else(|panic_payload| {
+        ecx.handle_ice();
+        panic::resume_unwind(panic_payload)
+    });
+
+    // Machine cleanup. Only do this if all threads have terminated; threads that are still running
+    // might cause Stacked Borrows errors (https://github.com/rust-lang/miri/issues/2396).
+    if ecx.have_all_terminated() {
+        // Even if all threads have terminated, we have to beware of data races since some threads
+        // might not have joined the main thread (https://github.com/rust-lang/miri/issues/2020,
+        // https://github.com/rust-lang/miri/issues/2508).
+        ecx.allow_data_races_all_threads_done();
+        EnvVars::cleanup(&mut ecx).expect("error during env var cleanup");
+    }
+
+    // Process the result.
+    match res {
+        Ok(return_code) => {
+            if !ignore_leaks {
+                // Check for thread leaks.
+                if !ecx.have_all_terminated() {
+                    tcx.sess.err(
+                        "the main thread terminated without waiting for all remaining threads",
+                    );
+                    tcx.sess.note_without_error("pass `-Zmiri-ignore-leaks` to disable this check");
+                    return None;
+                }
+                // Check for memory leaks.
+                info!("Additonal static roots: {:?}", ecx.machine.static_roots);
+                let leaks = ecx.leak_report(&ecx.machine.static_roots);
+                if leaks != 0 {
+                    tcx.sess.err("the evaluated program leaked memory");
+                    tcx.sess.note_without_error("pass `-Zmiri-ignore-leaks` to disable this check");
+                    // Ignore the provided return code - let the reported error
+                    // determine the return code.
+                    return None;
+                }
+            }
+            Some(return_code)
+        }
+        Err(e) => report_error(&ecx, e),
+    }
+}
+
+/// Turns an array of arguments into a Windows command line string.
+///
+/// The string will be UTF-16 encoded and NUL terminated.
+///
+/// Panics if the zeroth argument contains the `"` character because doublequotes
+/// in `argv[0]` cannot be encoded using the standard command line parsing rules.
+///
+/// Further reading:
+/// * [Parsing C++ command-line arguments](https://docs.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=msvc-160#parsing-c-command-line-arguments)
+/// * [The C/C++ Parameter Parsing Rules](https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULES)
+fn args_to_utf16_command_string<I, T>(mut args: I) -> Vec<u16>
+where
+    I: Iterator<Item = T>,
+    T: AsRef<str>,
+{
+    // Parse argv[0]. Slashes aren't escaped. Literal double quotes are not allowed.
+    let mut cmd = {
+        let arg0 = if let Some(arg0) = args.next() {
+            arg0
+        } else {
+            return vec![0];
+        };
+        let arg0 = arg0.as_ref();
+        if arg0.contains('"') {
+            panic!("argv[0] cannot contain a doublequote (\") character");
+        } else {
+            // Always surround argv[0] with quotes.
+            let mut s = String::new();
+            s.push('"');
+            s.push_str(arg0);
+            s.push('"');
+            s
+        }
+    };
+
+    // Build the other arguments.
+    for arg in args {
+        let arg = arg.as_ref();
+        cmd.push(' ');
+        if arg.is_empty() {
+            cmd.push_str("\"\"");
+        } else if !arg.bytes().any(|c| matches!(c, b'"' | b'\t' | b' ')) {
+            // No quote, tab, or space -- no escaping required.
+            cmd.push_str(arg);
+        } else {
+            // Spaces and tabs are escaped by surrounding them in quotes.
+            // Quotes are themselves escaped by using backslashes when in a
+            // quoted block.
+            // Backslashes only need to be escaped when one or more are directly
+            // followed by a quote. Otherwise they are taken literally.
+
+            cmd.push('"');
+            let mut chars = arg.chars().peekable();
+            loop {
+                let mut nslashes = 0;
+                while let Some(&'\\') = chars.peek() {
+                    chars.next();
+                    nslashes += 1;
+                }
+
+                match chars.next() {
+                    Some('"') => {
+                        cmd.extend(iter::repeat('\\').take(nslashes * 2 + 1));
+                        cmd.push('"');
+                    }
+                    Some(c) => {
+                        cmd.extend(iter::repeat('\\').take(nslashes));
+                        cmd.push(c);
+                    }
+                    None => {
+                        cmd.extend(iter::repeat('\\').take(nslashes * 2));
+                        break;
+                    }
+                }
+            }
+            cmd.push('"');
+        }
+    }
+
+    if cmd.contains('\0') {
+        panic!("interior null in command line arguments");
+    }
+    cmd.encode_utf16().chain(iter::once(0)).collect()
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    #[test]
+    #[should_panic(expected = "argv[0] cannot contain a doublequote (\") character")]
+    fn windows_argv0_panic_on_quote() {
+        args_to_utf16_command_string(["\""].iter());
+    }
+    #[test]
+    fn windows_argv0_no_escape() {
+        // Ensure that a trailing backslash in argv[0] is not escaped.
+        let cmd = String::from_utf16_lossy(&args_to_utf16_command_string(
+            [r"C:\Program Files\", "arg1", "arg 2", "arg \" 3"].iter(),
+        ));
+        assert_eq!(cmd.trim_end_matches('\0'), r#""C:\Program Files\" arg1 "arg 2" "arg \" 3""#);
+    }
+}
diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs
new file mode 100644
index 0000000000000..0f0bfa355bdc7
--- /dev/null
+++ b/src/tools/miri/src/helpers.rs
@@ -0,0 +1,1004 @@
+pub mod convert;
+
+use std::mem;
+use std::num::NonZeroUsize;
+use std::time::Duration;
+
+use log::trace;
+
+use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX};
+use rustc_middle::mir;
+use rustc_middle::ty::{
+    self,
+    layout::{LayoutOf, TyAndLayout},
+    List, TyCtxt,
+};
+use rustc_span::{def_id::CrateNum, sym, Span, Symbol};
+use rustc_target::abi::{Align, FieldsShape, Size, Variants};
+use rustc_target::spec::abi::Abi;
+
+use rand::RngCore;
+
+use crate::*;
+
+impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+
+// This mapping should match `decode_error_kind` in
+// <https://github.com/rust-lang/rust/blob/master/library/std/src/sys/unix/mod.rs>.
+const UNIX_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = {
+    use std::io::ErrorKind::*;
+    &[
+        ("E2BIG", ArgumentListTooLong),
+        ("EADDRINUSE", AddrInUse),
+        ("EADDRNOTAVAIL", AddrNotAvailable),
+        ("EBUSY", ResourceBusy),
+        ("ECONNABORTED", ConnectionAborted),
+        ("ECONNREFUSED", ConnectionRefused),
+        ("ECONNRESET", ConnectionReset),
+        ("EDEADLK", Deadlock),
+        ("EDQUOT", FilesystemQuotaExceeded),
+        ("EEXIST", AlreadyExists),
+        ("EFBIG", FileTooLarge),
+        ("EHOSTUNREACH", HostUnreachable),
+        ("EINTR", Interrupted),
+        ("EINVAL", InvalidInput),
+        ("EISDIR", IsADirectory),
+        ("ELOOP", FilesystemLoop),
+        ("ENOENT", NotFound),
+        ("ENOMEM", OutOfMemory),
+        ("ENOSPC", StorageFull),
+        ("ENOSYS", Unsupported),
+        ("EMLINK", TooManyLinks),
+        ("ENAMETOOLONG", InvalidFilename),
+        ("ENETDOWN", NetworkDown),
+        ("ENETUNREACH", NetworkUnreachable),
+        ("ENOTCONN", NotConnected),
+        ("ENOTDIR", NotADirectory),
+        ("ENOTEMPTY", DirectoryNotEmpty),
+        ("EPIPE", BrokenPipe),
+        ("EROFS", ReadOnlyFilesystem),
+        ("ESPIPE", NotSeekable),
+        ("ESTALE", StaleNetworkFileHandle),
+        ("ETIMEDOUT", TimedOut),
+        ("ETXTBSY", ExecutableFileBusy),
+        ("EXDEV", CrossesDevices),
+        // The following have two valid options. We have both for the forwards mapping; only the
+        // first one will be used for the backwards mapping.
+        ("EPERM", PermissionDenied),
+        ("EACCES", PermissionDenied),
+        ("EWOULDBLOCK", WouldBlock),
+        ("EAGAIN", WouldBlock),
+    ]
+};
+
+/// Gets an instance for a path.
+fn try_resolve_did<'tcx>(tcx: TyCtxt<'tcx>, path: &[&str]) -> Option<DefId> {
+    tcx.crates(()).iter().find(|&&krate| tcx.crate_name(krate).as_str() == path[0]).and_then(
+        |krate| {
+            let krate = DefId { krate: *krate, index: CRATE_DEF_INDEX };
+            let mut items = tcx.module_children(krate);
+            let mut path_it = path.iter().skip(1).peekable();
+
+            while let Some(segment) = path_it.next() {
+                for item in mem::take(&mut items).iter() {
+                    if item.ident.name.as_str() == *segment {
+                        if path_it.peek().is_none() {
+                            return Some(item.res.def_id());
+                        }
+
+                        items = tcx.module_children(item.res.def_id());
+                        break;
+                    }
+                }
+            }
+            None
+        },
+    )
+}
+
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    /// Gets an instance for a path; fails gracefully if the path does not exist.
+    fn try_resolve_path(&self, path: &[&str]) -> Option<ty::Instance<'tcx>> {
+        let did = try_resolve_did(self.eval_context_ref().tcx.tcx, path)?;
+        Some(ty::Instance::mono(self.eval_context_ref().tcx.tcx, did))
+    }
+
+    /// Gets an instance for a path.
+    fn resolve_path(&self, path: &[&str]) -> ty::Instance<'tcx> {
+        self.try_resolve_path(path)
+            .unwrap_or_else(|| panic!("failed to find required Rust item: {:?}", path))
+    }
+
+    /// Evaluates the scalar at the specified path. Returns Some(val)
+    /// if the path could be resolved, and None otherwise
+    fn eval_path_scalar(&self, path: &[&str]) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_ref();
+        let instance = this.resolve_path(path);
+        let cid = GlobalId { instance, promoted: None };
+        let const_val = this.eval_to_allocation(cid)?;
+        this.read_scalar(&const_val.into())
+    }
+
+    /// Helper function to get a `libc` constant as a `Scalar`.
+    fn eval_libc(&self, name: &str) -> InterpResult<'tcx, Scalar<Provenance>> {
+        self.eval_path_scalar(&["libc", name])
+    }
+
+    /// Helper function to get a `libc` constant as an `i32`.
+    fn eval_libc_i32(&self, name: &str) -> InterpResult<'tcx, i32> {
+        // TODO: Cache the result.
+        self.eval_libc(name)?.to_i32()
+    }
+
+    /// Helper function to get a `windows` constant as a `Scalar`.
+    fn eval_windows(&self, module: &str, name: &str) -> InterpResult<'tcx, Scalar<Provenance>> {
+        self.eval_context_ref().eval_path_scalar(&["std", "sys", "windows", module, name])
+    }
+
+    /// Helper function to get a `windows` constant as a `u64`.
+    fn eval_windows_u64(&self, module: &str, name: &str) -> InterpResult<'tcx, u64> {
+        // TODO: Cache the result.
+        self.eval_windows(module, name)?.to_u64()
+    }
+
+    /// Helper function to get the `TyAndLayout` of a `libc` type
+    fn libc_ty_layout(&self, name: &str) -> InterpResult<'tcx, TyAndLayout<'tcx>> {
+        let this = self.eval_context_ref();
+        let ty = this.resolve_path(&["libc", name]).ty(*this.tcx, ty::ParamEnv::reveal_all());
+        this.layout_of(ty)
+    }
+
+    /// Helper function to get the `TyAndLayout` of a `windows` type
+    fn windows_ty_layout(&self, name: &str) -> InterpResult<'tcx, TyAndLayout<'tcx>> {
+        let this = self.eval_context_ref();
+        let ty = this
+            .resolve_path(&["std", "sys", "windows", "c", name])
+            .ty(*this.tcx, ty::ParamEnv::reveal_all());
+        this.layout_of(ty)
+    }
+
+    /// Project to the given *named* field of the mplace (which must be a struct or union type).
+    fn mplace_field_named(
+        &self,
+        mplace: &MPlaceTy<'tcx, Provenance>,
+        name: &str,
+    ) -> InterpResult<'tcx, MPlaceTy<'tcx, Provenance>> {
+        let this = self.eval_context_ref();
+        let adt = mplace.layout.ty.ty_adt_def().unwrap();
+        for (idx, field) in adt.non_enum_variant().fields.iter().enumerate() {
+            if field.name.as_str() == name {
+                return this.mplace_field(mplace, idx);
+            }
+        }
+        bug!("No field named {} in type {}", name, mplace.layout.ty);
+    }
+
+    /// Write an int of the appropriate size to `dest`. The target type may be signed or unsigned,
+    /// we try to do the right thing anyway. `i128` can fit all integer types except for `u128` so
+    /// this method is fine for almost all integer types.
+    fn write_int(
+        &mut self,
+        i: impl Into<i128>,
+        dest: &PlaceTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx> {
+        assert!(dest.layout.abi.is_scalar(), "write_int on non-scalar type {}", dest.layout.ty);
+        let val = if dest.layout.abi.is_signed() {
+            Scalar::from_int(i, dest.layout.size)
+        } else {
+            Scalar::from_uint(u64::try_from(i.into()).unwrap(), dest.layout.size)
+        };
+        self.eval_context_mut().write_scalar(val, dest)
+    }
+
+    /// Write the first N fields of the given place.
+    fn write_int_fields(
+        &mut self,
+        values: &[i128],
+        dest: &MPlaceTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        for (idx, &val) in values.iter().enumerate() {
+            let field = this.mplace_field(dest, idx)?;
+            this.write_int(val, &field.into())?;
+        }
+        Ok(())
+    }
+
+    /// Write the given fields of the given place.
+    fn write_int_fields_named(
+        &mut self,
+        values: &[(&str, i128)],
+        dest: &MPlaceTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        for &(name, val) in values.iter() {
+            let field = this.mplace_field_named(dest, name)?;
+            this.write_int(val, &field.into())?;
+        }
+        Ok(())
+    }
+
+    /// Write a 0 of the appropriate size to `dest`.
+    fn write_null(&mut self, dest: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
+        self.write_int(0, dest)
+    }
+
+    /// Test if this pointer equals 0.
+    fn ptr_is_null(&self, ptr: Pointer<Option<Provenance>>) -> InterpResult<'tcx, bool> {
+        Ok(ptr.addr().bytes() == 0)
+    }
+
+    /// Get the `Place` for a local
+    fn local_place(&mut self, local: mir::Local) -> InterpResult<'tcx, PlaceTy<'tcx, Provenance>> {
+        let this = self.eval_context_mut();
+        let place = mir::Place { local, projection: List::empty() };
+        this.eval_place(place)
+    }
+
+    /// Generate some random bytes, and write them to `dest`.
+    fn gen_random(&mut self, ptr: Pointer<Option<Provenance>>, len: u64) -> InterpResult<'tcx> {
+        // Some programs pass in a null pointer and a length of 0
+        // to their platform's random-generation function (e.g. getrandom())
+        // on Linux. For compatibility with these programs, we don't perform
+        // any additional checks - it's okay if the pointer is invalid,
+        // since we wouldn't actually be writing to it.
+        if len == 0 {
+            return Ok(());
+        }
+        let this = self.eval_context_mut();
+
+        let mut data = vec![0; usize::try_from(len).unwrap()];
+
+        if this.machine.communicate() {
+            // Fill the buffer using the host's rng.
+            getrandom::getrandom(&mut data)
+                .map_err(|err| err_unsup_format!("host getrandom failed: {}", err))?;
+        } else {
+            let rng = this.machine.rng.get_mut();
+            rng.fill_bytes(&mut data);
+        }
+
+        this.write_bytes_ptr(ptr, data.iter().copied())
+    }
+
+    /// Call a function: Push the stack frame and pass the arguments.
+    /// For now, arguments must be scalars (so that the caller does not have to know the layout).
+    ///
+    /// If you do not provie a return place, a dangling zero-sized place will be created
+    /// for your convenience.
+    fn call_function(
+        &mut self,
+        f: ty::Instance<'tcx>,
+        caller_abi: Abi,
+        args: &[Immediate<Provenance>],
+        dest: Option<&PlaceTy<'tcx, Provenance>>,
+        stack_pop: StackPopCleanup,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        let param_env = ty::ParamEnv::reveal_all(); // in Miri this is always the param_env we use... and this.param_env is private.
+        let callee_abi = f.ty(*this.tcx, param_env).fn_sig(*this.tcx).abi();
+        if this.machine.enforce_abi && callee_abi != caller_abi {
+            throw_ub_format!(
+                "calling a function with ABI {} using caller ABI {}",
+                callee_abi.name(),
+                caller_abi.name()
+            )
+        }
+
+        // Push frame.
+        let mir = this.load_mir(f.def, None)?;
+        let dest = match dest {
+            Some(dest) => dest.clone(),
+            None => MPlaceTy::fake_alloc_zst(this.layout_of(mir.return_ty())?).into(),
+        };
+        this.push_stack_frame(f, mir, &dest, stack_pop)?;
+
+        // Initialize arguments.
+        let mut callee_args = this.frame().body.args_iter();
+        for arg in args {
+            let callee_arg = this.local_place(
+                callee_args
+                    .next()
+                    .ok_or_else(|| err_ub_format!("callee has fewer arguments than expected"))?,
+            )?;
+            this.write_immediate(*arg, &callee_arg)?;
+        }
+        if callee_args.next().is_some() {
+            throw_ub_format!("callee has more arguments than expected");
+        }
+
+        Ok(())
+    }
+
+    /// Visits the memory covered by `place`, sensitive to freezing: the 2nd parameter
+    /// of `action` will be true if this is frozen, false if this is in an `UnsafeCell`.
+    /// The range is relative to `place`.
+    fn visit_freeze_sensitive(
+        &self,
+        place: &MPlaceTy<'tcx, Provenance>,
+        size: Size,
+        mut action: impl FnMut(AllocRange, bool) -> InterpResult<'tcx>,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_ref();
+        trace!("visit_frozen(place={:?}, size={:?})", *place, size);
+        debug_assert_eq!(
+            size,
+            this.size_and_align_of_mplace(place)?
+                .map(|(size, _)| size)
+                .unwrap_or_else(|| place.layout.size)
+        );
+        // Store how far we proceeded into the place so far. Everything to the left of
+        // this offset has already been handled, in the sense that the frozen parts
+        // have had `action` called on them.
+        let start_addr = place.ptr.addr();
+        let mut cur_addr = start_addr;
+        // Called when we detected an `UnsafeCell` at the given offset and size.
+        // Calls `action` and advances `cur_ptr`.
+        let mut unsafe_cell_action = |unsafe_cell_ptr: &Pointer<Option<Provenance>>,
+                                      unsafe_cell_size: Size| {
+            // We assume that we are given the fields in increasing offset order,
+            // and nothing else changes.
+            let unsafe_cell_addr = unsafe_cell_ptr.addr();
+            assert!(unsafe_cell_addr >= cur_addr);
+            let frozen_size = unsafe_cell_addr - cur_addr;
+            // Everything between the cur_ptr and this `UnsafeCell` is frozen.
+            if frozen_size != Size::ZERO {
+                action(alloc_range(cur_addr - start_addr, frozen_size), /*frozen*/ true)?;
+            }
+            cur_addr += frozen_size;
+            // This `UnsafeCell` is NOT frozen.
+            if unsafe_cell_size != Size::ZERO {
+                action(
+                    alloc_range(cur_addr - start_addr, unsafe_cell_size),
+                    /*frozen*/ false,
+                )?;
+            }
+            cur_addr += unsafe_cell_size;
+            // Done
+            Ok(())
+        };
+        // Run a visitor
+        {
+            let mut visitor = UnsafeCellVisitor {
+                ecx: this,
+                unsafe_cell_action: |place| {
+                    trace!("unsafe_cell_action on {:?}", place.ptr);
+                    // We need a size to go on.
+                    let unsafe_cell_size = this
+                        .size_and_align_of_mplace(place)?
+                        .map(|(size, _)| size)
+                        // for extern types, just cover what we can
+                        .unwrap_or_else(|| place.layout.size);
+                    // Now handle this `UnsafeCell`, unless it is empty.
+                    if unsafe_cell_size != Size::ZERO {
+                        unsafe_cell_action(&place.ptr, unsafe_cell_size)
+                    } else {
+                        Ok(())
+                    }
+                },
+            };
+            visitor.visit_value(place)?;
+        }
+        // The part between the end_ptr and the end of the place is also frozen.
+        // So pretend there is a 0-sized `UnsafeCell` at the end.
+        unsafe_cell_action(&place.ptr.offset(size, this)?, Size::ZERO)?;
+        // Done!
+        return Ok(());
+
+        /// Visiting the memory covered by a `MemPlace`, being aware of
+        /// whether we are inside an `UnsafeCell` or not.
+        struct UnsafeCellVisitor<'ecx, 'mir, 'tcx, F>
+        where
+            F: FnMut(&MPlaceTy<'tcx, Provenance>) -> InterpResult<'tcx>,
+        {
+            ecx: &'ecx MiriInterpCx<'mir, 'tcx>,
+            unsafe_cell_action: F,
+        }
+
+        impl<'ecx, 'mir, 'tcx: 'mir, F> ValueVisitor<'mir, 'tcx, MiriMachine<'mir, 'tcx>>
+            for UnsafeCellVisitor<'ecx, 'mir, 'tcx, F>
+        where
+            F: FnMut(&MPlaceTy<'tcx, Provenance>) -> InterpResult<'tcx>,
+        {
+            type V = MPlaceTy<'tcx, Provenance>;
+
+            #[inline(always)]
+            fn ecx(&self) -> &MiriInterpCx<'mir, 'tcx> {
+                self.ecx
+            }
+
+            // Hook to detect `UnsafeCell`.
+            fn visit_value(&mut self, v: &MPlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
+                trace!("UnsafeCellVisitor: {:?} {:?}", *v, v.layout.ty);
+                let is_unsafe_cell = match v.layout.ty.kind() {
+                    ty::Adt(adt, _) =>
+                        Some(adt.did()) == self.ecx.tcx.lang_items().unsafe_cell_type(),
+                    _ => false,
+                };
+                if is_unsafe_cell {
+                    // We do not have to recurse further, this is an `UnsafeCell`.
+                    (self.unsafe_cell_action)(v)
+                } else if self.ecx.type_is_freeze(v.layout.ty) {
+                    // This is `Freeze`, there cannot be an `UnsafeCell`
+                    Ok(())
+                } else if matches!(v.layout.fields, FieldsShape::Union(..)) {
+                    // A (non-frozen) union. We fall back to whatever the type says.
+                    (self.unsafe_cell_action)(v)
+                } else {
+                    // We want to not actually read from memory for this visit. So, before
+                    // walking this value, we have to make sure it is not a
+                    // `Variants::Multiple`.
+                    match v.layout.variants {
+                        Variants::Multiple { .. } => {
+                            // A multi-variant enum, or generator, or so.
+                            // Treat this like a union: without reading from memory,
+                            // we cannot determine the variant we are in. Reading from
+                            // memory would be subject to Stacked Borrows rules, leading
+                            // to all sorts of "funny" recursion.
+                            // We only end up here if the type is *not* freeze, so we just call the
+                            // `UnsafeCell` action.
+                            (self.unsafe_cell_action)(v)
+                        }
+                        Variants::Single { .. } => {
+                            // Proceed further, try to find where exactly that `UnsafeCell`
+                            // is hiding.
+                            self.walk_value(v)
+                        }
+                    }
+                }
+            }
+
+            // Make sure we visit aggregrates in increasing offset order.
+            fn visit_aggregate(
+                &mut self,
+                place: &MPlaceTy<'tcx, Provenance>,
+                fields: impl Iterator<Item = InterpResult<'tcx, MPlaceTy<'tcx, Provenance>>>,
+            ) -> InterpResult<'tcx> {
+                match place.layout.fields {
+                    FieldsShape::Array { .. } => {
+                        // For the array layout, we know the iterator will yield sorted elements so
+                        // we can avoid the allocation.
+                        self.walk_aggregate(place, fields)
+                    }
+                    FieldsShape::Arbitrary { .. } => {
+                        // Gather the subplaces and sort them before visiting.
+                        let mut places = fields
+                            .collect::<InterpResult<'tcx, Vec<MPlaceTy<'tcx, Provenance>>>>()?;
+                        // we just compare offsets, the abs. value never matters
+                        places.sort_by_key(|place| place.ptr.addr());
+                        self.walk_aggregate(place, places.into_iter().map(Ok))
+                    }
+                    FieldsShape::Union { .. } | FieldsShape::Primitive => {
+                        // Uh, what?
+                        bug!("unions/primitives are not aggregates we should ever visit")
+                    }
+                }
+            }
+
+            fn visit_union(
+                &mut self,
+                _v: &MPlaceTy<'tcx, Provenance>,
+                _fields: NonZeroUsize,
+            ) -> InterpResult<'tcx> {
+                bug!("we should have already handled unions in `visit_value`")
+            }
+        }
+    }
+
+    /// Helper function used inside the shims of foreign functions to check that isolation is
+    /// disabled. It returns an error using the `name` of the foreign function if this is not the
+    /// case.
+    fn check_no_isolation(&self, name: &str) -> InterpResult<'tcx> {
+        if !self.eval_context_ref().machine.communicate() {
+            self.reject_in_isolation(name, RejectOpWith::Abort)?;
+        }
+        Ok(())
+    }
+
+    /// Helper function used inside the shims of foreign functions which reject the op
+    /// when isolation is enabled. It is used to print a warning/backtrace about the rejection.
+    fn reject_in_isolation(&self, op_name: &str, reject_with: RejectOpWith) -> InterpResult<'tcx> {
+        let this = self.eval_context_ref();
+        match reject_with {
+            RejectOpWith::Abort => isolation_abort_error(op_name),
+            RejectOpWith::WarningWithoutBacktrace => {
+                this.tcx
+                    .sess
+                    .warn(&format!("{} was made to return an error due to isolation", op_name));
+                Ok(())
+            }
+            RejectOpWith::Warning => {
+                this.emit_diagnostic(NonHaltingDiagnostic::RejectedIsolatedOp(op_name.to_string()));
+                Ok(())
+            }
+            RejectOpWith::NoWarning => Ok(()), // no warning
+        }
+    }
+
+    /// Helper function used inside the shims of foreign functions to assert that the target OS
+    /// is `target_os`. It panics showing a message with the `name` of the foreign function
+    /// if this is not the case.
+    fn assert_target_os(&self, target_os: &str, name: &str) {
+        assert_eq!(
+            self.eval_context_ref().tcx.sess.target.os,
+            target_os,
+            "`{}` is only available on the `{}` target OS",
+            name,
+            target_os,
+        )
+    }
+
+    /// Helper function used inside the shims of foreign functions to assert that the target OS
+    /// is part of the UNIX family. It panics showing a message with the `name` of the foreign function
+    /// if this is not the case.
+    fn assert_target_os_is_unix(&self, name: &str) {
+        assert!(
+            target_os_is_unix(self.eval_context_ref().tcx.sess.target.os.as_ref()),
+            "`{}` is only available for supported UNIX family targets",
+            name,
+        );
+    }
+
+    /// Get last error variable as a place, lazily allocating thread-local storage for it if
+    /// necessary.
+    fn last_error_place(&mut self) -> InterpResult<'tcx, MPlaceTy<'tcx, Provenance>> {
+        let this = self.eval_context_mut();
+        if let Some(errno_place) = this.active_thread_ref().last_error {
+            Ok(errno_place)
+        } else {
+            // Allocate new place, set initial value to 0.
+            let errno_layout = this.machine.layouts.u32;
+            let errno_place = this.allocate(errno_layout, MiriMemoryKind::Machine.into())?;
+            this.write_scalar(Scalar::from_u32(0), &errno_place.into())?;
+            this.active_thread_mut().last_error = Some(errno_place);
+            Ok(errno_place)
+        }
+    }
+
+    /// Sets the last error variable.
+    fn set_last_error(&mut self, scalar: Scalar<Provenance>) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        let errno_place = this.last_error_place()?;
+        this.write_scalar(scalar, &errno_place.into())
+    }
+
+    /// Gets the last error variable.
+    fn get_last_error(&mut self) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_mut();
+        let errno_place = this.last_error_place()?;
+        this.read_scalar(&errno_place.into())
+    }
+
+    /// This function tries to produce the most similar OS error from the `std::io::ErrorKind`
+    /// as a platform-specific errnum.
+    fn io_error_to_errnum(
+        &self,
+        err_kind: std::io::ErrorKind,
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_ref();
+        let target = &this.tcx.sess.target;
+        if target.families.iter().any(|f| f == "unix") {
+            for &(name, kind) in UNIX_IO_ERROR_TABLE {
+                if err_kind == kind {
+                    return this.eval_libc(name);
+                }
+            }
+            throw_unsup_format!("io error {:?} cannot be translated into a raw os error", err_kind)
+        } else if target.families.iter().any(|f| f == "windows") {
+            // FIXME: we have to finish implementing the Windows equivalent of this.
+            use std::io::ErrorKind::*;
+            this.eval_windows(
+                "c",
+                match err_kind {
+                    NotFound => "ERROR_FILE_NOT_FOUND",
+                    PermissionDenied => "ERROR_ACCESS_DENIED",
+                    _ =>
+                        throw_unsup_format!(
+                            "io error {:?} cannot be translated into a raw os error",
+                            err_kind
+                        ),
+                },
+            )
+        } else {
+            throw_unsup_format!(
+                "converting io::Error into errnum is unsupported for OS {}",
+                target.os
+            )
+        }
+    }
+
+    /// The inverse of `io_error_to_errnum`.
+    #[allow(clippy::needless_return)]
+    fn try_errnum_to_io_error(
+        &self,
+        errnum: Scalar<Provenance>,
+    ) -> InterpResult<'tcx, Option<std::io::ErrorKind>> {
+        let this = self.eval_context_ref();
+        let target = &this.tcx.sess.target;
+        if target.families.iter().any(|f| f == "unix") {
+            let errnum = errnum.to_i32()?;
+            for &(name, kind) in UNIX_IO_ERROR_TABLE {
+                if errnum == this.eval_libc_i32(name)? {
+                    return Ok(Some(kind));
+                }
+            }
+            // Our table is as complete as the mapping in std, so we are okay with saying "that's a
+            // strange one" here.
+            return Ok(None);
+        } else {
+            throw_unsup_format!(
+                "converting errnum into io::Error is unsupported for OS {}",
+                target.os
+            )
+        }
+    }
+
+    /// Sets the last OS error using a `std::io::ErrorKind`.
+    fn set_last_error_from_io_error(&mut self, err_kind: std::io::ErrorKind) -> InterpResult<'tcx> {
+        self.set_last_error(self.io_error_to_errnum(err_kind)?)
+    }
+
+    /// Helper function that consumes an `std::io::Result<T>` and returns an
+    /// `InterpResult<'tcx,T>::Ok` instead. In case the result is an error, this function returns
+    /// `Ok(-1)` and sets the last OS error accordingly.
+    ///
+    /// This function uses `T: From<i32>` instead of `i32` directly because some IO related
+    /// functions return different integer types (like `read`, that returns an `i64`).
+    fn try_unwrap_io_result<T: From<i32>>(
+        &mut self,
+        result: std::io::Result<T>,
+    ) -> InterpResult<'tcx, T> {
+        match result {
+            Ok(ok) => Ok(ok),
+            Err(e) => {
+                self.eval_context_mut().set_last_error_from_io_error(e.kind())?;
+                Ok((-1).into())
+            }
+        }
+    }
+
+    /// Calculates the MPlaceTy given the offset and layout of an access on an operand
+    fn deref_operand_and_offset(
+        &self,
+        op: &OpTy<'tcx, Provenance>,
+        offset: u64,
+        layout: TyAndLayout<'tcx>,
+    ) -> InterpResult<'tcx, MPlaceTy<'tcx, Provenance>> {
+        let this = self.eval_context_ref();
+        let op_place = this.deref_operand(op)?;
+        let offset = Size::from_bytes(offset);
+
+        // Ensure that the access is within bounds.
+        assert!(op_place.layout.size >= offset + layout.size);
+        let value_place = op_place.offset(offset, layout, this)?;
+        Ok(value_place)
+    }
+
+    fn read_scalar_at_offset(
+        &self,
+        op: &OpTy<'tcx, Provenance>,
+        offset: u64,
+        layout: TyAndLayout<'tcx>,
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_ref();
+        let value_place = this.deref_operand_and_offset(op, offset, layout)?;
+        this.read_scalar(&value_place.into())
+    }
+
+    fn write_immediate_at_offset(
+        &mut self,
+        op: &OpTy<'tcx, Provenance>,
+        offset: u64,
+        value: &ImmTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, ()> {
+        let this = self.eval_context_mut();
+        let value_place = this.deref_operand_and_offset(op, offset, value.layout)?;
+        this.write_immediate(**value, &value_place.into())
+    }
+
+    fn write_scalar_at_offset(
+        &mut self,
+        op: &OpTy<'tcx, Provenance>,
+        offset: u64,
+        value: impl Into<Scalar<Provenance>>,
+        layout: TyAndLayout<'tcx>,
+    ) -> InterpResult<'tcx, ()> {
+        self.write_immediate_at_offset(op, offset, &ImmTy::from_scalar(value.into(), layout))
+    }
+
+    /// Parse a `timespec` struct and return it as a `std::time::Duration`. It returns `None`
+    /// if the value in the `timespec` struct is invalid. Some libc functions will return
+    /// `EINVAL` in this case.
+    fn read_timespec(
+        &mut self,
+        tp: &MPlaceTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, Option<Duration>> {
+        let this = self.eval_context_mut();
+        let seconds_place = this.mplace_field(tp, 0)?;
+        let seconds_scalar = this.read_scalar(&seconds_place.into())?;
+        let seconds = seconds_scalar.to_machine_isize(this)?;
+        let nanoseconds_place = this.mplace_field(tp, 1)?;
+        let nanoseconds_scalar = this.read_scalar(&nanoseconds_place.into())?;
+        let nanoseconds = nanoseconds_scalar.to_machine_isize(this)?;
+
+        Ok(try {
+            // tv_sec must be non-negative.
+            let seconds: u64 = seconds.try_into().ok()?;
+            // tv_nsec must be non-negative.
+            let nanoseconds: u32 = nanoseconds.try_into().ok()?;
+            if nanoseconds >= 1_000_000_000 {
+                // tv_nsec must not be greater than 999,999,999.
+                None?
+            }
+            Duration::new(seconds, nanoseconds)
+        })
+    }
+
+    fn read_c_str<'a>(&'a self, ptr: Pointer<Option<Provenance>>) -> InterpResult<'tcx, &'a [u8]>
+    where
+        'tcx: 'a,
+        'mir: 'a,
+    {
+        let this = self.eval_context_ref();
+        let size1 = Size::from_bytes(1);
+
+        // Step 1: determine the length.
+        let mut len = Size::ZERO;
+        loop {
+            // FIXME: We are re-getting the allocation each time around the loop.
+            // Would be nice if we could somehow "extend" an existing AllocRange.
+            let alloc = this.get_ptr_alloc(ptr.offset(len, this)?, size1, Align::ONE)?.unwrap(); // not a ZST, so we will get a result
+            let byte = alloc.read_integer(alloc_range(Size::ZERO, size1))?.to_u8()?;
+            if byte == 0 {
+                break;
+            } else {
+                len += size1;
+            }
+        }
+
+        // Step 2: get the bytes.
+        this.read_bytes_ptr_strip_provenance(ptr, len)
+    }
+
+    fn read_wide_str(&self, mut ptr: Pointer<Option<Provenance>>) -> InterpResult<'tcx, Vec<u16>> {
+        let this = self.eval_context_ref();
+        let size2 = Size::from_bytes(2);
+        let align2 = Align::from_bytes(2).unwrap();
+
+        let mut wchars = Vec::new();
+        loop {
+            // FIXME: We are re-getting the allocation each time around the loop.
+            // Would be nice if we could somehow "extend" an existing AllocRange.
+            let alloc = this.get_ptr_alloc(ptr, size2, align2)?.unwrap(); // not a ZST, so we will get a result
+            let wchar = alloc.read_integer(alloc_range(Size::ZERO, size2))?.to_u16()?;
+            if wchar == 0 {
+                break;
+            } else {
+                wchars.push(wchar);
+                ptr = ptr.offset(size2, this)?;
+            }
+        }
+
+        Ok(wchars)
+    }
+
+    /// Check that the ABI is what we expect.
+    fn check_abi<'a>(&self, abi: Abi, exp_abi: Abi) -> InterpResult<'a, ()> {
+        if self.eval_context_ref().machine.enforce_abi && abi != exp_abi {
+            throw_ub_format!(
+                "calling a function with ABI {} using caller ABI {}",
+                exp_abi.name(),
+                abi.name()
+            )
+        }
+        Ok(())
+    }
+
+    fn frame_in_std(&self) -> bool {
+        let this = self.eval_context_ref();
+        let Some(start_fn) = this.tcx.lang_items().start_fn() else {
+            // no_std situations
+            return false;
+        };
+        let frame = this.frame();
+        // Make an attempt to get at the instance of the function this is inlined from.
+        let instance: Option<_> = try {
+            let scope = frame.current_source_info()?.scope;
+            let inlined_parent = frame.body.source_scopes[scope].inlined_parent_scope?;
+            let source = &frame.body.source_scopes[inlined_parent];
+            source.inlined.expect("inlined_parent_scope points to scope without inline info").0
+        };
+        // Fall back to the instance of the function itself.
+        let instance = instance.unwrap_or(frame.instance);
+        // Now check if this is in the same crate as start_fn.
+        // As a special exception we also allow unit tests from
+        // <https://github.com/rust-lang/miri-test-libstd/tree/master/std_miri_test> to call these
+        // shims.
+        let frame_crate = this.tcx.def_path(instance.def_id()).krate;
+        frame_crate == this.tcx.def_path(start_fn).krate
+            || this.tcx.crate_name(frame_crate).as_str() == "std_miri_test"
+    }
+
+    /// Handler that should be called when unsupported functionality is encountered.
+    /// This function will either panic within the context of the emulated application
+    /// or return an error in the Miri process context
+    ///
+    /// Return value of `Ok(bool)` indicates whether execution should continue.
+    fn handle_unsupported<S: AsRef<str>>(&mut self, error_msg: S) -> InterpResult<'tcx, ()> {
+        let this = self.eval_context_mut();
+        if this.machine.panic_on_unsupported {
+            // message is slightly different here to make automated analysis easier
+            let error_msg = format!("unsupported Miri functionality: {}", error_msg.as_ref());
+            this.start_panic(error_msg.as_ref(), StackPopUnwind::Skip)?;
+            Ok(())
+        } else {
+            throw_unsup_format!("{}", error_msg.as_ref());
+        }
+    }
+
+    fn check_abi_and_shim_symbol_clash(
+        &mut self,
+        abi: Abi,
+        exp_abi: Abi,
+        link_name: Symbol,
+    ) -> InterpResult<'tcx, ()> {
+        self.check_abi(abi, exp_abi)?;
+        if let Some((body, _)) = self.eval_context_mut().lookup_exported_symbol(link_name)? {
+            throw_machine_stop!(TerminationInfo::SymbolShimClashing {
+                link_name,
+                span: body.span.data(),
+            })
+        }
+        Ok(())
+    }
+
+    fn check_shim<'a, const N: usize>(
+        &mut self,
+        abi: Abi,
+        exp_abi: Abi,
+        link_name: Symbol,
+        args: &'a [OpTy<'tcx, Provenance>],
+    ) -> InterpResult<'tcx, &'a [OpTy<'tcx, Provenance>; N]>
+    where
+        &'a [OpTy<'tcx, Provenance>; N]: TryFrom<&'a [OpTy<'tcx, Provenance>]>,
+    {
+        self.check_abi_and_shim_symbol_clash(abi, exp_abi, link_name)?;
+        check_arg_count(args)
+    }
+
+    /// Mark a machine allocation that was just created as immutable.
+    fn mark_immutable(&mut self, mplace: &MemPlace<Provenance>) {
+        let this = self.eval_context_mut();
+        // This got just allocated, so there definitely is a pointer here.
+        let provenance = mplace.ptr.into_pointer_or_addr().unwrap().provenance;
+        this.alloc_mark_immutable(provenance.get_alloc_id().unwrap()).unwrap();
+    }
+
+    fn item_link_name(&self, def_id: DefId) -> Symbol {
+        let tcx = self.eval_context_ref().tcx;
+        match tcx.get_attrs(def_id, sym::link_name).filter_map(|a| a.value_str()).next() {
+            Some(name) => name,
+            None => tcx.item_name(def_id),
+        }
+    }
+}
+
+impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
+    pub fn current_span(&self) -> CurrentSpan<'_, 'mir, 'tcx> {
+        CurrentSpan { current_frame_idx: None, machine: self }
+    }
+}
+
+/// A `CurrentSpan` should be created infrequently (ideally once) per interpreter step. It does
+/// nothing on creation, but when `CurrentSpan::get` is called, searches the current stack for the
+/// topmost frame which corresponds to a local crate, and returns the current span in that frame.
+/// The result of that search is cached so that later calls are approximately free.
+#[derive(Clone)]
+pub struct CurrentSpan<'a, 'mir, 'tcx> {
+    current_frame_idx: Option<usize>,
+    machine: &'a MiriMachine<'mir, 'tcx>,
+}
+
+impl<'a, 'mir: 'a, 'tcx: 'a + 'mir> CurrentSpan<'a, 'mir, 'tcx> {
+    pub fn machine(&self) -> &'a MiriMachine<'mir, 'tcx> {
+        self.machine
+    }
+
+    /// Get the current span, skipping non-local frames.
+    /// This function is backed by a cache, and can be assumed to be very fast.
+    pub fn get(&mut self) -> Span {
+        let idx = self.current_frame_idx();
+        Self::frame_span(self.machine, idx)
+    }
+
+    /// Similar to `CurrentSpan::get`, but retrieves the parent frame of the first non-local frame.
+    /// This is useful when we are processing something which occurs on function-entry and we want
+    /// to point at the call to the function, not the function definition generally.
+    pub fn get_parent(&mut self) -> Span {
+        let idx = self.current_frame_idx();
+        Self::frame_span(self.machine, idx.wrapping_sub(1))
+    }
+
+    fn frame_span(machine: &MiriMachine<'_, '_>, idx: usize) -> Span {
+        machine
+            .threads
+            .active_thread_stack()
+            .get(idx)
+            .map(Frame::current_span)
+            .unwrap_or(rustc_span::DUMMY_SP)
+    }
+
+    fn current_frame_idx(&mut self) -> usize {
+        *self
+            .current_frame_idx
+            .get_or_insert_with(|| Self::compute_current_frame_index(self.machine))
+    }
+
+    // Find the position of the inner-most frame which is part of the crate being
+    // compiled/executed, part of the Cargo workspace, and is also not #[track_caller].
+    #[inline(never)]
+    fn compute_current_frame_index(machine: &MiriMachine<'_, '_>) -> usize {
+        machine
+            .threads
+            .active_thread_stack()
+            .iter()
+            .enumerate()
+            .rev()
+            .find_map(|(idx, frame)| {
+                let def_id = frame.instance.def_id();
+                if (def_id.is_local() || machine.local_crates.contains(&def_id.krate))
+                    && !frame.instance.def.requires_caller_location(machine.tcx)
+                {
+                    Some(idx)
+                } else {
+                    None
+                }
+            })
+            .unwrap_or(0)
+    }
+}
+
+/// Check that the number of args is what we expect.
+pub fn check_arg_count<'a, 'tcx, const N: usize>(
+    args: &'a [OpTy<'tcx, Provenance>],
+) -> InterpResult<'tcx, &'a [OpTy<'tcx, Provenance>; N]>
+where
+    &'a [OpTy<'tcx, Provenance>; N]: TryFrom<&'a [OpTy<'tcx, Provenance>]>,
+{
+    if let Ok(ops) = args.try_into() {
+        return Ok(ops);
+    }
+    throw_ub_format!("incorrect number of arguments: got {}, expected {}", args.len(), N)
+}
+
+pub fn isolation_abort_error<'tcx>(name: &str) -> InterpResult<'tcx> {
+    throw_machine_stop!(TerminationInfo::UnsupportedInIsolation(format!(
+        "{} not available when isolation is enabled",
+        name,
+    )))
+}
+
+/// Retrieve the list of local crates that should have been passed by cargo-miri in
+/// MIRI_LOCAL_CRATES and turn them into `CrateNum`s.
+pub fn get_local_crates(tcx: TyCtxt<'_>) -> Vec<CrateNum> {
+    // Convert the local crate names from the passed-in config into CrateNums so that they can
+    // be looked up quickly during execution
+    let local_crate_names = std::env::var("MIRI_LOCAL_CRATES")
+        .map(|crates| crates.split(',').map(|krate| krate.to_string()).collect::<Vec<_>>())
+        .unwrap_or_default();
+    let mut local_crates = Vec::new();
+    for &crate_num in tcx.crates(()) {
+        let name = tcx.crate_name(crate_num);
+        let name = name.as_str();
+        if local_crate_names.iter().any(|local_name| local_name == name) {
+            local_crates.push(crate_num);
+        }
+    }
+    local_crates
+}
+
+/// Helper function used inside the shims of foreign functions to check that
+/// `target_os` is a supported UNIX OS.
+pub fn target_os_is_unix(target_os: &str) -> bool {
+    matches!(target_os, "linux" | "macos" | "freebsd" | "android")
+}
diff --git a/src/tools/miri/src/helpers/convert.rs b/src/tools/miri/src/helpers/convert.rs
new file mode 100644
index 0000000000000..4506fe47495d0
--- /dev/null
+++ b/src/tools/miri/src/helpers/convert.rs
@@ -0,0 +1,49 @@
+use implementations::NarrowerThan;
+
+/// Replacement for `as` casts going from wide integer to narrower integer.
+///
+/// # Example
+///
+/// ```ignore
+/// let x = 99_u64;
+/// let lo = x.truncate::<u16>();
+/// // lo is of type u16, equivalent to `x as u16`.
+/// ```
+pub(crate) trait Truncate: Sized {
+    fn truncate<To>(self) -> To
+    where
+        To: NarrowerThan<Self>,
+    {
+        NarrowerThan::truncate_from(self)
+    }
+}
+
+impl Truncate for u16 {}
+impl Truncate for u32 {}
+impl Truncate for u64 {}
+impl Truncate for u128 {}
+
+mod implementations {
+    pub(crate) trait NarrowerThan<T> {
+        fn truncate_from(wide: T) -> Self;
+    }
+
+    macro_rules! impl_narrower_than {
+        ($(NarrowerThan<{$($ty:ty),*}> for $self:ty)*) => {
+            $($(
+                impl NarrowerThan<$ty> for $self {
+                    fn truncate_from(wide: $ty) -> Self {
+                        wide as Self
+                    }
+                }
+            )*)*
+        };
+    }
+
+    impl_narrower_than! {
+        NarrowerThan<{u128, u64, u32, u16}> for u8
+        NarrowerThan<{u128, u64, u32}> for u16
+        NarrowerThan<{u128, u64}> for u32
+        NarrowerThan<{u128}> for u64
+    }
+}
diff --git a/src/tools/miri/src/intptrcast.rs b/src/tools/miri/src/intptrcast.rs
new file mode 100644
index 0000000000000..b9e5def8fa7cb
--- /dev/null
+++ b/src/tools/miri/src/intptrcast.rs
@@ -0,0 +1,260 @@
+use std::cell::RefCell;
+use std::cmp::max;
+use std::collections::hash_map::Entry;
+
+use log::trace;
+use rand::Rng;
+
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_span::Span;
+use rustc_target::abi::{HasDataLayout, Size};
+
+use crate::*;
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum ProvenanceMode {
+    /// We support `expose_addr`/`from_exposed_addr` via "wildcard" provenance.
+    /// However, we want on `from_exposed_addr` to alert the user of the precision loss.
+    Default,
+    /// Like `Default`, but without the warning.
+    Permissive,
+    /// We error on `from_exposed_addr`, ensuring no precision loss.
+    Strict,
+}
+
+pub type GlobalState = RefCell<GlobalStateInner>;
+
+#[derive(Clone, Debug)]
+pub struct GlobalStateInner {
+    /// This is used as a map between the address of each allocation and its `AllocId`.
+    /// It is always sorted
+    int_to_ptr_map: Vec<(u64, AllocId)>,
+    /// The base address for each allocation.  We cannot put that into
+    /// `AllocExtra` because function pointers also have a base address, and
+    /// they do not have an `AllocExtra`.
+    /// This is the inverse of `int_to_ptr_map`.
+    base_addr: FxHashMap<AllocId, u64>,
+    /// Whether an allocation has been exposed or not. This cannot be put
+    /// into `AllocExtra` for the same reason as `base_addr`.
+    exposed: FxHashSet<AllocId>,
+    /// This is used as a memory address when a new pointer is casted to an integer. It
+    /// is always larger than any address that was previously made part of a block.
+    next_base_addr: u64,
+    /// The provenance to use for int2ptr casts
+    provenance_mode: ProvenanceMode,
+}
+
+impl GlobalStateInner {
+    pub fn new(config: &MiriConfig) -> Self {
+        GlobalStateInner {
+            int_to_ptr_map: Vec::default(),
+            base_addr: FxHashMap::default(),
+            exposed: FxHashSet::default(),
+            next_base_addr: STACK_ADDR,
+            provenance_mode: config.provenance_mode,
+        }
+    }
+}
+
+impl<'mir, 'tcx> GlobalStateInner {
+    // Returns the exposed `AllocId` that corresponds to the specified addr,
+    // or `None` if the addr is out of bounds
+    fn alloc_id_from_addr(ecx: &MiriInterpCx<'mir, 'tcx>, addr: u64) -> Option<AllocId> {
+        let global_state = ecx.machine.intptrcast.borrow();
+        assert!(global_state.provenance_mode != ProvenanceMode::Strict);
+
+        let pos = global_state.int_to_ptr_map.binary_search_by_key(&addr, |(addr, _)| *addr);
+
+        // Determine the in-bounds provenance for this pointer.
+        // (This is only called on an actual access, so in-bounds is the only possible kind of provenance.)
+        let alloc_id = match pos {
+            Ok(pos) => Some(global_state.int_to_ptr_map[pos].1),
+            Err(0) => None,
+            Err(pos) => {
+                // This is the largest of the adresses smaller than `int`,
+                // i.e. the greatest lower bound (glb)
+                let (glb, alloc_id) = global_state.int_to_ptr_map[pos - 1];
+                // This never overflows because `addr >= glb`
+                let offset = addr - glb;
+                // If the offset exceeds the size of the allocation, don't use this `alloc_id`.
+                let size = ecx.get_alloc_info(alloc_id).0;
+                if offset <= size.bytes() { Some(alloc_id) } else { None }
+            }
+        }?;
+
+        // We only use this provenance if it has been exposed, *and* is still live.
+        if global_state.exposed.contains(&alloc_id) {
+            let (_size, _align, kind) = ecx.get_alloc_info(alloc_id);
+            match kind {
+                AllocKind::LiveData | AllocKind::Function | AllocKind::VTable => {
+                    return Some(alloc_id);
+                }
+                AllocKind::Dead => {}
+            }
+        }
+
+        None
+    }
+
+    pub fn expose_ptr(
+        ecx: &mut MiriInterpCx<'mir, 'tcx>,
+        alloc_id: AllocId,
+        sb: SbTag,
+    ) -> InterpResult<'tcx> {
+        let global_state = ecx.machine.intptrcast.get_mut();
+        // In strict mode, we don't need this, so we can save some cycles by not tracking it.
+        if global_state.provenance_mode != ProvenanceMode::Strict {
+            trace!("Exposing allocation id {alloc_id:?}");
+            global_state.exposed.insert(alloc_id);
+            if ecx.machine.stacked_borrows.is_some() {
+                ecx.expose_tag(alloc_id, sb)?;
+            }
+        }
+        Ok(())
+    }
+
+    pub fn ptr_from_addr_transmute(
+        _ecx: &MiriInterpCx<'mir, 'tcx>,
+        addr: u64,
+    ) -> Pointer<Option<Provenance>> {
+        trace!("Transmuting {:#x} to a pointer", addr);
+
+        // We consider transmuted pointers to be "invalid" (`None` provenance).
+        Pointer::new(None, Size::from_bytes(addr))
+    }
+
+    pub fn ptr_from_addr_cast(
+        ecx: &MiriInterpCx<'mir, 'tcx>,
+        addr: u64,
+    ) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
+        trace!("Casting {:#x} to a pointer", addr);
+
+        let global_state = ecx.machine.intptrcast.borrow();
+
+        match global_state.provenance_mode {
+            ProvenanceMode::Default => {
+                // The first time this happens at a particular location, print a warning.
+                thread_local! {
+                    // `Span` is non-`Send`, so we use a thread-local instead.
+                    static PAST_WARNINGS: RefCell<FxHashSet<Span>> = RefCell::default();
+                }
+                PAST_WARNINGS.with_borrow_mut(|past_warnings| {
+                    let first = past_warnings.is_empty();
+                    if past_warnings.insert(ecx.cur_span()) {
+                        // Newly inserted, so first time we see this span.
+                        ecx.emit_diagnostic(NonHaltingDiagnostic::Int2Ptr { details: first });
+                    }
+                });
+            }
+            ProvenanceMode::Strict => {
+                throw_machine_stop!(TerminationInfo::Int2PtrWithStrictProvenance);
+            }
+            ProvenanceMode::Permissive => {}
+        }
+
+        // This is how wildcard pointers are born.
+        Ok(Pointer::new(Some(Provenance::Wildcard), Size::from_bytes(addr)))
+    }
+
+    fn alloc_base_addr(ecx: &MiriInterpCx<'mir, 'tcx>, alloc_id: AllocId) -> u64 {
+        let mut global_state = ecx.machine.intptrcast.borrow_mut();
+        let global_state = &mut *global_state;
+
+        match global_state.base_addr.entry(alloc_id) {
+            Entry::Occupied(entry) => *entry.get(),
+            Entry::Vacant(entry) => {
+                // There is nothing wrong with a raw pointer being cast to an integer only after
+                // it became dangling.  Hence we allow dead allocations.
+                let (size, align, _kind) = ecx.get_alloc_info(alloc_id);
+
+                // This allocation does not have a base address yet, pick one.
+                // Leave some space to the previous allocation, to give it some chance to be less aligned.
+                let slack = {
+                    let mut rng = ecx.machine.rng.borrow_mut();
+                    // This means that `(global_state.next_base_addr + slack) % 16` is uniformly distributed.
+                    rng.gen_range(0..16)
+                };
+                // From next_base_addr + slack, round up to adjust for alignment.
+                let base_addr = global_state.next_base_addr.checked_add(slack).unwrap();
+                let base_addr = Self::align_addr(base_addr, align.bytes());
+                entry.insert(base_addr);
+                trace!(
+                    "Assigning base address {:#x} to allocation {:?} (size: {}, align: {}, slack: {})",
+                    base_addr,
+                    alloc_id,
+                    size.bytes(),
+                    align.bytes(),
+                    slack,
+                );
+
+                // Remember next base address.  If this allocation is zero-sized, leave a gap
+                // of at least 1 to avoid two allocations having the same base address.
+                // (The logic in `alloc_id_from_addr` assumes unique addresses, and different
+                // function/vtable pointers need to be distinguishable!)
+                global_state.next_base_addr = base_addr.checked_add(max(size.bytes(), 1)).unwrap();
+                // Given that `next_base_addr` increases in each allocation, pushing the
+                // corresponding tuple keeps `int_to_ptr_map` sorted
+                global_state.int_to_ptr_map.push((base_addr, alloc_id));
+
+                base_addr
+            }
+        }
+    }
+
+    /// Convert a relative (tcx) pointer to an absolute address.
+    pub fn rel_ptr_to_addr(ecx: &MiriInterpCx<'mir, 'tcx>, ptr: Pointer<AllocId>) -> u64 {
+        let (alloc_id, offset) = ptr.into_parts(); // offset is relative (AllocId provenance)
+        let base_addr = GlobalStateInner::alloc_base_addr(ecx, alloc_id);
+
+        // Add offset with the right kind of pointer-overflowing arithmetic.
+        let dl = ecx.data_layout();
+        dl.overflowing_offset(base_addr, offset.bytes()).0
+    }
+
+    /// When a pointer is used for a memory access, this computes where in which allocation the
+    /// access is going.
+    pub fn abs_ptr_to_rel(
+        ecx: &MiriInterpCx<'mir, 'tcx>,
+        ptr: Pointer<Provenance>,
+    ) -> Option<(AllocId, Size)> {
+        let (tag, addr) = ptr.into_parts(); // addr is absolute (Tag provenance)
+
+        let alloc_id = if let Provenance::Concrete { alloc_id, .. } = tag {
+            alloc_id
+        } else {
+            // A wildcard pointer.
+            GlobalStateInner::alloc_id_from_addr(ecx, addr.bytes())?
+        };
+
+        let base_addr = GlobalStateInner::alloc_base_addr(ecx, alloc_id);
+
+        // Wrapping "addr - base_addr"
+        let dl = ecx.data_layout();
+        #[allow(clippy::cast_possible_wrap)] // we want to wrap here
+        let neg_base_addr = (base_addr as i64).wrapping_neg();
+        Some((
+            alloc_id,
+            Size::from_bytes(dl.overflowing_signed_offset(addr.bytes(), neg_base_addr).0),
+        ))
+    }
+
+    /// Shifts `addr` to make it aligned with `align` by rounding `addr` to the smallest multiple
+    /// of `align` that is larger or equal to `addr`
+    fn align_addr(addr: u64, align: u64) -> u64 {
+        match addr % align {
+            0 => addr,
+            rem => addr.checked_add(align).unwrap() - rem,
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_align_addr() {
+        assert_eq!(GlobalStateInner::align_addr(37, 4), 40);
+        assert_eq!(GlobalStateInner::align_addr(44, 4), 44);
+    }
+}
diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs
new file mode 100644
index 0000000000000..6006d6c89dbca
--- /dev/null
+++ b/src/tools/miri/src/lib.rs
@@ -0,0 +1,126 @@
+#![feature(rustc_private)]
+#![feature(map_first_last)]
+#![feature(map_try_insert)]
+#![feature(never_type)]
+#![feature(try_blocks)]
+#![feature(io_error_more)]
+#![feature(int_log)]
+#![feature(variant_count)]
+#![feature(yeet_expr)]
+#![feature(is_some_with)]
+#![feature(nonzero_ops)]
+#![feature(local_key_cell_methods)]
+// Configure clippy and other lints
+#![allow(
+    clippy::collapsible_else_if,
+    clippy::collapsible_if,
+    clippy::comparison_chain,
+    clippy::enum_variant_names,
+    clippy::field_reassign_with_default,
+    clippy::manual_map,
+    clippy::new_without_default,
+    clippy::single_match,
+    clippy::useless_format,
+    clippy::derive_partial_eq_without_eq,
+    clippy::derive_hash_xor_eq,
+    clippy::too_many_arguments,
+    clippy::type_complexity,
+    clippy::single_element_loop,
+    clippy::needless_return,
+    // We are not implementing queries here so it's fine
+    rustc::potential_query_instability
+)]
+#![warn(
+    rust_2018_idioms,
+    clippy::cast_possible_wrap, // unsigned -> signed
+    clippy::cast_sign_loss, // signed -> unsigned
+    clippy::cast_lossless,
+    clippy::cast_possible_truncation,
+)]
+
+extern crate rustc_apfloat;
+extern crate rustc_ast;
+#[macro_use]
+extern crate rustc_middle;
+extern crate rustc_const_eval;
+extern crate rustc_data_structures;
+extern crate rustc_hir;
+extern crate rustc_index;
+extern crate rustc_session;
+extern crate rustc_span;
+extern crate rustc_target;
+
+mod clock;
+mod concurrency;
+mod diagnostics;
+mod eval;
+mod helpers;
+mod intptrcast;
+mod machine;
+mod mono_hash_map;
+mod operator;
+mod range_map;
+mod shims;
+mod stacked_borrows;
+mod tag_gc;
+
+// Establish a "crate-wide prelude": we often import `crate::*`.
+
+// Make all those symbols available in the same place as our own.
+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::intrinsics::EvalContextExt as _;
+pub use crate::shims::os_str::EvalContextExt as _;
+pub use crate::shims::panic::{CatchUnwindData, EvalContextExt as _};
+pub use crate::shims::time::EvalContextExt as _;
+pub use crate::shims::tls::{EvalContextExt as _, TlsData};
+pub use crate::shims::EvalContextExt as _;
+
+pub use crate::clock::{Clock, Instant};
+pub use crate::concurrency::{
+    data_race::{
+        AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd,
+        EvalContextExt as DataRaceEvalContextExt,
+    },
+    sync::{CondvarId, EvalContextExt as SyncEvalContextExt, MutexId, RwLockId},
+    thread::{
+        EvalContextExt as ThreadsEvalContextExt, SchedulingAction, ThreadId, ThreadManager,
+        ThreadState, Time,
+    },
+};
+pub use crate::diagnostics::{
+    report_error, EvalContextExt as DiagnosticsEvalContextExt, NonHaltingDiagnostic,
+    TerminationInfo,
+};
+pub use crate::eval::{
+    create_ecx, eval_entry, AlignmentCheck, BacktraceStyle, IsolatedOp, MiriConfig, RejectOpWith,
+};
+pub use crate::helpers::{CurrentSpan, EvalContextExt as HelpersEvalContextExt};
+pub use crate::intptrcast::ProvenanceMode;
+pub use crate::machine::{
+    AllocExtra, FrameData, MiriInterpCx, MiriInterpCxExt, MiriMachine, MiriMemoryKind, Provenance,
+    ProvenanceExtra, NUM_CPUS, PAGE_SIZE, STACK_ADDR, STACK_SIZE,
+};
+pub use crate::mono_hash_map::MonoHashMap;
+pub use crate::operator::EvalContextExt as OperatorEvalContextExt;
+pub use crate::range_map::RangeMap;
+pub use crate::stacked_borrows::{
+    CallId, EvalContextExt as StackedBorEvalContextExt, Item, Permission, SbTag, Stack, Stacks,
+};
+pub use crate::tag_gc::EvalContextExt as _;
+
+/// Insert rustc arguments at the beginning of the argument list that Miri wants to be
+/// set per default, for maximal validation power.
+pub const MIRI_DEFAULT_ARGS: &[&str] = &[
+    "-Zalways-encode-mir",
+    "-Zmir-emit-retag",
+    "-Zmir-opt-level=0",
+    "--cfg=miri",
+    "-Cdebug-assertions=on",
+    "-Zextra-const-ub-checks",
+];
diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs
new file mode 100644
index 0000000000000..dcfb998c5645d
--- /dev/null
+++ b/src/tools/miri/src/machine.rs
@@ -0,0 +1,1077 @@
+//! Global machine state as well as implementation of the interpreter engine
+//! `Machine` trait.
+
+use std::borrow::Cow;
+use std::cell::RefCell;
+use std::fmt;
+
+use rand::rngs::StdRng;
+use rand::SeedableRng;
+
+use rustc_ast::ast::Mutability;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+#[allow(unused)]
+use rustc_data_structures::static_assert_size;
+use rustc_middle::{
+    mir,
+    ty::{
+        self,
+        layout::{LayoutCx, LayoutError, LayoutOf, TyAndLayout},
+        Instance, Ty, TyCtxt, TypeAndMut,
+    },
+};
+use rustc_span::def_id::{CrateNum, DefId};
+use rustc_span::Symbol;
+use rustc_target::abi::Size;
+use rustc_target::spec::abi::Abi;
+
+use crate::{
+    concurrency::{data_race, weak_memory},
+    shims::unix::FileHandler,
+    *,
+};
+
+// Some global facts about the emulated machine.
+pub const PAGE_SIZE: u64 = 4 * 1024; // FIXME: adjust to target architecture
+pub const STACK_ADDR: u64 = 32 * PAGE_SIZE; // not really about the "stack", but where we start assigning integer addresses to allocations
+pub const STACK_SIZE: u64 = 16 * PAGE_SIZE; // whatever
+pub const NUM_CPUS: u64 = 1;
+
+/// Extra data stored with each stack frame
+pub struct FrameData<'tcx> {
+    /// Extra data for Stacked Borrows.
+    pub stacked_borrows: Option<stacked_borrows::FrameExtra>,
+
+    /// If this is Some(), then this is a special "catch unwind" frame (the frame of `try_fn`
+    /// called by `try`). When this frame is popped during unwinding a panic,
+    /// we stop unwinding, use the `CatchUnwindData` to handle catching.
+    pub catch_unwind: Option<CatchUnwindData<'tcx>>,
+
+    /// If `measureme` profiling is enabled, holds timing information
+    /// for the start of this frame. When we finish executing this frame,
+    /// we use this to register a completed event with `measureme`.
+    pub timing: Option<measureme::DetachedTiming>,
+}
+
+impl<'tcx> std::fmt::Debug for FrameData<'tcx> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        // Omitting `timing`, it does not support `Debug`.
+        let FrameData { stacked_borrows, catch_unwind, timing: _ } = self;
+        f.debug_struct("FrameData")
+            .field("stacked_borrows", stacked_borrows)
+            .field("catch_unwind", catch_unwind)
+            .finish()
+    }
+}
+
+/// Extra memory kinds
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum MiriMemoryKind {
+    /// `__rust_alloc` memory.
+    Rust,
+    /// `malloc` memory.
+    C,
+    /// Windows `HeapAlloc` memory.
+    WinHeap,
+    /// Memory for args, errno, and other parts of the machine-managed environment.
+    /// This memory may leak.
+    Machine,
+    /// Memory allocated by the runtime (e.g. env vars). Separate from `Machine`
+    /// because we clean it up and leak-check it.
+    Runtime,
+    /// Globals copied from `tcx`.
+    /// This memory may leak.
+    Global,
+    /// Memory for extern statics.
+    /// This memory may leak.
+    ExternStatic,
+    /// Memory for thread-local statics.
+    /// This memory may leak.
+    Tls,
+}
+
+impl From<MiriMemoryKind> for MemoryKind<MiriMemoryKind> {
+    #[inline(always)]
+    fn from(kind: MiriMemoryKind) -> MemoryKind<MiriMemoryKind> {
+        MemoryKind::Machine(kind)
+    }
+}
+
+impl MayLeak for MiriMemoryKind {
+    #[inline(always)]
+    fn may_leak(self) -> bool {
+        use self::MiriMemoryKind::*;
+        match self {
+            Rust | C | WinHeap | Runtime => false,
+            Machine | Global | ExternStatic | Tls => true,
+        }
+    }
+}
+
+impl fmt::Display for MiriMemoryKind {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        use self::MiriMemoryKind::*;
+        match self {
+            Rust => write!(f, "Rust heap"),
+            C => write!(f, "C heap"),
+            WinHeap => write!(f, "Windows heap"),
+            Machine => write!(f, "machine-managed memory"),
+            Runtime => write!(f, "language runtime memory"),
+            Global => write!(f, "global (static or const)"),
+            ExternStatic => write!(f, "extern static"),
+            Tls => write!(f, "thread-local static"),
+        }
+    }
+}
+
+/// Pointer provenance.
+#[derive(Debug, Clone, Copy)]
+pub enum Provenance {
+    Concrete {
+        alloc_id: AllocId,
+        /// Stacked Borrows tag.
+        sb: SbTag,
+    },
+    Wildcard,
+}
+
+// This needs to be `Eq`+`Hash` because the `Machine` trait needs that because validity checking
+// *might* be recursive and then it has to track which places have already been visited.
+// However, comparing provenance is meaningless, since `Wildcard` might be any provenance -- and of
+// course we don't actually do recursive checking.
+// We could change `RefTracking` to strip provenance for its `seen` set but that type is generic so that is quite annoying.
+// Instead owe add the required instances but make them panic.
+impl PartialEq for Provenance {
+    fn eq(&self, _other: &Self) -> bool {
+        panic!("Provenance must not be compared")
+    }
+}
+impl Eq for Provenance {}
+impl std::hash::Hash for Provenance {
+    fn hash<H: std::hash::Hasher>(&self, _state: &mut H) {
+        panic!("Provenance must not be hashed")
+    }
+}
+
+/// The "extra" information a pointer has over a regular AllocId.
+#[derive(Copy, Clone, PartialEq)]
+pub enum ProvenanceExtra {
+    Concrete(SbTag),
+    Wildcard,
+}
+
+#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
+static_assert_size!(Pointer<Provenance>, 24);
+// FIXME: this would with in 24bytes but layout optimizations are not smart enough
+// #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
+//static_assert_size!(Pointer<Option<Provenance>>, 24);
+#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
+static_assert_size!(Scalar<Provenance>, 32);
+
+impl interpret::Provenance for Provenance {
+    /// We use absolute addresses in the `offset` of a `Pointer<Provenance>`.
+    const OFFSET_IS_ADDR: bool = true;
+
+    /// We cannot err on partial overwrites, it happens too often in practice (due to unions).
+    const ERR_ON_PARTIAL_PTR_OVERWRITE: bool = false;
+
+    fn fmt(ptr: &Pointer<Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let (prov, addr) = ptr.into_parts(); // address is absolute
+        write!(f, "{:#x}", addr.bytes())?;
+
+        match prov {
+            Provenance::Concrete { alloc_id, sb } => {
+                // Forward `alternate` flag to `alloc_id` printing.
+                if f.alternate() {
+                    write!(f, "[{:#?}]", alloc_id)?;
+                } else {
+                    write!(f, "[{:?}]", alloc_id)?;
+                }
+                // Print Stacked Borrows tag.
+                write!(f, "{:?}", sb)?;
+            }
+            Provenance::Wildcard => {
+                write!(f, "[wildcard]")?;
+            }
+        }
+
+        Ok(())
+    }
+
+    fn get_alloc_id(self) -> Option<AllocId> {
+        match self {
+            Provenance::Concrete { alloc_id, .. } => Some(alloc_id),
+            Provenance::Wildcard => None,
+        }
+    }
+
+    fn join(left: Option<Self>, right: Option<Self>) -> Option<Self> {
+        match (left, right) {
+            // If both are the *same* concrete tag, that is the result.
+            (
+                Some(Provenance::Concrete { alloc_id: left_alloc, sb: left_sb }),
+                Some(Provenance::Concrete { alloc_id: right_alloc, sb: right_sb }),
+            ) if left_alloc == right_alloc && left_sb == right_sb => left,
+            // If one side is a wildcard, the best possible outcome is that it is equal to the other
+            // one, and we use that.
+            (Some(Provenance::Wildcard), o) | (o, Some(Provenance::Wildcard)) => o,
+            // Otherwise, fall back to `None`.
+            _ => None,
+        }
+    }
+}
+
+impl fmt::Debug for ProvenanceExtra {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            ProvenanceExtra::Concrete(pid) => write!(f, "{pid:?}"),
+            ProvenanceExtra::Wildcard => write!(f, "<wildcard>"),
+        }
+    }
+}
+
+impl ProvenanceExtra {
+    pub fn and_then<T>(self, f: impl FnOnce(SbTag) -> Option<T>) -> Option<T> {
+        match self {
+            ProvenanceExtra::Concrete(pid) => f(pid),
+            ProvenanceExtra::Wildcard => None,
+        }
+    }
+}
+
+/// Extra per-allocation data
+#[derive(Debug, Clone)]
+pub struct AllocExtra {
+    /// Stacked Borrows state is only added if it is enabled.
+    pub stacked_borrows: Option<stacked_borrows::AllocExtra>,
+    /// Data race detection via the use of a vector-clock,
+    ///  this is only added if it is enabled.
+    pub data_race: Option<data_race::AllocExtra>,
+    /// Weak memory emulation via the use of store buffers,
+    ///  this is only added if it is enabled.
+    pub weak_memory: Option<weak_memory::AllocExtra>,
+}
+
+/// Precomputed layouts of primitive types
+pub struct PrimitiveLayouts<'tcx> {
+    pub unit: TyAndLayout<'tcx>,
+    pub i8: TyAndLayout<'tcx>,
+    pub i16: TyAndLayout<'tcx>,
+    pub i32: TyAndLayout<'tcx>,
+    pub isize: TyAndLayout<'tcx>,
+    pub u8: TyAndLayout<'tcx>,
+    pub u16: TyAndLayout<'tcx>,
+    pub u32: TyAndLayout<'tcx>,
+    pub usize: TyAndLayout<'tcx>,
+    pub bool: TyAndLayout<'tcx>,
+    pub mut_raw_ptr: TyAndLayout<'tcx>,   // *mut ()
+    pub const_raw_ptr: TyAndLayout<'tcx>, // *const ()
+}
+
+impl<'mir, 'tcx: 'mir> PrimitiveLayouts<'tcx> {
+    fn new(layout_cx: LayoutCx<'tcx, TyCtxt<'tcx>>) -> Result<Self, LayoutError<'tcx>> {
+        let tcx = layout_cx.tcx;
+        let mut_raw_ptr = tcx.mk_ptr(TypeAndMut { ty: tcx.types.unit, mutbl: Mutability::Mut });
+        let const_raw_ptr = tcx.mk_ptr(TypeAndMut { ty: tcx.types.unit, mutbl: Mutability::Not });
+        Ok(Self {
+            unit: layout_cx.layout_of(tcx.mk_unit())?,
+            i8: layout_cx.layout_of(tcx.types.i8)?,
+            i16: layout_cx.layout_of(tcx.types.i16)?,
+            i32: layout_cx.layout_of(tcx.types.i32)?,
+            isize: layout_cx.layout_of(tcx.types.isize)?,
+            u8: layout_cx.layout_of(tcx.types.u8)?,
+            u16: layout_cx.layout_of(tcx.types.u16)?,
+            u32: layout_cx.layout_of(tcx.types.u32)?,
+            usize: layout_cx.layout_of(tcx.types.usize)?,
+            bool: layout_cx.layout_of(tcx.types.bool)?,
+            mut_raw_ptr: layout_cx.layout_of(mut_raw_ptr)?,
+            const_raw_ptr: layout_cx.layout_of(const_raw_ptr)?,
+        })
+    }
+}
+
+/// The machine itself.
+pub struct MiriMachine<'mir, 'tcx> {
+    // We carry a copy of the global `TyCtxt` for convenience, so methods taking just `&Evaluator` have `tcx` access.
+    pub tcx: TyCtxt<'tcx>,
+
+    /// Stacked Borrows global data.
+    pub stacked_borrows: Option<stacked_borrows::GlobalState>,
+
+    /// Data race detector global data.
+    pub data_race: Option<data_race::GlobalState>,
+
+    /// Ptr-int-cast module global data.
+    pub intptrcast: intptrcast::GlobalState,
+
+    /// Environment variables set by `setenv`.
+    /// Miri does not expose env vars from the host to the emulated program.
+    pub(crate) env_vars: EnvVars<'tcx>,
+
+    /// Program arguments (`Option` because we can only initialize them after creating the ecx).
+    /// These are *pointers* to argc/argv because macOS.
+    /// We also need the full command line as one string because of Windows.
+    pub(crate) argc: Option<MemPlace<Provenance>>,
+    pub(crate) argv: Option<MemPlace<Provenance>>,
+    pub(crate) cmd_line: Option<MemPlace<Provenance>>,
+
+    /// TLS state.
+    pub(crate) tls: TlsData<'tcx>,
+
+    /// What should Miri do when an op requires communicating with the host,
+    /// such as accessing host env vars, random number generation, and
+    /// file system access.
+    pub(crate) isolated_op: IsolatedOp,
+
+    /// Whether to enforce the validity invariant.
+    pub(crate) validate: bool,
+
+    /// Whether to enforce [ABI](Abi) of function calls.
+    pub(crate) enforce_abi: bool,
+
+    /// The table of file descriptors.
+    pub(crate) file_handler: shims::unix::FileHandler,
+    /// The table of directory descriptors.
+    pub(crate) dir_handler: shims::unix::DirHandler,
+
+    /// This machine's monotone clock.
+    pub(crate) clock: Clock,
+
+    /// The set of threads.
+    pub(crate) threads: ThreadManager<'mir, 'tcx>,
+
+    /// Precomputed `TyLayout`s for primitive data types that are commonly used inside Miri.
+    pub(crate) layouts: PrimitiveLayouts<'tcx>,
+
+    /// Allocations that are considered roots of static memory (that may leak).
+    pub(crate) static_roots: Vec<AllocId>,
+
+    /// The `measureme` profiler used to record timing information about
+    /// the emulated program.
+    profiler: Option<measureme::Profiler>,
+    /// Used with `profiler` to cache the `StringId`s for event names
+    /// uesd with `measureme`.
+    string_cache: FxHashMap<String, measureme::StringId>,
+
+    /// Cache of `Instance` exported under the given `Symbol` name.
+    /// `None` means no `Instance` exported under the given name is found.
+    pub(crate) exported_symbols_cache: FxHashMap<Symbol, Option<Instance<'tcx>>>,
+
+    /// Whether to raise a panic in the context of the evaluated process when unsupported
+    /// functionality is encountered. If `false`, an error is propagated in the Miri application context
+    /// instead (default behavior)
+    pub(crate) panic_on_unsupported: bool,
+
+    /// Equivalent setting as RUST_BACKTRACE on encountering an error.
+    pub(crate) backtrace_style: BacktraceStyle,
+
+    /// Crates which are considered local for the purposes of error reporting.
+    pub(crate) local_crates: Vec<CrateNum>,
+
+    /// Mapping extern static names to their base pointer.
+    extern_statics: FxHashMap<Symbol, Pointer<Provenance>>,
+
+    /// The random number generator used for resolving non-determinism.
+    /// Needs to be queried by ptr_to_int, hence needs interior mutability.
+    pub(crate) rng: RefCell<StdRng>,
+
+    /// The allocation IDs to report when they are being allocated
+    /// (helps for debugging memory leaks and use after free bugs).
+    tracked_alloc_ids: FxHashSet<AllocId>,
+
+    /// Controls whether alignment of memory accesses is being checked.
+    pub(crate) check_alignment: AlignmentCheck,
+
+    /// Failure rate of compare_exchange_weak, between 0.0 and 1.0
+    pub(crate) cmpxchg_weak_failure_rate: f64,
+
+    /// Corresponds to -Zmiri-mute-stdout-stderr and doesn't write the output but acts as if it succeeded.
+    pub(crate) mute_stdout_stderr: bool,
+
+    /// Whether weak memory emulation is enabled
+    pub(crate) weak_memory: bool,
+
+    /// The probability of the active thread being preempted at the end of each basic block.
+    pub(crate) preemption_rate: f64,
+
+    /// If `Some`, we will report the current stack every N basic blocks.
+    pub(crate) report_progress: Option<u32>,
+    // The total number of blocks that have been executed.
+    pub(crate) basic_block_count: u64,
+
+    /// Handle of the optional shared object file for external functions.
+    #[cfg(unix)]
+    pub external_so_lib: Option<(libloading::Library, std::path::PathBuf)>,
+
+    /// Run a garbage collector for SbTags every N basic blocks.
+    pub(crate) gc_interval: u32,
+    /// The number of blocks that passed since the last SbTag GC pass.
+    pub(crate) since_gc: u32,
+}
+
+impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
+    pub(crate) fn new(config: &MiriConfig, layout_cx: LayoutCx<'tcx, TyCtxt<'tcx>>) -> Self {
+        let local_crates = helpers::get_local_crates(layout_cx.tcx);
+        let layouts =
+            PrimitiveLayouts::new(layout_cx).expect("Couldn't get layouts of primitive types");
+        let profiler = config.measureme_out.as_ref().map(|out| {
+            measureme::Profiler::new(out).expect("Couldn't create `measureme` profiler")
+        });
+        let rng = StdRng::seed_from_u64(config.seed.unwrap_or(0));
+        let stacked_borrows = config.stacked_borrows.then(|| {
+            RefCell::new(stacked_borrows::GlobalStateInner::new(
+                config.tracked_pointer_tags.clone(),
+                config.tracked_call_ids.clone(),
+                config.retag_fields,
+            ))
+        });
+        let data_race = config.data_race_detector.then(|| data_race::GlobalState::new(config));
+        MiriMachine {
+            tcx: layout_cx.tcx,
+            stacked_borrows,
+            data_race,
+            intptrcast: RefCell::new(intptrcast::GlobalStateInner::new(config)),
+            // `env_vars` depends on a full interpreter so we cannot properly initialize it yet.
+            env_vars: EnvVars::default(),
+            argc: None,
+            argv: None,
+            cmd_line: None,
+            tls: TlsData::default(),
+            isolated_op: config.isolated_op,
+            validate: config.validate,
+            enforce_abi: config.check_abi,
+            file_handler: FileHandler::new(config.mute_stdout_stderr),
+            dir_handler: Default::default(),
+            layouts,
+            threads: ThreadManager::default(),
+            static_roots: Vec::new(),
+            profiler,
+            string_cache: Default::default(),
+            exported_symbols_cache: FxHashMap::default(),
+            panic_on_unsupported: config.panic_on_unsupported,
+            backtrace_style: config.backtrace_style,
+            local_crates,
+            extern_statics: FxHashMap::default(),
+            rng: RefCell::new(rng),
+            tracked_alloc_ids: config.tracked_alloc_ids.clone(),
+            check_alignment: config.check_alignment,
+            cmpxchg_weak_failure_rate: config.cmpxchg_weak_failure_rate,
+            mute_stdout_stderr: config.mute_stdout_stderr,
+            weak_memory: config.weak_memory_emulation,
+            preemption_rate: config.preemption_rate,
+            report_progress: config.report_progress,
+            basic_block_count: 0,
+            clock: Clock::new(config.isolated_op == IsolatedOp::Allow),
+            #[cfg(unix)]
+            external_so_lib: config.external_so_file.as_ref().map(|lib_file_path| {
+                let target_triple = layout_cx.tcx.sess.opts.target_triple.triple();
+                // Check if host target == the session target.
+                if env!("TARGET") != target_triple {
+                    panic!(
+                        "calling external C functions in linked .so file requires host and target to be the same: host={}, target={}",
+                        env!("TARGET"),
+                        target_triple,
+                    );
+                }
+                // Note: it is the user's responsibility to provide a correct SO file.
+                // WATCH OUT: If an invalid/incorrect SO file is specified, this can cause
+                // undefined behaviour in Miri itself!
+                (
+                    unsafe {
+                        libloading::Library::new(lib_file_path)
+                            .expect("failed to read specified extern shared object file")
+                    },
+                    lib_file_path.clone(),
+                )
+            }),
+            gc_interval: config.gc_interval,
+            since_gc: 0,
+        }
+    }
+
+    pub(crate) fn late_init(
+        this: &mut MiriInterpCx<'mir, 'tcx>,
+        config: &MiriConfig,
+    ) -> InterpResult<'tcx> {
+        EnvVars::init(this, config)?;
+        MiriMachine::init_extern_statics(this)?;
+        ThreadManager::init(this);
+        Ok(())
+    }
+
+    fn add_extern_static(
+        this: &mut MiriInterpCx<'mir, 'tcx>,
+        name: &str,
+        ptr: Pointer<Option<Provenance>>,
+    ) {
+        // This got just allocated, so there definitely is a pointer here.
+        let ptr = ptr.into_pointer_or_addr().unwrap();
+        this.machine.extern_statics.try_insert(Symbol::intern(name), ptr).unwrap();
+    }
+
+    fn alloc_extern_static(
+        this: &mut MiriInterpCx<'mir, 'tcx>,
+        name: &str,
+        val: ImmTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx> {
+        let place = this.allocate(val.layout, MiriMemoryKind::ExternStatic.into())?;
+        this.write_immediate(*val, &place.into())?;
+        Self::add_extern_static(this, name, place.ptr);
+        Ok(())
+    }
+
+    /// Sets up the "extern statics" for this machine.
+    fn init_extern_statics(this: &mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx> {
+        match this.tcx.sess.target.os.as_ref() {
+            "linux" => {
+                // "environ"
+                Self::add_extern_static(
+                    this,
+                    "environ",
+                    this.machine.env_vars.environ.unwrap().ptr,
+                );
+                // A couple zero-initialized pointer-sized extern statics.
+                // Most of them are for weak symbols, which we all set to null (indicating that the
+                // symbol is not supported, and triggering fallback code which ends up calling a
+                // syscall that we do support).
+                for name in &["__cxa_thread_atexit_impl", "getrandom", "statx", "__clock_gettime64"]
+                {
+                    let val = ImmTy::from_int(0, this.machine.layouts.usize);
+                    Self::alloc_extern_static(this, name, val)?;
+                }
+            }
+            "freebsd" => {
+                // "environ"
+                Self::add_extern_static(
+                    this,
+                    "environ",
+                    this.machine.env_vars.environ.unwrap().ptr,
+                );
+            }
+            "android" => {
+                // "signal"
+                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.create_fn_alloc_ptr(FnVal::Other(dlsym));
+                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.
+                // Most of them are for weak symbols, which we all set to null (indicating that the
+                // symbol is not supported, and triggering fallback code.)
+                for name in &["bsd_signal"] {
+                    let val = ImmTy::from_int(0, this.machine.layouts.usize);
+                    Self::alloc_extern_static(this, name, val)?;
+                }
+            }
+            "windows" => {
+                // "_tls_used"
+                // This is some obscure hack that is part of the Windows TLS story. It's a `u8`.
+                let val = ImmTy::from_int(0, this.machine.layouts.u8);
+                Self::alloc_extern_static(this, "_tls_used", val)?;
+            }
+            _ => {} // No "extern statics" supported on this target
+        }
+        Ok(())
+    }
+
+    pub(crate) fn communicate(&self) -> bool {
+        self.isolated_op == IsolatedOp::Allow
+    }
+
+    /// Check whether the stack frame that this `FrameInfo` refers to is part of a local crate.
+    pub(crate) fn is_local(&self, frame: &FrameInfo<'_>) -> bool {
+        let def_id = frame.instance.def_id();
+        def_id.is_local() || self.local_crates.contains(&def_id.krate)
+    }
+}
+
+/// A rustc InterpCx for Miri.
+pub type MiriInterpCx<'mir, 'tcx> = InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>;
+
+/// A little trait that's useful to be inherited by extension traits.
+pub trait MiriInterpCxExt<'mir, 'tcx> {
+    fn eval_context_ref<'a>(&'a self) -> &'a MiriInterpCx<'mir, 'tcx>;
+    fn eval_context_mut<'a>(&'a mut self) -> &'a mut MiriInterpCx<'mir, 'tcx>;
+}
+impl<'mir, 'tcx> MiriInterpCxExt<'mir, 'tcx> for MiriInterpCx<'mir, 'tcx> {
+    #[inline(always)]
+    fn eval_context_ref(&self) -> &MiriInterpCx<'mir, 'tcx> {
+        self
+    }
+    #[inline(always)]
+    fn eval_context_mut(&mut self) -> &mut MiriInterpCx<'mir, 'tcx> {
+        self
+    }
+}
+
+/// Machine hook implementations.
+impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
+    type MemoryKind = MiriMemoryKind;
+    type ExtraFnVal = Dlsym;
+
+    type FrameExtra = FrameData<'tcx>;
+    type AllocExtra = AllocExtra;
+
+    type Provenance = Provenance;
+    type ProvenanceExtra = ProvenanceExtra;
+
+    type MemoryMap = MonoHashMap<
+        AllocId,
+        (MemoryKind<MiriMemoryKind>, Allocation<Provenance, Self::AllocExtra>),
+    >;
+
+    const GLOBAL_KIND: Option<MiriMemoryKind> = Some(MiriMemoryKind::Global);
+
+    const PANIC_ON_ALLOC_FAIL: bool = false;
+
+    #[inline(always)]
+    fn enforce_alignment(ecx: &MiriInterpCx<'mir, 'tcx>) -> bool {
+        ecx.machine.check_alignment != AlignmentCheck::None
+    }
+
+    #[inline(always)]
+    fn use_addr_for_alignment_check(ecx: &MiriInterpCx<'mir, 'tcx>) -> bool {
+        ecx.machine.check_alignment == AlignmentCheck::Int
+    }
+
+    #[inline(always)]
+    fn enforce_validity(ecx: &MiriInterpCx<'mir, 'tcx>) -> bool {
+        ecx.machine.validate
+    }
+
+    #[inline(always)]
+    fn enforce_abi(ecx: &MiriInterpCx<'mir, 'tcx>) -> bool {
+        ecx.machine.enforce_abi
+    }
+
+    #[inline(always)]
+    fn checked_binop_checks_overflow(ecx: &MiriInterpCx<'mir, 'tcx>) -> bool {
+        ecx.tcx.sess.overflow_checks()
+    }
+
+    #[inline(always)]
+    fn find_mir_or_eval_fn(
+        ecx: &mut MiriInterpCx<'mir, 'tcx>,
+        instance: ty::Instance<'tcx>,
+        abi: Abi,
+        args: &[OpTy<'tcx, Provenance>],
+        dest: &PlaceTy<'tcx, Provenance>,
+        ret: Option<mir::BasicBlock>,
+        unwind: StackPopUnwind,
+    ) -> InterpResult<'tcx, Option<(&'mir mir::Body<'tcx>, ty::Instance<'tcx>)>> {
+        ecx.find_mir_or_eval_fn(instance, abi, args, dest, ret, unwind)
+    }
+
+    #[inline(always)]
+    fn call_extra_fn(
+        ecx: &mut MiriInterpCx<'mir, 'tcx>,
+        fn_val: Dlsym,
+        abi: Abi,
+        args: &[OpTy<'tcx, Provenance>],
+        dest: &PlaceTy<'tcx, Provenance>,
+        ret: Option<mir::BasicBlock>,
+        _unwind: StackPopUnwind,
+    ) -> InterpResult<'tcx> {
+        ecx.call_dlsym(fn_val, abi, args, dest, ret)
+    }
+
+    #[inline(always)]
+    fn call_intrinsic(
+        ecx: &mut MiriInterpCx<'mir, 'tcx>,
+        instance: ty::Instance<'tcx>,
+        args: &[OpTy<'tcx, Provenance>],
+        dest: &PlaceTy<'tcx, Provenance>,
+        ret: Option<mir::BasicBlock>,
+        unwind: StackPopUnwind,
+    ) -> InterpResult<'tcx> {
+        ecx.call_intrinsic(instance, args, dest, ret, unwind)
+    }
+
+    #[inline(always)]
+    fn assert_panic(
+        ecx: &mut MiriInterpCx<'mir, 'tcx>,
+        msg: &mir::AssertMessage<'tcx>,
+        unwind: Option<mir::BasicBlock>,
+    ) -> InterpResult<'tcx> {
+        ecx.assert_panic(msg, unwind)
+    }
+
+    #[inline(always)]
+    fn abort(_ecx: &mut MiriInterpCx<'mir, 'tcx>, msg: String) -> InterpResult<'tcx, !> {
+        throw_machine_stop!(TerminationInfo::Abort(msg))
+    }
+
+    #[inline(always)]
+    fn binary_ptr_op(
+        ecx: &MiriInterpCx<'mir, 'tcx>,
+        bin_op: mir::BinOp,
+        left: &ImmTy<'tcx, Provenance>,
+        right: &ImmTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, (Scalar<Provenance>, bool, Ty<'tcx>)> {
+        ecx.binary_ptr_op(bin_op, left, right)
+    }
+
+    fn thread_local_static_base_pointer(
+        ecx: &mut MiriInterpCx<'mir, 'tcx>,
+        def_id: DefId,
+    ) -> InterpResult<'tcx, Pointer<Provenance>> {
+        ecx.get_or_create_thread_local_alloc(def_id)
+    }
+
+    fn extern_static_base_pointer(
+        ecx: &MiriInterpCx<'mir, 'tcx>,
+        def_id: DefId,
+    ) -> InterpResult<'tcx, Pointer<Provenance>> {
+        let link_name = ecx.item_link_name(def_id);
+        if let Some(&ptr) = ecx.machine.extern_statics.get(&link_name) {
+            // Various parts of the engine rely on `get_alloc_info` for size and alignment
+            // information. That uses the type information of this static.
+            // Make sure it matches the Miri allocation for this.
+            let Provenance::Concrete { alloc_id, .. } = ptr.provenance else {
+                panic!("extern_statics cannot contain wildcards")
+            };
+            let (shim_size, shim_align, _kind) = ecx.get_alloc_info(alloc_id);
+            let extern_decl_layout =
+                ecx.tcx.layout_of(ty::ParamEnv::empty().and(ecx.tcx.type_of(def_id))).unwrap();
+            if extern_decl_layout.size != shim_size || extern_decl_layout.align.abi != shim_align {
+                throw_unsup_format!(
+                    "`extern` static `{name}` from crate `{krate}` has been declared \
+                    with a size of {decl_size} bytes and alignment of {decl_align} bytes, \
+                    but Miri emulates it via an extern static shim \
+                    with a size of {shim_size} bytes and alignment of {shim_align} bytes",
+                    name = ecx.tcx.def_path_str(def_id),
+                    krate = ecx.tcx.crate_name(def_id.krate),
+                    decl_size = extern_decl_layout.size.bytes(),
+                    decl_align = extern_decl_layout.align.abi.bytes(),
+                    shim_size = shim_size.bytes(),
+                    shim_align = shim_align.bytes(),
+                )
+            }
+            Ok(ptr)
+        } else {
+            throw_unsup_format!(
+                "`extern` static `{name}` from crate `{krate}` is not supported by Miri",
+                name = ecx.tcx.def_path_str(def_id),
+                krate = ecx.tcx.crate_name(def_id.krate),
+            )
+        }
+    }
+
+    fn adjust_allocation<'b>(
+        ecx: &MiriInterpCx<'mir, 'tcx>,
+        id: AllocId,
+        alloc: Cow<'b, Allocation>,
+        kind: Option<MemoryKind<Self::MemoryKind>>,
+    ) -> InterpResult<'tcx, Cow<'b, Allocation<Self::Provenance, Self::AllocExtra>>> {
+        let kind = kind.expect("we set our STATIC_KIND so this cannot be None");
+        if ecx.machine.tracked_alloc_ids.contains(&id) {
+            ecx.emit_diagnostic(NonHaltingDiagnostic::CreatedAlloc(
+                id,
+                alloc.size(),
+                alloc.align,
+                kind,
+            ));
+        }
+
+        let alloc = alloc.into_owned();
+        let stacks = ecx.machine.stacked_borrows.as_ref().map(|stacked_borrows| {
+            Stacks::new_allocation(
+                id,
+                alloc.size(),
+                stacked_borrows,
+                kind,
+                ecx.machine.current_span(),
+            )
+        });
+        let race_alloc = ecx.machine.data_race.as_ref().map(|data_race| {
+            data_race::AllocExtra::new_allocation(
+                data_race,
+                &ecx.machine.threads,
+                alloc.size(),
+                kind,
+            )
+        });
+        let buffer_alloc = ecx.machine.weak_memory.then(weak_memory::AllocExtra::new_allocation);
+        let alloc: Allocation<Provenance, Self::AllocExtra> = alloc.adjust_from_tcx(
+            &ecx.tcx,
+            AllocExtra {
+                stacked_borrows: stacks.map(RefCell::new),
+                data_race: race_alloc,
+                weak_memory: buffer_alloc,
+            },
+            |ptr| ecx.global_base_pointer(ptr),
+        )?;
+        Ok(Cow::Owned(alloc))
+    }
+
+    fn adjust_alloc_base_pointer(
+        ecx: &MiriInterpCx<'mir, 'tcx>,
+        ptr: Pointer<AllocId>,
+    ) -> Pointer<Provenance> {
+        if cfg!(debug_assertions) {
+            // The machine promises to never call us on thread-local or extern statics.
+            let alloc_id = ptr.provenance;
+            match ecx.tcx.try_get_global_alloc(alloc_id) {
+                Some(GlobalAlloc::Static(def_id)) if ecx.tcx.is_thread_local_static(def_id) => {
+                    panic!("adjust_alloc_base_pointer called on thread-local static")
+                }
+                Some(GlobalAlloc::Static(def_id)) if ecx.tcx.is_foreign_item(def_id) => {
+                    panic!("adjust_alloc_base_pointer called on extern static")
+                }
+                _ => {}
+            }
+        }
+        let absolute_addr = intptrcast::GlobalStateInner::rel_ptr_to_addr(ecx, ptr);
+        let sb_tag = if let Some(stacked_borrows) = &ecx.machine.stacked_borrows {
+            stacked_borrows.borrow_mut().base_ptr_tag(ptr.provenance, &ecx.machine)
+        } else {
+            // Value does not matter, SB is disabled
+            SbTag::default()
+        };
+        Pointer::new(
+            Provenance::Concrete { alloc_id: ptr.provenance, sb: sb_tag },
+            Size::from_bytes(absolute_addr),
+        )
+    }
+
+    #[inline(always)]
+    fn ptr_from_addr_cast(
+        ecx: &MiriInterpCx<'mir, 'tcx>,
+        addr: u64,
+    ) -> InterpResult<'tcx, Pointer<Option<Self::Provenance>>> {
+        intptrcast::GlobalStateInner::ptr_from_addr_cast(ecx, addr)
+    }
+
+    fn expose_ptr(
+        ecx: &mut InterpCx<'mir, 'tcx, Self>,
+        ptr: Pointer<Self::Provenance>,
+    ) -> InterpResult<'tcx> {
+        match ptr.provenance {
+            Provenance::Concrete { alloc_id, sb } =>
+                intptrcast::GlobalStateInner::expose_ptr(ecx, alloc_id, sb),
+            Provenance::Wildcard => {
+                // No need to do anything for wildcard pointers as
+                // their provenances have already been previously exposed.
+                Ok(())
+            }
+        }
+    }
+
+    /// Convert a pointer with provenance into an allocation-offset pair,
+    /// or a `None` with an absolute address if that conversion is not possible.
+    fn ptr_get_alloc(
+        ecx: &MiriInterpCx<'mir, 'tcx>,
+        ptr: Pointer<Self::Provenance>,
+    ) -> Option<(AllocId, Size, Self::ProvenanceExtra)> {
+        let rel = intptrcast::GlobalStateInner::abs_ptr_to_rel(ecx, ptr);
+
+        rel.map(|(alloc_id, size)| {
+            let sb = match ptr.provenance {
+                Provenance::Concrete { sb, .. } => ProvenanceExtra::Concrete(sb),
+                Provenance::Wildcard => ProvenanceExtra::Wildcard,
+            };
+            (alloc_id, size, sb)
+        })
+    }
+
+    #[inline(always)]
+    fn before_memory_read(
+        _tcx: TyCtxt<'tcx>,
+        machine: &Self,
+        alloc_extra: &AllocExtra,
+        (alloc_id, prov_extra): (AllocId, Self::ProvenanceExtra),
+        range: AllocRange,
+    ) -> InterpResult<'tcx> {
+        if let Some(data_race) = &alloc_extra.data_race {
+            data_race.read(
+                alloc_id,
+                range,
+                machine.data_race.as_ref().unwrap(),
+                &machine.threads,
+            )?;
+        }
+        if let Some(stacked_borrows) = &alloc_extra.stacked_borrows {
+            stacked_borrows.borrow_mut().before_memory_read(
+                alloc_id,
+                prov_extra,
+                range,
+                machine.stacked_borrows.as_ref().unwrap(),
+                machine.current_span(),
+                &machine.threads,
+            )?;
+        }
+        if let Some(weak_memory) = &alloc_extra.weak_memory {
+            weak_memory.memory_accessed(range, machine.data_race.as_ref().unwrap());
+        }
+        Ok(())
+    }
+
+    #[inline(always)]
+    fn before_memory_write(
+        _tcx: TyCtxt<'tcx>,
+        machine: &mut Self,
+        alloc_extra: &mut AllocExtra,
+        (alloc_id, prov_extra): (AllocId, Self::ProvenanceExtra),
+        range: AllocRange,
+    ) -> InterpResult<'tcx> {
+        if let Some(data_race) = &mut alloc_extra.data_race {
+            data_race.write(
+                alloc_id,
+                range,
+                machine.data_race.as_mut().unwrap(),
+                &machine.threads,
+            )?;
+        }
+        if let Some(stacked_borrows) = &mut alloc_extra.stacked_borrows {
+            stacked_borrows.get_mut().before_memory_write(
+                alloc_id,
+                prov_extra,
+                range,
+                machine.stacked_borrows.as_ref().unwrap(),
+                machine.current_span(),
+                &machine.threads,
+            )?;
+        }
+        if let Some(weak_memory) = &alloc_extra.weak_memory {
+            weak_memory.memory_accessed(range, machine.data_race.as_ref().unwrap());
+        }
+        Ok(())
+    }
+
+    #[inline(always)]
+    fn before_memory_deallocation(
+        _tcx: TyCtxt<'tcx>,
+        machine: &mut Self,
+        alloc_extra: &mut AllocExtra,
+        (alloc_id, prove_extra): (AllocId, Self::ProvenanceExtra),
+        range: AllocRange,
+    ) -> InterpResult<'tcx> {
+        if machine.tracked_alloc_ids.contains(&alloc_id) {
+            machine.emit_diagnostic(NonHaltingDiagnostic::FreedAlloc(alloc_id));
+        }
+        if let Some(data_race) = &mut alloc_extra.data_race {
+            data_race.deallocate(
+                alloc_id,
+                range,
+                machine.data_race.as_mut().unwrap(),
+                &machine.threads,
+            )?;
+        }
+        if let Some(stacked_borrows) = &mut alloc_extra.stacked_borrows {
+            stacked_borrows.get_mut().before_memory_deallocation(
+                alloc_id,
+                prove_extra,
+                range,
+                machine.stacked_borrows.as_ref().unwrap(),
+                machine.current_span(),
+                &machine.threads,
+            )
+        } else {
+            Ok(())
+        }
+    }
+
+    #[inline(always)]
+    fn retag(
+        ecx: &mut InterpCx<'mir, 'tcx, Self>,
+        kind: mir::RetagKind,
+        place: &PlaceTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx> {
+        if ecx.machine.stacked_borrows.is_some() { ecx.retag(kind, place) } else { Ok(()) }
+    }
+
+    #[inline(always)]
+    fn init_frame_extra(
+        ecx: &mut InterpCx<'mir, 'tcx, Self>,
+        frame: Frame<'mir, 'tcx, Provenance>,
+    ) -> InterpResult<'tcx, Frame<'mir, 'tcx, Provenance, FrameData<'tcx>>> {
+        // Start recording our event before doing anything else
+        let timing = if let Some(profiler) = ecx.machine.profiler.as_ref() {
+            let fn_name = frame.instance.to_string();
+            let entry = ecx.machine.string_cache.entry(fn_name.clone());
+            let name = entry.or_insert_with(|| profiler.alloc_string(&*fn_name));
+
+            Some(profiler.start_recording_interval_event_detached(
+                *name,
+                measureme::EventId::from_label(*name),
+                ecx.get_active_thread().to_u32(),
+            ))
+        } else {
+            None
+        };
+
+        let stacked_borrows = ecx.machine.stacked_borrows.as_ref();
+
+        let extra = FrameData {
+            stacked_borrows: stacked_borrows.map(|sb| sb.borrow_mut().new_frame(&ecx.machine)),
+            catch_unwind: None,
+            timing,
+        };
+        Ok(frame.with_extra(extra))
+    }
+
+    fn stack<'a>(
+        ecx: &'a InterpCx<'mir, 'tcx, Self>,
+    ) -> &'a [Frame<'mir, 'tcx, Self::Provenance, Self::FrameExtra>] {
+        ecx.active_thread_stack()
+    }
+
+    fn stack_mut<'a>(
+        ecx: &'a mut InterpCx<'mir, 'tcx, Self>,
+    ) -> &'a mut Vec<Frame<'mir, 'tcx, Self::Provenance, Self::FrameExtra>> {
+        ecx.active_thread_stack_mut()
+    }
+
+    fn before_terminator(ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> {
+        ecx.machine.basic_block_count += 1u64; // a u64 that is only incremented by 1 will "never" overflow
+        ecx.machine.since_gc += 1;
+        // Possibly report our progress.
+        if let Some(report_progress) = ecx.machine.report_progress {
+            if ecx.machine.basic_block_count % u64::from(report_progress) == 0 {
+                ecx.emit_diagnostic(NonHaltingDiagnostic::ProgressReport {
+                    block_count: ecx.machine.basic_block_count,
+                });
+            }
+        }
+
+        // Search for SbTags to find all live pointers, then remove all other tags from borrow
+        // stacks.
+        // When debug assertions are enabled, run the GC as often as possible so that any cases
+        // where it mistakenly removes an important tag become visible.
+        if ecx.machine.gc_interval > 0 && ecx.machine.since_gc >= ecx.machine.gc_interval {
+            ecx.machine.since_gc = 0;
+            ecx.garbage_collect_tags()?;
+        }
+
+        // These are our preemption points.
+        ecx.maybe_preempt_active_thread();
+
+        // Make sure some time passes.
+        ecx.machine.clock.tick();
+
+        Ok(())
+    }
+
+    #[inline(always)]
+    fn after_stack_push(ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> {
+        if ecx.machine.stacked_borrows.is_some() { ecx.retag_return_place() } else { Ok(()) }
+    }
+
+    #[inline(always)]
+    fn after_stack_pop(
+        ecx: &mut InterpCx<'mir, 'tcx, Self>,
+        mut frame: Frame<'mir, 'tcx, Provenance, FrameData<'tcx>>,
+        unwinding: bool,
+    ) -> InterpResult<'tcx, StackPopJump> {
+        let timing = frame.extra.timing.take();
+        if let Some(stacked_borrows) = &ecx.machine.stacked_borrows {
+            stacked_borrows.borrow_mut().end_call(&frame.extra);
+        }
+        let res = ecx.handle_stack_pop_unwind(frame.extra, unwinding);
+        if let Some(profiler) = ecx.machine.profiler.as_ref() {
+            profiler.finish_recording_interval_event(timing.unwrap());
+        }
+        res
+    }
+}
diff --git a/src/tools/miri/src/mono_hash_map.rs b/src/tools/miri/src/mono_hash_map.rs
new file mode 100644
index 0000000000000..45057632df9b5
--- /dev/null
+++ b/src/tools/miri/src/mono_hash_map.rs
@@ -0,0 +1,110 @@
+//! This is a "monotonic `FxHashMap`": A `FxHashMap` that, when shared, can be pushed to but not
+//! otherwise mutated. We also box items in the map. This means we can safely provide
+//! shared references into existing items in the `FxHashMap`, because they will not be dropped
+//! (from being removed) or moved (because they are boxed).
+//! The API is is completely tailored to what `memory.rs` needs. It is still in
+//! a separate file to minimize the amount of code that has to care about the unsafety.
+
+use std::borrow::Borrow;
+use std::cell::RefCell;
+use std::collections::hash_map::Entry;
+use std::hash::Hash;
+
+use rustc_data_structures::fx::FxHashMap;
+
+use crate::AllocMap;
+
+#[derive(Debug, Clone)]
+pub struct MonoHashMap<K: Hash + Eq, V>(RefCell<FxHashMap<K, Box<V>>>);
+
+impl<K: Hash + Eq, V> MonoHashMap<K, V> {
+    /// This function exists for priroda to be able to iterate over all evaluator memory.
+    ///
+    /// The function is somewhat roundabout with the closure argument because internally the
+    /// `MonoHashMap` uses a `RefCell`. When iterating over the `FxHashMap` inside the `RefCell`,
+    /// we need to keep a borrow to the `FxHashMap` inside the iterator. The borrow is only alive
+    /// as long as the `Ref` returned by `RefCell::borrow()` is alive. So we can't return the
+    /// iterator, as that would drop the `Ref`. We can't return both, as it's not possible in Rust
+    /// to have a struct/tuple with a field that refers to another field.
+    pub fn iter<T>(&self, f: impl FnOnce(&mut dyn Iterator<Item = (&K, &V)>) -> T) -> T {
+        f(&mut self.0.borrow().iter().map(|(k, v)| (k, &**v)))
+    }
+}
+
+impl<K: Hash + Eq, V> Default for MonoHashMap<K, V> {
+    fn default() -> Self {
+        MonoHashMap(RefCell::new(Default::default()))
+    }
+}
+
+impl<K: Hash + Eq, V> AllocMap<K, V> for MonoHashMap<K, V> {
+    #[inline(always)]
+    fn contains_key<Q: ?Sized + Hash + Eq>(&mut self, k: &Q) -> bool
+    where
+        K: Borrow<Q>,
+    {
+        self.0.get_mut().contains_key(k)
+    }
+
+    #[inline(always)]
+    fn insert(&mut self, k: K, v: V) -> Option<V> {
+        self.0.get_mut().insert(k, Box::new(v)).map(|x| *x)
+    }
+
+    #[inline(always)]
+    fn remove<Q: ?Sized + Hash + Eq>(&mut self, k: &Q) -> Option<V>
+    where
+        K: Borrow<Q>,
+    {
+        self.0.get_mut().remove(k).map(|x| *x)
+    }
+
+    #[inline(always)]
+    fn filter_map_collect<T>(&self, mut f: impl FnMut(&K, &V) -> Option<T>) -> Vec<T> {
+        self.0.borrow().iter().filter_map(move |(k, v)| f(k, v)).collect()
+    }
+
+    /// The most interesting method: Providing a shared reference without
+    /// holding the `RefCell` open, and inserting new data if the key
+    /// is not used yet.
+    /// `vacant` is called if the key is not found in the map;
+    /// if it returns a reference, that is used directly, if it
+    /// returns owned data, that is put into the map and returned.
+    #[inline(always)]
+    fn get_or<E>(&self, k: K, vacant: impl FnOnce() -> Result<V, E>) -> Result<&V, E> {
+        // We cannot hold borrow_mut while calling `vacant`, since that might have to do lookups in this very map.
+        if let Some(v) = self.0.borrow().get(&k) {
+            let val: *const V = &**v;
+            // This is safe because `val` points into a `Box`, that we know will not move and
+            // will also not be dropped as long as the shared reference `self` is live.
+            return unsafe { Ok(&*val) };
+        }
+        let new_val = Box::new(vacant()?);
+        let val: *const V = &**self.0.borrow_mut().try_insert(k, new_val).ok().unwrap();
+        // This is safe because `val` points into a `Box`, that we know will not move and
+        // will also not be dropped as long as the shared reference `self` is live.
+        unsafe { Ok(&*val) }
+    }
+
+    /// Read-only lookup (avoid read-acquiring the RefCell).
+    fn get(&self, k: K) -> Option<&V> {
+        let val: *const V = match self.0.borrow().get(&k) {
+            Some(v) => &**v,
+            None => return None,
+        };
+        // This is safe because `val` points into a `Box`, that we know will not move and
+        // will also not be dropped as long as the shared reference `self` is live.
+        unsafe { Some(&*val) }
+    }
+
+    #[inline(always)]
+    fn get_mut_or<E>(&mut self, k: K, vacant: impl FnOnce() -> Result<V, E>) -> Result<&mut V, E> {
+        match self.0.get_mut().entry(k) {
+            Entry::Occupied(e) => Ok(e.into_mut()),
+            Entry::Vacant(e) => {
+                let v = vacant()?;
+                Ok(e.insert(Box::new(v)))
+            }
+        }
+    }
+}
diff --git a/src/tools/miri/src/operator.rs b/src/tools/miri/src/operator.rs
new file mode 100644
index 0000000000000..a0ef7fcad16dc
--- /dev/null
+++ b/src/tools/miri/src/operator.rs
@@ -0,0 +1,90 @@
+use log::trace;
+
+use rustc_middle::{mir, ty::Ty};
+use rustc_target::abi::Size;
+
+use crate::*;
+
+pub trait EvalContextExt<'tcx> {
+    fn binary_ptr_op(
+        &self,
+        bin_op: mir::BinOp,
+        left: &ImmTy<'tcx, Provenance>,
+        right: &ImmTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, (Scalar<Provenance>, bool, Ty<'tcx>)>;
+}
+
+impl<'mir, 'tcx> EvalContextExt<'tcx> for super::MiriInterpCx<'mir, 'tcx> {
+    fn binary_ptr_op(
+        &self,
+        bin_op: mir::BinOp,
+        left: &ImmTy<'tcx, Provenance>,
+        right: &ImmTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, (Scalar<Provenance>, bool, Ty<'tcx>)> {
+        use rustc_middle::mir::BinOp::*;
+
+        trace!("ptr_op: {:?} {:?} {:?}", *left, bin_op, *right);
+
+        Ok(match bin_op {
+            Eq | Ne | Lt | Le | Gt | Ge => {
+                assert_eq!(left.layout.abi, right.layout.abi); // types an differ, e.g. fn ptrs with different `for`
+                let size = self.pointer_size();
+                // Just compare the bits. ScalarPairs are compared lexicographically.
+                // We thus always compare pairs and simply fill scalars up with 0.
+                let left = match **left {
+                    Immediate::Scalar(l) => (l.to_bits(size)?, 0),
+                    Immediate::ScalarPair(l1, l2) => (l1.to_bits(size)?, l2.to_bits(size)?),
+                    Immediate::Uninit => panic!("we should never see uninit data here"),
+                };
+                let right = match **right {
+                    Immediate::Scalar(r) => (r.to_bits(size)?, 0),
+                    Immediate::ScalarPair(r1, r2) => (r1.to_bits(size)?, r2.to_bits(size)?),
+                    Immediate::Uninit => panic!("we should never see uninit data here"),
+                };
+                let res = match bin_op {
+                    Eq => left == right,
+                    Ne => left != right,
+                    Lt => left < right,
+                    Le => left <= right,
+                    Gt => left > right,
+                    Ge => left >= right,
+                    _ => bug!(),
+                };
+                (Scalar::from_bool(res), false, self.tcx.types.bool)
+            }
+
+            Offset => {
+                assert!(left.layout.ty.is_unsafe_ptr());
+                let ptr = left.to_scalar().to_pointer(self)?;
+                let offset = right.to_scalar().to_machine_isize(self)?;
+
+                let pointee_ty =
+                    left.layout.ty.builtin_deref(true).expect("Offset called on non-ptr type").ty;
+                let ptr = self.ptr_offset_inbounds(ptr, pointee_ty, offset)?;
+                (Scalar::from_maybe_pointer(ptr, self), false, left.layout.ty)
+            }
+
+            // Some more operations are possible with atomics.
+            // The return value always has the provenance of the *left* operand.
+            Add | Sub | BitOr | BitAnd | BitXor => {
+                assert!(left.layout.ty.is_unsafe_ptr());
+                assert!(right.layout.ty.is_unsafe_ptr());
+                let ptr = left.to_scalar().to_pointer(self)?;
+                // We do the actual operation with usize-typed scalars.
+                let left = ImmTy::from_uint(ptr.addr().bytes(), self.machine.layouts.usize);
+                let right = ImmTy::from_uint(
+                    right.to_scalar().to_machine_usize(self)?,
+                    self.machine.layouts.usize,
+                );
+                let (result, overflowing, _ty) =
+                    self.overflowing_binary_op(bin_op, &left, &right)?;
+                // Construct a new pointer with the provenance of `ptr` (the LHS).
+                let result_ptr =
+                    Pointer::new(ptr.provenance, Size::from_bytes(result.to_machine_usize(self)?));
+                (Scalar::from_maybe_pointer(result_ptr, self), overflowing, left.layout.ty)
+            }
+
+            _ => span_bug!(self.cur_span(), "Invalid operator on pointers: {:?}", bin_op),
+        })
+    }
+}
diff --git a/src/tools/miri/src/range_map.rs b/src/tools/miri/src/range_map.rs
new file mode 100644
index 0000000000000..c77ea63b0873f
--- /dev/null
+++ b/src/tools/miri/src/range_map.rs
@@ -0,0 +1,300 @@
+//! Implements a map from integer indices to data.
+//! Rather than storing data for every index, internally, this maps entire ranges to the data.
+//! To this end, the APIs all work on ranges, not on individual integers. Ranges are split as
+//! necessary (e.g., when [0,5) is first associated with X, and then [1,2) is mutated).
+//! Users must not depend on whether a range is coalesced or not, even though this is observable
+//! via the iteration APIs.
+
+use std::ops;
+
+use rustc_target::abi::Size;
+
+#[derive(Clone, Debug)]
+struct Elem<T> {
+    /// The range covered by this element; never empty.
+    range: ops::Range<u64>,
+    /// The data stored for this element.
+    data: T,
+}
+#[derive(Clone, Debug)]
+pub struct RangeMap<T> {
+    v: Vec<Elem<T>>,
+}
+
+impl<T> RangeMap<T> {
+    /// Creates a new `RangeMap` for the given size, and with the given initial value used for
+    /// the entire range.
+    #[inline(always)]
+    pub fn new(size: Size, init: T) -> RangeMap<T> {
+        let size = size.bytes();
+        let mut map = RangeMap { v: Vec::new() };
+        if size > 0 {
+            map.v.push(Elem { range: 0..size, data: init });
+        }
+        map
+    }
+
+    /// Finds the index containing the given offset.
+    fn find_offset(&self, offset: u64) -> usize {
+        // We do a binary search.
+        let mut left = 0usize; // inclusive
+        let mut right = self.v.len(); // exclusive
+        loop {
+            debug_assert!(left < right, "find_offset: offset {} is out-of-bounds", offset);
+            let candidate = left.checked_add(right).unwrap() / 2;
+            let elem = &self.v[candidate];
+            if offset < elem.range.start {
+                // We are too far right (offset is further left).
+                debug_assert!(candidate < right); // we are making progress
+                right = candidate;
+            } else if offset >= elem.range.end {
+                // We are too far left (offset is further right).
+                debug_assert!(candidate >= left); // we are making progress
+                left = candidate + 1;
+            } else {
+                // This is it!
+                return candidate;
+            }
+        }
+    }
+
+    /// Provides read-only iteration over everything in the given range. This does
+    /// *not* split items if they overlap with the edges. Do not use this to mutate
+    /// through interior mutability.
+    ///
+    /// The iterator also provides the offset of the given element.
+    pub fn iter(&self, offset: Size, len: Size) -> impl Iterator<Item = (Size, &T)> {
+        let offset = offset.bytes();
+        let len = len.bytes();
+        // Compute a slice starting with the elements we care about.
+        let slice: &[Elem<T>] = if len == 0 {
+            // We just need any empty iterator. We don't even want to
+            // yield the element that surrounds this position.
+            &[]
+        } else {
+            let first_idx = self.find_offset(offset);
+            &self.v[first_idx..]
+        };
+        // The first offset that is not included any more.
+        let end = offset + len;
+        assert!(
+            end <= self.v.last().unwrap().range.end,
+            "iterating beyond the bounds of this RangeMap"
+        );
+        slice
+            .iter()
+            .take_while(move |elem| elem.range.start < end)
+            .map(|elem| (Size::from_bytes(elem.range.start), &elem.data))
+    }
+
+    pub fn iter_mut_all(&mut self) -> impl Iterator<Item = &mut T> {
+        self.v.iter_mut().map(|elem| &mut elem.data)
+    }
+
+    // Splits the element situated at the given `index`, such that the 2nd one starts at offset
+    // `split_offset`. Do nothing if the element already starts there.
+    // Returns whether a split was necessary.
+    fn split_index(&mut self, index: usize, split_offset: u64) -> bool
+    where
+        T: Clone,
+    {
+        let elem = &mut self.v[index];
+        if split_offset == elem.range.start || split_offset == elem.range.end {
+            // Nothing to do.
+            return false;
+        }
+        debug_assert!(
+            elem.range.contains(&split_offset),
+            "the `split_offset` is not in the element to be split"
+        );
+
+        // Now we really have to split. Reduce length of first element.
+        let second_range = split_offset..elem.range.end;
+        elem.range.end = split_offset;
+        // Copy the data, and insert second element.
+        let second = Elem { range: second_range, data: elem.data.clone() };
+        self.v.insert(index + 1, second);
+        true
+    }
+
+    /// Provides mutable iteration over everything in the given range. As a side-effect,
+    /// this will split entries in the map that are only partially hit by the given range,
+    /// to make sure that when they are mutated, the effect is constrained to the given range.
+    /// Moreover, this will opportunistically merge neighbouring equal blocks.
+    ///
+    /// The iterator also provides the offset of the given element.
+    pub fn iter_mut(&mut self, offset: Size, len: Size) -> impl Iterator<Item = (Size, &mut T)>
+    where
+        T: Clone + PartialEq,
+    {
+        let offset = offset.bytes();
+        let len = len.bytes();
+        // Compute a slice containing exactly the elements we care about
+        let slice: &mut [Elem<T>] = if len == 0 {
+            // We just need any empty iterator. We don't even want to
+            // yield the element that surrounds this position, nor do
+            // any splitting.
+            &mut []
+        } else {
+            // Make sure we got a clear beginning
+            let mut first_idx = self.find_offset(offset);
+            if self.split_index(first_idx, offset) {
+                // The newly created 2nd element is ours
+                first_idx += 1;
+            }
+            // No more mutation.
+            let first_idx = first_idx;
+            // Find our end. Linear scan, but that's ok because the iteration
+            // is doing the same linear scan anyway -- no increase in complexity.
+            // We combine this scan with a scan for duplicates that we can merge, to reduce
+            // the number of elements.
+            // We stop searching after the first "block" of size 1, to avoid spending excessive
+            // amounts of time on the merging.
+            let mut equal_since_idx = first_idx;
+            // Once we see too many non-mergeable blocks, we stop.
+            // The initial value is chosen via... magic. Benchmarking and magic.
+            let mut successful_merge_count = 3usize;
+            // When the loop is done, this is the first excluded element.
+            let mut end_idx = first_idx;
+            loop {
+                // Compute if `end` is the last element we need to look at.
+                let done = self.v[end_idx].range.end >= offset + len;
+                // We definitely need to include `end`, so move the index.
+                end_idx += 1;
+                debug_assert!(
+                    done || end_idx < self.v.len(),
+                    "iter_mut: end-offset {} is out-of-bounds",
+                    offset + len
+                );
+                // see if we want to merge everything in `equal_since..end` (exclusive at the end!)
+                if successful_merge_count > 0 {
+                    if done || self.v[end_idx].data != self.v[equal_since_idx].data {
+                        // Everything in `equal_since..end` was equal. Make them just one element covering
+                        // the entire range.
+                        let removed_elems = end_idx - equal_since_idx - 1; // number of elements that we would remove
+                        if removed_elems > 0 {
+                            // Adjust the range of the first element to cover all of them.
+                            let equal_until = self.v[end_idx - 1].range.end; // end of range of last of the equal elements
+                            self.v[equal_since_idx].range.end = equal_until;
+                            // Delete the rest of them.
+                            self.v.splice(equal_since_idx + 1..end_idx, std::iter::empty());
+                            // Adjust `end_idx` because we made the list shorter.
+                            end_idx -= removed_elems;
+                            // Adjust the count for the cutoff.
+                            successful_merge_count += removed_elems;
+                        } else {
+                            // Adjust the count for the cutoff.
+                            successful_merge_count -= 1;
+                        }
+                        // Go on scanning for the next block starting here.
+                        equal_since_idx = end_idx;
+                    }
+                }
+                // Leave loop if this is the last element.
+                if done {
+                    break;
+                }
+            }
+            // Move to last included instead of first excluded index.
+            let end_idx = end_idx - 1;
+            // We need to split the end as well. Even if this performs a
+            // split, we don't have to adjust our index as we only care about
+            // the first part of the split.
+            self.split_index(end_idx, offset + len);
+            // Now we yield the slice. `end` is inclusive.
+            &mut self.v[first_idx..=end_idx]
+        };
+        slice.iter_mut().map(|elem| (Size::from_bytes(elem.range.start), &mut elem.data))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    /// Query the map at every offset in the range and collect the results.
+    fn to_vec<T: Copy>(map: &RangeMap<T>, offset: u64, len: u64) -> Vec<T> {
+        (offset..offset + len)
+            .into_iter()
+            .map(|i| {
+                map.iter(Size::from_bytes(i), Size::from_bytes(1)).next().map(|(_, &t)| t).unwrap()
+            })
+            .collect()
+    }
+
+    #[test]
+    fn basic_insert() {
+        let mut map = RangeMap::<i32>::new(Size::from_bytes(20), -1);
+        // Insert.
+        for (_, x) in map.iter_mut(Size::from_bytes(10), Size::from_bytes(1)) {
+            *x = 42;
+        }
+        // Check.
+        assert_eq!(to_vec(&map, 10, 1), vec![42]);
+        assert_eq!(map.v.len(), 3);
+
+        // Insert with size 0.
+        for (_, x) in map.iter_mut(Size::from_bytes(10), Size::from_bytes(0)) {
+            *x = 19;
+        }
+        for (_, x) in map.iter_mut(Size::from_bytes(11), Size::from_bytes(0)) {
+            *x = 19;
+        }
+        assert_eq!(to_vec(&map, 10, 2), vec![42, -1]);
+        assert_eq!(map.v.len(), 3);
+    }
+
+    #[test]
+    fn gaps() {
+        let mut map = RangeMap::<i32>::new(Size::from_bytes(20), -1);
+        for (_, x) in map.iter_mut(Size::from_bytes(11), Size::from_bytes(1)) {
+            *x = 42;
+        }
+        for (_, x) in map.iter_mut(Size::from_bytes(15), Size::from_bytes(1)) {
+            *x = 43;
+        }
+        assert_eq!(map.v.len(), 5);
+        assert_eq!(to_vec(&map, 10, 10), vec![-1, 42, -1, -1, -1, 43, -1, -1, -1, -1]);
+
+        for (_, x) in map.iter_mut(Size::from_bytes(10), Size::from_bytes(10)) {
+            if *x < 42 {
+                *x = 23;
+            }
+        }
+        assert_eq!(map.v.len(), 6);
+        assert_eq!(to_vec(&map, 10, 10), vec![23, 42, 23, 23, 23, 43, 23, 23, 23, 23]);
+        assert_eq!(to_vec(&map, 13, 5), vec![23, 23, 43, 23, 23]);
+
+        for (_, x) in map.iter_mut(Size::from_bytes(15), Size::from_bytes(5)) {
+            *x = 19;
+        }
+        assert_eq!(map.v.len(), 6);
+        assert_eq!(to_vec(&map, 10, 10), vec![23, 42, 23, 23, 23, 19, 19, 19, 19, 19]);
+        // Should be seeing two blocks with 19.
+        assert_eq!(
+            map.iter(Size::from_bytes(15), Size::from_bytes(2))
+                .map(|(_, &t)| t)
+                .collect::<Vec<_>>(),
+            vec![19, 19]
+        );
+
+        // A NOP `iter_mut` should trigger merging.
+        for _ in map.iter_mut(Size::from_bytes(15), Size::from_bytes(5)) {}
+        assert_eq!(map.v.len(), 5);
+        assert_eq!(to_vec(&map, 10, 10), vec![23, 42, 23, 23, 23, 19, 19, 19, 19, 19]);
+    }
+
+    #[test]
+    #[should_panic]
+    fn out_of_range_iter_mut() {
+        let mut map = RangeMap::<i32>::new(Size::from_bytes(20), -1);
+        let _ = map.iter_mut(Size::from_bytes(11), Size::from_bytes(11));
+    }
+
+    #[test]
+    #[should_panic]
+    fn out_of_range_iter() {
+        let map = RangeMap::<i32>::new(Size::from_bytes(20), -1);
+        let _ = map.iter(Size::from_bytes(11), Size::from_bytes(11));
+    }
+}
diff --git a/src/tools/miri/src/shims/backtrace.rs b/src/tools/miri/src/shims/backtrace.rs
new file mode 100644
index 0000000000000..15987eee537fd
--- /dev/null
+++ b/src/tools/miri/src/shims/backtrace.rs
@@ -0,0 +1,254 @@
+use crate::*;
+use rustc_ast::ast::Mutability;
+use rustc_middle::ty::layout::LayoutOf as _;
+use rustc_middle::ty::{self, Instance};
+use rustc_span::{BytePos, Loc, Symbol};
+use rustc_target::{abi::Size, spec::abi::Abi};
+
+impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    fn handle_miri_backtrace_size(
+        &mut self,
+        abi: Abi,
+        link_name: Symbol,
+        args: &[OpTy<'tcx, Provenance>],
+        dest: &PlaceTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        let [flags] = this.check_shim(abi, Abi::Rust, link_name, args)?;
+
+        let flags = this.read_scalar(flags)?.to_u64()?;
+        if flags != 0 {
+            throw_unsup_format!("unknown `miri_backtrace_size` flags {}", flags);
+        }
+
+        let frame_count = this.active_thread_stack().len();
+
+        this.write_scalar(Scalar::from_machine_usize(frame_count.try_into().unwrap(), this), dest)
+    }
+
+    fn handle_miri_get_backtrace(
+        &mut self,
+        abi: Abi,
+        link_name: Symbol,
+        args: &[OpTy<'tcx, Provenance>],
+        dest: &PlaceTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        let tcx = this.tcx;
+
+        let flags = if let Some(flags_op) = args.get(0) {
+            this.read_scalar(flags_op)?.to_u64()?
+        } else {
+            throw_ub_format!("expected at least 1 argument")
+        };
+
+        let mut data = Vec::new();
+        for frame in this.active_thread_stack().iter().rev() {
+            let mut span = frame.current_span();
+            // Match the behavior of runtime backtrace spans
+            // by using a non-macro span in our backtrace. See `FunctionCx::debug_loc`.
+            if span.from_expansion() && !tcx.sess.opts.unstable_opts.debug_macros {
+                span = rustc_span::hygiene::walk_chain(span, frame.body.span.ctxt())
+            }
+            data.push((frame.instance, span.lo()));
+        }
+
+        let ptrs: Vec<_> = data
+            .into_iter()
+            .map(|(instance, pos)| {
+                // We represent a frame pointer by using the `span.lo` value
+                // as an offset into the function's allocation. This gives us an
+                // opaque pointer that we can return to user code, and allows us
+                // to reconstruct the needed frame information in `handle_miri_resolve_frame`.
+                // Note that we never actually read or write anything from/to this pointer -
+                // all of the data is represented by the pointer value itself.
+                let fn_ptr = this.create_fn_alloc_ptr(FnVal::Instance(instance));
+                fn_ptr.wrapping_offset(Size::from_bytes(pos.0), this)
+            })
+            .collect();
+
+        let len: u64 = ptrs.len().try_into().unwrap();
+
+        let ptr_ty = this.machine.layouts.mut_raw_ptr.ty;
+        let array_layout = this.layout_of(tcx.mk_array(ptr_ty, len)).unwrap();
+
+        match flags {
+            // storage for pointers is allocated by miri
+            // deallocating the slice is undefined behavior with a custom global allocator
+            0 => {
+                let [_flags] = this.check_shim(abi, Abi::Rust, link_name, args)?;
+
+                let alloc = this.allocate(array_layout, MiriMemoryKind::Rust.into())?;
+
+                // Write pointers into array
+                for (i, ptr) in ptrs.into_iter().enumerate() {
+                    let place = this.mplace_index(&alloc, i as u64)?;
+
+                    this.write_pointer(ptr, &place.into())?;
+                }
+
+                this.write_immediate(
+                    Immediate::new_slice(Scalar::from_maybe_pointer(alloc.ptr, this), len, this),
+                    dest,
+                )?;
+            }
+            // storage for pointers is allocated by the caller
+            1 => {
+                let [_flags, buf] = this.check_shim(abi, Abi::Rust, link_name, args)?;
+
+                let buf_place = this.deref_operand(buf)?;
+
+                let ptr_layout = this.layout_of(ptr_ty)?;
+
+                for (i, ptr) in ptrs.into_iter().enumerate() {
+                    let offset = ptr_layout.size * i.try_into().unwrap();
+
+                    let op_place = buf_place.offset(offset, ptr_layout, this)?;
+
+                    this.write_pointer(ptr, &op_place.into())?;
+                }
+            }
+            _ => throw_unsup_format!("unknown `miri_get_backtrace` flags {}", flags),
+        };
+
+        Ok(())
+    }
+
+    fn resolve_frame_pointer(
+        &mut self,
+        ptr: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, (Instance<'tcx>, Loc, String, String)> {
+        let this = self.eval_context_mut();
+
+        let ptr = this.read_pointer(ptr)?;
+        // Take apart the pointer, we need its pieces. The offset encodes the span.
+        let (alloc_id, offset, _prov) = this.ptr_get_alloc_id(ptr)?;
+
+        // This has to be an actual global fn ptr, not a dlsym function.
+        let fn_instance = if let Some(GlobalAlloc::Function(instance)) =
+            this.tcx.try_get_global_alloc(alloc_id)
+        {
+            instance
+        } else {
+            throw_ub_format!("expected static function pointer, found {:?}", ptr);
+        };
+
+        let lo =
+            this.tcx.sess.source_map().lookup_char_pos(BytePos(offset.bytes().try_into().unwrap()));
+
+        let name = fn_instance.to_string();
+        let filename = lo.file.name.prefer_remapped().to_string();
+
+        Ok((fn_instance, lo, name, filename))
+    }
+
+    fn handle_miri_resolve_frame(
+        &mut self,
+        abi: Abi,
+        link_name: Symbol,
+        args: &[OpTy<'tcx, Provenance>],
+        dest: &PlaceTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        let [ptr, flags] = this.check_shim(abi, Abi::Rust, link_name, args)?;
+
+        let flags = this.read_scalar(flags)?.to_u64()?;
+
+        let (fn_instance, lo, name, filename) = this.resolve_frame_pointer(ptr)?;
+
+        // Reconstruct the original function pointer,
+        // which we pass to user code.
+        let fn_ptr = this.create_fn_alloc_ptr(FnVal::Instance(fn_instance));
+
+        let num_fields = dest.layout.fields.count();
+
+        if !(4..=5).contains(&num_fields) {
+            // Always mention 5 fields, since the 4-field struct
+            // is deprecated and slated for removal.
+            throw_ub_format!(
+                "bad declaration of miri_resolve_frame - should return a struct with 5 fields"
+            );
+        }
+
+        // `u32` is not enough to fit line/colno, which can be `usize`. It seems unlikely that a
+        // file would have more than 2^32 lines or columns, but whatever, just default to 0.
+        let lineno: u32 = u32::try_from(lo.line).unwrap_or(0);
+        // `lo.col` is 0-based - add 1 to make it 1-based for the caller.
+        let colno: u32 = u32::try_from(lo.col.0.saturating_add(1)).unwrap_or(0);
+
+        let dest = this.force_allocation(dest)?;
+        if let ty::Adt(adt, _) = dest.layout.ty.kind() {
+            if !adt.repr().c() {
+                throw_ub_format!(
+                    "miri_resolve_frame must be declared with a `#[repr(C)]` return type"
+                );
+            }
+        }
+
+        match flags {
+            0 => {
+                // These are "mutable" allocations as we consider them to be owned by the callee.
+                let name_alloc =
+                    this.allocate_str(&name, MiriMemoryKind::Rust.into(), Mutability::Mut);
+                let filename_alloc =
+                    this.allocate_str(&filename, MiriMemoryKind::Rust.into(), Mutability::Mut);
+
+                this.write_immediate(
+                    name_alloc.to_ref(this),
+                    &this.mplace_field(&dest, 0)?.into(),
+                )?;
+                this.write_immediate(
+                    filename_alloc.to_ref(this),
+                    &this.mplace_field(&dest, 1)?.into(),
+                )?;
+            }
+            1 => {
+                this.write_scalar(
+                    Scalar::from_machine_usize(name.len().try_into().unwrap(), this),
+                    &this.mplace_field(&dest, 0)?.into(),
+                )?;
+                this.write_scalar(
+                    Scalar::from_machine_usize(filename.len().try_into().unwrap(), this),
+                    &this.mplace_field(&dest, 1)?.into(),
+                )?;
+            }
+            _ => throw_unsup_format!("unknown `miri_resolve_frame` flags {}", flags),
+        }
+
+        this.write_scalar(Scalar::from_u32(lineno), &this.mplace_field(&dest, 2)?.into())?;
+        this.write_scalar(Scalar::from_u32(colno), &this.mplace_field(&dest, 3)?.into())?;
+
+        // Support a 4-field struct for now - this is deprecated
+        // and slated for removal.
+        if num_fields == 5 {
+            this.write_pointer(fn_ptr, &this.mplace_field(&dest, 4)?.into())?;
+        }
+
+        Ok(())
+    }
+
+    fn handle_miri_resolve_frame_names(
+        &mut self,
+        abi: Abi,
+        link_name: Symbol,
+        args: &[OpTy<'tcx, Provenance>],
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+
+        let [ptr, flags, name_ptr, filename_ptr] =
+            this.check_shim(abi, Abi::Rust, link_name, args)?;
+
+        let flags = this.read_scalar(flags)?.to_u64()?;
+        if flags != 0 {
+            throw_unsup_format!("unknown `miri_resolve_frame_names` flags {}", flags);
+        }
+
+        let (_, _, name, filename) = this.resolve_frame_pointer(ptr)?;
+
+        this.write_bytes_ptr(this.read_pointer(name_ptr)?, name.bytes())?;
+        this.write_bytes_ptr(this.read_pointer(filename_ptr)?, filename.bytes())?;
+
+        Ok(())
+    }
+}
diff --git a/src/tools/miri/src/shims/dlsym.rs b/src/tools/miri/src/shims/dlsym.rs
new file mode 100644
index 0000000000000..8bf6d24f85f31
--- /dev/null
+++ b/src/tools/miri/src/shims/dlsym.rs
@@ -0,0 +1,48 @@
+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/env.rs b/src/tools/miri/src/shims/env.rs
new file mode 100644
index 0000000000000..95051c998e5fd
--- /dev/null
+++ b/src/tools/miri/src/shims/env.rs
@@ -0,0 +1,469 @@
+use std::env;
+use std::ffi::{OsStr, OsString};
+use std::io::ErrorKind;
+use std::mem;
+
+use rustc_const_eval::interpret::Pointer;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_middle::ty::layout::LayoutOf;
+use rustc_target::abi::Size;
+
+use crate::helpers::target_os_is_unix;
+use crate::*;
+
+/// Check whether an operation that writes to a target buffer was successful.
+/// Accordingly select return value.
+/// Local helper function to be used in Windows shims.
+fn windows_check_buffer_size((success, len): (bool, u64)) -> u32 {
+    if success {
+        // If the function succeeds, the return value is the number of characters stored in the target buffer,
+        // not including the terminating null character.
+        u32::try_from(len.checked_sub(1).unwrap()).unwrap()
+    } else {
+        // If the target buffer was not large enough to hold the data, the return value is the buffer size, in characters,
+        // required to hold the string and its terminating null character.
+        u32::try_from(len).unwrap()
+    }
+}
+
+#[derive(Default)]
+pub struct EnvVars<'tcx> {
+    /// Stores pointers to the environment variables. These variables must be stored as
+    /// null-terminated target strings (c_str or wide_str) with the `"{name}={value}"` format.
+    map: FxHashMap<OsString, Pointer<Option<Provenance>>>,
+
+    /// Place where the `environ` static is stored. Lazily initialized, but then never changes.
+    pub(crate) environ: Option<MPlaceTy<'tcx, Provenance>>,
+}
+
+impl<'tcx> EnvVars<'tcx> {
+    pub(crate) fn init<'mir>(
+        ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
+        config: &MiriConfig,
+    ) -> InterpResult<'tcx> {
+        let target_os = ecx.tcx.sess.target.os.as_ref();
+
+        // Skip the loop entirely if we don't want to forward anything.
+        if ecx.machine.communicate() || !config.forwarded_env_vars.is_empty() {
+            for (name, value) in &config.env {
+                let forward = ecx.machine.communicate()
+                    || config.forwarded_env_vars.iter().any(|v| **v == *name);
+                if forward {
+                    let var_ptr = match target_os {
+                        target if target_os_is_unix(target) =>
+                            alloc_env_var_as_c_str(name.as_ref(), value.as_ref(), ecx)?,
+                        "windows" => alloc_env_var_as_wide_str(name.as_ref(), value.as_ref(), ecx)?,
+                        unsupported =>
+                            throw_unsup_format!(
+                                "environment support for target OS `{}` not yet available",
+                                unsupported
+                            ),
+                    };
+                    ecx.machine.env_vars.map.insert(name.clone(), var_ptr);
+                }
+            }
+        }
+        ecx.update_environ()
+    }
+
+    pub(crate) fn cleanup<'mir>(
+        ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
+    ) -> InterpResult<'tcx> {
+        // Deallocate individual env vars.
+        let env_vars = mem::take(&mut ecx.machine.env_vars.map);
+        for (_name, ptr) in env_vars {
+            ecx.deallocate_ptr(ptr, None, MiriMemoryKind::Runtime.into())?;
+        }
+        // Deallocate environ var list.
+        let environ = ecx.machine.env_vars.environ.unwrap();
+        let old_vars_ptr = ecx.read_pointer(&environ.into())?;
+        ecx.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Runtime.into())?;
+        Ok(())
+    }
+}
+
+fn alloc_env_var_as_c_str<'mir, 'tcx>(
+    name: &OsStr,
+    value: &OsStr,
+    ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
+) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
+    let mut name_osstring = name.to_os_string();
+    name_osstring.push("=");
+    name_osstring.push(value);
+    ecx.alloc_os_str_as_c_str(name_osstring.as_os_str(), MiriMemoryKind::Runtime.into())
+}
+
+fn alloc_env_var_as_wide_str<'mir, 'tcx>(
+    name: &OsStr,
+    value: &OsStr,
+    ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
+) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
+    let mut name_osstring = name.to_os_string();
+    name_osstring.push("=");
+    name_osstring.push(value);
+    ecx.alloc_os_str_as_wide_str(name_osstring.as_os_str(), MiriMemoryKind::Runtime.into())
+}
+
+impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    fn getenv(
+        &mut self,
+        name_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
+        let this = self.eval_context_mut();
+        this.assert_target_os_is_unix("getenv");
+
+        let name_ptr = this.read_pointer(name_op)?;
+        let name = this.read_os_str_from_c_str(name_ptr)?;
+        Ok(match this.machine.env_vars.map.get(name) {
+            Some(var_ptr) => {
+                // The offset is used to strip the "{name}=" part of the string.
+                var_ptr.offset(
+                    Size::from_bytes(u64::try_from(name.len()).unwrap().checked_add(1).unwrap()),
+                    this,
+                )?
+            }
+            None => Pointer::null(),
+        })
+    }
+
+    #[allow(non_snake_case)]
+    fn GetEnvironmentVariableW(
+        &mut self,
+        name_op: &OpTy<'tcx, Provenance>, // LPCWSTR
+        buf_op: &OpTy<'tcx, Provenance>,  // LPWSTR
+        size_op: &OpTy<'tcx, Provenance>, // DWORD
+    ) -> InterpResult<'tcx, u32> {
+        // ^ Returns DWORD (u32 on Windows)
+
+        let this = self.eval_context_mut();
+        this.assert_target_os("windows", "GetEnvironmentVariableW");
+
+        let name_ptr = this.read_pointer(name_op)?;
+        let name = this.read_os_str_from_wide_str(name_ptr)?;
+        Ok(match this.machine.env_vars.map.get(&name) {
+            Some(var_ptr) => {
+                // The offset is used to strip the "{name}=" part of the string.
+                #[rustfmt::skip]
+                let name_offset_bytes = u64::try_from(name.len()).unwrap()
+                    .checked_add(1).unwrap()
+                    .checked_mul(2).unwrap();
+                let var_ptr = var_ptr.offset(Size::from_bytes(name_offset_bytes), this)?;
+                let var = this.read_os_str_from_wide_str(var_ptr)?;
+
+                let buf_ptr = this.read_pointer(buf_op)?;
+                // `buf_size` represents the size in characters.
+                let buf_size = u64::from(this.read_scalar(size_op)?.to_u32()?);
+                windows_check_buffer_size(this.write_os_str_to_wide_str(&var, buf_ptr, buf_size)?)
+            }
+            None => {
+                let envvar_not_found = this.eval_windows("c", "ERROR_ENVVAR_NOT_FOUND")?;
+                this.set_last_error(envvar_not_found)?;
+                0 // return zero upon failure
+            }
+        })
+    }
+
+    #[allow(non_snake_case)]
+    fn GetEnvironmentStringsW(&mut self) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
+        let this = self.eval_context_mut();
+        this.assert_target_os("windows", "GetEnvironmentStringsW");
+
+        // Info on layout of environment blocks in Windows:
+        // https://docs.microsoft.com/en-us/windows/win32/procthread/environment-variables
+        let mut env_vars = std::ffi::OsString::new();
+        for &item in this.machine.env_vars.map.values() {
+            let env_var = this.read_os_str_from_wide_str(item)?;
+            env_vars.push(env_var);
+            env_vars.push("\0");
+        }
+        // Allocate environment block & Store environment variables to environment block.
+        // Final null terminator(block terminator) is added by `alloc_os_str_to_wide_str`.
+        let envblock_ptr =
+            this.alloc_os_str_as_wide_str(&env_vars, MiriMemoryKind::Runtime.into())?;
+        // If the function succeeds, the return value is a pointer to the environment block of the current process.
+        Ok(envblock_ptr)
+    }
+
+    #[allow(non_snake_case)]
+    fn FreeEnvironmentStringsW(
+        &mut self,
+        env_block_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+        this.assert_target_os("windows", "FreeEnvironmentStringsW");
+
+        let env_block_ptr = this.read_pointer(env_block_op)?;
+        let result = this.deallocate_ptr(env_block_ptr, None, MiriMemoryKind::Runtime.into());
+        // If the function succeeds, the return value is nonzero.
+        Ok(i32::from(result.is_ok()))
+    }
+
+    fn setenv(
+        &mut self,
+        name_op: &OpTy<'tcx, Provenance>,
+        value_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+        this.assert_target_os_is_unix("setenv");
+
+        let name_ptr = this.read_pointer(name_op)?;
+        let value_ptr = this.read_pointer(value_op)?;
+
+        let mut new = None;
+        if !this.ptr_is_null(name_ptr)? {
+            let name = this.read_os_str_from_c_str(name_ptr)?;
+            if !name.is_empty() && !name.to_string_lossy().contains('=') {
+                let value = this.read_os_str_from_c_str(value_ptr)?;
+                new = Some((name.to_owned(), value.to_owned()));
+            }
+        }
+        if let Some((name, value)) = new {
+            let var_ptr = alloc_env_var_as_c_str(&name, &value, this)?;
+            if let Some(var) = this.machine.env_vars.map.insert(name, var_ptr) {
+                this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?;
+            }
+            this.update_environ()?;
+            Ok(0) // return zero on success
+        } else {
+            // name argument is a null pointer, points to an empty string, or points to a string containing an '=' character.
+            let einval = this.eval_libc("EINVAL")?;
+            this.set_last_error(einval)?;
+            Ok(-1)
+        }
+    }
+
+    #[allow(non_snake_case)]
+    fn SetEnvironmentVariableW(
+        &mut self,
+        name_op: &OpTy<'tcx, Provenance>,  // LPCWSTR
+        value_op: &OpTy<'tcx, Provenance>, // LPCWSTR
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+        this.assert_target_os("windows", "SetEnvironmentVariableW");
+
+        let name_ptr = this.read_pointer(name_op)?;
+        let value_ptr = this.read_pointer(value_op)?;
+
+        if this.ptr_is_null(name_ptr)? {
+            // ERROR CODE is not clearly explained in docs.. For now, throw UB instead.
+            throw_ub_format!("pointer to environment variable name is NULL");
+        }
+
+        let name = this.read_os_str_from_wide_str(name_ptr)?;
+        if name.is_empty() {
+            throw_unsup_format!("environment variable name is an empty string");
+        } else if name.to_string_lossy().contains('=') {
+            throw_unsup_format!("environment variable name contains '='");
+        } else if this.ptr_is_null(value_ptr)? {
+            // Delete environment variable `{name}`
+            if let Some(var) = this.machine.env_vars.map.remove(&name) {
+                this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?;
+                this.update_environ()?;
+            }
+            Ok(1) // return non-zero on success
+        } else {
+            let value = this.read_os_str_from_wide_str(value_ptr)?;
+            let var_ptr = alloc_env_var_as_wide_str(&name, &value, this)?;
+            if let Some(var) = this.machine.env_vars.map.insert(name, var_ptr) {
+                this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?;
+            }
+            this.update_environ()?;
+            Ok(1) // return non-zero on success
+        }
+    }
+
+    fn unsetenv(&mut self, name_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+        this.assert_target_os_is_unix("unsetenv");
+
+        let name_ptr = this.read_pointer(name_op)?;
+        let mut success = None;
+        if !this.ptr_is_null(name_ptr)? {
+            let name = this.read_os_str_from_c_str(name_ptr)?.to_owned();
+            if !name.is_empty() && !name.to_string_lossy().contains('=') {
+                success = Some(this.machine.env_vars.map.remove(&name));
+            }
+        }
+        if let Some(old) = success {
+            if let Some(var) = old {
+                this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?;
+            }
+            this.update_environ()?;
+            Ok(0)
+        } else {
+            // name argument is a null pointer, points to an empty string, or points to a string containing an '=' character.
+            let einval = this.eval_libc("EINVAL")?;
+            this.set_last_error(einval)?;
+            Ok(-1)
+        }
+    }
+
+    fn getcwd(
+        &mut self,
+        buf_op: &OpTy<'tcx, Provenance>,
+        size_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
+        let this = self.eval_context_mut();
+        this.assert_target_os_is_unix("getcwd");
+
+        let buf = this.read_pointer(buf_op)?;
+        let size = this.read_scalar(size_op)?.to_machine_usize(&*this.tcx)?;
+
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`getcwd`", reject_with)?;
+            this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
+            return Ok(Pointer::null());
+        }
+
+        // If we cannot get the current directory, we return null
+        match env::current_dir() {
+            Ok(cwd) => {
+                if this.write_path_to_c_str(&cwd, buf, size)?.0 {
+                    return Ok(buf);
+                }
+                let erange = this.eval_libc("ERANGE")?;
+                this.set_last_error(erange)?;
+            }
+            Err(e) => this.set_last_error_from_io_error(e.kind())?,
+        }
+
+        Ok(Pointer::null())
+    }
+
+    #[allow(non_snake_case)]
+    fn GetCurrentDirectoryW(
+        &mut self,
+        size_op: &OpTy<'tcx, Provenance>, // DWORD
+        buf_op: &OpTy<'tcx, Provenance>,  // LPTSTR
+    ) -> InterpResult<'tcx, u32> {
+        let this = self.eval_context_mut();
+        this.assert_target_os("windows", "GetCurrentDirectoryW");
+
+        let size = u64::from(this.read_scalar(size_op)?.to_u32()?);
+        let buf = this.read_pointer(buf_op)?;
+
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`GetCurrentDirectoryW`", reject_with)?;
+            this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
+            return Ok(0);
+        }
+
+        // If we cannot get the current directory, we return 0
+        match env::current_dir() {
+            Ok(cwd) =>
+                return Ok(windows_check_buffer_size(this.write_path_to_wide_str(&cwd, buf, size)?)),
+            Err(e) => this.set_last_error_from_io_error(e.kind())?,
+        }
+        Ok(0)
+    }
+
+    fn chdir(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+        this.assert_target_os_is_unix("chdir");
+
+        let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
+
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`chdir`", reject_with)?;
+            this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
+
+            return Ok(-1);
+        }
+
+        match env::set_current_dir(path) {
+            Ok(()) => Ok(0),
+            Err(e) => {
+                this.set_last_error_from_io_error(e.kind())?;
+                Ok(-1)
+            }
+        }
+    }
+
+    #[allow(non_snake_case)]
+    fn SetCurrentDirectoryW(
+        &mut self,
+        path_op: &OpTy<'tcx, Provenance>, // LPCTSTR
+    ) -> InterpResult<'tcx, i32> {
+        // ^ Returns BOOL (i32 on Windows)
+
+        let this = self.eval_context_mut();
+        this.assert_target_os("windows", "SetCurrentDirectoryW");
+
+        let path = this.read_path_from_wide_str(this.read_pointer(path_op)?)?;
+
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`SetCurrentDirectoryW`", reject_with)?;
+            this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
+
+            return Ok(0);
+        }
+
+        match env::set_current_dir(path) {
+            Ok(()) => Ok(1),
+            Err(e) => {
+                this.set_last_error_from_io_error(e.kind())?;
+                Ok(0)
+            }
+        }
+    }
+
+    /// Updates the `environ` static.
+    /// The first time it gets called, also initializes `extra.environ`.
+    fn update_environ(&mut self) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        // Deallocate the old environ list, if any.
+        if let Some(environ) = this.machine.env_vars.environ {
+            let old_vars_ptr = this.read_pointer(&environ.into())?;
+            this.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Runtime.into())?;
+        } else {
+            // No `environ` allocated yet, let's do that.
+            // This is memory backing an extern static, hence `ExternStatic`, not `Env`.
+            let layout = this.machine.layouts.mut_raw_ptr;
+            let place = this.allocate(layout, MiriMemoryKind::ExternStatic.into())?;
+            this.machine.env_vars.environ = Some(place);
+        }
+
+        // Collect all the pointers to each variable in a vector.
+        let mut vars: Vec<Pointer<Option<Provenance>>> =
+            this.machine.env_vars.map.values().copied().collect();
+        // Add the trailing null pointer.
+        vars.push(Pointer::null());
+        // Make an array with all these pointers inside Miri.
+        let tcx = this.tcx;
+        let vars_layout = this.layout_of(
+            tcx.mk_array(this.machine.layouts.mut_raw_ptr.ty, u64::try_from(vars.len()).unwrap()),
+        )?;
+        let vars_place = this.allocate(vars_layout, MiriMemoryKind::Runtime.into())?;
+        for (idx, var) in vars.into_iter().enumerate() {
+            let place = this.mplace_field(&vars_place, idx)?;
+            this.write_pointer(var, &place.into())?;
+        }
+        this.write_pointer(vars_place.ptr, &this.machine.env_vars.environ.unwrap().into())?;
+
+        Ok(())
+    }
+
+    fn getpid(&mut self) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+        this.assert_target_os_is_unix("getpid");
+
+        this.check_no_isolation("`getpid`")?;
+
+        // The reason we need to do this wacky of a conversion is because
+        // `libc::getpid` returns an i32, however, `std::process::id()` return an u32.
+        // So we un-do the conversion that stdlib does and turn it back into an i32.
+        #[allow(clippy::cast_possible_wrap)]
+        Ok(std::process::id() as i32)
+    }
+
+    #[allow(non_snake_case)]
+    fn GetCurrentProcessId(&mut self) -> InterpResult<'tcx, u32> {
+        let this = self.eval_context_mut();
+        this.assert_target_os("windows", "GetCurrentProcessId");
+
+        this.check_no_isolation("`GetCurrentProcessId`")?;
+
+        Ok(std::process::id())
+    }
+}
diff --git a/src/tools/miri/src/shims/ffi_support.rs b/src/tools/miri/src/shims/ffi_support.rs
new file mode 100644
index 0000000000000..0813554e9d24e
--- /dev/null
+++ b/src/tools/miri/src/shims/ffi_support.rs
@@ -0,0 +1,291 @@
+use libffi::{high::call as ffi, low::CodePtr};
+use std::ops::Deref;
+
+use rustc_middle::ty::{self as ty, IntTy, Ty, UintTy};
+use rustc_span::Symbol;
+use rustc_target::abi::HasDataLayout;
+
+use crate::*;
+
+impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    /// Extract the scalar value from the result of reading a scalar from the machine,
+    /// and convert it to a `CArg`.
+    fn scalar_to_carg(
+        k: Scalar<Provenance>,
+        arg_type: Ty<'tcx>,
+        cx: &impl HasDataLayout,
+    ) -> InterpResult<'tcx, CArg> {
+        match arg_type.kind() {
+            // If the primitive provided can be converted to a type matching the type pattern
+            // then create a `CArg` of this primitive value with the corresponding `CArg` constructor.
+            // the ints
+            ty::Int(IntTy::I8) => {
+                return Ok(CArg::Int8(k.to_i8()?));
+            }
+            ty::Int(IntTy::I16) => {
+                return Ok(CArg::Int16(k.to_i16()?));
+            }
+            ty::Int(IntTy::I32) => {
+                return Ok(CArg::Int32(k.to_i32()?));
+            }
+            ty::Int(IntTy::I64) => {
+                return Ok(CArg::Int64(k.to_i64()?));
+            }
+            ty::Int(IntTy::Isize) => {
+                // This will fail if host != target, but then the entire FFI thing probably won't work well
+                // in that situation.
+                return Ok(CArg::ISize(k.to_machine_isize(cx)?.try_into().unwrap()));
+            }
+            // the uints
+            ty::Uint(UintTy::U8) => {
+                return Ok(CArg::UInt8(k.to_u8()?));
+            }
+            ty::Uint(UintTy::U16) => {
+                return Ok(CArg::UInt16(k.to_u16()?));
+            }
+            ty::Uint(UintTy::U32) => {
+                return Ok(CArg::UInt32(k.to_u32()?));
+            }
+            ty::Uint(UintTy::U64) => {
+                return Ok(CArg::UInt64(k.to_u64()?));
+            }
+            ty::Uint(UintTy::Usize) => {
+                // This will fail if host != target, but then the entire FFI thing probably won't work well
+                // in that situation.
+                return Ok(CArg::USize(k.to_machine_usize(cx)?.try_into().unwrap()));
+            }
+            _ => {}
+        }
+        // If no primitives were returned then we have an unsupported type.
+        throw_unsup_format!(
+            "unsupported scalar argument type to external C function: {:?}",
+            arg_type
+        );
+    }
+
+    /// Call external C function and
+    /// store output, depending on return type in the function signature.
+    fn call_external_c_and_store_return<'a>(
+        &mut self,
+        link_name: Symbol,
+        dest: &PlaceTy<'tcx, Provenance>,
+        ptr: CodePtr,
+        libffi_args: Vec<libffi::high::Arg<'a>>,
+    ) -> InterpResult<'tcx, ()> {
+        let this = self.eval_context_mut();
+
+        // Unsafe because of the call to external C code.
+        // Because this is calling a C function it is not necessarily sound,
+        // but there is no way around this and we've checked as much as we can.
+        unsafe {
+            // If the return type of a function is a primitive integer type,
+            // then call the function (`ptr`) with arguments `libffi_args`, store the return value as the specified
+            // primitive integer type, and then write this value out to the miri memory as an integer.
+            match dest.layout.ty.kind() {
+                // ints
+                ty::Int(IntTy::I8) => {
+                    let x = ffi::call::<i8>(ptr, libffi_args.as_slice());
+                    this.write_int(x, dest)?;
+                    return Ok(());
+                }
+                ty::Int(IntTy::I16) => {
+                    let x = ffi::call::<i16>(ptr, libffi_args.as_slice());
+                    this.write_int(x, dest)?;
+                    return Ok(());
+                }
+                ty::Int(IntTy::I32) => {
+                    let x = ffi::call::<i32>(ptr, libffi_args.as_slice());
+                    this.write_int(x, dest)?;
+                    return Ok(());
+                }
+                ty::Int(IntTy::I64) => {
+                    let x = ffi::call::<i64>(ptr, libffi_args.as_slice());
+                    this.write_int(x, dest)?;
+                    return Ok(());
+                }
+                ty::Int(IntTy::Isize) => {
+                    let x = ffi::call::<isize>(ptr, libffi_args.as_slice());
+                    // `isize` doesn't `impl Into<i128>`, so convert manually.
+                    // Convert to `i64` since this covers both 32- and 64-bit machines.
+                    this.write_int(i64::try_from(x).unwrap(), dest)?;
+                    return Ok(());
+                }
+                // uints
+                ty::Uint(UintTy::U8) => {
+                    let x = ffi::call::<u8>(ptr, libffi_args.as_slice());
+                    this.write_int(x, dest)?;
+                    return Ok(());
+                }
+                ty::Uint(UintTy::U16) => {
+                    let x = ffi::call::<u16>(ptr, libffi_args.as_slice());
+                    this.write_int(x, dest)?;
+                    return Ok(());
+                }
+                ty::Uint(UintTy::U32) => {
+                    let x = ffi::call::<u32>(ptr, libffi_args.as_slice());
+                    this.write_int(x, dest)?;
+                    return Ok(());
+                }
+                ty::Uint(UintTy::U64) => {
+                    let x = ffi::call::<u64>(ptr, libffi_args.as_slice());
+                    this.write_int(x, dest)?;
+                    return Ok(());
+                }
+                ty::Uint(UintTy::Usize) => {
+                    let x = ffi::call::<usize>(ptr, libffi_args.as_slice());
+                    // `usize` doesn't `impl Into<i128>`, so convert manually.
+                    // Convert to `u64` since this covers both 32- and 64-bit machines.
+                    this.write_int(u64::try_from(x).unwrap(), dest)?;
+                    return Ok(());
+                }
+                // Functions with no declared return type (i.e., the default return)
+                // have the output_type `Tuple([])`.
+                ty::Tuple(t_list) =>
+                    if t_list.len() == 0 {
+                        ffi::call::<()>(ptr, libffi_args.as_slice());
+                        return Ok(());
+                    },
+                _ => {}
+            }
+            // FIXME ellen! deal with all the other return types
+            throw_unsup_format!("unsupported return type to external C function: {:?}", link_name);
+        }
+    }
+
+    /// Get the pointer to the function of the specified name in the shared object file,
+    /// if it exists. The function must be in the shared object file specified: we do *not*
+    /// return pointers to functions in dependencies of the library.  
+    fn get_func_ptr_explicitly_from_lib(&mut self, link_name: Symbol) -> Option<CodePtr> {
+        let this = self.eval_context_mut();
+        // Try getting the function from the shared library.
+        // On windows `_lib_path` will be unused, hence the name starting with `_`.
+        let (lib, _lib_path) = this.machine.external_so_lib.as_ref().unwrap();
+        let func: libloading::Symbol<'_, unsafe extern "C" fn()> = unsafe {
+            match lib.get(link_name.as_str().as_bytes()) {
+                Ok(x) => x,
+                Err(_) => {
+                    return None;
+                }
+            }
+        };
+
+        // FIXME: this is a hack!
+        // The `libloading` crate will automatically load system libraries like `libc`.
+        // On linux `libloading` is based on `dlsym`: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#202
+        // and `dlsym`(https://linux.die.net/man/3/dlsym) looks through the dependency tree of the
+        // library if it can't find the symbol in the library itself.
+        // So, in order to check if the function was actually found in the specified
+        // `machine.external_so_lib` we need to check its `dli_fname` and compare it to
+        // the specified SO file path.
+        // This code is a reimplementation of the mechanism for getting `dli_fname` in `libloading`,
+        // from: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#411
+        // using the `libc` crate where this interface is public.
+        // No `libc::dladdr` on windows.
+        #[cfg(unix)]
+        let mut info = std::mem::MaybeUninit::<libc::Dl_info>::uninit();
+        #[cfg(unix)]
+        unsafe {
+            if libc::dladdr(*func.deref() as *const _, info.as_mut_ptr()) != 0 {
+                if std::ffi::CStr::from_ptr(info.assume_init().dli_fname).to_str().unwrap()
+                    != _lib_path.to_str().unwrap()
+                {
+                    return None;
+                }
+            }
+        }
+        // Return a pointer to the function.
+        Some(CodePtr(*func.deref() as *mut _))
+    }
+
+    /// Call specified external C function, with supplied arguments.
+    /// Need to convert all the arguments from their hir representations to
+    /// a form compatible with C (through `libffi` call).
+    /// Then, convert return from the C call into a corresponding form that
+    /// can be stored in Miri internal memory.
+    fn call_external_c_fct(
+        &mut self,
+        link_name: Symbol,
+        dest: &PlaceTy<'tcx, Provenance>,
+        args: &[OpTy<'tcx, Provenance>],
+    ) -> InterpResult<'tcx, bool> {
+        // Get the pointer to the function in the shared object file if it exists.
+        let code_ptr = match self.get_func_ptr_explicitly_from_lib(link_name) {
+            Some(ptr) => ptr,
+            None => {
+                // Shared object file does not export this function -- try the shims next.
+                return Ok(false);
+            }
+        };
+
+        let this = self.eval_context_mut();
+
+        // Get the function arguments, and convert them to `libffi`-compatible form.
+        let mut libffi_args = Vec::<CArg>::with_capacity(args.len());
+        for cur_arg in args.iter() {
+            libffi_args.push(Self::scalar_to_carg(
+                this.read_scalar(cur_arg)?,
+                cur_arg.layout.ty,
+                this,
+            )?);
+        }
+
+        // Convert them to `libffi::high::Arg` type.
+        let libffi_args = libffi_args
+            .iter()
+            .map(|cur_arg| cur_arg.arg_downcast())
+            .collect::<Vec<libffi::high::Arg<'_>>>();
+
+        // Call the function and store output, depending on return type in the function signature.
+        self.call_external_c_and_store_return(link_name, dest, code_ptr, libffi_args)?;
+        Ok(true)
+    }
+}
+
+#[derive(Debug, Clone)]
+/// Enum of supported arguments to external C functions.
+// We introduce this enum instead of just calling `ffi::arg` and storing a list
+// of `libffi::high::Arg` directly, because the `libffi::high::Arg` just wraps a reference
+// to the value it represents: https://docs.rs/libffi/latest/libffi/high/call/struct.Arg.html
+// and we need to store a copy of the value, and pass a reference to this copy to C instead.
+pub enum CArg {
+    /// 8-bit signed integer.
+    Int8(i8),
+    /// 16-bit signed integer.
+    Int16(i16),
+    /// 32-bit signed integer.
+    Int32(i32),
+    /// 64-bit signed integer.
+    Int64(i64),
+    /// isize.
+    ISize(isize),
+    /// 8-bit unsigned integer.
+    UInt8(u8),
+    /// 16-bit unsigned integer.
+    UInt16(u16),
+    /// 32-bit unsigned integer.
+    UInt32(u32),
+    /// 64-bit unsigned integer.
+    UInt64(u64),
+    /// usize.
+    USize(usize),
+}
+
+impl<'a> CArg {
+    /// Convert a `CArg` to a `libffi` argument type.
+    fn arg_downcast(&'a self) -> libffi::high::Arg<'a> {
+        match self {
+            CArg::Int8(i) => ffi::arg(i),
+            CArg::Int16(i) => ffi::arg(i),
+            CArg::Int32(i) => ffi::arg(i),
+            CArg::Int64(i) => ffi::arg(i),
+            CArg::ISize(i) => ffi::arg(i),
+            CArg::UInt8(i) => ffi::arg(i),
+            CArg::UInt16(i) => ffi::arg(i),
+            CArg::UInt32(i) => ffi::arg(i),
+            CArg::UInt64(i) => ffi::arg(i),
+            CArg::USize(i) => ffi::arg(i),
+        }
+    }
+}
diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs
new file mode 100644
index 0000000000000..bb62a2a7ec1b5
--- /dev/null
+++ b/src/tools/miri/src/shims/foreign_items.rs
@@ -0,0 +1,811 @@
+use std::{collections::hash_map::Entry, iter};
+
+use log::trace;
+
+use rustc_apfloat::Float;
+use rustc_ast::expand::allocator::AllocatorKind;
+use rustc_hir::{
+    def::DefKind,
+    def_id::{CrateNum, DefId, LOCAL_CRATE},
+};
+use rustc_middle::middle::{
+    codegen_fn_attrs::CodegenFnAttrFlags, dependency_format::Linkage,
+    exported_symbols::ExportedSymbol,
+};
+use rustc_middle::mir;
+use rustc_middle::ty;
+use rustc_session::config::CrateType;
+use rustc_span::Symbol;
+use rustc_target::{
+    abi::{Align, Size},
+    spec::abi::Abi,
+};
+
+use super::backtrace::EvalContextExt as _;
+use crate::helpers::{convert::Truncate, target_os_is_unix};
+#[cfg(unix)]
+use crate::shims::ffi_support::EvalContextExt as _;
+use crate::*;
+
+/// Returned by `emulate_foreign_item_by_name`.
+pub enum EmulateByNameResult<'mir, 'tcx> {
+    /// 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" | "powerpc" | "powerpc64" | "asmjs" | "wasm32" => 8,
+            "x86_64" | "aarch64" | "mips64" | "s390x" | "sparc64" => 16,
+            arch => bug!("Unsupported target architecture: {}", arch),
+        };
+        // 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
+            }
+        }
+        Align::from_bytes(prev_power_of_two(size)).unwrap()
+    }
+
+    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())
+            }
+        }
+    }
+
+    /// Lookup the body of a function that has `link_name` as the symbol name.
+    fn lookup_exported_symbol(
+        &mut self,
+        link_name: Symbol,
+    ) -> InterpResult<'tcx, Option<(&'mir mir::Body<'tcx>, ty::Instance<'tcx>)>> {
+        let this = self.eval_context_mut();
+        let tcx = this.tcx.tcx;
+
+        // If the result was cached, just return it.
+        // (Cannot use `or_insert` since the code below might have to throw an error.)
+        let entry = this.machine.exported_symbols_cache.entry(link_name);
+        let instance = *match entry {
+            Entry::Occupied(e) => e.into_mut(),
+            Entry::Vacant(e) => {
+                // Find it if it was not cached.
+                let mut instance_and_crate: Option<(ty::Instance<'_>, CrateNum)> = None;
+                // `dependency_formats` includes all the transitive informations needed to link a crate,
+                // which is what we need here since we need to dig out `exported_symbols` from all transitive
+                // dependencies.
+                let dependency_formats = tcx.dependency_formats(());
+                let dependency_format = dependency_formats
+                    .iter()
+                    .find(|(crate_type, _)| *crate_type == CrateType::Executable)
+                    .expect("interpreting a non-executable crate");
+                for cnum in iter::once(LOCAL_CRATE).chain(
+                    dependency_format.1.iter().enumerate().filter_map(|(num, &linkage)| {
+                        // We add 1 to the number because that's what rustc also does everywhere it
+                        // calls `CrateNum::new`...
+                        #[allow(clippy::integer_arithmetic)]
+                        (linkage != Linkage::NotLinked).then_some(CrateNum::new(num + 1))
+                    }),
+                ) {
+                    // We can ignore `_export_info` here: we are a Rust crate, and everything is exported
+                    // from a Rust crate.
+                    for &(symbol, _export_info) in tcx.exported_symbols(cnum) {
+                        if let ExportedSymbol::NonGeneric(def_id) = symbol {
+                            let attrs = tcx.codegen_fn_attrs(def_id);
+                            let symbol_name = if let Some(export_name) = attrs.export_name {
+                                export_name
+                            } else if attrs.flags.contains(CodegenFnAttrFlags::NO_MANGLE) {
+                                tcx.item_name(def_id)
+                            } else {
+                                // Skip over items without an explicitly defined symbol name.
+                                continue;
+                            };
+                            if symbol_name == link_name {
+                                if let Some((original_instance, original_cnum)) = instance_and_crate
+                                {
+                                    // Make sure we are consistent wrt what is 'first' and 'second'.
+                                    let original_span =
+                                        tcx.def_span(original_instance.def_id()).data();
+                                    let span = tcx.def_span(def_id).data();
+                                    if original_span < span {
+                                        throw_machine_stop!(
+                                            TerminationInfo::MultipleSymbolDefinitions {
+                                                link_name,
+                                                first: original_span,
+                                                first_crate: tcx.crate_name(original_cnum),
+                                                second: span,
+                                                second_crate: tcx.crate_name(cnum),
+                                            }
+                                        );
+                                    } else {
+                                        throw_machine_stop!(
+                                            TerminationInfo::MultipleSymbolDefinitions {
+                                                link_name,
+                                                first: span,
+                                                first_crate: tcx.crate_name(cnum),
+                                                second: original_span,
+                                                second_crate: tcx.crate_name(original_cnum),
+                                            }
+                                        );
+                                    }
+                                }
+                                if !matches!(tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn) {
+                                    throw_ub_format!(
+                                        "attempt to call an exported symbol that is not defined as a function"
+                                    );
+                                }
+                                instance_and_crate = Some((ty::Instance::mono(tcx, def_id), cnum));
+                            }
+                        }
+                    }
+                }
+
+                e.insert(instance_and_crate.map(|ic| ic.0))
+            }
+        };
+        match instance {
+            None => Ok(None), // no symbol with this name
+            Some(instance) => Ok(Some((this.load_mir(instance.def, None)?, instance))),
+        }
+    }
+
+    /// 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: StackPopUnwind,
+    ) -> 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.into()));
+                    }
+                    "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,
+        };
+
+        // 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))?;
+                return Ok(None);
+            }
+        }
+
+        Ok(None)
+    }
+
+    /// Emulates calling the internal __rust_* allocator functions
+    fn emulate_allocator(
+        &mut self,
+        symbol: Symbol,
+        default: impl FnOnce(&mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx>,
+    ) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
+        let this = self.eval_context_mut();
+
+        let allocator_kind = if let Some(allocator_kind) = this.tcx.allocator_kind(()) {
+            allocator_kind
+        } else {
+            // in real code, this symbol does not exist without an allocator
+            return Ok(EmulateByNameResult::NotSupported);
+        };
+
+        match allocator_kind {
+            AllocatorKind::Global => {
+                let (body, instance) = this
+                    .lookup_exported_symbol(symbol)?
+                    .expect("symbol should be present if there is a global allocator");
+
+                Ok(EmulateByNameResult::MirBody(body, instance))
+            }
+            AllocatorKind::Default => {
+                default(this)?;
+                Ok(EmulateByNameResult::NeedsJumping)
+            }
+        }
+    }
+
+    /// Emulates calling a foreign item using its name.
+    fn emulate_foreign_item_by_name(
+        &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();
+
+        // First deal with any external C functions in linked .so file.
+        #[cfg(unix)]
+        if this.machine.external_so_lib.as_ref().is_some() {
+            // An Ok(false) here means that the function being called was not exported
+            // 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);
+            }
+        }
+
+        // When adding a new shim, you should follow the following pattern:
+        // ```
+        // "shim_name" => {
+        //     let [arg1, arg2, arg3] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+        //     let result = this.shim_name(arg1, arg2, arg3)?;
+        //     this.write_scalar(result, dest)?;
+        // }
+        // ```
+        // and then define `shim_name` as a helper function in an extension trait in a suitable file
+        // (see e.g. `unix/fs.rs`):
+        // ```
+        // fn shim_name(
+        //     &mut self,
+        //     arg1: &OpTy<'tcx, Provenance>,
+        //     arg2: &OpTy<'tcx, Provenance>,
+        //     arg3: &OpTy<'tcx, Provenance>)
+        // -> InterpResult<'tcx, Scalar<Provenance>> {
+        //     let this = self.eval_context_mut();
+        //
+        //     // First thing: load all the arguments. Details depend on the shim.
+        //     let arg1 = this.read_scalar(arg1)?.to_u32()?;
+        //     let arg2 = this.read_pointer(arg2)?; // when you need to work with the pointer directly
+        //     let arg3 = this.deref_operand(arg3)?; // when you want to load/store through the pointer at its declared type
+        //
+        //     // ...
+        //
+        //     Ok(Scalar::from_u32(42))
+        // }
+        // ```
+        // You might find existing shims not following this pattern, most
+        // likely because they predate it or because for some reason they cannot be made to fit.
+
+        // Here we dispatch all the shims for foreign functions. If you have a platform specific
+        // shim, add it to the corresponding submodule.
+        match link_name.as_str() {
+            // Miri-specific extern functions
+            "miri_static_root" => {
+                let [ptr] = this.check_shim(abi, Abi::Rust, link_name, args)?;
+                let ptr = this.read_pointer(ptr)?;
+                let (alloc_id, offset, _) = this.ptr_get_alloc_id(ptr)?;
+                if offset != Size::ZERO {
+                    throw_unsup_format!("pointer passed to miri_static_root must point to beginning of an allocated block");
+                }
+                this.machine.static_roots.push(alloc_id);
+            }
+
+            // Obtains the size of a Miri backtrace. See the README for details.
+            "miri_backtrace_size" => {
+                this.handle_miri_backtrace_size(abi, link_name, args, dest)?;
+            }
+
+            // Obtains a Miri backtrace. See the README for details.
+            "miri_get_backtrace" => {
+                // `check_shim` happens inside `handle_miri_get_backtrace`.
+                this.handle_miri_get_backtrace(abi, link_name, args, dest)?;
+            }
+
+            // Resolves a Miri backtrace frame. See the README for details.
+            "miri_resolve_frame" => {
+                // `check_shim` happens inside `handle_miri_resolve_frame`.
+                this.handle_miri_resolve_frame(abi, link_name, args, dest)?;
+            }
+
+            // Writes the function and file names of a Miri backtrace frame into a user provided buffer. See the README for details.
+            "miri_resolve_frame_names" => {
+                this.handle_miri_resolve_frame_names(abi, link_name, args)?;
+            }
+
+            // Standard C allocation
+            "malloc" => {
+                let [size] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let size = this.read_scalar(size)?.to_machine_usize(this)?;
+                let res = this.malloc(size, /*zero_init:*/ false, MiriMemoryKind::C)?;
+                this.write_pointer(res, dest)?;
+            }
+            "calloc" => {
+                let [items, len] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let items = this.read_scalar(items)?.to_machine_usize(this)?;
+                let len = this.read_scalar(len)?.to_machine_usize(this)?;
+                let size =
+                    items.checked_mul(len).ok_or_else(|| err_ub_format!("overflow during calloc size computation"))?;
+                let res = this.malloc(size, /*zero_init:*/ true, MiriMemoryKind::C)?;
+                this.write_pointer(res, dest)?;
+            }
+            "free" => {
+                let [ptr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let ptr = this.read_pointer(ptr)?;
+                this.free(ptr, MiriMemoryKind::C)?;
+            }
+            "realloc" => {
+                let [old_ptr, new_size] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let old_ptr = this.read_pointer(old_ptr)?;
+                let new_size = this.read_scalar(new_size)?.to_machine_usize(this)?;
+                let res = this.realloc(old_ptr, new_size, MiriMemoryKind::C)?;
+                this.write_pointer(res, dest)?;
+            }
+
+            // Rust allocation
+            "__rust_alloc" => {
+                let [size, align] = this.check_shim(abi, Abi::Rust, link_name, args)?;
+                let size = this.read_scalar(size)?.to_machine_usize(this)?;
+                let align = this.read_scalar(align)?.to_machine_usize(this)?;
+
+                return this.emulate_allocator(Symbol::intern("__rg_alloc"), |this| {
+                    Self::check_alloc_request(size, align)?;
+
+                    let ptr = this.allocate_ptr(
+                        Size::from_bytes(size),
+                        Align::from_bytes(align).unwrap(),
+                        MiriMemoryKind::Rust.into(),
+                    )?;
+
+                    this.write_pointer(ptr, dest)
+                });
+            }
+            "__rust_alloc_zeroed" => {
+                let [size, align] = this.check_shim(abi, Abi::Rust, link_name, args)?;
+                let size = this.read_scalar(size)?.to_machine_usize(this)?;
+                let align = this.read_scalar(align)?.to_machine_usize(this)?;
+
+                return this.emulate_allocator(Symbol::intern("__rg_alloc_zeroed"), |this| {
+                    Self::check_alloc_request(size, align)?;
+
+                    let ptr = this.allocate_ptr(
+                        Size::from_bytes(size),
+                        Align::from_bytes(align).unwrap(),
+                        MiriMemoryKind::Rust.into(),
+                    )?;
+
+                    // We just allocated this, the access is definitely in-bounds.
+                    this.write_bytes_ptr(ptr.into(), iter::repeat(0u8).take(usize::try_from(size).unwrap())).unwrap();
+                    this.write_pointer(ptr, dest)
+                });
+            }
+            "__rust_dealloc" => {
+                let [ptr, old_size, align] = this.check_shim(abi, Abi::Rust, link_name, args)?;
+                let ptr = this.read_pointer(ptr)?;
+                let old_size = this.read_scalar(old_size)?.to_machine_usize(this)?;
+                let align = this.read_scalar(align)?.to_machine_usize(this)?;
+
+                return this.emulate_allocator(Symbol::intern("__rg_dealloc"), |this| {
+                    // No need to check old_size/align; we anyway check that they match the allocation.
+                    this.deallocate_ptr(
+                        ptr,
+                        Some((Size::from_bytes(old_size), Align::from_bytes(align).unwrap())),
+                        MiriMemoryKind::Rust.into(),
+                    )
+                });
+            }
+            "__rust_realloc" => {
+                let [ptr, old_size, align, new_size] = this.check_shim(abi, Abi::Rust, link_name, args)?;
+                let ptr = this.read_pointer(ptr)?;
+                let old_size = this.read_scalar(old_size)?.to_machine_usize(this)?;
+                let align = this.read_scalar(align)?.to_machine_usize(this)?;
+                let new_size = this.read_scalar(new_size)?.to_machine_usize(this)?;
+                // No need to check old_size; we anyway check that they match the allocation.
+
+                return this.emulate_allocator(Symbol::intern("__rg_realloc"), |this| {
+                    Self::check_alloc_request(new_size, align)?;
+
+                    let align = Align::from_bytes(align).unwrap();
+                    let new_ptr = this.reallocate_ptr(
+                        ptr,
+                        Some((Size::from_bytes(old_size), align)),
+                        Size::from_bytes(new_size),
+                        align,
+                        MiriMemoryKind::Rust.into(),
+                    )?;
+                    this.write_pointer(new_ptr, dest)
+                });
+            }
+
+            // C memory handling functions
+            "memcmp" => {
+                let [left, right, n] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let left = this.read_pointer(left)?;
+                let right = this.read_pointer(right)?;
+                let n = Size::from_bytes(this.read_scalar(n)?.to_machine_usize(this)?);
+
+                let result = {
+                    let left_bytes = this.read_bytes_ptr_strip_provenance(left, n)?;
+                    let right_bytes = this.read_bytes_ptr_strip_provenance(right, n)?;
+
+                    use std::cmp::Ordering::*;
+                    match left_bytes.cmp(right_bytes) {
+                        Less => -1i32,
+                        Equal => 0,
+                        Greater => 1,
+                    }
+                };
+
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "memrchr" => {
+                let [ptr, val, num] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let ptr = this.read_pointer(ptr)?;
+                let val = this.read_scalar(val)?.to_i32()?;
+                let num = this.read_scalar(num)?.to_machine_usize(this)?;
+                // The docs say val is "interpreted as unsigned char".
+                #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
+                let val = val as u8;
+
+                if let Some(idx) = this
+                    .read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(num))?
+                    .iter()
+                    .rev()
+                    .position(|&c| c == val)
+                {
+                    let idx = u64::try_from(idx).unwrap();
+                    #[allow(clippy::integer_arithmetic)] // idx < num, so this never wraps
+                    let new_ptr = ptr.offset(Size::from_bytes(num - idx - 1), this)?;
+                    this.write_pointer(new_ptr, dest)?;
+                } else {
+                    this.write_null(dest)?;
+                }
+            }
+            "memchr" => {
+                let [ptr, val, num] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let ptr = this.read_pointer(ptr)?;
+                let val = this.read_scalar(val)?.to_i32()?;
+                let num = this.read_scalar(num)?.to_machine_usize(this)?;
+                // The docs say val is "interpreted as unsigned char".
+                #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
+                let val = val as u8;
+
+                let idx = this
+                    .read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(num))?
+                    .iter()
+                    .position(|&c| c == val);
+                if let Some(idx) = idx {
+                    let new_ptr = ptr.offset(Size::from_bytes(idx as u64), this)?;
+                    this.write_pointer(new_ptr, dest)?;
+                } else {
+                    this.write_null(dest)?;
+                }
+            }
+            "strlen" => {
+                let [ptr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let ptr = this.read_pointer(ptr)?;
+                let n = this.read_c_str(ptr)?.len();
+                this.write_scalar(Scalar::from_machine_usize(u64::try_from(n).unwrap(), this), dest)?;
+            }
+
+            // math functions (note that there are also intrinsics for some other functions)
+            #[rustfmt::skip]
+            | "cbrtf"
+            | "coshf"
+            | "sinhf"
+            | "tanf"
+            | "tanhf"
+            | "acosf"
+            | "asinf"
+            | "atanf"
+            | "log1pf"
+            | "expm1f"
+            => {
+                let [f] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                // FIXME: Using host floats.
+                let f = f32::from_bits(this.read_scalar(f)?.to_u32()?);
+                let res = match link_name.as_str() {
+                    "cbrtf" => f.cbrt(),
+                    "coshf" => f.cosh(),
+                    "sinhf" => f.sinh(),
+                    "tanf" => f.tan(),
+                    "tanhf" => f.tanh(),
+                    "acosf" => f.acos(),
+                    "asinf" => f.asin(),
+                    "atanf" => f.atan(),
+                    "log1pf" => f.ln_1p(),
+                    "expm1f" => f.exp_m1(),
+                    _ => bug!(),
+                };
+                this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?;
+            }
+            #[rustfmt::skip]
+            | "_hypotf"
+            | "hypotf"
+            | "atan2f"
+            | "fdimf"
+            => {
+                let [f1, f2] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                // underscore case for windows, here and below
+                // (see https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/floating-point-primitives?view=vs-2019)
+                // FIXME: Using host floats.
+                let f1 = f32::from_bits(this.read_scalar(f1)?.to_u32()?);
+                let f2 = f32::from_bits(this.read_scalar(f2)?.to_u32()?);
+                let res = match link_name.as_str() {
+                    "_hypotf" | "hypotf" => f1.hypot(f2),
+                    "atan2f" => f1.atan2(f2),
+                    #[allow(deprecated)]
+                    "fdimf" => f1.abs_sub(f2),
+                    _ => bug!(),
+                };
+                this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?;
+            }
+            #[rustfmt::skip]
+            | "cbrt"
+            | "cosh"
+            | "sinh"
+            | "tan"
+            | "tanh"
+            | "acos"
+            | "asin"
+            | "atan"
+            | "log1p"
+            | "expm1"
+            => {
+                let [f] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                // FIXME: Using host floats.
+                let f = f64::from_bits(this.read_scalar(f)?.to_u64()?);
+                let res = match link_name.as_str() {
+                    "cbrt" => f.cbrt(),
+                    "cosh" => f.cosh(),
+                    "sinh" => f.sinh(),
+                    "tan" => f.tan(),
+                    "tanh" => f.tanh(),
+                    "acos" => f.acos(),
+                    "asin" => f.asin(),
+                    "atan" => f.atan(),
+                    "log1p" => f.ln_1p(),
+                    "expm1" => f.exp_m1(),
+                    _ => bug!(),
+                };
+                this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?;
+            }
+            #[rustfmt::skip]
+            | "_hypot"
+            | "hypot"
+            | "atan2"
+            | "fdim"
+            => {
+                let [f1, f2] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                // FIXME: Using host floats.
+                let f1 = f64::from_bits(this.read_scalar(f1)?.to_u64()?);
+                let f2 = f64::from_bits(this.read_scalar(f2)?.to_u64()?);
+                let res = match link_name.as_str() {
+                    "_hypot" | "hypot" => f1.hypot(f2),
+                    "atan2" => f1.atan2(f2),
+                    #[allow(deprecated)]
+                    "fdim" => f1.abs_sub(f2),
+                    _ => bug!(),
+                };
+                this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?;
+            }
+            #[rustfmt::skip]
+            | "_ldexp"
+            | "ldexp"
+            | "scalbn"
+            => {
+                let [x, exp] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                // For radix-2 (binary) systems, `ldexp` and `scalbn` are the same.
+                let x = this.read_scalar(x)?.to_f64()?;
+                let exp = this.read_scalar(exp)?.to_i32()?;
+
+                // Saturating cast to i16. Even those are outside the valid exponent range so
+                // `scalbn` below will do its over/underflow handling.
+                let exp = if exp > i32::from(i16::MAX) {
+                    i16::MAX
+                } else if exp < i32::from(i16::MIN) {
+                    i16::MIN
+                } else {
+                    exp.try_into().unwrap()
+                };
+
+                let res = x.scalbn(exp);
+                this.write_scalar(Scalar::from_f64(res), dest)?;
+            }
+
+            // Architecture-specific shims
+            "llvm.x86.addcarry.64" if this.tcx.sess.target.arch == "x86_64" => {
+                // Computes u8+u64+u64, returning tuple (u8,u64) comprising the output carry and truncated sum.
+                let [c_in, a, b] = this.check_shim(abi, Abi::Unadjusted, link_name, args)?;
+                let c_in = this.read_scalar(c_in)?.to_u8()?;
+                let a = this.read_scalar(a)?.to_u64()?;
+                let b = this.read_scalar(b)?.to_u64()?;
+
+                #[allow(clippy::integer_arithmetic)] // adding two u64 and a u8 cannot wrap in a u128
+                let wide_sum = u128::from(c_in) + u128::from(a) + u128::from(b);
+                #[allow(clippy::integer_arithmetic)] // it's a u128, we can shift by 64
+                let (c_out, sum) = ((wide_sum >> 64).truncate::<u8>(), wide_sum.truncate::<u64>());
+
+                let c_out_field = this.place_field(dest, 0)?;
+                this.write_scalar(Scalar::from_u8(c_out), &c_out_field)?;
+                let sum_field = this.place_field(dest, 1)?;
+                this.write_scalar(Scalar::from_u64(sum), &sum_field)?;
+            }
+            "llvm.x86.sse2.pause" if this.tcx.sess.target.arch == "x86" || this.tcx.sess.target.arch == "x86_64" => {
+                let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                this.yield_active_thread();
+            }
+            "llvm.aarch64.isb" if this.tcx.sess.target.arch == "aarch64" => {
+                let [arg] = this.check_shim(abi, Abi::Unadjusted, link_name, args)?;
+                let arg = this.read_scalar(arg)?.to_i32()?;
+                match arg {
+                    15 => { // SY ("full system scope")
+                        this.yield_active_thread();
+                    }
+                    _ => {
+                        throw_unsup_format!("unsupported llvm.aarch64.isb argument {}", arg);
+                    }
+                }
+            }
+
+            // Platform-specific shims
+            _ => match this.tcx.sess.target.os.as_ref() {
+                target if target_os_is_unix(target) => return shims::unix::foreign_items::EvalContextExt::emulate_foreign_item_by_name(this, link_name, abi, args, dest),
+                "windows" => return shims::windows::foreign_items::EvalContextExt::emulate_foreign_item_by_name(this, link_name, abi, args, dest),
+                target => throw_unsup_format!("the target `{}` is not supported", target),
+            }
+        };
+        // 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)
+    }
+
+    /// Check some basic requirements for this allocation request:
+    /// non-zero size, power-of-two alignment.
+    fn check_alloc_request(size: u64, align: u64) -> InterpResult<'tcx> {
+        if size == 0 {
+            throw_ub_format!("creating allocation with size 0");
+        }
+        if !align.is_power_of_two() {
+            throw_ub_format!("creating allocation with non-power-of-two alignment {}", align);
+        }
+        Ok(())
+    }
+}
diff --git a/src/tools/miri/src/shims/intrinsics/atomic.rs b/src/tools/miri/src/shims/intrinsics/atomic.rs
new file mode 100644
index 0000000000000..50f69bdca3631
--- /dev/null
+++ b/src/tools/miri/src/shims/intrinsics/atomic.rs
@@ -0,0 +1,288 @@
+use rustc_middle::{mir, mir::BinOp, ty};
+
+use crate::*;
+use helpers::check_arg_count;
+
+pub enum AtomicOp {
+    /// The `bool` indicates whether the result of the operation should be negated
+    /// (must be a boolean-typed operation).
+    MirOp(mir::BinOp, bool),
+    Max,
+    Min,
+}
+
+impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    /// Calls the atomic intrinsic `intrinsic`; the `atomic_` prefix has already been removed.
+    fn emulate_atomic_intrinsic(
+        &mut self,
+        intrinsic_name: &str,
+        args: &[OpTy<'tcx, Provenance>],
+        dest: &PlaceTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+
+        let intrinsic_structure: Vec<_> = intrinsic_name.split('_').collect();
+
+        fn read_ord<'tcx>(ord: &str) -> InterpResult<'tcx, AtomicReadOrd> {
+            Ok(match ord {
+                "seqcst" => AtomicReadOrd::SeqCst,
+                "acquire" => AtomicReadOrd::Acquire,
+                "relaxed" => AtomicReadOrd::Relaxed,
+                _ => throw_unsup_format!("unsupported read ordering `{ord}`"),
+            })
+        }
+
+        fn write_ord<'tcx>(ord: &str) -> InterpResult<'tcx, AtomicWriteOrd> {
+            Ok(match ord {
+                "seqcst" => AtomicWriteOrd::SeqCst,
+                "release" => AtomicWriteOrd::Release,
+                "relaxed" => AtomicWriteOrd::Relaxed,
+                _ => throw_unsup_format!("unsupported write ordering `{ord}`"),
+            })
+        }
+
+        fn rw_ord<'tcx>(ord: &str) -> InterpResult<'tcx, AtomicRwOrd> {
+            Ok(match ord {
+                "seqcst" => AtomicRwOrd::SeqCst,
+                "acqrel" => AtomicRwOrd::AcqRel,
+                "acquire" => AtomicRwOrd::Acquire,
+                "release" => AtomicRwOrd::Release,
+                "relaxed" => AtomicRwOrd::Relaxed,
+                _ => throw_unsup_format!("unsupported read-write ordering `{ord}`"),
+            })
+        }
+
+        fn fence_ord<'tcx>(ord: &str) -> InterpResult<'tcx, AtomicFenceOrd> {
+            Ok(match ord {
+                "seqcst" => AtomicFenceOrd::SeqCst,
+                "acqrel" => AtomicFenceOrd::AcqRel,
+                "acquire" => AtomicFenceOrd::Acquire,
+                "release" => AtomicFenceOrd::Release,
+                _ => throw_unsup_format!("unsupported fence ordering `{ord}`"),
+            })
+        }
+
+        match &*intrinsic_structure {
+            ["load", ord] => this.atomic_load(args, dest, read_ord(ord)?)?,
+            ["store", ord] => this.atomic_store(args, write_ord(ord)?)?,
+
+            ["fence", ord] => this.atomic_fence_intrinsic(args, fence_ord(ord)?)?,
+            ["singlethreadfence", ord] => this.compiler_fence_intrinsic(args, fence_ord(ord)?)?,
+
+            ["xchg", ord] => this.atomic_exchange(args, dest, rw_ord(ord)?)?,
+            ["cxchg", ord1, ord2] =>
+                this.atomic_compare_exchange(args, dest, rw_ord(ord1)?, read_ord(ord2)?)?,
+            ["cxchgweak", ord1, ord2] =>
+                this.atomic_compare_exchange_weak(args, dest, rw_ord(ord1)?, read_ord(ord2)?)?,
+
+            ["or", ord] =>
+                this.atomic_op(args, dest, AtomicOp::MirOp(BinOp::BitOr, false), rw_ord(ord)?)?,
+            ["xor", ord] =>
+                this.atomic_op(args, dest, AtomicOp::MirOp(BinOp::BitXor, false), rw_ord(ord)?)?,
+            ["and", ord] =>
+                this.atomic_op(args, dest, AtomicOp::MirOp(BinOp::BitAnd, false), rw_ord(ord)?)?,
+            ["nand", ord] =>
+                this.atomic_op(args, dest, AtomicOp::MirOp(BinOp::BitAnd, true), rw_ord(ord)?)?,
+            ["xadd", ord] =>
+                this.atomic_op(args, dest, AtomicOp::MirOp(BinOp::Add, false), rw_ord(ord)?)?,
+            ["xsub", ord] =>
+                this.atomic_op(args, dest, AtomicOp::MirOp(BinOp::Sub, false), rw_ord(ord)?)?,
+            ["min", ord] => {
+                // Later we will use the type to indicate signed vs unsigned,
+                // so make sure it matches the intrinsic name.
+                assert!(matches!(args[1].layout.ty.kind(), ty::Int(_)));
+                this.atomic_op(args, dest, AtomicOp::Min, rw_ord(ord)?)?;
+            }
+            ["umin", ord] => {
+                // Later we will use the type to indicate signed vs unsigned,
+                // so make sure it matches the intrinsic name.
+                assert!(matches!(args[1].layout.ty.kind(), ty::Uint(_)));
+                this.atomic_op(args, dest, AtomicOp::Min, rw_ord(ord)?)?;
+            }
+            ["max", ord] => {
+                // Later we will use the type to indicate signed vs unsigned,
+                // so make sure it matches the intrinsic name.
+                assert!(matches!(args[1].layout.ty.kind(), ty::Int(_)));
+                this.atomic_op(args, dest, AtomicOp::Max, rw_ord(ord)?)?;
+            }
+            ["umax", ord] => {
+                // Later we will use the type to indicate signed vs unsigned,
+                // so make sure it matches the intrinsic name.
+                assert!(matches!(args[1].layout.ty.kind(), ty::Uint(_)));
+                this.atomic_op(args, dest, AtomicOp::Max, rw_ord(ord)?)?;
+            }
+
+            _ => throw_unsup_format!("unimplemented intrinsic: `atomic_{intrinsic_name}`"),
+        }
+        Ok(())
+    }
+}
+
+impl<'mir, 'tcx: 'mir> EvalContextPrivExt<'mir, 'tcx> for MiriInterpCx<'mir, 'tcx> {}
+trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
+    fn atomic_load(
+        &mut self,
+        args: &[OpTy<'tcx, Provenance>],
+        dest: &PlaceTy<'tcx, Provenance>,
+        atomic: AtomicReadOrd,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+
+        let [place] = check_arg_count(args)?;
+        let place = this.deref_operand(place)?;
+
+        // Perform atomic load.
+        let val = this.read_scalar_atomic(&place, atomic)?;
+        // Perform regular store.
+        this.write_scalar(val, dest)?;
+        Ok(())
+    }
+
+    fn atomic_store(
+        &mut self,
+        args: &[OpTy<'tcx, Provenance>],
+        atomic: AtomicWriteOrd,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+
+        let [place, val] = check_arg_count(args)?;
+        let place = this.deref_operand(place)?;
+
+        // Perform regular load.
+        let val = this.read_scalar(val)?;
+        // Perform atomic store
+        this.write_scalar_atomic(val, &place, atomic)?;
+        Ok(())
+    }
+
+    fn compiler_fence_intrinsic(
+        &mut self,
+        args: &[OpTy<'tcx, Provenance>],
+        atomic: AtomicFenceOrd,
+    ) -> InterpResult<'tcx> {
+        let [] = check_arg_count(args)?;
+        let _ = atomic;
+        //FIXME: compiler fences are currently ignored
+        Ok(())
+    }
+
+    fn atomic_fence_intrinsic(
+        &mut self,
+        args: &[OpTy<'tcx, Provenance>],
+        atomic: AtomicFenceOrd,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        let [] = check_arg_count(args)?;
+        this.atomic_fence(atomic)?;
+        Ok(())
+    }
+
+    fn atomic_op(
+        &mut self,
+        args: &[OpTy<'tcx, Provenance>],
+        dest: &PlaceTy<'tcx, Provenance>,
+        atomic_op: AtomicOp,
+        atomic: AtomicRwOrd,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+
+        let [place, rhs] = check_arg_count(args)?;
+        let place = this.deref_operand(place)?;
+        let rhs = this.read_immediate(rhs)?;
+
+        if !place.layout.ty.is_integral() && !place.layout.ty.is_unsafe_ptr() {
+            span_bug!(
+                this.cur_span(),
+                "atomic arithmetic operations only work on integer and raw pointer types",
+            );
+        }
+        if rhs.layout.ty != place.layout.ty {
+            span_bug!(this.cur_span(), "atomic arithmetic operation type mismatch");
+        }
+
+        match atomic_op {
+            AtomicOp::Min => {
+                let old = this.atomic_min_max_scalar(&place, rhs, true, atomic)?;
+                this.write_immediate(*old, dest)?; // old value is returned
+                Ok(())
+            }
+            AtomicOp::Max => {
+                let old = this.atomic_min_max_scalar(&place, rhs, false, atomic)?;
+                this.write_immediate(*old, dest)?; // old value is returned
+                Ok(())
+            }
+            AtomicOp::MirOp(op, neg) => {
+                let old = this.atomic_op_immediate(&place, &rhs, op, neg, atomic)?;
+                this.write_immediate(*old, dest)?; // old value is returned
+                Ok(())
+            }
+        }
+    }
+
+    fn atomic_exchange(
+        &mut self,
+        args: &[OpTy<'tcx, Provenance>],
+        dest: &PlaceTy<'tcx, Provenance>,
+        atomic: AtomicRwOrd,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+
+        let [place, new] = check_arg_count(args)?;
+        let place = this.deref_operand(place)?;
+        let new = this.read_scalar(new)?;
+
+        let old = this.atomic_exchange_scalar(&place, new, atomic)?;
+        this.write_scalar(old, dest)?; // old value is returned
+        Ok(())
+    }
+
+    fn atomic_compare_exchange_impl(
+        &mut self,
+        args: &[OpTy<'tcx, Provenance>],
+        dest: &PlaceTy<'tcx, Provenance>,
+        success: AtomicRwOrd,
+        fail: AtomicReadOrd,
+        can_fail_spuriously: bool,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+
+        let [place, expect_old, new] = check_arg_count(args)?;
+        let place = this.deref_operand(place)?;
+        let expect_old = this.read_immediate(expect_old)?; // read as immediate for the sake of `binary_op()`
+        let new = this.read_scalar(new)?;
+
+        let old = this.atomic_compare_exchange_scalar(
+            &place,
+            &expect_old,
+            new,
+            success,
+            fail,
+            can_fail_spuriously,
+        )?;
+
+        // Return old value.
+        this.write_immediate(old, dest)?;
+        Ok(())
+    }
+
+    fn atomic_compare_exchange(
+        &mut self,
+        args: &[OpTy<'tcx, Provenance>],
+        dest: &PlaceTy<'tcx, Provenance>,
+        success: AtomicRwOrd,
+        fail: AtomicReadOrd,
+    ) -> InterpResult<'tcx> {
+        self.atomic_compare_exchange_impl(args, dest, success, fail, false)
+    }
+
+    fn atomic_compare_exchange_weak(
+        &mut self,
+        args: &[OpTy<'tcx, Provenance>],
+        dest: &PlaceTy<'tcx, Provenance>,
+        success: AtomicRwOrd,
+        fail: AtomicReadOrd,
+    ) -> InterpResult<'tcx> {
+        self.atomic_compare_exchange_impl(args, dest, success, fail, true)
+    }
+}
diff --git a/src/tools/miri/src/shims/intrinsics/mod.rs b/src/tools/miri/src/shims/intrinsics/mod.rs
new file mode 100644
index 0000000000000..e0985ace5be7d
--- /dev/null
+++ b/src/tools/miri/src/shims/intrinsics/mod.rs
@@ -0,0 +1,429 @@
+mod atomic;
+mod simd;
+
+use std::iter;
+
+use log::trace;
+
+use rustc_apfloat::{Float, Round};
+use rustc_middle::ty::layout::{IntegerExt, LayoutOf};
+use rustc_middle::{
+    mir,
+    ty::{self, FloatTy, Ty},
+};
+use rustc_target::abi::Integer;
+
+use crate::*;
+use atomic::EvalContextExt as _;
+use helpers::check_arg_count;
+use simd::EvalContextExt as _;
+
+impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    fn call_intrinsic(
+        &mut self,
+        instance: ty::Instance<'tcx>,
+        args: &[OpTy<'tcx, Provenance>],
+        dest: &PlaceTy<'tcx, Provenance>,
+        ret: Option<mir::BasicBlock>,
+        _unwind: StackPopUnwind,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+
+        // See if the core engine can handle this intrinsic.
+        if this.emulate_intrinsic(instance, args, dest, ret)? {
+            return Ok(());
+        }
+
+        // All remaining supported intrinsics have a return place.
+        let intrinsic_name = this.tcx.item_name(instance.def_id());
+        let intrinsic_name = intrinsic_name.as_str();
+        let ret = match ret {
+            None => throw_unsup_format!("unimplemented (diverging) intrinsic: `{intrinsic_name}`"),
+            Some(p) => p,
+        };
+
+        // Some intrinsics are special and need the "ret".
+        match intrinsic_name {
+            "try" => return this.handle_try(args, dest, ret),
+            _ => {}
+        }
+
+        // The rest jumps to `ret` immediately.
+        this.emulate_intrinsic_by_name(intrinsic_name, args, dest)?;
+
+        trace!("{:?}", this.dump_place(**dest));
+        this.go_to_block(ret);
+        Ok(())
+    }
+
+    /// Emulates a Miri-supported intrinsic (not supported by the core engine).
+    fn emulate_intrinsic_by_name(
+        &mut self,
+        intrinsic_name: &str,
+        args: &[OpTy<'tcx, Provenance>],
+        dest: &PlaceTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+
+        if let Some(name) = intrinsic_name.strip_prefix("atomic_") {
+            return this.emulate_atomic_intrinsic(name, args, dest);
+        }
+        if let Some(name) = intrinsic_name.strip_prefix("simd_") {
+            return this.emulate_simd_intrinsic(name, args, dest);
+        }
+
+        match intrinsic_name {
+            // Miri overwriting CTFE intrinsics.
+            "ptr_guaranteed_cmp" => {
+                let [left, right] = check_arg_count(args)?;
+                let left = this.read_immediate(left)?;
+                let right = this.read_immediate(right)?;
+                let (val, _overflowed, _ty) =
+                    this.overflowing_binary_op(mir::BinOp::Eq, &left, &right)?;
+                // We're type punning a bool as an u8 here.
+                this.write_scalar(val, dest)?;
+            }
+            "const_allocate" => {
+                // For now, for compatibility with the run-time implementation of this, we just return null.
+                // See <https://github.com/rust-lang/rust/issues/93935>.
+                this.write_null(dest)?;
+            }
+            "const_deallocate" => {
+                // complete NOP
+            }
+
+            // Raw memory accesses
+            "volatile_load" => {
+                let [place] = check_arg_count(args)?;
+                let place = this.deref_operand(place)?;
+                this.copy_op(&place.into(), dest, /*allow_transmute*/ false)?;
+            }
+            "volatile_store" => {
+                let [place, dest] = check_arg_count(args)?;
+                let place = this.deref_operand(place)?;
+                this.copy_op(dest, &place.into(), /*allow_transmute*/ false)?;
+            }
+
+            "write_bytes" | "volatile_set_memory" => {
+                let [ptr, val_byte, count] = check_arg_count(args)?;
+                let ty = ptr.layout.ty.builtin_deref(true).unwrap().ty;
+                let ty_layout = this.layout_of(ty)?;
+                let val_byte = this.read_scalar(val_byte)?.to_u8()?;
+                let ptr = this.read_pointer(ptr)?;
+                let count = this.read_scalar(count)?.to_machine_usize(this)?;
+                // `checked_mul` enforces a too small bound (the correct one would probably be machine_isize_max),
+                // but no actual allocation can be big enough for the difference to be noticeable.
+                let byte_count = ty_layout.size.checked_mul(count, this).ok_or_else(|| {
+                    err_ub_format!("overflow computing total size of `{intrinsic_name}`")
+                })?;
+                this.write_bytes_ptr(ptr, iter::repeat(val_byte).take(byte_count.bytes_usize()))?;
+            }
+
+            // Floating-point operations
+            "fabsf32" => {
+                let [f] = check_arg_count(args)?;
+                let f = this.read_scalar(f)?.to_f32()?;
+                // Can be implemented in soft-floats.
+                this.write_scalar(Scalar::from_f32(f.abs()), dest)?;
+            }
+            "fabsf64" => {
+                let [f] = check_arg_count(args)?;
+                let f = this.read_scalar(f)?.to_f64()?;
+                // Can be implemented in soft-floats.
+                this.write_scalar(Scalar::from_f64(f.abs()), dest)?;
+            }
+            #[rustfmt::skip]
+            | "sinf32"
+            | "cosf32"
+            | "sqrtf32"
+            | "expf32"
+            | "exp2f32"
+            | "logf32"
+            | "log10f32"
+            | "log2f32"
+            | "floorf32"
+            | "ceilf32"
+            | "truncf32"
+            | "roundf32"
+            => {
+                let [f] = check_arg_count(args)?;
+                // FIXME: Using host floats.
+                let f = f32::from_bits(this.read_scalar(f)?.to_u32()?);
+                let f = match intrinsic_name {
+                    "sinf32" => f.sin(),
+                    "cosf32" => f.cos(),
+                    "sqrtf32" => f.sqrt(),
+                    "expf32" => f.exp(),
+                    "exp2f32" => f.exp2(),
+                    "logf32" => f.ln(),
+                    "log10f32" => f.log10(),
+                    "log2f32" => f.log2(),
+                    "floorf32" => f.floor(),
+                    "ceilf32" => f.ceil(),
+                    "truncf32" => f.trunc(),
+                    "roundf32" => f.round(),
+                    _ => bug!(),
+                };
+                this.write_scalar(Scalar::from_u32(f.to_bits()), dest)?;
+            }
+
+            #[rustfmt::skip]
+            | "sinf64"
+            | "cosf64"
+            | "sqrtf64"
+            | "expf64"
+            | "exp2f64"
+            | "logf64"
+            | "log10f64"
+            | "log2f64"
+            | "floorf64"
+            | "ceilf64"
+            | "truncf64"
+            | "roundf64"
+            => {
+                let [f] = check_arg_count(args)?;
+                // FIXME: Using host floats.
+                let f = f64::from_bits(this.read_scalar(f)?.to_u64()?);
+                let f = match intrinsic_name {
+                    "sinf64" => f.sin(),
+                    "cosf64" => f.cos(),
+                    "sqrtf64" => f.sqrt(),
+                    "expf64" => f.exp(),
+                    "exp2f64" => f.exp2(),
+                    "logf64" => f.ln(),
+                    "log10f64" => f.log10(),
+                    "log2f64" => f.log2(),
+                    "floorf64" => f.floor(),
+                    "ceilf64" => f.ceil(),
+                    "truncf64" => f.trunc(),
+                    "roundf64" => f.round(),
+                    _ => bug!(),
+                };
+                this.write_scalar(Scalar::from_u64(f.to_bits()), dest)?;
+            }
+
+            #[rustfmt::skip]
+            | "fadd_fast"
+            | "fsub_fast"
+            | "fmul_fast"
+            | "fdiv_fast"
+            | "frem_fast"
+            => {
+                let [a, b] = check_arg_count(args)?;
+                let a = this.read_immediate(a)?;
+                let b = this.read_immediate(b)?;
+                let op = match intrinsic_name {
+                    "fadd_fast" => mir::BinOp::Add,
+                    "fsub_fast" => mir::BinOp::Sub,
+                    "fmul_fast" => mir::BinOp::Mul,
+                    "fdiv_fast" => mir::BinOp::Div,
+                    "frem_fast" => mir::BinOp::Rem,
+                    _ => bug!(),
+                };
+                let float_finite = |x: &ImmTy<'tcx, _>| -> InterpResult<'tcx, bool> {
+                    Ok(match x.layout.ty.kind() {
+                        ty::Float(FloatTy::F32) => x.to_scalar().to_f32()?.is_finite(),
+                        ty::Float(FloatTy::F64) => x.to_scalar().to_f64()?.is_finite(),
+                        _ => bug!(
+                            "`{intrinsic_name}` called with non-float input type {ty:?}",
+                            ty = x.layout.ty,
+                        ),
+                    })
+                };
+                match (float_finite(&a)?, float_finite(&b)?) {
+                    (false, false) => throw_ub_format!(
+                        "`{intrinsic_name}` intrinsic called with non-finite value as both parameters",
+                    ),
+                    (false, _) => throw_ub_format!(
+                        "`{intrinsic_name}` intrinsic called with non-finite value as first parameter",
+                    ),
+                    (_, false) => throw_ub_format!(
+                        "`{intrinsic_name}` intrinsic called with non-finite value as second parameter",
+                    ),
+                    _ => {}
+                }
+                this.binop_ignore_overflow(op, &a, &b, dest)?;
+            }
+
+            #[rustfmt::skip]
+            | "minnumf32"
+            | "maxnumf32"
+            | "copysignf32"
+            => {
+                let [a, b] = check_arg_count(args)?;
+                let a = this.read_scalar(a)?.to_f32()?;
+                let b = this.read_scalar(b)?.to_f32()?;
+                let res = match intrinsic_name {
+                    "minnumf32" => a.min(b),
+                    "maxnumf32" => a.max(b),
+                    "copysignf32" => a.copy_sign(b),
+                    _ => bug!(),
+                };
+                this.write_scalar(Scalar::from_f32(res), dest)?;
+            }
+
+            #[rustfmt::skip]
+            | "minnumf64"
+            | "maxnumf64"
+            | "copysignf64"
+            => {
+                let [a, b] = check_arg_count(args)?;
+                let a = this.read_scalar(a)?.to_f64()?;
+                let b = this.read_scalar(b)?.to_f64()?;
+                let res = match intrinsic_name {
+                    "minnumf64" => a.min(b),
+                    "maxnumf64" => a.max(b),
+                    "copysignf64" => a.copy_sign(b),
+                    _ => bug!(),
+                };
+                this.write_scalar(Scalar::from_f64(res), dest)?;
+            }
+
+            "powf32" => {
+                let [f, f2] = check_arg_count(args)?;
+                // FIXME: Using host floats.
+                let f = f32::from_bits(this.read_scalar(f)?.to_u32()?);
+                let f2 = f32::from_bits(this.read_scalar(f2)?.to_u32()?);
+                let res = f.powf(f2);
+                this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?;
+            }
+
+            "powf64" => {
+                let [f, f2] = check_arg_count(args)?;
+                // FIXME: Using host floats.
+                let f = f64::from_bits(this.read_scalar(f)?.to_u64()?);
+                let f2 = f64::from_bits(this.read_scalar(f2)?.to_u64()?);
+                let res = f.powf(f2);
+                this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?;
+            }
+
+            "fmaf32" => {
+                let [a, b, c] = check_arg_count(args)?;
+                // FIXME: Using host floats, to work around https://github.com/rust-lang/miri/issues/2468.
+                let a = f32::from_bits(this.read_scalar(a)?.to_u32()?);
+                let b = f32::from_bits(this.read_scalar(b)?.to_u32()?);
+                let c = f32::from_bits(this.read_scalar(c)?.to_u32()?);
+                let res = a.mul_add(b, c);
+                this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?;
+            }
+
+            "fmaf64" => {
+                let [a, b, c] = check_arg_count(args)?;
+                // FIXME: Using host floats, to work around https://github.com/rust-lang/miri/issues/2468.
+                let a = f64::from_bits(this.read_scalar(a)?.to_u64()?);
+                let b = f64::from_bits(this.read_scalar(b)?.to_u64()?);
+                let c = f64::from_bits(this.read_scalar(c)?.to_u64()?);
+                let res = a.mul_add(b, c);
+                this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?;
+            }
+
+            "powif32" => {
+                let [f, i] = check_arg_count(args)?;
+                // FIXME: Using host floats.
+                let f = f32::from_bits(this.read_scalar(f)?.to_u32()?);
+                let i = this.read_scalar(i)?.to_i32()?;
+                let res = f.powi(i);
+                this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?;
+            }
+
+            "powif64" => {
+                let [f, i] = check_arg_count(args)?;
+                // FIXME: Using host floats.
+                let f = f64::from_bits(this.read_scalar(f)?.to_u64()?);
+                let i = this.read_scalar(i)?.to_i32()?;
+                let res = f.powi(i);
+                this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?;
+            }
+
+            "float_to_int_unchecked" => {
+                let [val] = check_arg_count(args)?;
+                let val = this.read_immediate(val)?;
+
+                let res = match val.layout.ty.kind() {
+                    ty::Float(FloatTy::F32) =>
+                        this.float_to_int_unchecked(val.to_scalar().to_f32()?, dest.layout.ty)?,
+                    ty::Float(FloatTy::F64) =>
+                        this.float_to_int_unchecked(val.to_scalar().to_f64()?, dest.layout.ty)?,
+                    _ =>
+                        span_bug!(
+                            this.cur_span(),
+                            "`float_to_int_unchecked` called with non-float input type {:?}",
+                            val.layout.ty
+                        ),
+                };
+
+                this.write_scalar(res, dest)?;
+            }
+
+            // Other
+            "exact_div" => {
+                let [num, denom] = check_arg_count(args)?;
+                this.exact_div(&this.read_immediate(num)?, &this.read_immediate(denom)?, dest)?;
+            }
+
+            "breakpoint" => {
+                let [] = check_arg_count(args)?;
+                // normally this would raise a SIGTRAP, which aborts if no debugger is connected
+                throw_machine_stop!(TerminationInfo::Abort(format!("Trace/breakpoint trap")))
+            }
+
+            name => throw_unsup_format!("unimplemented intrinsic: `{name}`"),
+        }
+
+        Ok(())
+    }
+
+    fn float_to_int_unchecked<F>(
+        &self,
+        f: F,
+        dest_ty: Ty<'tcx>,
+    ) -> InterpResult<'tcx, Scalar<Provenance>>
+    where
+        F: Float + Into<Scalar<Provenance>>,
+    {
+        let this = self.eval_context_ref();
+
+        // Step 1: cut off the fractional part of `f`. The result of this is
+        // guaranteed to be precisely representable in IEEE floats.
+        let f = f.round_to_integral(Round::TowardZero).value;
+
+        // Step 2: Cast the truncated float to the target integer type and see if we lose any information in this step.
+        Ok(match dest_ty.kind() {
+            // Unsigned
+            ty::Uint(t) => {
+                let size = Integer::from_uint_ty(this, *t).size();
+                let res = f.to_u128(size.bits_usize());
+                if res.status.is_empty() {
+                    // No status flags means there was no further rounding or other loss of precision.
+                    Scalar::from_uint(res.value, size)
+                } else {
+                    // `f` was not representable in this integer type.
+                    throw_ub_format!(
+                        "`float_to_int_unchecked` intrinsic called on {f} which cannot be represented in target type `{dest_ty:?}`",
+                    );
+                }
+            }
+            // Signed
+            ty::Int(t) => {
+                let size = Integer::from_int_ty(this, *t).size();
+                let res = f.to_i128(size.bits_usize());
+                if res.status.is_empty() {
+                    // No status flags means there was no further rounding or other loss of precision.
+                    Scalar::from_int(res.value, size)
+                } else {
+                    // `f` was not representable in this integer type.
+                    throw_ub_format!(
+                        "`float_to_int_unchecked` intrinsic called on {f} which cannot be represented in target type `{dest_ty:?}`",
+                    );
+                }
+            }
+            // Nothing else
+            _ =>
+                span_bug!(
+                    this.cur_span(),
+                    "`float_to_int_unchecked` called with non-int output type {dest_ty:?}"
+                ),
+        })
+    }
+}
diff --git a/src/tools/miri/src/shims/intrinsics/simd.rs b/src/tools/miri/src/shims/intrinsics/simd.rs
new file mode 100644
index 0000000000000..163d185f66f3a
--- /dev/null
+++ b/src/tools/miri/src/shims/intrinsics/simd.rs
@@ -0,0 +1,627 @@
+use rustc_apfloat::Float;
+use rustc_middle::ty::layout::{HasParamEnv, LayoutOf};
+use rustc_middle::{mir, ty, ty::FloatTy};
+use rustc_target::abi::{Endian, HasDataLayout, Size};
+
+use crate::*;
+use helpers::check_arg_count;
+
+impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    /// Calls the simd intrinsic `intrinsic`; the `simd_` prefix has already been removed.
+    fn emulate_simd_intrinsic(
+        &mut self,
+        intrinsic_name: &str,
+        args: &[OpTy<'tcx, Provenance>],
+        dest: &PlaceTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        match intrinsic_name {
+            #[rustfmt::skip]
+            | "neg"
+            | "fabs"
+            | "ceil"
+            | "floor"
+            | "round"
+            | "trunc"
+            | "fsqrt" => {
+                let [op] = check_arg_count(args)?;
+                let (op, op_len) = this.operand_to_simd(op)?;
+                let (dest, dest_len) = this.place_to_simd(dest)?;
+
+                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),
+                }
+                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),
+                    _ => unreachable!(),
+                };
+
+                for i in 0..dest_len {
+                    let op = this.read_immediate(&this.mplace_index(&op, i)?.into())?;
+                    let dest = this.mplace_index(&dest, i)?;
+                    let val = match which {
+                        Op::MirOp(mir_op) => this.unary_op(mir_op, &op)?.to_scalar(),
+                        Op::Abs => {
+                            // Works for f32 and f64.
+                            let ty::Float(float_ty) = op.layout.ty.kind() else {
+                                span_bug!(this.cur_span(), "{} operand is not a float", intrinsic_name)
+                            };
+                            let op = op.to_scalar();
+                            match float_ty {
+                                FloatTy::F32 => Scalar::from_f32(op.to_f32()?.abs()),
+                                FloatTy::F64 => Scalar::from_f64(op.to_f64()?.abs()),
+                            }
+                        }
+                        Op::HostOp(host_op) => {
+                            let ty::Float(float_ty) = op.layout.ty.kind() else {
+                                span_bug!(this.cur_span(), "{} operand is not a float", intrinsic_name)
+                            };
+                            // FIXME using host floats
+                            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(),
+                                    };
+                                    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(),
+                                    };
+                                    Scalar::from_u64(res.to_bits())
+                                }
+                            }
+
+                        }
+                    };
+                    this.write_scalar(val, &dest.into())?;
+                }
+            }
+            #[rustfmt::skip]
+            | "add"
+            | "sub"
+            | "mul"
+            | "div"
+            | "rem"
+            | "shl"
+            | "shr"
+            | "and"
+            | "or"
+            | "xor"
+            | "eq"
+            | "ne"
+            | "lt"
+            | "le"
+            | "gt"
+            | "ge"
+            | "fmax"
+            | "fmin"
+            | "saturating_add"
+            | "saturating_sub"
+            | "arith_offset" => {
+                use mir::BinOp;
+
+                let [left, right] = check_arg_count(args)?;
+                let (left, left_len) = this.operand_to_simd(left)?;
+                let (right, right_len) = this.operand_to_simd(right)?;
+                let (dest, dest_len) = this.place_to_simd(dest)?;
+
+                assert_eq!(dest_len, left_len);
+                assert_eq!(dest_len, right_len);
+
+                enum Op {
+                    MirOp(BinOp),
+                    SaturatingOp(BinOp),
+                    FMax,
+                    FMin,
+                    WrappingOffset,
+                }
+                let which = match intrinsic_name {
+                    "add" => Op::MirOp(BinOp::Add),
+                    "sub" => Op::MirOp(BinOp::Sub),
+                    "mul" => Op::MirOp(BinOp::Mul),
+                    "div" => Op::MirOp(BinOp::Div),
+                    "rem" => Op::MirOp(BinOp::Rem),
+                    "shl" => Op::MirOp(BinOp::Shl),
+                    "shr" => Op::MirOp(BinOp::Shr),
+                    "and" => Op::MirOp(BinOp::BitAnd),
+                    "or" => Op::MirOp(BinOp::BitOr),
+                    "xor" => Op::MirOp(BinOp::BitXor),
+                    "eq" => Op::MirOp(BinOp::Eq),
+                    "ne" => Op::MirOp(BinOp::Ne),
+                    "lt" => Op::MirOp(BinOp::Lt),
+                    "le" => Op::MirOp(BinOp::Le),
+                    "gt" => Op::MirOp(BinOp::Gt),
+                    "ge" => Op::MirOp(BinOp::Ge),
+                    "fmax" => Op::FMax,
+                    "fmin" => Op::FMin,
+                    "saturating_add" => Op::SaturatingOp(BinOp::Add),
+                    "saturating_sub" => Op::SaturatingOp(BinOp::Sub),
+                    "arith_offset" => Op::WrappingOffset,
+                    _ => unreachable!(),
+                };
+
+                for i in 0..dest_len {
+                    let left = this.read_immediate(&this.mplace_index(&left, i)?.into())?;
+                    let right = this.read_immediate(&this.mplace_index(&right, i)?.into())?;
+                    let dest = this.mplace_index(&dest, i)?;
+                    let val = match which {
+                        Op::MirOp(mir_op) => {
+                            let (val, overflowed, ty) = this.overflowing_binary_op(mir_op, &left, &right)?;
+                            if matches!(mir_op, BinOp::Shl | BinOp::Shr) {
+                                // Shifts have extra UB as SIMD operations that the MIR binop does not have.
+                                // See <https://github.com/rust-lang/rust/issues/91237>.
+                                if overflowed {
+                                    let r_val = right.to_scalar().to_bits(right.layout.size)?;
+                                    throw_ub_format!("overflowing shift by {r_val} in `simd_{intrinsic_name}` in SIMD lane {i}");
+                                }
+                            }
+                            if matches!(mir_op, BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge) {
+                                // Special handling for boolean-returning operations
+                                assert_eq!(ty, this.tcx.types.bool);
+                                let val = val.to_bool().unwrap();
+                                bool_to_simd_element(val, dest.layout.size)
+                            } else {
+                                assert_ne!(ty, this.tcx.types.bool);
+                                assert_eq!(ty, dest.layout.ty);
+                                val
+                            }
+                        }
+                        Op::SaturatingOp(mir_op) => {
+                            this.saturating_arith(mir_op, &left, &right)?
+                        }
+                        Op::WrappingOffset => {
+                            let ptr = left.to_scalar().to_pointer(this)?;
+                            let offset_count = right.to_scalar().to_machine_isize(this)?;
+                            let pointee_ty = left.layout.ty.builtin_deref(true).unwrap().ty;
+
+                            let pointee_size = i64::try_from(this.layout_of(pointee_ty)?.size.bytes()).unwrap();
+                            let offset_bytes = offset_count.wrapping_mul(pointee_size);
+                            let offset_ptr = ptr.wrapping_signed_offset(offset_bytes, this);
+                            Scalar::from_maybe_pointer(offset_ptr, this)
+                        }
+                        Op::FMax => {
+                            fmax_op(&left, &right)?
+                        }
+                        Op::FMin => {
+                            fmin_op(&left, &right)?
+                        }
+                    };
+                    this.write_scalar(val, &dest.into())?;
+                }
+            }
+            "fma" => {
+                let [a, b, c] = check_arg_count(args)?;
+                let (a, a_len) = this.operand_to_simd(a)?;
+                let (b, b_len) = this.operand_to_simd(b)?;
+                let (c, c_len) = this.operand_to_simd(c)?;
+                let (dest, dest_len) = this.place_to_simd(dest)?;
+
+                assert_eq!(dest_len, a_len);
+                assert_eq!(dest_len, b_len);
+                assert_eq!(dest_len, c_len);
+
+                for i in 0..dest_len {
+                    let a = this.read_scalar(&this.mplace_index(&a, i)?.into())?;
+                    let b = this.read_scalar(&this.mplace_index(&b, i)?.into())?;
+                    let c = this.read_scalar(&this.mplace_index(&c, i)?.into())?;
+                    let dest = this.mplace_index(&dest, i)?;
+
+                    // Works for f32 and f64.
+                    // FIXME: using host floats to work around https://github.com/rust-lang/miri/issues/2468.
+                    let ty::Float(float_ty) = dest.layout.ty.kind() else {
+                        span_bug!(this.cur_span(), "{} operand is not a float", intrinsic_name)
+                    };
+                    let val = match float_ty {
+                        FloatTy::F32 => {
+                            let a = f32::from_bits(a.to_u32()?);
+                            let b = f32::from_bits(b.to_u32()?);
+                            let c = f32::from_bits(c.to_u32()?);
+                            let res = a.mul_add(b, c);
+                            Scalar::from_u32(res.to_bits())
+                        }
+                        FloatTy::F64 => {
+                            let a = f64::from_bits(a.to_u64()?);
+                            let b = f64::from_bits(b.to_u64()?);
+                            let c = f64::from_bits(c.to_u64()?);
+                            let res = a.mul_add(b, c);
+                            Scalar::from_u64(res.to_bits())
+                        }
+                    };
+                    this.write_scalar(val, &dest.into())?;
+                }
+            }
+            #[rustfmt::skip]
+            | "reduce_and"
+            | "reduce_or"
+            | "reduce_xor"
+            | "reduce_any"
+            | "reduce_all"
+            | "reduce_max"
+            | "reduce_min" => {
+                use mir::BinOp;
+
+                let [op] = check_arg_count(args)?;
+                let (op, op_len) = this.operand_to_simd(op)?;
+
+                let imm_from_bool =
+                    |b| ImmTy::from_scalar(Scalar::from_bool(b), this.machine.layouts.bool);
+
+                enum Op {
+                    MirOp(BinOp),
+                    MirOpBool(BinOp),
+                    Max,
+                    Min,
+                }
+                let which = match intrinsic_name {
+                    "reduce_and" => Op::MirOp(BinOp::BitAnd),
+                    "reduce_or" => Op::MirOp(BinOp::BitOr),
+                    "reduce_xor" => Op::MirOp(BinOp::BitXor),
+                    "reduce_any" => Op::MirOpBool(BinOp::BitOr),
+                    "reduce_all" => Op::MirOpBool(BinOp::BitAnd),
+                    "reduce_max" => Op::Max,
+                    "reduce_min" => Op::Min,
+                    _ => unreachable!(),
+                };
+
+                // Initialize with first lane, then proceed with the rest.
+                let mut res = this.read_immediate(&this.mplace_index(&op, 0)?.into())?;
+                if matches!(which, Op::MirOpBool(_)) {
+                    // Convert to `bool` scalar.
+                    res = imm_from_bool(simd_element_to_bool(res)?);
+                }
+                for i in 1..op_len {
+                    let op = this.read_immediate(&this.mplace_index(&op, i)?.into())?;
+                    res = match which {
+                        Op::MirOp(mir_op) => {
+                            this.binary_op(mir_op, &res, &op)?
+                        }
+                        Op::MirOpBool(mir_op) => {
+                            let op = imm_from_bool(simd_element_to_bool(op)?);
+                            this.binary_op(mir_op, &res, &op)?
+                        }
+                        Op::Max => {
+                            if matches!(res.layout.ty.kind(), ty::Float(_)) {
+                                ImmTy::from_scalar(fmax_op(&res, &op)?, res.layout)
+                            } else {
+                                // Just boring integers, so NaNs to worry about
+                                if this.binary_op(BinOp::Ge, &res, &op)?.to_scalar().to_bool()? {
+                                    res
+                                } else {
+                                    op
+                                }
+                            }
+                        }
+                        Op::Min => {
+                            if matches!(res.layout.ty.kind(), ty::Float(_)) {
+                                ImmTy::from_scalar(fmin_op(&res, &op)?, res.layout)
+                            } else {
+                                // Just boring integers, so NaNs to worry about
+                                if this.binary_op(BinOp::Le, &res, &op)?.to_scalar().to_bool()? {
+                                    res
+                                } else {
+                                    op
+                                }
+                            }
+                        }
+                    };
+                }
+                this.write_immediate(*res, dest)?;
+            }
+            #[rustfmt::skip]
+            | "reduce_add_ordered"
+            | "reduce_mul_ordered" => {
+                use mir::BinOp;
+
+                let [op, init] = check_arg_count(args)?;
+                let (op, op_len) = this.operand_to_simd(op)?;
+                let init = this.read_immediate(init)?;
+
+                let mir_op = match intrinsic_name {
+                    "reduce_add_ordered" => BinOp::Add,
+                    "reduce_mul_ordered" => BinOp::Mul,
+                    _ => unreachable!(),
+                };
+
+                let mut res = init;
+                for i in 0..op_len {
+                    let op = this.read_immediate(&this.mplace_index(&op, i)?.into())?;
+                    res = this.binary_op(mir_op, &res, &op)?;
+                }
+                this.write_immediate(*res, dest)?;
+            }
+            "select" => {
+                let [mask, yes, no] = check_arg_count(args)?;
+                let (mask, mask_len) = this.operand_to_simd(mask)?;
+                let (yes, yes_len) = this.operand_to_simd(yes)?;
+                let (no, no_len) = this.operand_to_simd(no)?;
+                let (dest, dest_len) = this.place_to_simd(dest)?;
+
+                assert_eq!(dest_len, mask_len);
+                assert_eq!(dest_len, yes_len);
+                assert_eq!(dest_len, no_len);
+
+                for i in 0..dest_len {
+                    let mask = this.read_immediate(&this.mplace_index(&mask, i)?.into())?;
+                    let yes = this.read_immediate(&this.mplace_index(&yes, i)?.into())?;
+                    let no = this.read_immediate(&this.mplace_index(&no, i)?.into())?;
+                    let dest = this.mplace_index(&dest, i)?;
+
+                    let val = if simd_element_to_bool(mask)? { yes } else { no };
+                    this.write_immediate(*val, &dest.into())?;
+                }
+            }
+            "select_bitmask" => {
+                let [mask, yes, no] = check_arg_count(args)?;
+                let (yes, yes_len) = this.operand_to_simd(yes)?;
+                let (no, no_len) = this.operand_to_simd(no)?;
+                let (dest, dest_len) = this.place_to_simd(dest)?;
+                let bitmask_len = dest_len.max(8);
+
+                assert!(mask.layout.ty.is_integral());
+                assert!(bitmask_len <= 64);
+                assert_eq!(bitmask_len, mask.layout.size.bits());
+                assert_eq!(dest_len, yes_len);
+                assert_eq!(dest_len, no_len);
+                let dest_len = u32::try_from(dest_len).unwrap();
+                let bitmask_len = u32::try_from(bitmask_len).unwrap();
+
+                let mask: u64 =
+                    this.read_scalar(mask)?.to_bits(mask.layout.size)?.try_into().unwrap();
+                for i in 0..dest_len {
+                    let mask = mask
+                        & 1u64
+                            .checked_shl(simd_bitmask_index(i, dest_len, this.data_layout().endian))
+                            .unwrap();
+                    let yes = this.read_immediate(&this.mplace_index(&yes, i.into())?.into())?;
+                    let no = this.read_immediate(&this.mplace_index(&no, i.into())?.into())?;
+                    let dest = this.mplace_index(&dest, i.into())?;
+
+                    let val = if mask != 0 { yes } else { no };
+                    this.write_immediate(*val, &dest.into())?;
+                }
+                for i in dest_len..bitmask_len {
+                    // If the mask is "padded", ensure that padding is all-zero.
+                    let mask = mask & 1u64.checked_shl(i).unwrap();
+                    if mask != 0 {
+                        throw_ub_format!(
+                            "a SIMD bitmask less than 8 bits long must be filled with 0s for the remaining bits"
+                        );
+                    }
+                }
+            }
+            #[rustfmt::skip]
+            "cast" | "as" => {
+                let [op] = check_arg_count(args)?;
+                let (op, op_len) = this.operand_to_simd(op)?;
+                let (dest, dest_len) = this.place_to_simd(dest)?;
+
+                assert_eq!(dest_len, op_len);
+
+                let safe_cast = intrinsic_name == "as";
+
+                for i in 0..dest_len {
+                    let op = this.read_immediate(&this.mplace_index(&op, i)?.into())?;
+                    let dest = this.mplace_index(&dest, i)?;
+
+                    let val = match (op.layout.ty.kind(), dest.layout.ty.kind()) {
+                        // Int-to-(int|float): always safe
+                        (ty::Int(_) | ty::Uint(_), ty::Int(_) | ty::Uint(_) | ty::Float(_)) =>
+                            this.misc_cast(&op, dest.layout.ty)?,
+                        // Float-to-float: always safe
+                        (ty::Float(_), ty::Float(_)) =>
+                            this.misc_cast(&op, dest.layout.ty)?,
+                        // Float-to-int in safe mode
+                        (ty::Float(_), ty::Int(_) | ty::Uint(_)) if safe_cast =>
+                            this.misc_cast(&op, dest.layout.ty)?,
+                        // Float-to-int in unchecked mode
+                        (ty::Float(FloatTy::F32), ty::Int(_) | ty::Uint(_)) if !safe_cast =>
+                            this.float_to_int_unchecked(op.to_scalar().to_f32()?, dest.layout.ty)?.into(),
+                        (ty::Float(FloatTy::F64), ty::Int(_) | ty::Uint(_)) if !safe_cast =>
+                            this.float_to_int_unchecked(op.to_scalar().to_f64()?, dest.layout.ty)?.into(),
+                        _ =>
+                            throw_unsup_format!(
+                                "Unsupported SIMD cast from element type {from_ty} to {to_ty}",
+                                from_ty = op.layout.ty,
+                                to_ty = dest.layout.ty,
+                            ),
+                    };
+                    this.write_immediate(val, &dest.into())?;
+                }
+            }
+            "shuffle" => {
+                let [left, right, index] = check_arg_count(args)?;
+                let (left, left_len) = this.operand_to_simd(left)?;
+                let (right, right_len) = this.operand_to_simd(right)?;
+                let (dest, dest_len) = this.place_to_simd(dest)?;
+
+                // `index` is an array, not a SIMD type
+                let ty::Array(_, index_len) = index.layout.ty.kind() else {
+                    span_bug!(this.cur_span(), "simd_shuffle index argument has non-array type {}", index.layout.ty)
+                };
+                let index_len = index_len.eval_usize(*this.tcx, this.param_env());
+
+                assert_eq!(left_len, right_len);
+                assert_eq!(index_len, dest_len);
+
+                for i in 0..dest_len {
+                    let src_index: u64 = this
+                        .read_immediate(&this.operand_index(index, i)?)?
+                        .to_scalar()
+                        .to_u32()?
+                        .into();
+                    let dest = this.mplace_index(&dest, i)?;
+
+                    let val = if src_index < left_len {
+                        this.read_immediate(&this.mplace_index(&left, src_index)?.into())?
+                    } else if src_index < left_len.checked_add(right_len).unwrap() {
+                        let right_idx = src_index.checked_sub(left_len).unwrap();
+                        this.read_immediate(&this.mplace_index(&right, right_idx)?.into())?
+                    } else {
+                        span_bug!(
+                            this.cur_span(),
+                            "simd_shuffle index {src_index} is out of bounds for 2 vectors of size {left_len}",
+                        );
+                    };
+                    this.write_immediate(*val, &dest.into())?;
+                }
+            }
+            "gather" => {
+                let [passthru, ptrs, mask] = check_arg_count(args)?;
+                let (passthru, passthru_len) = this.operand_to_simd(passthru)?;
+                let (ptrs, ptrs_len) = this.operand_to_simd(ptrs)?;
+                let (mask, mask_len) = this.operand_to_simd(mask)?;
+                let (dest, dest_len) = this.place_to_simd(dest)?;
+
+                assert_eq!(dest_len, passthru_len);
+                assert_eq!(dest_len, ptrs_len);
+                assert_eq!(dest_len, mask_len);
+
+                for i in 0..dest_len {
+                    let passthru = this.read_immediate(&this.mplace_index(&passthru, i)?.into())?;
+                    let ptr = this.read_immediate(&this.mplace_index(&ptrs, i)?.into())?;
+                    let mask = this.read_immediate(&this.mplace_index(&mask, i)?.into())?;
+                    let dest = this.mplace_index(&dest, i)?;
+
+                    let val = if simd_element_to_bool(mask)? {
+                        let place = this.deref_operand(&ptr.into())?;
+                        this.read_immediate(&place.into())?
+                    } else {
+                        passthru
+                    };
+                    this.write_immediate(*val, &dest.into())?;
+                }
+            }
+            "scatter" => {
+                let [value, ptrs, mask] = check_arg_count(args)?;
+                let (value, value_len) = this.operand_to_simd(value)?;
+                let (ptrs, ptrs_len) = this.operand_to_simd(ptrs)?;
+                let (mask, mask_len) = this.operand_to_simd(mask)?;
+
+                assert_eq!(ptrs_len, value_len);
+                assert_eq!(ptrs_len, mask_len);
+
+                for i in 0..ptrs_len {
+                    let value = this.read_immediate(&this.mplace_index(&value, i)?.into())?;
+                    let ptr = this.read_immediate(&this.mplace_index(&ptrs, i)?.into())?;
+                    let mask = this.read_immediate(&this.mplace_index(&mask, i)?.into())?;
+
+                    if simd_element_to_bool(mask)? {
+                        let place = this.deref_operand(&ptr.into())?;
+                        this.write_immediate(*value, &place.into())?;
+                    }
+                }
+            }
+            "bitmask" => {
+                let [op] = check_arg_count(args)?;
+                let (op, op_len) = this.operand_to_simd(op)?;
+                let bitmask_len = op_len.max(8);
+
+                assert!(dest.layout.ty.is_integral());
+                assert!(bitmask_len <= 64);
+                assert_eq!(bitmask_len, dest.layout.size.bits());
+                let op_len = u32::try_from(op_len).unwrap();
+
+                let mut res = 0u64;
+                for i in 0..op_len {
+                    let op = this.read_immediate(&this.mplace_index(&op, i.into())?.into())?;
+                    if simd_element_to_bool(op)? {
+                        res |= 1u64
+                            .checked_shl(simd_bitmask_index(i, op_len, this.data_layout().endian))
+                            .unwrap();
+                    }
+                }
+                this.write_int(res, dest)?;
+            }
+
+            name => throw_unsup_format!("unimplemented intrinsic: `simd_{name}`"),
+        }
+        Ok(())
+    }
+}
+
+fn bool_to_simd_element(b: bool, size: Size) -> Scalar<Provenance> {
+    // SIMD uses all-1 as pattern for "true"
+    let val = if b { -1 } else { 0 };
+    Scalar::from_int(val, size)
+}
+
+fn simd_element_to_bool(elem: ImmTy<'_, Provenance>) -> InterpResult<'_, bool> {
+    let val = elem.to_scalar().to_int(elem.layout.size)?;
+    Ok(match val {
+        0 => false,
+        -1 => true,
+        _ => throw_ub_format!("each element of a SIMD mask must be all-0-bits or all-1-bits"),
+    })
+}
+
+fn simd_bitmask_index(idx: u32, vec_len: u32, endianess: Endian) -> u32 {
+    assert!(idx < vec_len);
+    match endianess {
+        Endian::Little => idx,
+        #[allow(clippy::integer_arithmetic)] // idx < vec_len
+        Endian::Big => vec_len - 1 - idx, // reverse order of bits
+    }
+}
+
+fn fmax_op<'tcx>(
+    left: &ImmTy<'tcx, Provenance>,
+    right: &ImmTy<'tcx, Provenance>,
+) -> InterpResult<'tcx, Scalar<Provenance>> {
+    assert_eq!(left.layout.ty, right.layout.ty);
+    let ty::Float(float_ty) = left.layout.ty.kind() else {
+        bug!("fmax operand is not a float")
+    };
+    let left = left.to_scalar();
+    let right = right.to_scalar();
+    Ok(match float_ty {
+        FloatTy::F32 => Scalar::from_f32(left.to_f32()?.max(right.to_f32()?)),
+        FloatTy::F64 => Scalar::from_f64(left.to_f64()?.max(right.to_f64()?)),
+    })
+}
+
+fn fmin_op<'tcx>(
+    left: &ImmTy<'tcx, Provenance>,
+    right: &ImmTy<'tcx, Provenance>,
+) -> InterpResult<'tcx, Scalar<Provenance>> {
+    assert_eq!(left.layout.ty, right.layout.ty);
+    let ty::Float(float_ty) = left.layout.ty.kind() else {
+        bug!("fmin operand is not a float")
+    };
+    let left = left.to_scalar();
+    let right = right.to_scalar();
+    Ok(match float_ty {
+        FloatTy::F32 => Scalar::from_f32(left.to_f32()?.min(right.to_f32()?)),
+        FloatTy::F64 => Scalar::from_f64(left.to_f64()?.min(right.to_f64()?)),
+    })
+}
diff --git a/src/tools/miri/src/shims/mod.rs b/src/tools/miri/src/shims/mod.rs
new file mode 100644
index 0000000000000..8cb648e517328
--- /dev/null
+++ b/src/tools/miri/src/shims/mod.rs
@@ -0,0 +1,107 @@
+#![warn(clippy::integer_arithmetic)]
+
+mod backtrace;
+#[cfg(unix)]
+pub mod ffi_support;
+pub mod foreign_items;
+pub mod intrinsics;
+pub mod unix;
+pub mod windows;
+
+pub mod dlsym;
+pub mod env;
+pub mod os_str;
+pub mod panic;
+pub mod time;
+pub mod tls;
+
+// End module management, begin local code
+
+use log::trace;
+
+use rustc_middle::{mir, ty};
+use rustc_target::spec::abi::Abi;
+
+use crate::*;
+use helpers::check_arg_count;
+
+impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    fn find_mir_or_eval_fn(
+        &mut self,
+        instance: ty::Instance<'tcx>,
+        abi: Abi,
+        args: &[OpTy<'tcx, Provenance>],
+        dest: &PlaceTy<'tcx, Provenance>,
+        ret: Option<mir::BasicBlock>,
+        unwind: StackPopUnwind,
+    ) -> InterpResult<'tcx, Option<(&'mir mir::Body<'tcx>, ty::Instance<'tcx>)>> {
+        let this = self.eval_context_mut();
+        trace!("eval_fn_call: {:#?}, {:?}", instance, dest);
+
+        // There are some more lang items we want to hook that CTFE does not hook (yet).
+        if this.tcx.lang_items().align_offset_fn() == Some(instance.def.def_id()) {
+            let [ptr, align] = check_arg_count(args)?;
+            if this.align_offset(ptr, align, dest, ret, unwind)? {
+                return Ok(None);
+            }
+        }
+
+        // Try to see if we can do something about foreign items.
+        if this.tcx.is_foreign_item(instance.def_id()) {
+            // An external function call that does not have a MIR body. We either find MIR elsewhere
+            // or emulate its effect.
+            // This will be Ok(None) if we're emulating the intrinsic entirely within Miri (no need
+            // to run extra MIR), and Ok(Some(body)) if we found MIR to run for the
+            // foreign function
+            // Any needed call to `goto_block` will be performed by `emulate_foreign_item`.
+            return this.emulate_foreign_item(instance.def_id(), abi, args, dest, ret, unwind);
+        }
+
+        // Otherwise, load the MIR.
+        Ok(Some((this.load_mir(instance.def, None)?, instance)))
+    }
+
+    /// Returns `true` if the computation was performed, and `false` if we should just evaluate
+    /// the actual MIR of `align_offset`.
+    fn align_offset(
+        &mut self,
+        ptr_op: &OpTy<'tcx, Provenance>,
+        align_op: &OpTy<'tcx, Provenance>,
+        dest: &PlaceTy<'tcx, Provenance>,
+        ret: Option<mir::BasicBlock>,
+        unwind: StackPopUnwind,
+    ) -> InterpResult<'tcx, bool> {
+        let this = self.eval_context_mut();
+        let ret = ret.unwrap();
+
+        if this.machine.check_alignment != AlignmentCheck::Symbolic {
+            // Just use actual implementation.
+            return Ok(false);
+        }
+
+        let req_align = this.read_scalar(align_op)?.to_machine_usize(this)?;
+
+        // Stop if the alignment is not a power of two.
+        if !req_align.is_power_of_two() {
+            this.start_panic("align_offset: align is not a power-of-two", unwind)?;
+            return Ok(true); // nothing left to do
+        }
+
+        let ptr = this.read_pointer(ptr_op)?;
+        if let Ok((alloc_id, _offset, _)) = this.ptr_try_get_alloc_id(ptr) {
+            // Only do anything if we can identify the allocation this goes to.
+            let (_size, cur_align, _kind) = this.get_alloc_info(alloc_id);
+            if cur_align.bytes() >= req_align {
+                // If the allocation alignment is at least the required alignment we use the
+                // real implementation.
+                return Ok(false);
+            }
+        }
+
+        // Return error result (usize::MAX), and jump to caller.
+        this.write_scalar(Scalar::from_machine_usize(this.machine_usize_max(), this), dest)?;
+        this.go_to_block(ret);
+        Ok(true)
+    }
+}
diff --git a/src/tools/miri/src/shims/os_str.rs b/src/tools/miri/src/shims/os_str.rs
new file mode 100644
index 0000000000000..407dab970ad7d
--- /dev/null
+++ b/src/tools/miri/src/shims/os_str.rs
@@ -0,0 +1,308 @@
+use std::borrow::Cow;
+use std::ffi::{OsStr, OsString};
+use std::iter;
+use std::path::{Path, PathBuf};
+
+#[cfg(unix)]
+use std::os::unix::ffi::{OsStrExt, OsStringExt};
+#[cfg(windows)]
+use std::os::windows::ffi::{OsStrExt, OsStringExt};
+
+use rustc_middle::ty::layout::LayoutOf;
+use rustc_target::abi::{Align, Size};
+
+use crate::*;
+
+/// Represent how path separator conversion should be done.
+pub enum PathConversion {
+    HostToTarget,
+    TargetToHost,
+}
+
+#[cfg(unix)]
+pub fn os_str_to_bytes<'a, 'tcx>(os_str: &'a OsStr) -> InterpResult<'tcx, &'a [u8]> {
+    Ok(os_str.as_bytes())
+}
+
+#[cfg(not(unix))]
+pub fn os_str_to_bytes<'a, 'tcx>(os_str: &'a OsStr) -> InterpResult<'tcx, &'a [u8]> {
+    // On non-unix platforms the best we can do to transform bytes from/to OS strings is to do the
+    // intermediate transformation into strings. Which invalidates non-utf8 paths that are actually
+    // valid.
+    os_str
+        .to_str()
+        .map(|s| s.as_bytes())
+        .ok_or_else(|| err_unsup_format!("{:?} is not a valid utf-8 string", os_str).into())
+}
+
+#[cfg(unix)]
+pub fn bytes_to_os_str<'a, 'tcx>(bytes: &'a [u8]) -> InterpResult<'tcx, &'a OsStr> {
+    Ok(OsStr::from_bytes(bytes))
+}
+#[cfg(not(unix))]
+pub fn bytes_to_os_str<'a, 'tcx>(bytes: &'a [u8]) -> InterpResult<'tcx, &'a OsStr> {
+    let s = std::str::from_utf8(bytes)
+        .map_err(|_| err_unsup_format!("{:?} is not a valid utf-8 string", bytes))?;
+    Ok(OsStr::new(s))
+}
+
+impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    /// Helper function to read an OsString from a null-terminated sequence of bytes, which is what
+    /// the Unix APIs usually handle.
+    fn read_os_str_from_c_str<'a>(
+        &'a self,
+        ptr: Pointer<Option<Provenance>>,
+    ) -> InterpResult<'tcx, &'a OsStr>
+    where
+        'tcx: 'a,
+        'mir: 'a,
+    {
+        let this = self.eval_context_ref();
+        let bytes = this.read_c_str(ptr)?;
+        bytes_to_os_str(bytes)
+    }
+
+    /// Helper function to read an OsString from a 0x0000-terminated sequence of u16,
+    /// which is what the Windows APIs usually handle.
+    fn read_os_str_from_wide_str<'a>(
+        &'a self,
+        ptr: Pointer<Option<Provenance>>,
+    ) -> InterpResult<'tcx, OsString>
+    where
+        'tcx: 'a,
+        'mir: 'a,
+    {
+        #[cfg(windows)]
+        pub fn u16vec_to_osstring<'tcx>(u16_vec: Vec<u16>) -> InterpResult<'tcx, OsString> {
+            Ok(OsString::from_wide(&u16_vec[..]))
+        }
+        #[cfg(not(windows))]
+        pub fn u16vec_to_osstring<'tcx>(u16_vec: Vec<u16>) -> InterpResult<'tcx, OsString> {
+            let s = String::from_utf16(&u16_vec[..])
+                .map_err(|_| err_unsup_format!("{:?} is not a valid utf-16 string", u16_vec))?;
+            Ok(s.into())
+        }
+
+        let u16_vec = self.eval_context_ref().read_wide_str(ptr)?;
+        u16vec_to_osstring(u16_vec)
+    }
+
+    /// Helper function to write an OsStr as a null-terminated sequence of bytes, which is what
+    /// the Unix APIs usually handle. This function returns `Ok((false, length))` without trying
+    /// to write if `size` is not large enough to fit the contents of `os_string` plus a null
+    /// terminator. It returns `Ok((true, length))` if the writing process was successful. The
+    /// string length returned does include the null terminator.
+    fn write_os_str_to_c_str(
+        &mut self,
+        os_str: &OsStr,
+        ptr: Pointer<Option<Provenance>>,
+        size: u64,
+    ) -> InterpResult<'tcx, (bool, u64)> {
+        let bytes = os_str_to_bytes(os_str)?;
+        // If `size` is smaller or equal than `bytes.len()`, writing `bytes` plus the required null
+        // terminator to memory using the `ptr` pointer would cause an out-of-bounds access.
+        let string_length = u64::try_from(bytes.len()).unwrap();
+        let string_length = string_length.checked_add(1).unwrap();
+        if size < string_length {
+            return Ok((false, string_length));
+        }
+        self.eval_context_mut()
+            .write_bytes_ptr(ptr, bytes.iter().copied().chain(iter::once(0u8)))?;
+        Ok((true, string_length))
+    }
+
+    /// Helper function to write an OsStr as a 0x0000-terminated u16-sequence, which is what
+    /// the Windows APIs usually handle. This function returns `Ok((false, length))` without trying
+    /// to write if `size` is not large enough to fit the contents of `os_string` plus a null
+    /// terminator. It returns `Ok((true, length))` if the writing process was successful. The
+    /// string length returned does include the null terminator. Length is measured in units of
+    /// `u16.`
+    fn write_os_str_to_wide_str(
+        &mut self,
+        os_str: &OsStr,
+        ptr: Pointer<Option<Provenance>>,
+        size: u64,
+    ) -> InterpResult<'tcx, (bool, u64)> {
+        #[cfg(windows)]
+        fn os_str_to_u16vec<'tcx>(os_str: &OsStr) -> InterpResult<'tcx, Vec<u16>> {
+            Ok(os_str.encode_wide().collect())
+        }
+        #[cfg(not(windows))]
+        fn os_str_to_u16vec<'tcx>(os_str: &OsStr) -> InterpResult<'tcx, Vec<u16>> {
+            // On non-Windows platforms the best we can do to transform Vec<u16> from/to OS strings is to do the
+            // intermediate transformation into strings. Which invalidates non-utf8 paths that are actually
+            // valid.
+            os_str
+                .to_str()
+                .map(|s| s.encode_utf16().collect())
+                .ok_or_else(|| err_unsup_format!("{:?} is not a valid utf-8 string", os_str).into())
+        }
+
+        let u16_vec = os_str_to_u16vec(os_str)?;
+        // If `size` is smaller or equal than `bytes.len()`, writing `bytes` plus the required
+        // 0x0000 terminator to memory would cause an out-of-bounds access.
+        let string_length = u64::try_from(u16_vec.len()).unwrap();
+        let string_length = string_length.checked_add(1).unwrap();
+        if size < string_length {
+            return Ok((false, string_length));
+        }
+
+        // Store the UTF-16 string.
+        let size2 = Size::from_bytes(2);
+        let this = self.eval_context_mut();
+        let mut alloc = this
+            .get_ptr_alloc_mut(ptr, size2 * string_length, Align::from_bytes(2).unwrap())?
+            .unwrap(); // not a ZST, so we will get a result
+        for (offset, wchar) in u16_vec.into_iter().chain(iter::once(0x0000)).enumerate() {
+            let offset = u64::try_from(offset).unwrap();
+            alloc.write_scalar(alloc_range(size2 * offset, size2), Scalar::from_u16(wchar))?;
+        }
+        Ok((true, string_length))
+    }
+
+    /// Allocate enough memory to store the given `OsStr` as a null-terminated sequence of bytes.
+    fn alloc_os_str_as_c_str(
+        &mut self,
+        os_str: &OsStr,
+        memkind: MemoryKind<MiriMemoryKind>,
+    ) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
+        let size = u64::try_from(os_str.len()).unwrap().checked_add(1).unwrap(); // Make space for `0` terminator.
+        let this = self.eval_context_mut();
+
+        let arg_type = this.tcx.mk_array(this.tcx.types.u8, size);
+        let arg_place = this.allocate(this.layout_of(arg_type).unwrap(), memkind)?;
+        assert!(self.write_os_str_to_c_str(os_str, arg_place.ptr, size).unwrap().0);
+        Ok(arg_place.ptr)
+    }
+
+    /// Allocate enough memory to store the given `OsStr` as a null-terminated sequence of `u16`.
+    fn alloc_os_str_as_wide_str(
+        &mut self,
+        os_str: &OsStr,
+        memkind: MemoryKind<MiriMemoryKind>,
+    ) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
+        let size = u64::try_from(os_str.len()).unwrap().checked_add(1).unwrap(); // Make space for `0x0000` terminator.
+        let this = self.eval_context_mut();
+
+        let arg_type = this.tcx.mk_array(this.tcx.types.u16, size);
+        let arg_place = this.allocate(this.layout_of(arg_type).unwrap(), memkind)?;
+        assert!(self.write_os_str_to_wide_str(os_str, arg_place.ptr, size).unwrap().0);
+        Ok(arg_place.ptr)
+    }
+
+    /// Read a null-terminated sequence of bytes, and perform path separator conversion if needed.
+    fn read_path_from_c_str<'a>(
+        &'a self,
+        ptr: Pointer<Option<Provenance>>,
+    ) -> InterpResult<'tcx, Cow<'a, Path>>
+    where
+        'tcx: 'a,
+        'mir: 'a,
+    {
+        let this = self.eval_context_ref();
+        let os_str = this.read_os_str_from_c_str(ptr)?;
+
+        Ok(match this.convert_path_separator(Cow::Borrowed(os_str), PathConversion::TargetToHost) {
+            Cow::Borrowed(x) => Cow::Borrowed(Path::new(x)),
+            Cow::Owned(y) => Cow::Owned(PathBuf::from(y)),
+        })
+    }
+
+    /// Read a null-terminated sequence of `u16`s, and perform path separator conversion if needed.
+    fn read_path_from_wide_str(
+        &self,
+        ptr: Pointer<Option<Provenance>>,
+    ) -> InterpResult<'tcx, PathBuf> {
+        let this = self.eval_context_ref();
+        let os_str = this.read_os_str_from_wide_str(ptr)?;
+
+        Ok(this
+            .convert_path_separator(Cow::Owned(os_str), PathConversion::TargetToHost)
+            .into_owned()
+            .into())
+    }
+
+    /// Write a Path to the machine memory (as a null-terminated sequence of bytes),
+    /// adjusting path separators if needed.
+    fn write_path_to_c_str(
+        &mut self,
+        path: &Path,
+        ptr: Pointer<Option<Provenance>>,
+        size: u64,
+    ) -> InterpResult<'tcx, (bool, u64)> {
+        let this = self.eval_context_mut();
+        let os_str = this
+            .convert_path_separator(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
+        this.write_os_str_to_c_str(&os_str, ptr, size)
+    }
+
+    /// Write a Path to the machine memory (as a null-terminated sequence of `u16`s),
+    /// adjusting path separators if needed.
+    fn write_path_to_wide_str(
+        &mut self,
+        path: &Path,
+        ptr: Pointer<Option<Provenance>>,
+        size: u64,
+    ) -> InterpResult<'tcx, (bool, u64)> {
+        let this = self.eval_context_mut();
+        let os_str = this
+            .convert_path_separator(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
+        this.write_os_str_to_wide_str(&os_str, ptr, size)
+    }
+
+    /// Allocate enough memory to store a Path as a null-terminated sequence of bytes,
+    /// adjusting path separators if needed.
+    fn alloc_path_as_c_str(
+        &mut self,
+        path: &Path,
+        memkind: MemoryKind<MiriMemoryKind>,
+    ) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
+        let this = self.eval_context_mut();
+        let os_str = this
+            .convert_path_separator(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
+        this.alloc_os_str_as_c_str(&os_str, memkind)
+    }
+
+    fn convert_path_separator<'a>(
+        &self,
+        os_str: Cow<'a, OsStr>,
+        direction: PathConversion,
+    ) -> Cow<'a, OsStr> {
+        let this = self.eval_context_ref();
+        let target_os = &this.tcx.sess.target.os;
+        #[cfg(windows)]
+        return if target_os == "windows" {
+            // Windows-on-Windows, all fine.
+            os_str
+        } else {
+            // Unix target, Windows host.
+            let (from, to) = match direction {
+                PathConversion::HostToTarget => ('\\', '/'),
+                PathConversion::TargetToHost => ('/', '\\'),
+            };
+            let converted = os_str
+                .encode_wide()
+                .map(|wchar| if wchar == from as u16 { to as u16 } else { wchar })
+                .collect::<Vec<_>>();
+            Cow::Owned(OsString::from_wide(&converted))
+        };
+        #[cfg(unix)]
+        return if target_os == "windows" {
+            // Windows target, Unix host.
+            let (from, to) = match direction {
+                PathConversion::HostToTarget => ('/', '\\'),
+                PathConversion::TargetToHost => ('\\', '/'),
+            };
+            let converted = os_str
+                .as_bytes()
+                .iter()
+                .map(|&wchar| if wchar == from as u8 { to as u8 } else { wchar })
+                .collect::<Vec<_>>();
+            Cow::Owned(OsString::from_vec(converted))
+        } else {
+            // Unix-on-Unix, all is fine.
+            os_str
+        };
+    }
+}
diff --git a/src/tools/miri/src/shims/panic.rs b/src/tools/miri/src/shims/panic.rs
new file mode 100644
index 0000000000000..2e8245acf4a68
--- /dev/null
+++ b/src/tools/miri/src/shims/panic.rs
@@ -0,0 +1,227 @@
+//! Panic runtime for Miri.
+//!
+//! The core pieces of the runtime are:
+//! - An implementation of `__rust_maybe_catch_panic` that pushes the invoked stack frame with
+//!   some extra metadata derived from the panic-catching arguments of `__rust_maybe_catch_panic`.
+//! - A hack in `libpanic_unwind` that calls the `miri_start_panic` intrinsic instead of the
+//!   target-native panic runtime. (This lives in the rustc repo.)
+//! - An implementation of `miri_start_panic` that stores its argument (the panic payload), and then
+//!   immediately returns, but on the *unwind* edge (not the normal return edge), thus initiating unwinding.
+//! - A hook executed each time a frame is popped, such that if the frame pushed by `__rust_maybe_catch_panic`
+//!   gets popped *during unwinding*, we take the panic payload and store it according to the extra
+//!   metadata we remembered when pushing said frame.
+
+use log::trace;
+
+use rustc_ast::Mutability;
+use rustc_middle::{mir, ty};
+use rustc_span::Symbol;
+use rustc_target::spec::abi::Abi;
+use rustc_target::spec::PanicStrategy;
+
+use crate::*;
+use helpers::check_arg_count;
+
+/// Holds all of the relevant data for when unwinding hits a `try` frame.
+#[derive(Debug)]
+pub struct CatchUnwindData<'tcx> {
+    /// The `catch_fn` callback to call in case of a panic.
+    catch_fn: Pointer<Option<Provenance>>,
+    /// The `data` argument for that callback.
+    data: Scalar<Provenance>,
+    /// The return place from the original call to `try`.
+    dest: PlaceTy<'tcx, Provenance>,
+    /// The return block from the original call to `try`.
+    ret: mir::BasicBlock,
+}
+
+impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    /// Handles the special `miri_start_panic` intrinsic, which is called
+    /// by libpanic_unwind to delegate the actual unwinding process to Miri.
+    fn handle_miri_start_panic(
+        &mut self,
+        abi: Abi,
+        link_name: Symbol,
+        args: &[OpTy<'tcx, Provenance>],
+        unwind: StackPopUnwind,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+
+        trace!("miri_start_panic: {:?}", this.frame().instance);
+
+        // Get the raw pointer stored in arg[0] (the panic payload).
+        let [payload] = this.check_shim(abi, Abi::Rust, link_name, args)?;
+        let payload = this.read_scalar(payload)?;
+        let thread = this.active_thread_mut();
+        assert!(thread.panic_payload.is_none(), "the panic runtime should avoid double-panics");
+        thread.panic_payload = Some(payload);
+
+        // Jump to the unwind block to begin unwinding.
+        this.unwind_to_block(unwind)?;
+        Ok(())
+    }
+
+    /// Handles the `try` intrinsic, the underlying implementation of `std::panicking::try`.
+    fn handle_try(
+        &mut self,
+        args: &[OpTy<'tcx, Provenance>],
+        dest: &PlaceTy<'tcx, Provenance>,
+        ret: mir::BasicBlock,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+
+        // Signature:
+        //   fn r#try(try_fn: fn(*mut u8), data: *mut u8, catch_fn: fn(*mut u8, *mut u8)) -> i32
+        // Calls `try_fn` with `data` as argument. If that executes normally, returns 0.
+        // If that unwinds, calls `catch_fn` with the first argument being `data` and
+        // then second argument being a target-dependent `payload` (i.e. it is up to us to define
+        // what that is), and returns 1.
+        // The `payload` is passed (by libstd) to `__rust_panic_cleanup`, which is then expected to
+        // return a `Box<dyn Any + Send + 'static>`.
+        // In Miri, `miri_start_panic` is passed exactly that type, so we make the `payload` simply
+        // a pointer to `Box<dyn Any + Send + 'static>`.
+
+        // Get all the arguments.
+        let [try_fn, data, catch_fn] = check_arg_count(args)?;
+        let try_fn = this.read_pointer(try_fn)?;
+        let data = this.read_scalar(data)?;
+        let catch_fn = this.read_pointer(catch_fn)?;
+
+        // Now we make a function call, and pass `data` as first and only argument.
+        let f_instance = this.get_ptr_fn(try_fn)?.as_instance()?;
+        trace!("try_fn: {:?}", f_instance);
+        this.call_function(
+            f_instance,
+            Abi::Rust,
+            &[data.into()],
+            None,
+            // Directly return to caller.
+            StackPopCleanup::Goto { ret: Some(ret), unwind: StackPopUnwind::Skip },
+        )?;
+
+        // We ourselves will return `0`, eventually (will be overwritten if we catch a panic).
+        this.write_null(dest)?;
+
+        // In unwind mode, we tag this frame with the extra data needed to catch unwinding.
+        // This lets `handle_stack_pop` (below) know that we should stop unwinding
+        // when we pop this frame.
+        if this.tcx.sess.panic_strategy() == PanicStrategy::Unwind {
+            this.frame_mut().extra.catch_unwind =
+                Some(CatchUnwindData { catch_fn, data, dest: dest.clone(), ret });
+        }
+
+        Ok(())
+    }
+
+    fn handle_stack_pop_unwind(
+        &mut self,
+        mut extra: FrameData<'tcx>,
+        unwinding: bool,
+    ) -> InterpResult<'tcx, StackPopJump> {
+        let this = self.eval_context_mut();
+        trace!("handle_stack_pop_unwind(extra = {:?}, unwinding = {})", extra, unwinding);
+
+        // We only care about `catch_panic` if we're unwinding - if we're doing a normal
+        // return, then we don't need to do anything special.
+        if let (true, Some(catch_unwind)) = (unwinding, extra.catch_unwind.take()) {
+            // We've just popped a frame that was pushed by `try`,
+            // and we are unwinding, so we should catch that.
+            trace!(
+                "unwinding: found catch_panic frame during unwinding: {:?}",
+                this.frame().instance
+            );
+
+            // We set the return value of `try` to 1, since there was a panic.
+            this.write_scalar(Scalar::from_i32(1), &catch_unwind.dest)?;
+
+            // The Thread's `panic_payload` holds what was passed to `miri_start_panic`.
+            // This is exactly the second argument we need to pass to `catch_fn`.
+            let payload = this.active_thread_mut().panic_payload.take().unwrap();
+
+            // Push the `catch_fn` stackframe.
+            let f_instance = this.get_ptr_fn(catch_unwind.catch_fn)?.as_instance()?;
+            trace!("catch_fn: {:?}", f_instance);
+            this.call_function(
+                f_instance,
+                Abi::Rust,
+                &[catch_unwind.data.into(), payload.into()],
+                None,
+                // Directly return to caller of `try`.
+                StackPopCleanup::Goto { ret: Some(catch_unwind.ret), unwind: StackPopUnwind::Skip },
+            )?;
+
+            // We pushed a new stack frame, the engine should not do any jumping now!
+            Ok(StackPopJump::NoJump)
+        } else {
+            Ok(StackPopJump::Normal)
+        }
+    }
+
+    /// Start a panic in the interpreter with the given message as payload.
+    fn start_panic(&mut self, msg: &str, unwind: StackPopUnwind) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+
+        // First arg: message.
+        let msg = this.allocate_str(msg, MiriMemoryKind::Machine.into(), Mutability::Not);
+
+        // Call the lang item.
+        let panic = this.tcx.lang_items().panic_fn().unwrap();
+        let panic = ty::Instance::mono(this.tcx.tcx, panic);
+        this.call_function(
+            panic,
+            Abi::Rust,
+            &[msg.to_ref(this)],
+            None,
+            StackPopCleanup::Goto { ret: None, unwind },
+        )
+    }
+
+    fn assert_panic(
+        &mut self,
+        msg: &mir::AssertMessage<'tcx>,
+        unwind: Option<mir::BasicBlock>,
+    ) -> InterpResult<'tcx> {
+        use rustc_middle::mir::AssertKind::*;
+        let this = self.eval_context_mut();
+
+        match msg {
+            BoundsCheck { index, len } => {
+                // Forward to `panic_bounds_check` lang item.
+
+                // First arg: index.
+                let index = this.read_scalar(&this.eval_operand(index, None)?)?;
+                // Second arg: len.
+                let len = this.read_scalar(&this.eval_operand(len, None)?)?;
+
+                // Call the lang item.
+                let panic_bounds_check = this.tcx.lang_items().panic_bounds_check_fn().unwrap();
+                let panic_bounds_check = ty::Instance::mono(this.tcx.tcx, panic_bounds_check);
+                this.call_function(
+                    panic_bounds_check,
+                    Abi::Rust,
+                    &[index.into(), len.into()],
+                    None,
+                    StackPopCleanup::Goto {
+                        ret: None,
+                        unwind: match unwind {
+                            Some(cleanup) => StackPopUnwind::Cleanup(cleanup),
+                            None => StackPopUnwind::Skip,
+                        },
+                    },
+                )?;
+            }
+            _ => {
+                // Forward everything else to `panic` lang item.
+                this.start_panic(
+                    msg.description(),
+                    match unwind {
+                        Some(cleanup) => StackPopUnwind::Cleanup(cleanup),
+                        None => StackPopUnwind::Skip,
+                    },
+                )?;
+            }
+        }
+        Ok(())
+    }
+}
diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs
new file mode 100644
index 0000000000000..24fe524539396
--- /dev/null
+++ b/src/tools/miri/src/shims/time.rs
@@ -0,0 +1,255 @@
+use std::time::{Duration, SystemTime};
+
+use crate::*;
+
+/// Returns the time elapsed between the provided time and the unix epoch as a `Duration`.
+pub fn system_time_to_duration<'tcx>(time: &SystemTime) -> InterpResult<'tcx, Duration> {
+    time.duration_since(SystemTime::UNIX_EPOCH)
+        .map_err(|_| err_unsup_format!("times before the Unix epoch are not supported").into())
+}
+
+impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    fn clock_gettime(
+        &mut self,
+        clk_id_op: &OpTy<'tcx, Provenance>,
+        tp_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        // This clock support is deliberately minimal because a lot of clock types have fiddly
+        // properties (is it possible for Miri to be suspended independently of the host?). If you
+        // have a use for another clock type, please open an issue.
+
+        let this = self.eval_context_mut();
+
+        this.assert_target_os("linux", "clock_gettime");
+
+        let clk_id = this.read_scalar(clk_id_op)?.to_i32()?;
+
+        // Linux has two main kinds of clocks. REALTIME clocks return the actual time since the
+        // Unix epoch, including effects which may cause time to move backwards such as NTP.
+        // Linux further distinguishes regular and "coarse" clocks, but the "coarse" version
+        // is just specified to be "faster and less precise", so we implement both the same way.
+        let absolute_clocks =
+            [this.eval_libc_i32("CLOCK_REALTIME")?, this.eval_libc_i32("CLOCK_REALTIME_COARSE")?];
+        // The second kind is MONOTONIC clocks for which 0 is an arbitrary time point, but they are
+        // never allowed to go backwards. We don't need to do any additonal monotonicity
+        // enforcement because std::time::Instant already guarantees that it is monotonic.
+        let relative_clocks =
+            [this.eval_libc_i32("CLOCK_MONOTONIC")?, this.eval_libc_i32("CLOCK_MONOTONIC_COARSE")?];
+
+        let duration = if absolute_clocks.contains(&clk_id) {
+            this.check_no_isolation("`clock_gettime` with `REALTIME` clocks")?;
+            system_time_to_duration(&SystemTime::now())?
+        } else if relative_clocks.contains(&clk_id) {
+            this.machine.clock.now().duration_since(this.machine.clock.anchor())
+        } else {
+            let einval = this.eval_libc("EINVAL")?;
+            this.set_last_error(einval)?;
+            return Ok(Scalar::from_i32(-1));
+        };
+
+        let tv_sec = duration.as_secs();
+        let tv_nsec = duration.subsec_nanos();
+
+        this.write_int_fields(&[tv_sec.into(), tv_nsec.into()], &this.deref_operand(tp_op)?)?;
+
+        Ok(Scalar::from_i32(0))
+    }
+
+    fn gettimeofday(
+        &mut self,
+        tv_op: &OpTy<'tcx, Provenance>,
+        tz_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        this.assert_target_os_is_unix("gettimeofday");
+        this.check_no_isolation("`gettimeofday`")?;
+
+        // Using tz is obsolete and should always be null
+        let tz = this.read_pointer(tz_op)?;
+        if !this.ptr_is_null(tz)? {
+            let einval = this.eval_libc("EINVAL")?;
+            this.set_last_error(einval)?;
+            return Ok(-1);
+        }
+
+        let duration = system_time_to_duration(&SystemTime::now())?;
+        let tv_sec = duration.as_secs();
+        let tv_usec = duration.subsec_micros();
+
+        this.write_int_fields(&[tv_sec.into(), tv_usec.into()], &this.deref_operand(tv_op)?)?;
+
+        Ok(0)
+    }
+
+    #[allow(non_snake_case, clippy::integer_arithmetic)]
+    fn GetSystemTimeAsFileTime(
+        &mut self,
+        LPFILETIME_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+
+        this.assert_target_os("windows", "GetSystemTimeAsFileTime");
+        this.check_no_isolation("`GetSystemTimeAsFileTime`")?;
+
+        let NANOS_PER_SEC = this.eval_windows_u64("time", "NANOS_PER_SEC")?;
+        let INTERVALS_PER_SEC = this.eval_windows_u64("time", "INTERVALS_PER_SEC")?;
+        let INTERVALS_TO_UNIX_EPOCH = this.eval_windows_u64("time", "INTERVALS_TO_UNIX_EPOCH")?;
+        let NANOS_PER_INTERVAL = NANOS_PER_SEC / INTERVALS_PER_SEC;
+        let SECONDS_TO_UNIX_EPOCH = INTERVALS_TO_UNIX_EPOCH / INTERVALS_PER_SEC;
+
+        let duration = system_time_to_duration(&SystemTime::now())?
+            + Duration::from_secs(SECONDS_TO_UNIX_EPOCH);
+        let duration_ticks = u64::try_from(duration.as_nanos() / u128::from(NANOS_PER_INTERVAL))
+            .map_err(|_| err_unsup_format!("programs running more than 2^64 Windows ticks after the Windows epoch are not supported"))?;
+
+        let dwLowDateTime = u32::try_from(duration_ticks & 0x00000000FFFFFFFF).unwrap();
+        let dwHighDateTime = u32::try_from((duration_ticks & 0xFFFFFFFF00000000) >> 32).unwrap();
+        this.write_int_fields(
+            &[dwLowDateTime.into(), dwHighDateTime.into()],
+            &this.deref_operand(LPFILETIME_op)?,
+        )?;
+
+        Ok(())
+    }
+
+    #[allow(non_snake_case)]
+    fn QueryPerformanceCounter(
+        &mut self,
+        lpPerformanceCount_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        this.assert_target_os("windows", "QueryPerformanceCounter");
+
+        // QueryPerformanceCounter uses a hardware counter as its basis.
+        // Miri will emulate a counter with a resolution of 1 nanosecond.
+        let duration = this.machine.clock.now().duration_since(this.machine.clock.anchor());
+        let qpc = i64::try_from(duration.as_nanos()).map_err(|_| {
+            err_unsup_format!("programs running longer than 2^63 nanoseconds are not supported")
+        })?;
+        this.write_scalar(
+            Scalar::from_i64(qpc),
+            &this.deref_operand(lpPerformanceCount_op)?.into(),
+        )?;
+        Ok(-1) // return non-zero on success
+    }
+
+    #[allow(non_snake_case)]
+    fn QueryPerformanceFrequency(
+        &mut self,
+        lpFrequency_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        this.assert_target_os("windows", "QueryPerformanceFrequency");
+
+        // Retrieves the frequency of the hardware performance counter.
+        // The frequency of the performance counter is fixed at system boot and
+        // is consistent across all processors.
+        // Miri emulates a "hardware" performance counter with a resolution of 1ns,
+        // and thus 10^9 counts per second.
+        this.write_scalar(
+            Scalar::from_i64(1_000_000_000),
+            &this.deref_operand(lpFrequency_op)?.into(),
+        )?;
+        Ok(-1) // Return non-zero on success
+    }
+
+    fn mach_absolute_time(&self) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_ref();
+
+        this.assert_target_os("macos", "mach_absolute_time");
+
+        // This returns a u64, with time units determined dynamically by `mach_timebase_info`.
+        // We return plain nanoseconds.
+        let duration = this.machine.clock.now().duration_since(this.machine.clock.anchor());
+        let res = u64::try_from(duration.as_nanos()).map_err(|_| {
+            err_unsup_format!("programs running longer than 2^64 nanoseconds are not supported")
+        })?;
+        Ok(Scalar::from_u64(res))
+    }
+
+    fn mach_timebase_info(
+        &mut self,
+        info_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_mut();
+
+        this.assert_target_os("macos", "mach_timebase_info");
+
+        let info = this.deref_operand(info_op)?;
+
+        // Since our emulated ticks in `mach_absolute_time` *are* nanoseconds,
+        // no scaling needs to happen.
+        let (numer, denom) = (1, 1);
+        this.write_int_fields(&[numer.into(), denom.into()], &info)?;
+
+        Ok(Scalar::from_i32(0)) // KERN_SUCCESS
+    }
+
+    fn nanosleep(
+        &mut self,
+        req_op: &OpTy<'tcx, Provenance>,
+        _rem: &OpTy<'tcx, Provenance>, // Signal handlers are not supported, so rem will never be written to.
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        this.assert_target_os_is_unix("nanosleep");
+
+        let duration = match this.read_timespec(&this.deref_operand(req_op)?)? {
+            Some(duration) => duration,
+            None => {
+                let einval = this.eval_libc("EINVAL")?;
+                this.set_last_error(einval)?;
+                return Ok(-1);
+            }
+        };
+        // If adding the duration overflows, let's just sleep for an hour. Waking up early is always acceptable.
+        let now = this.machine.clock.now();
+        let timeout_time = now
+            .checked_add(duration)
+            .unwrap_or_else(|| now.checked_add(Duration::from_secs(3600)).unwrap());
+
+        let active_thread = this.get_active_thread();
+        this.block_thread(active_thread);
+
+        this.register_timeout_callback(
+            active_thread,
+            Time::Monotonic(timeout_time),
+            Box::new(move |ecx| {
+                ecx.unblock_thread(active_thread);
+                Ok(())
+            }),
+        );
+
+        Ok(0)
+    }
+
+    #[allow(non_snake_case)]
+    fn Sleep(&mut self, timeout: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+
+        this.assert_target_os("windows", "Sleep");
+
+        let timeout_ms = this.read_scalar(timeout)?.to_u32()?;
+
+        let duration = Duration::from_millis(timeout_ms.into());
+        let timeout_time = this.machine.clock.now().checked_add(duration).unwrap();
+
+        let active_thread = this.get_active_thread();
+        this.block_thread(active_thread);
+
+        this.register_timeout_callback(
+            active_thread,
+            Time::Monotonic(timeout_time),
+            Box::new(move |ecx| {
+                ecx.unblock_thread(active_thread);
+                Ok(())
+            }),
+        );
+
+        Ok(())
+    }
+}
diff --git a/src/tools/miri/src/shims/tls.rs b/src/tools/miri/src/shims/tls.rs
new file mode 100644
index 0000000000000..d93d6a16a0736
--- /dev/null
+++ b/src/tools/miri/src/shims/tls.rs
@@ -0,0 +1,400 @@
+//! Implement thread-local storage.
+
+use std::collections::btree_map::Entry as BTreeEntry;
+use std::collections::hash_map::Entry as HashMapEntry;
+use std::collections::BTreeMap;
+
+use log::trace;
+
+use rustc_data_structures::fx::FxHashMap;
+use rustc_middle::ty;
+use rustc_target::abi::{HasDataLayout, Size};
+use rustc_target::spec::abi::Abi;
+
+use crate::*;
+
+pub type TlsKey = u128;
+
+#[derive(Clone, Debug)]
+pub struct TlsEntry<'tcx> {
+    /// The data for this key. None is used to represent NULL.
+    /// (We normalize this early to avoid having to do a NULL-ptr-test each time we access the data.)
+    data: BTreeMap<ThreadId, Scalar<Provenance>>,
+    dtor: Option<ty::Instance<'tcx>>,
+}
+
+#[derive(Clone, Debug)]
+struct RunningDtorsState {
+    /// The last TlsKey used to retrieve a TLS destructor. `None` means that we
+    /// have not tried to retrieve a TLS destructor yet or that we already tried
+    /// all keys.
+    last_dtor_key: Option<TlsKey>,
+}
+
+#[derive(Debug)]
+pub struct TlsData<'tcx> {
+    /// The Key to use for the next thread-local allocation.
+    next_key: TlsKey,
+
+    /// pthreads-style thread-local storage.
+    keys: BTreeMap<TlsKey, TlsEntry<'tcx>>,
+
+    /// A single per thread destructor of the thread local storage (that's how
+    /// things work on macOS) with a data argument.
+    macos_thread_dtors: BTreeMap<ThreadId, (ty::Instance<'tcx>, Scalar<Provenance>)>,
+
+    /// State for currently running TLS dtors. If this map contains a key for a
+    /// specific thread, it means that we are in the "destruct" phase, during
+    /// which some operations are UB.
+    dtors_running: FxHashMap<ThreadId, RunningDtorsState>,
+}
+
+impl<'tcx> Default for TlsData<'tcx> {
+    fn default() -> Self {
+        TlsData {
+            next_key: 1, // start with 1 as we must not use 0 on Windows
+            keys: Default::default(),
+            macos_thread_dtors: Default::default(),
+            dtors_running: Default::default(),
+        }
+    }
+}
+
+impl<'tcx> TlsData<'tcx> {
+    /// Generate a new TLS key with the given destructor.
+    /// `max_size` determines the integer size the key has to fit in.
+    #[allow(clippy::integer_arithmetic)]
+    pub fn create_tls_key(
+        &mut self,
+        dtor: Option<ty::Instance<'tcx>>,
+        max_size: Size,
+    ) -> InterpResult<'tcx, TlsKey> {
+        let new_key = self.next_key;
+        self.next_key += 1;
+        self.keys.try_insert(new_key, TlsEntry { data: Default::default(), dtor }).unwrap();
+        trace!("New TLS key allocated: {} with dtor {:?}", new_key, dtor);
+
+        if max_size.bits() < 128 && new_key >= (1u128 << max_size.bits()) {
+            throw_unsup_format!("we ran out of TLS key space");
+        }
+        Ok(new_key)
+    }
+
+    pub fn delete_tls_key(&mut self, key: TlsKey) -> InterpResult<'tcx> {
+        match self.keys.remove(&key) {
+            Some(_) => {
+                trace!("TLS key {} removed", key);
+                Ok(())
+            }
+            None => throw_ub_format!("removing a non-existig TLS key: {}", key),
+        }
+    }
+
+    pub fn load_tls(
+        &self,
+        key: TlsKey,
+        thread_id: ThreadId,
+        cx: &impl HasDataLayout,
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        match self.keys.get(&key) {
+            Some(TlsEntry { data, .. }) => {
+                let value = data.get(&thread_id).copied();
+                trace!("TLS key {} for thread {:?} loaded: {:?}", key, thread_id, value);
+                Ok(value.unwrap_or_else(|| Scalar::null_ptr(cx)))
+            }
+            None => throw_ub_format!("loading from a non-existing TLS key: {}", key),
+        }
+    }
+
+    pub fn store_tls(
+        &mut self,
+        key: TlsKey,
+        thread_id: ThreadId,
+        new_data: Scalar<Provenance>,
+        cx: &impl HasDataLayout,
+    ) -> InterpResult<'tcx> {
+        match self.keys.get_mut(&key) {
+            Some(TlsEntry { data, .. }) => {
+                if new_data.to_machine_usize(cx)? != 0 {
+                    trace!("TLS key {} for thread {:?} stored: {:?}", key, thread_id, new_data);
+                    data.insert(thread_id, new_data);
+                } else {
+                    trace!("TLS key {} for thread {:?} removed", key, thread_id);
+                    data.remove(&thread_id);
+                }
+                Ok(())
+            }
+            None => throw_ub_format!("storing to a non-existing TLS key: {}", key),
+        }
+    }
+
+    /// Set the thread wide destructor of the thread local storage for the given
+    /// thread. This function is used to implement `_tlv_atexit` shim on MacOS.
+    ///
+    /// Thread wide dtors are available only on MacOS. There is one destructor
+    /// per thread as can be guessed from the following comment in the
+    /// [`_tlv_atexit`
+    /// implementation](https://github.com/opensource-apple/dyld/blob/195030646877261f0c8c7ad8b001f52d6a26f514/src/threadLocalVariables.c#L389):
+    ///
+    /// NOTE: this does not need locks because it only operates on current thread data
+    pub fn set_macos_thread_dtor(
+        &mut self,
+        thread: ThreadId,
+        dtor: ty::Instance<'tcx>,
+        data: Scalar<Provenance>,
+    ) -> InterpResult<'tcx> {
+        if self.dtors_running.contains_key(&thread) {
+            // UB, according to libstd docs.
+            throw_ub_format!(
+                "setting thread's local storage destructor while destructors are already running"
+            );
+        }
+        if self.macos_thread_dtors.insert(thread, (dtor, data)).is_some() {
+            throw_unsup_format!(
+                "setting more than one thread local storage destructor for the same thread is not supported"
+            );
+        }
+        Ok(())
+    }
+
+    /// Returns a dtor, its argument and its index, if one is supposed to run.
+    /// `key` is the last dtors that was run; we return the *next* one after that.
+    ///
+    /// An optional destructor function may be associated with each key value.
+    /// At thread exit, if a key value has a non-NULL destructor pointer,
+    /// and the thread has a non-NULL value associated with that key,
+    /// the value of the key is set to NULL, and then the function pointed
+    /// to is called with the previously associated value as its sole argument.
+    /// **The order of destructor calls is unspecified if more than one destructor
+    /// exists for a thread when it exits.**
+    ///
+    /// If, after all the destructors have been called for all non-NULL values
+    /// with associated destructors, there are still some non-NULL values with
+    /// associated destructors, then the process is repeated.
+    /// If, after at least {PTHREAD_DESTRUCTOR_ITERATIONS} iterations of destructor
+    /// calls for outstanding non-NULL values, there are still some non-NULL values
+    /// with associated destructors, implementations may stop calling destructors,
+    /// or they may continue calling destructors until no non-NULL values with
+    /// associated destructors exist, even though this might result in an infinite loop.
+    fn fetch_tls_dtor(
+        &mut self,
+        key: Option<TlsKey>,
+        thread_id: ThreadId,
+    ) -> Option<(ty::Instance<'tcx>, Scalar<Provenance>, TlsKey)> {
+        use std::ops::Bound::*;
+
+        let thread_local = &mut self.keys;
+        let start = match key {
+            Some(key) => Excluded(key),
+            None => Unbounded,
+        };
+        // We interpret the documentaion above (taken from POSIX) as saying that we need to iterate
+        // over all keys and run each destructor at least once before running any destructor a 2nd
+        // time. That's why we have `key` to indicate how far we got in the current iteration. If we
+        // return `None`, `schedule_next_pthread_tls_dtor` will re-try with `ket` set to `None` to
+        // start the next round.
+        // TODO: In the future, we might consider randomizing destructor order, but we still have to
+        // uphold this requirement.
+        for (&key, TlsEntry { data, dtor }) in thread_local.range_mut((start, Unbounded)) {
+            match data.entry(thread_id) {
+                BTreeEntry::Occupied(entry) => {
+                    if let Some(dtor) = dtor {
+                        // Set TLS data to NULL, and call dtor with old value.
+                        let data_scalar = entry.remove();
+                        let ret = Some((*dtor, data_scalar, key));
+                        return ret;
+                    }
+                }
+                BTreeEntry::Vacant(_) => {}
+            }
+        }
+        None
+    }
+
+    /// Set that dtors are running for `thread`. It is guaranteed not to change
+    /// the existing values stored in `dtors_running` for this thread. Returns
+    /// `true` if dtors for `thread` are already running.
+    fn set_dtors_running_for_thread(&mut self, thread: ThreadId) -> bool {
+        match self.dtors_running.entry(thread) {
+            HashMapEntry::Occupied(_) => true,
+            HashMapEntry::Vacant(entry) => {
+                // We cannot just do `self.dtors_running.insert` because that
+                // would overwrite `last_dtor_key` with `None`.
+                entry.insert(RunningDtorsState { last_dtor_key: None });
+                false
+            }
+        }
+    }
+
+    /// Delete all TLS entries for the given thread. This function should be
+    /// called after all TLS destructors have already finished.
+    fn delete_all_thread_tls(&mut self, thread_id: ThreadId) {
+        for TlsEntry { data, .. } in self.keys.values_mut() {
+            data.remove(&thread_id);
+        }
+    }
+
+    pub fn iter(&self, mut visitor: impl FnMut(&Scalar<Provenance>)) {
+        for scalar in self.keys.values().flat_map(|v| v.data.values()) {
+            visitor(scalar);
+        }
+    }
+}
+
+impl<'mir, 'tcx: 'mir> EvalContextPrivExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    /// Schedule TLS destructors for Windows.
+    /// On windows, TLS destructors are managed by std.
+    fn schedule_windows_tls_dtors(&mut self) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        let active_thread = this.get_active_thread();
+
+        // Windows has a special magic linker section that is run on certain events.
+        // Instead of searching for that section and supporting arbitrary hooks in there
+        // (that would be basically https://github.com/rust-lang/miri/issues/450),
+        // we specifically look up the static in libstd that we know is placed
+        // in that section.
+        let thread_callback =
+            this.eval_windows("thread_local_key", "p_thread_callback")?.to_pointer(this)?;
+        let thread_callback = this.get_ptr_fn(thread_callback)?.as_instance()?;
+
+        // FIXME: Technically, the reason should be `DLL_PROCESS_DETACH` when the main thread exits
+        // but std treats both the same.
+        let reason = this.eval_windows("c", "DLL_THREAD_DETACH")?;
+
+        // The signature of this function is `unsafe extern "system" fn(h: c::LPVOID, dwReason: c::DWORD, pv: c::LPVOID)`.
+        // FIXME: `h` should be a handle to the current module and what `pv` should be is unknown
+        // but both are ignored by std
+        this.call_function(
+            thread_callback,
+            Abi::System { unwind: false },
+            &[Scalar::null_ptr(this).into(), reason.into(), Scalar::null_ptr(this).into()],
+            None,
+            StackPopCleanup::Root { cleanup: true },
+        )?;
+
+        this.enable_thread(active_thread);
+        Ok(())
+    }
+
+    /// Schedule the MacOS thread destructor of the thread local storage to be
+    /// executed. Returns `true` if scheduled.
+    ///
+    /// Note: It is safe to call this function also on other Unixes.
+    fn schedule_macos_tls_dtor(&mut self) -> InterpResult<'tcx, bool> {
+        let this = self.eval_context_mut();
+        let thread_id = this.get_active_thread();
+        if let Some((instance, data)) = this.machine.tls.macos_thread_dtors.remove(&thread_id) {
+            trace!("Running macos dtor {:?} on {:?} at {:?}", instance, data, thread_id);
+
+            this.call_function(
+                instance,
+                Abi::C { unwind: false },
+                &[data.into()],
+                None,
+                StackPopCleanup::Root { cleanup: true },
+            )?;
+
+            // Enable the thread so that it steps through the destructor which
+            // we just scheduled. Since we deleted the destructor, it is
+            // guaranteed that we will schedule it again. The `dtors_running`
+            // flag will prevent the code from adding the destructor again.
+            this.enable_thread(thread_id);
+            Ok(true)
+        } else {
+            Ok(false)
+        }
+    }
+
+    /// Schedule a pthread TLS destructor. Returns `true` if found
+    /// a destructor to schedule, and `false` otherwise.
+    fn schedule_next_pthread_tls_dtor(&mut self) -> InterpResult<'tcx, bool> {
+        let this = self.eval_context_mut();
+        let active_thread = this.get_active_thread();
+
+        assert!(this.has_terminated(active_thread), "running TLS dtors for non-terminated thread");
+        // Fetch next dtor after `key`.
+        let last_key = this.machine.tls.dtors_running[&active_thread].last_dtor_key;
+        let dtor = match this.machine.tls.fetch_tls_dtor(last_key, active_thread) {
+            dtor @ Some(_) => dtor,
+            // We ran each dtor once, start over from the beginning.
+            None => this.machine.tls.fetch_tls_dtor(None, active_thread),
+        };
+        if let Some((instance, ptr, key)) = dtor {
+            this.machine.tls.dtors_running.get_mut(&active_thread).unwrap().last_dtor_key =
+                Some(key);
+            trace!("Running TLS dtor {:?} on {:?} at {:?}", instance, ptr, active_thread);
+            assert!(
+                !ptr.to_machine_usize(this).unwrap() != 0,
+                "data can't be NULL when dtor is called!"
+            );
+
+            this.call_function(
+                instance,
+                Abi::C { unwind: false },
+                &[ptr.into()],
+                None,
+                StackPopCleanup::Root { cleanup: true },
+            )?;
+
+            this.enable_thread(active_thread);
+            return Ok(true);
+        }
+        this.machine.tls.dtors_running.get_mut(&active_thread).unwrap().last_dtor_key = None;
+
+        Ok(false)
+    }
+}
+
+impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    /// Schedule an active thread's TLS destructor to run on the active thread.
+    /// Note that this function does not run the destructors itself, it just
+    /// schedules them one by one each time it is called and reenables the
+    /// thread so that it can be executed normally by the main execution loop.
+    ///
+    /// Note: we consistently run TLS destructors for all threads, including the
+    /// main thread. However, it is not clear that we should run the TLS
+    /// destructors for the main thread. See issue:
+    /// <https://github.com/rust-lang/rust/issues/28129>.
+    fn schedule_next_tls_dtor_for_active_thread(&mut self) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        let active_thread = this.get_active_thread();
+        trace!("schedule_next_tls_dtor_for_active_thread on thread {:?}", active_thread);
+
+        if !this.machine.tls.set_dtors_running_for_thread(active_thread) {
+            // This is the first time we got asked to schedule a destructor. The
+            // Windows schedule destructor function must be called exactly once,
+            // this is why it is in this block.
+            if this.tcx.sess.target.os == "windows" {
+                // On Windows, we signal that the thread quit by starting the
+                // relevant function, reenabling the thread, and going back to
+                // the scheduler.
+                this.schedule_windows_tls_dtors()?;
+                return Ok(());
+            }
+        }
+        // The remaining dtors make some progress each time around the scheduler loop,
+        // until they return `false` to indicate that they are done.
+
+        // The macOS thread wide destructor runs "before any TLS slots get
+        // freed", so do that first.
+        if this.schedule_macos_tls_dtor()? {
+            // We have scheduled a MacOS dtor to run on the thread. Execute it
+            // to completion and come back here. Scheduling a destructor
+            // destroys it, so we will not enter this branch again.
+            return Ok(());
+        }
+        if this.schedule_next_pthread_tls_dtor()? {
+            // We have scheduled a pthread destructor and removed it from the
+            // destructors list. Run it to completion and come back here.
+            return Ok(());
+        }
+
+        // All dtors done!
+        this.machine.tls.delete_all_thread_tls(active_thread);
+        this.thread_terminated()?;
+
+        Ok(())
+    }
+}
diff --git a/src/tools/miri/src/shims/unix/android/dlsym.rs b/src/tools/miri/src/shims/unix/android/dlsym.rs
new file mode 100644
index 0000000000000..4cb78d4dabdcf
--- /dev/null
+++ b/src/tools/miri/src/shims/unix/android/dlsym.rs
@@ -0,0 +1,54 @@
+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 &[ref _sig, ref _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
new file mode 100644
index 0000000000000..756aed369f15b
--- /dev/null
+++ b/src/tools/miri/src/shims/unix/android/foreign_items.rs
@@ -0,0 +1,26 @@
+use rustc_span::Symbol;
+use rustc_target::spec::abi::Abi;
+
+use crate::*;
+use shims::foreign_items::EmulateByNameResult;
+
+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(
+        &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)]
+        match link_name.as_str() {
+            _ => return Ok(EmulateByNameResult::NotSupported),
+        }
+
+        #[allow(unreachable_code)]
+        Ok(EmulateByNameResult::NeedsJumping)
+    }
+}
diff --git a/src/tools/miri/src/shims/unix/android/mod.rs b/src/tools/miri/src/shims/unix/android/mod.rs
new file mode 100644
index 0000000000000..434f5f30b5a56
--- /dev/null
+++ b/src/tools/miri/src/shims/unix/android/mod.rs
@@ -0,0 +1,2 @@
+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
new file mode 100644
index 0000000000000..8bc19d18f2b29
--- /dev/null
+++ b/src/tools/miri/src/shims/unix/dlsym.rs
@@ -0,0 +1,55 @@
+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
new file mode 100644
index 0000000000000..153e5852dcc88
--- /dev/null
+++ b/src/tools/miri/src/shims/unix/foreign_items.rs
@@ -0,0 +1,606 @@
+use std::ffi::OsStr;
+
+use log::trace;
+
+use rustc_middle::ty::layout::LayoutOf;
+use rustc_span::Symbol;
+use rustc_target::abi::{Align, Size};
+use rustc_target::spec::abi::Abi;
+
+use crate::*;
+use shims::foreign_items::EmulateByNameResult;
+use shims::unix::fs::EvalContextExt as _;
+use shims::unix::sync::EvalContextExt as _;
+use shims::unix::thread::EvalContextExt as _;
+
+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(
+        &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();
+
+        // See `fn emulate_foreign_item_by_name` in `shims/foreign_items.rs` for the general pattern.
+        #[rustfmt::skip]
+        match link_name.as_str() {
+            // Environment related shims
+            "getenv" => {
+                let [name] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.getenv(name)?;
+                this.write_pointer(result, dest)?;
+            }
+            "unsetenv" => {
+                let [name] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.unsetenv(name)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "setenv" => {
+                let [name, value, overwrite] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                this.read_scalar(overwrite)?.to_i32()?;
+                let result = this.setenv(name, value)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "getcwd" => {
+                let [buf, size] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.getcwd(buf, size)?;
+                this.write_pointer(result, dest)?;
+            }
+            "chdir" => {
+                let [path] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.chdir(path)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+
+            // File related shims
+            "open" | "open64" => {
+                // `open` is variadic, the third argument is only present when the second argument has O_CREAT (or on linux O_TMPFILE, but miri doesn't support that) set
+                this.check_abi_and_shim_symbol_clash(abi, Abi::C { unwind: false }, link_name)?;
+                let result = this.open(args)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "close" => {
+                let [fd] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.close(fd)?;
+                this.write_scalar(result, dest)?;
+            }
+            "fcntl" => {
+                // `fcntl` is variadic. The argument count is checked based on the first argument
+                // in `this.fcntl()`, so we do not use `check_shim` here.
+                this.check_abi_and_shim_symbol_clash(abi, Abi::C { unwind: false }, link_name)?;
+                let result = this.fcntl(args)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "read" => {
+                let [fd, buf, count] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let fd = this.read_scalar(fd)?.to_i32()?;
+                let buf = this.read_pointer(buf)?;
+                let count = this.read_scalar(count)?.to_machine_usize(this)?;
+                let result = this.read(fd, buf, count)?;
+                this.write_scalar(Scalar::from_machine_isize(result, this), dest)?;
+            }
+            "write" => {
+                let [fd, buf, n] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let fd = this.read_scalar(fd)?.to_i32()?;
+                let buf = this.read_pointer(buf)?;
+                let count = this.read_scalar(n)?.to_machine_usize(this)?;
+                trace!("Called write({:?}, {:?}, {:?})", fd, buf, count);
+                let result = this.write(fd, buf, count)?;
+                // Now, `result` is the value we return back to the program.
+                this.write_scalar(Scalar::from_machine_isize(result, this), dest)?;
+            }
+            "unlink" => {
+                let [path] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.unlink(path)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "symlink" => {
+                let [target, linkpath] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.symlink(target, linkpath)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "rename" => {
+                let [oldpath, newpath] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.rename(oldpath, newpath)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "mkdir" => {
+                let [path, mode] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.mkdir(path, mode)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "rmdir" => {
+                let [path] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.rmdir(path)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "opendir" => {
+                let [name] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.opendir(name)?;
+                this.write_scalar(result, dest)?;
+            }
+            "closedir" => {
+                let [dirp] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.closedir(dirp)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "lseek64" => {
+                let [fd, offset, whence] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.lseek64(fd, offset, whence)?;
+                this.write_scalar(result, dest)?;
+            }
+            "ftruncate64" => {
+                let [fd, length] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.ftruncate64(fd, length)?;
+                this.write_scalar(result, dest)?;
+            }
+            "fsync" => {
+                let [fd] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.fsync(fd)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "fdatasync" => {
+                let [fd] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.fdatasync(fd)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "readlink" => {
+                let [pathname, buf, bufsize] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.readlink(pathname, buf, bufsize)?;
+                this.write_scalar(Scalar::from_machine_isize(result, this), dest)?;
+            }
+            "posix_fadvise" => {
+                let [fd, offset, len, advice] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                this.read_scalar(fd)?.to_i32()?;
+                this.read_scalar(offset)?.to_machine_isize(this)?;
+                this.read_scalar(len)?.to_machine_isize(this)?;
+                this.read_scalar(advice)?.to_i32()?;
+                // fadvise is only informational, we can ignore it.
+                this.write_null(dest)?;
+            }
+            "realpath" => {
+                let [path, resolved_path] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.realpath(path, resolved_path)?;
+                this.write_scalar(result, dest)?;
+            }
+            "mkstemp" => {
+                let [template] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.mkstemp(template)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+
+            // Time related shims
+            "gettimeofday" => {
+                let [tv, tz] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.gettimeofday(tv, tz)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+
+            // Allocation
+            "posix_memalign" => {
+                let [ret, align, size] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let ret = this.deref_operand(ret)?;
+                let align = this.read_scalar(align)?.to_machine_usize(this)?;
+                let size = this.read_scalar(size)?.to_machine_usize(this)?;
+                // Align must be power of 2, and also at least ptr-sized (POSIX rules).
+                // But failure to adhere to this is not UB, it's an error condition.
+                if !align.is_power_of_two() || align < this.pointer_size().bytes() {
+                    let einval = this.eval_libc_i32("EINVAL")?;
+                    this.write_int(einval, dest)?;
+                } else {
+                    if size == 0 {
+                        this.write_null(&ret.into())?;
+                    } else {
+                        let ptr = this.allocate_ptr(
+                            Size::from_bytes(size),
+                            Align::from_bytes(align).unwrap(),
+                            MiriMemoryKind::C.into(),
+                        )?;
+                        this.write_pointer(ptr, &ret.into())?;
+                    }
+                    this.write_null(dest)?;
+                }
+            }
+
+            // Dynamic symbol loading
+            "dlsym" => {
+                let [handle, symbol] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                this.read_scalar(handle)?.to_machine_usize(this)?;
+                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.create_fn_alloc_ptr(FnVal::Other(dlsym));
+                    this.write_pointer(ptr, dest)?;
+                } else {
+                    this.write_null(dest)?;
+                }
+            }
+
+            // Querying system information
+            "sysconf" => {
+                let [name] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let name = this.read_scalar(name)?.to_i32()?;
+
+                // FIXME: Which of these are POSIX, and which are GNU/Linux?
+                // At least the names seem to all also exist on macOS.
+                let sysconfs: &[(&str, fn(&MiriInterpCx<'_, '_>) -> Scalar<Provenance>)] = &[
+                    ("_SC_PAGESIZE", |this| Scalar::from_int(PAGE_SIZE, this.pointer_size())),
+                    ("_SC_NPROCESSORS_CONF", |this| Scalar::from_int(NUM_CPUS, this.pointer_size())),
+                    ("_SC_NPROCESSORS_ONLN", |this| Scalar::from_int(NUM_CPUS, this.pointer_size())),
+                    // 512 seems to be a reasonable default. The value is not critical, in
+                    // the sense that getpwuid_r takes and checks the buffer length.
+                    ("_SC_GETPW_R_SIZE_MAX", |this| Scalar::from_int(512, this.pointer_size()))
+                ];
+                let mut result = None;
+                for &(sysconf_name, value) in sysconfs {
+                    let sysconf_name = this.eval_libc_i32(sysconf_name)?;
+                    if sysconf_name == name {
+                        result = Some(value(this));
+                        break;
+                    }
+                }
+                if let Some(result) = result {
+                    this.write_scalar(result, dest)?;
+                } else {
+                    throw_unsup_format!("unimplemented sysconf name: {}", name)
+                }
+            }
+
+            // Thread-local storage
+            "pthread_key_create" => {
+                let [key, dtor] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let key_place = this.deref_operand(key)?;
+                let dtor = this.read_pointer(dtor)?;
+
+                // Extract the function type out of the signature (that seems easier than constructing it ourselves).
+                let dtor = if !this.ptr_is_null(dtor)? {
+                    Some(this.get_ptr_fn(dtor)?.as_instance()?)
+                } else {
+                    None
+                };
+
+                // Figure out how large a pthread TLS key actually is.
+                // To this end, deref the argument type. This is `libc::pthread_key_t`.
+                let key_type = key.layout.ty
+                    .builtin_deref(true)
+                    .ok_or_else(|| err_ub_format!(
+                        "wrong signature used for `pthread_key_create`: first argument must be a raw pointer."
+                    ))?
+                    .ty;
+                let key_layout = this.layout_of(key_type)?;
+
+                // Create key and write it into the memory where `key_ptr` wants it.
+                let key = this.machine.tls.create_tls_key(dtor, key_layout.size)?;
+                this.write_scalar(Scalar::from_uint(key, key_layout.size), &key_place.into())?;
+
+                // Return success (`0`).
+                this.write_null(dest)?;
+            }
+            "pthread_key_delete" => {
+                let [key] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let key = this.read_scalar(key)?.to_bits(key.layout.size)?;
+                this.machine.tls.delete_tls_key(key)?;
+                // Return success (0)
+                this.write_null(dest)?;
+            }
+            "pthread_getspecific" => {
+                let [key] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let key = this.read_scalar(key)?.to_bits(key.layout.size)?;
+                let active_thread = this.get_active_thread();
+                let ptr = this.machine.tls.load_tls(key, active_thread, this)?;
+                this.write_scalar(ptr, dest)?;
+            }
+            "pthread_setspecific" => {
+                let [key, new_ptr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let key = this.read_scalar(key)?.to_bits(key.layout.size)?;
+                let active_thread = this.get_active_thread();
+                let new_data = this.read_scalar(new_ptr)?;
+                this.machine.tls.store_tls(key, active_thread, new_data, &*this.tcx)?;
+
+                // Return success (`0`).
+                this.write_null(dest)?;
+            }
+
+            // Synchronization primitives
+            "pthread_mutexattr_init" => {
+                let [attr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.pthread_mutexattr_init(attr)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "pthread_mutexattr_settype" => {
+                let [attr, kind] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.pthread_mutexattr_settype(attr, kind)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "pthread_mutexattr_destroy" => {
+                let [attr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.pthread_mutexattr_destroy(attr)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "pthread_mutex_init" => {
+                let [mutex, attr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.pthread_mutex_init(mutex, attr)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "pthread_mutex_lock" => {
+                let [mutex] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.pthread_mutex_lock(mutex)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "pthread_mutex_trylock" => {
+                let [mutex] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.pthread_mutex_trylock(mutex)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "pthread_mutex_unlock" => {
+                let [mutex] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.pthread_mutex_unlock(mutex)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "pthread_mutex_destroy" => {
+                let [mutex] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.pthread_mutex_destroy(mutex)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "pthread_rwlock_rdlock" => {
+                let [rwlock] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.pthread_rwlock_rdlock(rwlock)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "pthread_rwlock_tryrdlock" => {
+                let [rwlock] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.pthread_rwlock_tryrdlock(rwlock)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "pthread_rwlock_wrlock" => {
+                let [rwlock] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.pthread_rwlock_wrlock(rwlock)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "pthread_rwlock_trywrlock" => {
+                let [rwlock] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.pthread_rwlock_trywrlock(rwlock)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "pthread_rwlock_unlock" => {
+                let [rwlock] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.pthread_rwlock_unlock(rwlock)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "pthread_rwlock_destroy" => {
+                let [rwlock] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.pthread_rwlock_destroy(rwlock)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "pthread_condattr_init" => {
+                let [attr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.pthread_condattr_init(attr)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "pthread_condattr_destroy" => {
+                let [attr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.pthread_condattr_destroy(attr)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "pthread_cond_init" => {
+                let [cond, attr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.pthread_cond_init(cond, attr)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "pthread_cond_signal" => {
+                let [cond] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.pthread_cond_signal(cond)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "pthread_cond_broadcast" => {
+                let [cond] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.pthread_cond_broadcast(cond)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "pthread_cond_wait" => {
+                let [cond, mutex] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.pthread_cond_wait(cond, mutex)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "pthread_cond_timedwait" => {
+                let [cond, mutex, abstime] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                this.pthread_cond_timedwait(cond, mutex, abstime, dest)?;
+            }
+            "pthread_cond_destroy" => {
+                let [cond] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.pthread_cond_destroy(cond)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+
+            // Threading
+            "pthread_create" => {
+                let [thread, attr, start, arg] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.pthread_create(thread, attr, start, arg)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "pthread_join" => {
+                let [thread, retval] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.pthread_join(thread, retval)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "pthread_detach" => {
+                let [thread] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.pthread_detach(thread)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "pthread_self" => {
+                let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let res = this.pthread_self()?;
+                this.write_scalar(res, dest)?;
+            }
+            "sched_yield" => {
+                let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.sched_yield()?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "nanosleep" => {
+                let [req, rem] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.nanosleep(req, rem)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+
+            // Miscellaneous
+            "isatty" => {
+                let [fd] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.isatty(fd)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "pthread_atfork" => {
+                let [prepare, parent, child] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                this.read_pointer(prepare)?;
+                this.read_pointer(parent)?;
+                this.read_pointer(child)?;
+                // We do not support forking, so there is nothing to do here.
+                this.write_null(dest)?;
+            }
+            "strerror_r" | "__xpg_strerror_r" => {
+                let [errnum, buf, buflen] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let errnum = this.read_scalar(errnum)?;
+                let buf = this.read_pointer(buf)?;
+                let buflen = this.read_scalar(buflen)?.to_machine_usize(this)?;
+
+                let error = this.try_errnum_to_io_error(errnum)?;
+                let formatted = match error {
+                    Some(err) => format!("{err}"),
+                    None => format!("<unknown errnum in strerror_r: {errnum}>"),
+                };
+                let (complete, _) = this.write_os_str_to_c_str(OsStr::new(&formatted), buf, buflen)?;
+                let ret = if complete { 0 } else { this.eval_libc_i32("ERANGE")? };
+                this.write_int(ret, dest)?;
+            }
+            "getpid" => {
+                let [] = this.check_shim(abi, Abi::C { unwind: false}, link_name, args)?;
+                let result = this.getpid()?;
+                this.write_scalar(Scalar::from_i32(result), 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.
+            "pthread_attr_getguardsize"
+            if this.frame_in_std() => {
+                let [_attr, guard_size] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let guard_size = this.deref_operand(guard_size)?;
+                let guard_size_layout = this.libc_ty_layout("size_t")?;
+                this.write_scalar(Scalar::from_uint(crate::PAGE_SIZE, guard_size_layout.size), &guard_size.into())?;
+
+                // Return success (`0`).
+                this.write_null(dest)?;
+            }
+
+            | "pthread_attr_init"
+            | "pthread_attr_destroy"
+            if this.frame_in_std() => {
+                let [_] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                this.write_null(dest)?;
+            }
+            | "pthread_attr_setstacksize"
+            if this.frame_in_std() => {
+                let [_, _] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                this.write_null(dest)?;
+            }
+
+            "pthread_attr_getstack"
+            if this.frame_in_std() => {
+                // We don't support "pthread_attr_setstack", so we just pretend all stacks have the same values here.
+                // Hence we can mostly ignore the input `attr_place`.
+                let [attr_place, addr_place, size_place] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let _attr_place = this.deref_operand(attr_place)?;
+                let addr_place = this.deref_operand(addr_place)?;
+                let size_place = this.deref_operand(size_place)?;
+
+                this.write_scalar(
+                    Scalar::from_uint(STACK_ADDR, this.pointer_size()),
+                    &addr_place.into(),
+                )?;
+                this.write_scalar(
+                    Scalar::from_uint(STACK_SIZE, this.pointer_size()),
+                    &size_place.into(),
+                )?;
+
+                // Return success (`0`).
+                this.write_null(dest)?;
+            }
+
+            | "signal"
+            | "sigaltstack"
+            if this.frame_in_std() => {
+                let [_, _] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                this.write_null(dest)?;
+            }
+            | "sigaction"
+            | "mprotect"
+            if this.frame_in_std() => {
+                let [_, _, _] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                this.write_null(dest)?;
+            }
+
+            "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.
+                this.write_int(super::UID, dest)?;
+            }
+
+            "getpwuid_r" if this.frame_in_std() => {
+                let [uid, pwd, buf, buflen, result] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                this.check_no_isolation("`getpwuid_r`")?;
+
+                let uid = this.read_scalar(uid)?.to_u32()?;
+                let pwd = this.deref_operand(pwd)?;
+                let buf = this.read_pointer(buf)?;
+                let buflen = this.read_scalar(buflen)?.to_machine_usize(this)?;
+                let result = this.deref_operand(result)?;
+
+                // Must be for "us".
+                if uid != crate::shims::unix::UID {
+                    throw_unsup_format!("`getpwuid_r` on other users is not supported");
+                }
+
+                // Reset all fields to `uninit` to make sure nobody reads them.
+                // (This is a std-only shim so we are okay with such hacks.)
+                this.write_uninit(&pwd.into())?;
+
+                // We only set the home_dir field.
+                #[allow(deprecated)]
+                let home_dir = std::env::home_dir().unwrap();
+                let (written, _) = this.write_path_to_c_str(&home_dir, buf, buflen)?;
+                let pw_dir = this.mplace_field_named(&pwd, "pw_dir")?;
+                this.write_pointer(buf, &pw_dir.into())?;
+
+                if written {
+                    this.write_pointer(pwd.ptr, &result.into())?;
+                    this.write_null(dest)?;
+                } else {
+                    this.write_null(&result.into())?;
+                    this.write_scalar(this.eval_libc("ERANGE")?, dest)?;
+                }
+            }
+
+            // Platform-specific shims
+            _ => {
+                let target_os = &*this.tcx.sess.target.os;
+                match target_os {
+                    "android" => return shims::unix::android::foreign_items::EvalContextExt::emulate_foreign_item_by_name(this, link_name, abi, args, dest),
+                    "freebsd" => return shims::unix::freebsd::foreign_items::EvalContextExt::emulate_foreign_item_by_name(this, link_name, abi, args, dest),
+                    "linux" => return shims::unix::linux::foreign_items::EvalContextExt::emulate_foreign_item_by_name(this, link_name, abi, args, dest),
+                    "macos" => return shims::unix::macos::foreign_items::EvalContextExt::emulate_foreign_item_by_name(this, link_name, abi, args, dest),
+                    _ => panic!("unsupported Unix OS {target_os}"),
+                }
+            }
+        };
+
+        Ok(EmulateByNameResult::NeedsJumping)
+    }
+}
diff --git a/src/tools/miri/src/shims/unix/freebsd/dlsym.rs b/src/tools/miri/src/shims/unix/freebsd/dlsym.rs
new file mode 100644
index 0000000000000..d759ffb8994b7
--- /dev/null
+++ b/src/tools/miri/src/shims/unix/freebsd/dlsym.rs
@@ -0,0 +1,36 @@
+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
new file mode 100644
index 0000000000000..70798f9817453
--- /dev/null
+++ b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs
@@ -0,0 +1,45 @@
+use rustc_span::Symbol;
+use rustc_target::spec::abi::Abi;
+
+use crate::*;
+use shims::foreign_items::EmulateByNameResult;
+use shims::unix::thread::EvalContextExt as _;
+
+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(
+        &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();
+        match link_name.as_str() {
+            // Threading
+            "pthread_attr_get_np" if this.frame_in_std() => {
+                let [_thread, _attr] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                this.write_null(dest)?;
+            }
+            "pthread_set_name_np" => {
+                let [thread, name] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let res =
+                    this.pthread_setname_np(this.read_scalar(thread)?, this.read_scalar(name)?)?;
+                this.write_scalar(res, dest)?;
+            }
+
+            // errno
+            "__error" => {
+                let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let errno_place = this.last_error_place()?;
+                this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?;
+            }
+
+            _ => return Ok(EmulateByNameResult::NotSupported),
+        }
+        Ok(EmulateByNameResult::NeedsJumping)
+    }
+}
diff --git a/src/tools/miri/src/shims/unix/freebsd/mod.rs b/src/tools/miri/src/shims/unix/freebsd/mod.rs
new file mode 100644
index 0000000000000..434f5f30b5a56
--- /dev/null
+++ b/src/tools/miri/src/shims/unix/freebsd/mod.rs
@@ -0,0 +1,2 @@
+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
new file mode 100644
index 0000000000000..8464c4589ed5d
--- /dev/null
+++ b/src/tools/miri/src/shims/unix/fs.rs
@@ -0,0 +1,1952 @@
+use std::borrow::Cow;
+use std::collections::BTreeMap;
+use std::convert::TryInto;
+use std::fs::{
+    read_dir, remove_dir, remove_file, rename, DirBuilder, File, FileType, OpenOptions, ReadDir,
+};
+use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
+use std::path::{Path, PathBuf};
+use std::time::SystemTime;
+
+use log::trace;
+
+use rustc_data_structures::fx::FxHashMap;
+use rustc_middle::ty::{self, layout::LayoutOf};
+use rustc_target::abi::{Align, Size};
+
+use crate::shims::os_str::bytes_to_os_str;
+use crate::*;
+use shims::os_str::os_str_to_bytes;
+use shims::time::system_time_to_duration;
+
+#[derive(Debug)]
+struct FileHandle {
+    file: File,
+    writable: bool,
+}
+
+trait FileDescriptor: std::fmt::Debug {
+    fn name(&self) -> &'static str;
+
+    fn as_file_handle<'tcx>(&self) -> InterpResult<'tcx, &FileHandle> {
+        throw_unsup_format!("{} cannot be used as FileHandle", self.name());
+    }
+
+    fn read<'tcx>(
+        &mut self,
+        _communicate_allowed: bool,
+        _bytes: &mut [u8],
+    ) -> InterpResult<'tcx, io::Result<usize>> {
+        throw_unsup_format!("cannot read from {}", self.name());
+    }
+
+    fn write<'tcx>(
+        &self,
+        _communicate_allowed: bool,
+        _bytes: &[u8],
+    ) -> InterpResult<'tcx, io::Result<usize>> {
+        throw_unsup_format!("cannot write to {}", self.name());
+    }
+
+    fn seek<'tcx>(
+        &mut self,
+        _communicate_allowed: bool,
+        _offset: SeekFrom,
+    ) -> InterpResult<'tcx, io::Result<u64>> {
+        throw_unsup_format!("cannot seek on {}", self.name());
+    }
+
+    fn close<'tcx>(
+        self: Box<Self>,
+        _communicate_allowed: bool,
+    ) -> InterpResult<'tcx, io::Result<i32>> {
+        throw_unsup_format!("cannot close {}", self.name());
+    }
+
+    fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>>;
+
+    #[cfg(unix)]
+    fn as_unix_host_fd(&self) -> Option<i32> {
+        None
+    }
+}
+
+impl FileDescriptor for FileHandle {
+    fn name(&self) -> &'static str {
+        "FILE"
+    }
+
+    fn as_file_handle<'tcx>(&self) -> InterpResult<'tcx, &FileHandle> {
+        Ok(self)
+    }
+
+    fn read<'tcx>(
+        &mut self,
+        communicate_allowed: bool,
+        bytes: &mut [u8],
+    ) -> InterpResult<'tcx, io::Result<usize>> {
+        assert!(communicate_allowed, "isolation should have prevented even opening a file");
+        Ok(self.file.read(bytes))
+    }
+
+    fn write<'tcx>(
+        &self,
+        communicate_allowed: bool,
+        bytes: &[u8],
+    ) -> InterpResult<'tcx, io::Result<usize>> {
+        assert!(communicate_allowed, "isolation should have prevented even opening a file");
+        Ok((&mut &self.file).write(bytes))
+    }
+
+    fn seek<'tcx>(
+        &mut self,
+        communicate_allowed: bool,
+        offset: SeekFrom,
+    ) -> InterpResult<'tcx, io::Result<u64>> {
+        assert!(communicate_allowed, "isolation should have prevented even opening a file");
+        Ok(self.file.seek(offset))
+    }
+
+    fn close<'tcx>(
+        self: Box<Self>,
+        communicate_allowed: bool,
+    ) -> InterpResult<'tcx, io::Result<i32>> {
+        assert!(communicate_allowed, "isolation should have prevented even opening a file");
+        // We sync the file if it was opened in a mode different than read-only.
+        if self.writable {
+            // `File::sync_all` does the checks that are done when closing a file. We do this to
+            // to handle possible errors correctly.
+            let result = self.file.sync_all().map(|_| 0i32);
+            // Now we actually close the file.
+            drop(self);
+            // And return the result.
+            Ok(result)
+        } else {
+            // We drop the file, this closes it but ignores any errors
+            // produced when closing it. This is done because
+            // `File::sync_all` cannot be done over files like
+            // `/dev/urandom` which are read-only. Check
+            // https://github.com/rust-lang/miri/issues/999#issuecomment-568920439
+            // for a deeper discussion.
+            drop(self);
+            Ok(Ok(0))
+        }
+    }
+
+    fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
+        let duplicated = self.file.try_clone()?;
+        Ok(Box::new(FileHandle { file: duplicated, writable: self.writable }))
+    }
+
+    #[cfg(unix)]
+    fn as_unix_host_fd(&self) -> Option<i32> {
+        use std::os::unix::io::AsRawFd;
+        Some(self.file.as_raw_fd())
+    }
+}
+
+impl FileDescriptor for io::Stdin {
+    fn name(&self) -> &'static str {
+        "stdin"
+    }
+
+    fn read<'tcx>(
+        &mut self,
+        communicate_allowed: bool,
+        bytes: &mut [u8],
+    ) -> InterpResult<'tcx, io::Result<usize>> {
+        if !communicate_allowed {
+            // We want isolation mode to be deterministic, so we have to disallow all reads, even stdin.
+            helpers::isolation_abort_error("`read` from stdin")?;
+        }
+        Ok(Read::read(self, bytes))
+    }
+
+    fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
+        Ok(Box::new(io::stdin()))
+    }
+
+    #[cfg(unix)]
+    fn as_unix_host_fd(&self) -> Option<i32> {
+        Some(libc::STDIN_FILENO)
+    }
+}
+
+impl FileDescriptor for io::Stdout {
+    fn name(&self) -> &'static str {
+        "stdout"
+    }
+
+    fn write<'tcx>(
+        &self,
+        _communicate_allowed: bool,
+        bytes: &[u8],
+    ) -> InterpResult<'tcx, io::Result<usize>> {
+        // We allow writing to stderr even with isolation enabled.
+        let result = Write::write(&mut { self }, bytes);
+        // Stdout is buffered, flush to make sure it appears on the
+        // screen.  This is the write() syscall of the interpreted
+        // program, we want it to correspond to a write() syscall on
+        // the host -- there is no good in adding extra buffering
+        // here.
+        io::stdout().flush().unwrap();
+
+        Ok(result)
+    }
+
+    fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
+        Ok(Box::new(io::stdout()))
+    }
+
+    #[cfg(unix)]
+    fn as_unix_host_fd(&self) -> Option<i32> {
+        Some(libc::STDOUT_FILENO)
+    }
+}
+
+impl FileDescriptor for io::Stderr {
+    fn name(&self) -> &'static str {
+        "stderr"
+    }
+
+    fn write<'tcx>(
+        &self,
+        _communicate_allowed: bool,
+        bytes: &[u8],
+    ) -> InterpResult<'tcx, io::Result<usize>> {
+        // We allow writing to stderr even with isolation enabled.
+        // No need to flush, stderr is not buffered.
+        Ok(Write::write(&mut { self }, bytes))
+    }
+
+    fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
+        Ok(Box::new(io::stderr()))
+    }
+
+    #[cfg(unix)]
+    fn as_unix_host_fd(&self) -> Option<i32> {
+        Some(libc::STDERR_FILENO)
+    }
+}
+
+#[derive(Debug)]
+struct DummyOutput;
+
+impl FileDescriptor for DummyOutput {
+    fn name(&self) -> &'static str {
+        "stderr and stdout"
+    }
+
+    fn write<'tcx>(
+        &self,
+        _communicate_allowed: bool,
+        bytes: &[u8],
+    ) -> InterpResult<'tcx, io::Result<usize>> {
+        // We just don't write anything, but report to the user that we did.
+        Ok(Ok(bytes.len()))
+    }
+
+    fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
+        Ok(Box::new(DummyOutput))
+    }
+}
+
+#[derive(Debug)]
+pub struct FileHandler {
+    handles: BTreeMap<i32, Box<dyn FileDescriptor>>,
+}
+
+impl FileHandler {
+    pub(crate) fn new(mute_stdout_stderr: bool) -> FileHandler {
+        let mut handles: BTreeMap<_, Box<dyn FileDescriptor>> = BTreeMap::new();
+        handles.insert(0i32, Box::new(io::stdin()));
+        if mute_stdout_stderr {
+            handles.insert(1i32, Box::new(DummyOutput));
+            handles.insert(2i32, Box::new(DummyOutput));
+        } else {
+            handles.insert(1i32, Box::new(io::stdout()));
+            handles.insert(2i32, Box::new(io::stderr()));
+        }
+        FileHandler { handles }
+    }
+
+    fn insert_fd(&mut self, file_handle: Box<dyn FileDescriptor>) -> i32 {
+        self.insert_fd_with_min_fd(file_handle, 0)
+    }
+
+    fn insert_fd_with_min_fd(&mut self, file_handle: Box<dyn FileDescriptor>, min_fd: i32) -> i32 {
+        // Find the lowest unused FD, starting from min_fd. If the first such unused FD is in
+        // between used FDs, the find_map combinator will return it. If the first such unused FD
+        // is after all other used FDs, the find_map combinator will return None, and we will use
+        // the FD following the greatest FD thus far.
+        let candidate_new_fd =
+            self.handles.range(min_fd..).zip(min_fd..).find_map(|((fd, _fh), counter)| {
+                if *fd != counter {
+                    // There was a gap in the fds stored, return the first unused one
+                    // (note that this relies on BTreeMap iterating in key order)
+                    Some(counter)
+                } else {
+                    // This fd is used, keep going
+                    None
+                }
+            });
+        let new_fd = candidate_new_fd.unwrap_or_else(|| {
+            // find_map ran out of BTreeMap entries before finding a free fd, use one plus the
+            // maximum fd in the map
+            self.handles
+                .last_key_value()
+                .map(|(fd, _)| fd.checked_add(1).unwrap())
+                .unwrap_or(min_fd)
+        });
+
+        self.handles.try_insert(new_fd, file_handle).unwrap();
+        new_fd
+    }
+}
+
+impl<'mir, 'tcx: 'mir> EvalContextExtPrivate<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+trait EvalContextExtPrivate<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    fn macos_stat_write_buf(
+        &mut self,
+        metadata: FileMetadata,
+        buf_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        let mode: u16 = metadata.mode.to_u16()?;
+
+        let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));
+        let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
+        let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
+
+        let buf = this.deref_operand(buf_op)?;
+        this.write_int_fields_named(
+            &[
+                ("st_dev", 0),
+                ("st_mode", mode.into()),
+                ("st_nlink", 0),
+                ("st_ino", 0),
+                ("st_uid", 0),
+                ("st_gid", 0),
+                ("st_rdev", 0),
+                ("st_atime", access_sec.into()),
+                ("st_atime_nsec", access_nsec.into()),
+                ("st_mtime", modified_sec.into()),
+                ("st_mtime_nsec", modified_nsec.into()),
+                ("st_ctime", 0),
+                ("st_ctime_nsec", 0),
+                ("st_birthtime", created_sec.into()),
+                ("st_birthtime_nsec", created_nsec.into()),
+                ("st_size", metadata.size.into()),
+                ("st_blocks", 0),
+                ("st_blksize", 0),
+                ("st_flags", 0),
+                ("st_gen", 0),
+            ],
+            &buf,
+        )?;
+
+        Ok(0)
+    }
+
+    /// Function used when a handle is not found inside `FileHandler`. It returns `Ok(-1)`and sets
+    /// the last OS error to `libc::EBADF` (invalid file descriptor). This function uses
+    /// `T: From<i32>` instead of `i32` directly because some fs functions return different integer
+    /// types (like `read`, that returns an `i64`).
+    fn handle_not_found<T: From<i32>>(&mut self) -> InterpResult<'tcx, T> {
+        let this = self.eval_context_mut();
+        let ebadf = this.eval_libc("EBADF")?;
+        this.set_last_error(ebadf)?;
+        Ok((-1).into())
+    }
+
+    fn file_type_to_d_type(
+        &mut self,
+        file_type: std::io::Result<FileType>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+        match file_type {
+            Ok(file_type) => {
+                if file_type.is_dir() {
+                    Ok(this.eval_libc("DT_DIR")?.to_u8()?.into())
+                } else if file_type.is_file() {
+                    Ok(this.eval_libc("DT_REG")?.to_u8()?.into())
+                } else if file_type.is_symlink() {
+                    Ok(this.eval_libc("DT_LNK")?.to_u8()?.into())
+                } else {
+                    // Certain file types are only supported when the host is a Unix system.
+                    // (i.e. devices and sockets) If it is, check those cases, if not, fall back to
+                    // DT_UNKNOWN sooner.
+
+                    #[cfg(unix)]
+                    {
+                        use std::os::unix::fs::FileTypeExt;
+                        if file_type.is_block_device() {
+                            Ok(this.eval_libc("DT_BLK")?.to_u8()?.into())
+                        } else if file_type.is_char_device() {
+                            Ok(this.eval_libc("DT_CHR")?.to_u8()?.into())
+                        } else if file_type.is_fifo() {
+                            Ok(this.eval_libc("DT_FIFO")?.to_u8()?.into())
+                        } else if file_type.is_socket() {
+                            Ok(this.eval_libc("DT_SOCK")?.to_u8()?.into())
+                        } else {
+                            Ok(this.eval_libc("DT_UNKNOWN")?.to_u8()?.into())
+                        }
+                    }
+                    #[cfg(not(unix))]
+                    Ok(this.eval_libc("DT_UNKNOWN")?.to_u8()?.into())
+                }
+            }
+            Err(e) =>
+                match e.raw_os_error() {
+                    Some(error) => Ok(error),
+                    None =>
+                        throw_unsup_format!(
+                            "the error {} couldn't be converted to a return value",
+                            e
+                        ),
+                },
+        }
+    }
+}
+
+/// An open directory, tracked by DirHandler.
+#[derive(Debug)]
+pub struct OpenDir {
+    /// The directory reader on the host.
+    read_dir: ReadDir,
+    /// The most recent entry returned by readdir()
+    entry: Pointer<Option<Provenance>>,
+}
+
+impl OpenDir {
+    fn new(read_dir: ReadDir) -> Self {
+        // We rely on `free` being a NOP on null pointers.
+        Self { read_dir, entry: Pointer::null() }
+    }
+}
+
+#[derive(Debug)]
+pub struct DirHandler {
+    /// Directory iterators used to emulate libc "directory streams", as used in opendir, readdir,
+    /// and closedir.
+    ///
+    /// When opendir is called, a directory iterator is created on the host for the target
+    /// directory, and an entry is stored in this hash map, indexed by an ID which represents
+    /// the directory stream. When readdir is called, the directory stream ID is used to look up
+    /// the corresponding ReadDir iterator from this map, and information from the next
+    /// directory entry is returned. When closedir is called, the ReadDir iterator is removed from
+    /// the map.
+    streams: FxHashMap<u64, OpenDir>,
+    /// ID number to be used by the next call to opendir
+    next_id: u64,
+}
+
+impl DirHandler {
+    #[allow(clippy::integer_arithmetic)]
+    fn insert_new(&mut self, read_dir: ReadDir) -> u64 {
+        let id = self.next_id;
+        self.next_id += 1;
+        self.streams.try_insert(id, OpenDir::new(read_dir)).unwrap();
+        id
+    }
+}
+
+impl Default for DirHandler {
+    fn default() -> DirHandler {
+        DirHandler {
+            streams: FxHashMap::default(),
+            // Skip 0 as an ID, because it looks like a null pointer to libc
+            next_id: 1,
+        }
+    }
+}
+
+fn maybe_sync_file(
+    file: &File,
+    writable: bool,
+    operation: fn(&File) -> std::io::Result<()>,
+) -> std::io::Result<i32> {
+    if !writable && cfg!(windows) {
+        // sync_all() and sync_data() will return an error on Windows hosts if the file is not opened
+        // for writing. (FlushFileBuffers requires that the file handle have the
+        // GENERIC_WRITE right)
+        Ok(0i32)
+    } else {
+        let result = operation(file);
+        result.map(|_| 0i32)
+    }
+}
+
+impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    fn open(&mut self, args: &[OpTy<'tcx, Provenance>]) -> InterpResult<'tcx, i32> {
+        if args.len() < 2 {
+            throw_ub_format!(
+                "incorrect number of arguments for `open`: got {}, expected at least 2",
+                args.len()
+            );
+        }
+
+        let this = self.eval_context_mut();
+
+        let path = this.read_pointer(&args[0])?;
+        let flag = this.read_scalar(&args[1])?.to_i32()?;
+
+        let mut options = OpenOptions::new();
+
+        let o_rdonly = this.eval_libc_i32("O_RDONLY")?;
+        let o_wronly = this.eval_libc_i32("O_WRONLY")?;
+        let o_rdwr = this.eval_libc_i32("O_RDWR")?;
+        // The first two bits of the flag correspond to the access mode in linux, macOS and
+        // windows. We need to check that in fact the access mode flags for the current target
+        // only use these two bits, otherwise we are in an unsupported target and should error.
+        if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 {
+            throw_unsup_format!("access mode flags on this target are unsupported");
+        }
+        let mut writable = true;
+
+        // Now we check the access mode
+        let access_mode = flag & 0b11;
+
+        if access_mode == o_rdonly {
+            writable = false;
+            options.read(true);
+        } else if access_mode == o_wronly {
+            options.write(true);
+        } else if access_mode == o_rdwr {
+            options.read(true).write(true);
+        } else {
+            throw_unsup_format!("unsupported access mode {:#x}", access_mode);
+        }
+        // We need to check that there aren't unsupported options in `flag`. For this we try to
+        // reproduce the content of `flag` in the `mirror` variable using only the supported
+        // options.
+        let mut mirror = access_mode;
+
+        let o_append = this.eval_libc_i32("O_APPEND")?;
+        if flag & o_append != 0 {
+            options.append(true);
+            mirror |= o_append;
+        }
+        let o_trunc = this.eval_libc_i32("O_TRUNC")?;
+        if flag & o_trunc != 0 {
+            options.truncate(true);
+            mirror |= o_trunc;
+        }
+        let o_creat = this.eval_libc_i32("O_CREAT")?;
+        if flag & o_creat != 0 {
+            // Get the mode.  On macOS, the argument type `mode_t` is actually `u16`, but
+            // C integer promotion rules mean that on the ABI level, it gets passed as `u32`
+            // (see https://github.com/rust-lang/rust/issues/71915).
+            let mode = if let Some(arg) = args.get(2) {
+                this.read_scalar(arg)?.to_u32()?
+            } else {
+                throw_ub_format!(
+                    "incorrect number of arguments for `open` with `O_CREAT`: got {}, expected at least 3",
+                    args.len()
+                );
+            };
+
+            if mode != 0o666 {
+                throw_unsup_format!("non-default mode 0o{:o} is not supported", mode);
+            }
+
+            mirror |= o_creat;
+
+            let o_excl = this.eval_libc_i32("O_EXCL")?;
+            if flag & o_excl != 0 {
+                mirror |= o_excl;
+                options.create_new(true);
+            } else {
+                options.create(true);
+            }
+        }
+        let o_cloexec = this.eval_libc_i32("O_CLOEXEC")?;
+        if flag & o_cloexec != 0 {
+            // We do not need to do anything for this flag because `std` already sets it.
+            // (Technically we do not support *not* setting this flag, but we ignore that.)
+            mirror |= o_cloexec;
+        }
+        // If `flag` is not equal to `mirror`, there is an unsupported option enabled in `flag`,
+        // then we throw an error.
+        if flag != mirror {
+            throw_unsup_format!("unsupported flags {:#x}", flag & !mirror);
+        }
+
+        let path = this.read_path_from_c_str(path)?;
+
+        // Reject if isolation is enabled.
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`open`", reject_with)?;
+            this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
+            return Ok(-1);
+        }
+
+        let fd = options.open(&path).map(|file| {
+            let fh = &mut this.machine.file_handler;
+            fh.insert_fd(Box::new(FileHandle { file, writable }))
+        });
+
+        this.try_unwrap_io_result(fd)
+    }
+
+    fn fcntl(&mut self, args: &[OpTy<'tcx, Provenance>]) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        if args.len() < 2 {
+            throw_ub_format!(
+                "incorrect number of arguments for fcntl: got {}, expected at least 2",
+                args.len()
+            );
+        }
+        let fd = this.read_scalar(&args[0])?.to_i32()?;
+        let cmd = this.read_scalar(&args[1])?.to_i32()?;
+
+        // Reject if isolation is enabled.
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`fcntl`", reject_with)?;
+            this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
+            return Ok(-1);
+        }
+
+        // We only support getting the flags for a descriptor.
+        if cmd == this.eval_libc_i32("F_GETFD")? {
+            // Currently this is the only flag that `F_GETFD` returns. It is OK to just return the
+            // `FD_CLOEXEC` value without checking if the flag is set for the file because `std`
+            // always sets this flag when opening a file. However we still need to check that the
+            // file itself is open.
+            if this.machine.file_handler.handles.contains_key(&fd) {
+                Ok(this.eval_libc_i32("FD_CLOEXEC")?)
+            } else {
+                this.handle_not_found()
+            }
+        } else if cmd == this.eval_libc_i32("F_DUPFD")?
+            || cmd == this.eval_libc_i32("F_DUPFD_CLOEXEC")?
+        {
+            // Note that we always assume the FD_CLOEXEC flag is set for every open file, in part
+            // because exec() isn't supported. The F_DUPFD and F_DUPFD_CLOEXEC commands only
+            // differ in whether the FD_CLOEXEC flag is pre-set on the new file descriptor,
+            // thus they can share the same implementation here.
+            if args.len() < 3 {
+                throw_ub_format!(
+                    "incorrect number of arguments for fcntl with cmd=`F_DUPFD`/`F_DUPFD_CLOEXEC`: got {}, expected at least 3",
+                    args.len()
+                );
+            }
+            let start = this.read_scalar(&args[2])?.to_i32()?;
+
+            let fh = &mut this.machine.file_handler;
+
+            match fh.handles.get_mut(&fd) {
+                Some(file_descriptor) => {
+                    let dup_result = file_descriptor.dup();
+                    match dup_result {
+                        Ok(dup_fd) => Ok(fh.insert_fd_with_min_fd(dup_fd, start)),
+                        Err(e) => {
+                            this.set_last_error_from_io_error(e.kind())?;
+                            Ok(-1)
+                        }
+                    }
+                }
+                None => this.handle_not_found(),
+            }
+        } else if this.tcx.sess.target.os == "macos" && cmd == this.eval_libc_i32("F_FULLFSYNC")? {
+            if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
+                // FIXME: Support fullfsync for all FDs
+                let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
+                let io_result = maybe_sync_file(file, *writable, File::sync_all);
+                this.try_unwrap_io_result(io_result)
+            } else {
+                this.handle_not_found()
+            }
+        } else {
+            throw_unsup_format!("the {:#x} command is not supported for `fcntl`)", cmd);
+        }
+    }
+
+    fn close(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_mut();
+
+        let fd = this.read_scalar(fd_op)?.to_i32()?;
+
+        Ok(Scalar::from_i32(
+            if let Some(file_descriptor) = this.machine.file_handler.handles.remove(&fd) {
+                let result = file_descriptor.close(this.machine.communicate())?;
+                this.try_unwrap_io_result(result)?
+            } else {
+                this.handle_not_found()?
+            },
+        ))
+    }
+
+    fn read(
+        &mut self,
+        fd: i32,
+        buf: Pointer<Option<Provenance>>,
+        count: u64,
+    ) -> InterpResult<'tcx, i64> {
+        let this = self.eval_context_mut();
+
+        // Isolation check is done via `FileDescriptor` trait.
+
+        trace!("Reading from FD {}, size {}", fd, count);
+
+        // Check that the *entire* buffer is actually valid memory.
+        this.check_ptr_access_align(
+            buf,
+            Size::from_bytes(count),
+            Align::ONE,
+            CheckInAllocMsg::MemoryAccessTest,
+        )?;
+
+        // We cap the number of read bytes to the largest value that we are able to fit in both the
+        // host's and target's `isize`. This saves us from having to handle overflows later.
+        let count = count
+            .min(u64::try_from(this.machine_isize_max()).unwrap())
+            .min(u64::try_from(isize::MAX).unwrap());
+        let communicate = this.machine.communicate();
+
+        if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
+            trace!("read: FD mapped to {:?}", file_descriptor);
+            // We want to read at most `count` bytes. We are sure that `count` is not negative
+            // because it was a target's `usize`. Also we are sure that its smaller than
+            // `usize::MAX` because it is bounded by the host's `isize`.
+            let mut bytes = vec![0; usize::try_from(count).unwrap()];
+            // `File::read` never returns a value larger than `count`,
+            // so this cannot fail.
+            let result =
+                file_descriptor.read(communicate, &mut bytes)?.map(|c| i64::try_from(c).unwrap());
+
+            match result {
+                Ok(read_bytes) => {
+                    // If reading to `bytes` did not fail, we write those bytes to the buffer.
+                    this.write_bytes_ptr(buf, bytes)?;
+                    Ok(read_bytes)
+                }
+                Err(e) => {
+                    this.set_last_error_from_io_error(e.kind())?;
+                    Ok(-1)
+                }
+            }
+        } else {
+            trace!("read: FD not found");
+            this.handle_not_found()
+        }
+    }
+
+    fn write(
+        &mut self,
+        fd: i32,
+        buf: Pointer<Option<Provenance>>,
+        count: u64,
+    ) -> InterpResult<'tcx, i64> {
+        let this = self.eval_context_mut();
+
+        // Isolation check is done via `FileDescriptor` trait.
+
+        // Check that the *entire* buffer is actually valid memory.
+        this.check_ptr_access_align(
+            buf,
+            Size::from_bytes(count),
+            Align::ONE,
+            CheckInAllocMsg::MemoryAccessTest,
+        )?;
+
+        // We cap the number of written bytes to the largest value that we are able to fit in both the
+        // host's and target's `isize`. This saves us from having to handle overflows later.
+        let count = count
+            .min(u64::try_from(this.machine_isize_max()).unwrap())
+            .min(u64::try_from(isize::MAX).unwrap());
+        let communicate = this.machine.communicate();
+
+        if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
+            let bytes = this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(count))?;
+            let result =
+                file_descriptor.write(communicate, bytes)?.map(|c| i64::try_from(c).unwrap());
+            this.try_unwrap_io_result(result)
+        } else {
+            this.handle_not_found()
+        }
+    }
+
+    fn lseek64(
+        &mut self,
+        fd_op: &OpTy<'tcx, Provenance>,
+        offset_op: &OpTy<'tcx, Provenance>,
+        whence_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_mut();
+
+        // Isolation check is done via `FileDescriptor` trait.
+
+        let fd = this.read_scalar(fd_op)?.to_i32()?;
+        let offset = this.read_scalar(offset_op)?.to_i64()?;
+        let whence = this.read_scalar(whence_op)?.to_i32()?;
+
+        let seek_from = if whence == this.eval_libc_i32("SEEK_SET")? {
+            SeekFrom::Start(u64::try_from(offset).unwrap())
+        } else if whence == this.eval_libc_i32("SEEK_CUR")? {
+            SeekFrom::Current(offset)
+        } else if whence == this.eval_libc_i32("SEEK_END")? {
+            SeekFrom::End(offset)
+        } else {
+            let einval = this.eval_libc("EINVAL")?;
+            this.set_last_error(einval)?;
+            return Ok(Scalar::from_i64(-1));
+        };
+
+        let communicate = this.machine.communicate();
+        Ok(Scalar::from_i64(
+            if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
+                let result = file_descriptor
+                    .seek(communicate, seek_from)?
+                    .map(|offset| i64::try_from(offset).unwrap());
+                this.try_unwrap_io_result(result)?
+            } else {
+                this.handle_not_found()?
+            },
+        ))
+    }
+
+    fn unlink(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
+
+        // Reject if isolation is enabled.
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`unlink`", reject_with)?;
+            this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
+            return Ok(-1);
+        }
+
+        let result = remove_file(path).map(|_| 0);
+        this.try_unwrap_io_result(result)
+    }
+
+    fn symlink(
+        &mut self,
+        target_op: &OpTy<'tcx, Provenance>,
+        linkpath_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        #[cfg(unix)]
+        fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
+            std::os::unix::fs::symlink(src, dst)
+        }
+
+        #[cfg(windows)]
+        fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
+            use std::os::windows::fs;
+            if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }
+        }
+
+        let this = self.eval_context_mut();
+        let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
+        let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
+
+        // Reject if isolation is enabled.
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`symlink`", reject_with)?;
+            this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
+            return Ok(-1);
+        }
+
+        let result = create_link(&target, &linkpath).map(|_| 0);
+        this.try_unwrap_io_result(result)
+    }
+
+    fn macos_stat(
+        &mut self,
+        path_op: &OpTy<'tcx, Provenance>,
+        buf_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_mut();
+        this.assert_target_os("macos", "stat");
+
+        let path_scalar = this.read_pointer(path_op)?;
+        let path = this.read_path_from_c_str(path_scalar)?.into_owned();
+
+        // Reject if isolation is enabled.
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`stat`", reject_with)?;
+            let eacc = this.eval_libc("EACCES")?;
+            this.set_last_error(eacc)?;
+            return Ok(Scalar::from_i32(-1));
+        }
+
+        // `stat` always follows symlinks.
+        let metadata = match FileMetadata::from_path(this, &path, true)? {
+            Some(metadata) => metadata,
+            None => return Ok(Scalar::from_i32(-1)), // `FileMetadata` has set errno
+        };
+
+        Ok(Scalar::from_i32(this.macos_stat_write_buf(metadata, buf_op)?))
+    }
+
+    // `lstat` is used to get symlink metadata.
+    fn macos_lstat(
+        &mut self,
+        path_op: &OpTy<'tcx, Provenance>,
+        buf_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_mut();
+        this.assert_target_os("macos", "lstat");
+
+        let path_scalar = this.read_pointer(path_op)?;
+        let path = this.read_path_from_c_str(path_scalar)?.into_owned();
+
+        // Reject if isolation is enabled.
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`lstat`", reject_with)?;
+            let eacc = this.eval_libc("EACCES")?;
+            this.set_last_error(eacc)?;
+            return Ok(Scalar::from_i32(-1));
+        }
+
+        let metadata = match FileMetadata::from_path(this, &path, false)? {
+            Some(metadata) => metadata,
+            None => return Ok(Scalar::from_i32(-1)), // `FileMetadata` has set errno
+        };
+
+        Ok(Scalar::from_i32(this.macos_stat_write_buf(metadata, buf_op)?))
+    }
+
+    fn macos_fstat(
+        &mut self,
+        fd_op: &OpTy<'tcx, Provenance>,
+        buf_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_mut();
+
+        this.assert_target_os("macos", "fstat");
+
+        let fd = this.read_scalar(fd_op)?.to_i32()?;
+
+        // Reject if isolation is enabled.
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`fstat`", reject_with)?;
+            // Set error code as "EBADF" (bad fd)
+            return Ok(Scalar::from_i32(this.handle_not_found()?));
+        }
+
+        let metadata = match FileMetadata::from_fd(this, fd)? {
+            Some(metadata) => metadata,
+            None => return Ok(Scalar::from_i32(-1)),
+        };
+        Ok(Scalar::from_i32(this.macos_stat_write_buf(metadata, buf_op)?))
+    }
+
+    fn linux_statx(
+        &mut self,
+        dirfd_op: &OpTy<'tcx, Provenance>,    // Should be an `int`
+        pathname_op: &OpTy<'tcx, Provenance>, // Should be a `const char *`
+        flags_op: &OpTy<'tcx, Provenance>,    // Should be an `int`
+        mask_op: &OpTy<'tcx, Provenance>,     // Should be an `unsigned int`
+        statxbuf_op: &OpTy<'tcx, Provenance>, // Should be a `struct statx *`
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        this.assert_target_os("linux", "statx");
+
+        let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
+        let pathname_ptr = this.read_pointer(pathname_op)?;
+        let flags = this.read_scalar(flags_op)?.to_i32()?;
+        let _mask = this.read_scalar(mask_op)?.to_u32()?;
+        let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
+
+        // If the statxbuf or pathname pointers are null, the function fails with `EFAULT`.
+        if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
+            let efault = this.eval_libc("EFAULT")?;
+            this.set_last_error(efault)?;
+            return Ok(-1);
+        }
+
+        // Under normal circumstances, we would use `deref_operand(statxbuf_op)` to produce a
+        // proper `MemPlace` and then write the results of this function to it. However, the
+        // `syscall` function is untyped. This means that all the `statx` parameters are provided
+        // as `isize`s instead of having the proper types. Thus, we have to recover the layout of
+        // `statxbuf_op` by using the `libc::statx` struct type.
+        let statxbuf = {
+            // FIXME: This long path is required because `libc::statx` is an struct and also a
+            // function and `resolve_path` is returning the latter.
+            let statx_ty = this
+                .resolve_path(&["libc", "unix", "linux_like", "linux", "gnu", "statx"])
+                .ty(*this.tcx, ty::ParamEnv::reveal_all());
+            let statx_layout = this.layout_of(statx_ty)?;
+            MPlaceTy::from_aligned_ptr(statxbuf_ptr, statx_layout)
+        };
+
+        let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
+        // See <https://github.com/rust-lang/rust/pull/79196> for a discussion of argument sizes.
+        let empty_path_flag = flags & this.eval_libc("AT_EMPTY_PATH")?.to_i32()? != 0;
+        // We only support:
+        // * interpreting `path` as an absolute directory,
+        // * interpreting `path` as a path relative to `dirfd` when the latter is `AT_FDCWD`, or
+        // * interpreting `dirfd` as any file descriptor when `path` is empty and AT_EMPTY_PATH is
+        // set.
+        // Other behaviors cannot be tested from `libstd` and thus are not implemented. If you
+        // found this error, please open an issue reporting it.
+        if !(path.is_absolute()
+            || dirfd == this.eval_libc_i32("AT_FDCWD")?
+            || (path.as_os_str().is_empty() && empty_path_flag))
+        {
+            throw_unsup_format!(
+                "using statx is only supported with absolute paths, relative paths with the file \
+                descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
+                file descriptor"
+            )
+        }
+
+        // Reject if isolation is enabled.
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`statx`", reject_with)?;
+            let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD")? {
+                // since `path` is provided, either absolute or
+                // relative to CWD, `EACCES` is the most relevant.
+                this.eval_libc("EACCES")?
+            } else {
+                // `dirfd` is set to target file, and `path` is empty
+                // (or we would have hit the `throw_unsup_format`
+                // above). `EACCES` would violate the spec.
+                assert!(empty_path_flag);
+                this.eval_libc("EBADF")?
+            };
+            this.set_last_error(ecode)?;
+            return Ok(-1);
+        }
+
+        // the `_mask_op` paramter specifies the file information that the caller requested.
+        // However `statx` is allowed to return information that was not requested or to not
+        // return information that was requested. This `mask` represents the information we can
+        // actually provide for any target.
+        let mut mask =
+            this.eval_libc("STATX_TYPE")?.to_u32()? | this.eval_libc("STATX_SIZE")?.to_u32()?;
+
+        // If the `AT_SYMLINK_NOFOLLOW` flag is set, we query the file's metadata without following
+        // symbolic links.
+        let follow_symlink = flags & this.eval_libc("AT_SYMLINK_NOFOLLOW")?.to_i32()? == 0;
+
+        // If the path is empty, and the AT_EMPTY_PATH flag is set, we query the open file
+        // represented by dirfd, whether it's a directory or otherwise.
+        let metadata = if path.as_os_str().is_empty() && empty_path_flag {
+            FileMetadata::from_fd(this, dirfd)?
+        } else {
+            FileMetadata::from_path(this, &path, follow_symlink)?
+        };
+        let metadata = match metadata {
+            Some(metadata) => metadata,
+            None => return Ok(-1),
+        };
+
+        // The `mode` field specifies the type of the file and the permissions over the file for
+        // the owner, its group and other users. Given that we can only provide the file type
+        // without using platform specific methods, we only set the bits corresponding to the file
+        // type. This should be an `__u16` but `libc` provides its values as `u32`.
+        let mode: u16 = metadata
+            .mode
+            .to_u32()?
+            .try_into()
+            .unwrap_or_else(|_| bug!("libc contains bad value for constant"));
+
+        // We need to set the corresponding bits of `mask` if the access, creation and modification
+        // times were available. Otherwise we let them be zero.
+        let (access_sec, access_nsec) = metadata
+            .accessed
+            .map(|tup| {
+                mask |= this.eval_libc("STATX_ATIME")?.to_u32()?;
+                InterpResult::Ok(tup)
+            })
+            .unwrap_or(Ok((0, 0)))?;
+
+        let (created_sec, created_nsec) = metadata
+            .created
+            .map(|tup| {
+                mask |= this.eval_libc("STATX_BTIME")?.to_u32()?;
+                InterpResult::Ok(tup)
+            })
+            .unwrap_or(Ok((0, 0)))?;
+
+        let (modified_sec, modified_nsec) = metadata
+            .modified
+            .map(|tup| {
+                mask |= this.eval_libc("STATX_MTIME")?.to_u32()?;
+                InterpResult::Ok(tup)
+            })
+            .unwrap_or(Ok((0, 0)))?;
+
+        // Now we write everything to `statxbuf`. We write a zero for the unavailable fields.
+        this.write_int_fields_named(
+            &[
+                ("stx_mask", mask.into()),
+                ("stx_blksize", 0),
+                ("stx_attributes", 0),
+                ("stx_nlink", 0),
+                ("stx_uid", 0),
+                ("stx_gid", 0),
+                ("stx_mode", mode.into()),
+                ("stx_ino", 0),
+                ("stx_size", metadata.size.into()),
+                ("stx_blocks", 0),
+                ("stx_attributes_mask", 0),
+                ("stx_rdev_major", 0),
+                ("stx_rdev_minor", 0),
+                ("stx_dev_major", 0),
+                ("stx_dev_minor", 0),
+            ],
+            &statxbuf,
+        )?;
+        #[rustfmt::skip]
+        this.write_int_fields_named(
+            &[
+                ("tv_sec", access_sec.into()),
+                ("tv_nsec", access_nsec.into()),
+            ],
+            &this.mplace_field_named(&statxbuf, "stx_atime")?,
+        )?;
+        #[rustfmt::skip]
+        this.write_int_fields_named(
+            &[
+                ("tv_sec", created_sec.into()),
+                ("tv_nsec", created_nsec.into()),
+            ],
+            &this.mplace_field_named(&statxbuf, "stx_btime")?,
+        )?;
+        #[rustfmt::skip]
+        this.write_int_fields_named(
+            &[
+                ("tv_sec", 0.into()),
+                ("tv_nsec", 0.into()),
+            ],
+            &this.mplace_field_named(&statxbuf, "stx_ctime")?,
+        )?;
+        #[rustfmt::skip]
+        this.write_int_fields_named(
+            &[
+                ("tv_sec", modified_sec.into()),
+                ("tv_nsec", modified_nsec.into()),
+            ],
+            &this.mplace_field_named(&statxbuf, "stx_mtime")?,
+        )?;
+
+        Ok(0)
+    }
+
+    fn rename(
+        &mut self,
+        oldpath_op: &OpTy<'tcx, Provenance>,
+        newpath_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        let oldpath_ptr = this.read_pointer(oldpath_op)?;
+        let newpath_ptr = this.read_pointer(newpath_op)?;
+
+        if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
+            let efault = this.eval_libc("EFAULT")?;
+            this.set_last_error(efault)?;
+            return Ok(-1);
+        }
+
+        let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
+        let newpath = this.read_path_from_c_str(newpath_ptr)?;
+
+        // Reject if isolation is enabled.
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`rename`", reject_with)?;
+            this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
+            return Ok(-1);
+        }
+
+        let result = rename(oldpath, newpath).map(|_| 0);
+
+        this.try_unwrap_io_result(result)
+    }
+
+    fn mkdir(
+        &mut self,
+        path_op: &OpTy<'tcx, Provenance>,
+        mode_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        #[cfg_attr(not(unix), allow(unused_variables))]
+        let mode = if this.tcx.sess.target.os == "macos" {
+            u32::from(this.read_scalar(mode_op)?.to_u16()?)
+        } else {
+            this.read_scalar(mode_op)?.to_u32()?
+        };
+
+        let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
+
+        // Reject if isolation is enabled.
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`mkdir`", reject_with)?;
+            this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
+            return Ok(-1);
+        }
+
+        #[cfg_attr(not(unix), allow(unused_mut))]
+        let mut builder = DirBuilder::new();
+
+        // If the host supports it, forward on the mode of the directory
+        // (i.e. permission bits and the sticky bit)
+        #[cfg(unix)]
+        {
+            use std::os::unix::fs::DirBuilderExt;
+            builder.mode(mode);
+        }
+
+        let result = builder.create(path).map(|_| 0i32);
+
+        this.try_unwrap_io_result(result)
+    }
+
+    fn rmdir(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
+
+        // Reject if isolation is enabled.
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`rmdir`", reject_with)?;
+            this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
+            return Ok(-1);
+        }
+
+        let result = remove_dir(path).map(|_| 0i32);
+
+        this.try_unwrap_io_result(result)
+    }
+
+    fn opendir(
+        &mut self,
+        name_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_mut();
+
+        let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
+
+        // Reject if isolation is enabled.
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`opendir`", reject_with)?;
+            let eacc = this.eval_libc("EACCES")?;
+            this.set_last_error(eacc)?;
+            return Ok(Scalar::null_ptr(this));
+        }
+
+        let result = read_dir(name);
+
+        match result {
+            Ok(dir_iter) => {
+                let id = this.machine.dir_handler.insert_new(dir_iter);
+
+                // The libc API for opendir says that this method returns a pointer to an opaque
+                // structure, but we are returning an ID number. Thus, pass it as a scalar of
+                // pointer width.
+                Ok(Scalar::from_machine_usize(id, this))
+            }
+            Err(e) => {
+                this.set_last_error_from_io_error(e.kind())?;
+                Ok(Scalar::null_ptr(this))
+            }
+        }
+    }
+
+    fn linux_readdir64(
+        &mut self,
+        dirp_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_mut();
+
+        this.assert_target_os("linux", "readdir64");
+
+        let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
+
+        // Reject if isolation is enabled.
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`readdir`", reject_with)?;
+            let eacc = this.eval_libc("EBADF")?;
+            this.set_last_error(eacc)?;
+            return Ok(Scalar::null_ptr(this));
+        }
+
+        let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).ok_or_else(|| {
+            err_unsup_format!("the DIR pointer passed to readdir64 did not come from opendir")
+        })?;
+
+        let entry = match open_dir.read_dir.next() {
+            Some(Ok(dir_entry)) => {
+                // Write the directory entry into a newly allocated buffer.
+                // The name is written with write_bytes, while the rest of the
+                // dirent64 struct is written using write_int_fields.
+
+                // For reference:
+                // pub struct dirent64 {
+                //     pub d_ino: ino64_t,
+                //     pub d_off: off64_t,
+                //     pub d_reclen: c_ushort,
+                //     pub d_type: c_uchar,
+                //     pub d_name: [c_char; 256],
+                // }
+
+                let mut name = dir_entry.file_name(); // not a Path as there are no separators!
+                name.push("\0"); // Add a NUL terminator
+                let name_bytes = os_str_to_bytes(&name)?;
+                let name_len = u64::try_from(name_bytes.len()).unwrap();
+
+                let dirent64_layout = this.libc_ty_layout("dirent64")?;
+                let d_name_offset = dirent64_layout.fields.offset(4 /* d_name */).bytes();
+                let size = d_name_offset.checked_add(name_len).unwrap();
+
+                let entry =
+                    this.malloc(size, /*zero_init:*/ false, MiriMemoryKind::Runtime)?;
+
+                // If the host is a Unix system, fill in the inode number with its real value.
+                // If not, use 0 as a fallback value.
+                #[cfg(unix)]
+                let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
+                #[cfg(not(unix))]
+                let ino = 0u64;
+
+                let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
+
+                this.write_int_fields_named(
+                    &[
+                        ("d_ino", ino.into()),
+                        ("d_off", 0),
+                        ("d_reclen", size.into()),
+                        ("d_type", file_type.into()),
+                    ],
+                    &MPlaceTy::from_aligned_ptr(entry, dirent64_layout),
+                )?;
+
+                let name_ptr = entry.offset(Size::from_bytes(d_name_offset), this)?;
+                this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
+
+                entry
+            }
+            None => {
+                // end of stream: return NULL
+                Pointer::null()
+            }
+            Some(Err(e)) => {
+                this.set_last_error_from_io_error(e.kind())?;
+                Pointer::null()
+            }
+        };
+
+        let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).unwrap();
+        let old_entry = std::mem::replace(&mut open_dir.entry, entry);
+        this.free(old_entry, MiriMemoryKind::Runtime)?;
+
+        Ok(Scalar::from_maybe_pointer(entry, this))
+    }
+
+    fn macos_readdir_r(
+        &mut self,
+        dirp_op: &OpTy<'tcx, Provenance>,
+        entry_op: &OpTy<'tcx, Provenance>,
+        result_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_mut();
+
+        this.assert_target_os("macos", "readdir_r");
+
+        let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
+
+        // Reject if isolation is enabled.
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`readdir_r`", reject_with)?;
+            // Set error code as "EBADF" (bad fd)
+            return Ok(Scalar::from_i32(this.handle_not_found()?));
+        }
+
+        let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).ok_or_else(|| {
+            err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")
+        })?;
+        Ok(Scalar::from_i32(match open_dir.read_dir.next() {
+            Some(Ok(dir_entry)) => {
+                // Write into entry, write pointer to result, return 0 on success.
+                // The name is written with write_os_str_to_c_str, while the rest of the
+                // dirent struct is written using write_int_fields.
+
+                // For reference:
+                // pub struct dirent {
+                //     pub d_ino: u64,
+                //     pub d_seekoff: u64,
+                //     pub d_reclen: u16,
+                //     pub d_namlen: u16,
+                //     pub d_type: u8,
+                //     pub d_name: [c_char; 1024],
+                // }
+
+                let entry_place = this.deref_operand(entry_op)?;
+                let name_place = this.mplace_field(&entry_place, 5)?;
+
+                let file_name = dir_entry.file_name(); // not a Path as there are no separators!
+                let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str(
+                    &file_name,
+                    name_place.ptr,
+                    name_place.layout.size.bytes(),
+                )?;
+                let file_name_len = file_name_buf_len.checked_sub(1).unwrap();
+                if !name_fits {
+                    throw_unsup_format!(
+                        "a directory entry had a name too large to fit in libc::dirent"
+                    );
+                }
+
+                let entry_place = this.deref_operand(entry_op)?;
+
+                // If the host is a Unix system, fill in the inode number with its real value.
+                // If not, use 0 as a fallback value.
+                #[cfg(unix)]
+                let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
+                #[cfg(not(unix))]
+                let ino = 0u64;
+
+                let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
+
+                this.write_int_fields_named(
+                    &[
+                        ("d_ino", ino.into()),
+                        ("d_seekoff", 0),
+                        ("d_reclen", 0),
+                        ("d_namlen", file_name_len.into()),
+                        ("d_type", file_type.into()),
+                    ],
+                    &entry_place,
+                )?;
+
+                let result_place = this.deref_operand(result_op)?;
+                this.write_scalar(this.read_scalar(entry_op)?, &result_place.into())?;
+
+                0
+            }
+            None => {
+                // end of stream: return 0, assign *result=NULL
+                this.write_null(&this.deref_operand(result_op)?.into())?;
+                0
+            }
+            Some(Err(e)) =>
+                match e.raw_os_error() {
+                    // return positive error number on error
+                    Some(error) => error,
+                    None => {
+                        throw_unsup_format!(
+                            "the error {} couldn't be converted to a return value",
+                            e
+                        )
+                    }
+                },
+        }))
+    }
+
+    fn closedir(&mut self, dirp_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
+
+        // Reject if isolation is enabled.
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`closedir`", reject_with)?;
+            // Set error code as "EBADF" (bad fd)
+            return this.handle_not_found();
+        }
+
+        if let Some(open_dir) = this.machine.dir_handler.streams.remove(&dirp) {
+            this.free(open_dir.entry, MiriMemoryKind::Runtime)?;
+            drop(open_dir);
+            Ok(0)
+        } else {
+            this.handle_not_found()
+        }
+    }
+
+    fn ftruncate64(
+        &mut self,
+        fd_op: &OpTy<'tcx, Provenance>,
+        length_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_mut();
+
+        let fd = this.read_scalar(fd_op)?.to_i32()?;
+        let length = this.read_scalar(length_op)?.to_i64()?;
+
+        // Reject if isolation is enabled.
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`ftruncate64`", reject_with)?;
+            // Set error code as "EBADF" (bad fd)
+            return Ok(Scalar::from_i32(this.handle_not_found()?));
+        }
+
+        Ok(Scalar::from_i32(
+            if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
+                // FIXME: Support ftruncate64 for all FDs
+                let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
+                if *writable {
+                    if let Ok(length) = length.try_into() {
+                        let result = file.set_len(length);
+                        this.try_unwrap_io_result(result.map(|_| 0i32))?
+                    } else {
+                        let einval = this.eval_libc("EINVAL")?;
+                        this.set_last_error(einval)?;
+                        -1
+                    }
+                } else {
+                    // The file is not writable
+                    let einval = this.eval_libc("EINVAL")?;
+                    this.set_last_error(einval)?;
+                    -1
+                }
+            } else {
+                this.handle_not_found()?
+            },
+        ))
+    }
+
+    fn fsync(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
+        // On macOS, `fsync` (unlike `fcntl(F_FULLFSYNC)`) does not wait for the
+        // underlying disk to finish writing. In the interest of host compatibility,
+        // we conservatively implement this with `sync_all`, which
+        // *does* wait for the disk.
+
+        let this = self.eval_context_mut();
+
+        let fd = this.read_scalar(fd_op)?.to_i32()?;
+
+        // Reject if isolation is enabled.
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`fsync`", reject_with)?;
+            // Set error code as "EBADF" (bad fd)
+            return this.handle_not_found();
+        }
+
+        if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
+            // FIXME: Support fsync for all FDs
+            let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
+            let io_result = maybe_sync_file(file, *writable, File::sync_all);
+            this.try_unwrap_io_result(io_result)
+        } else {
+            this.handle_not_found()
+        }
+    }
+
+    fn fdatasync(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        let fd = this.read_scalar(fd_op)?.to_i32()?;
+
+        // Reject if isolation is enabled.
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`fdatasync`", reject_with)?;
+            // Set error code as "EBADF" (bad fd)
+            return this.handle_not_found();
+        }
+
+        if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
+            // FIXME: Support fdatasync for all FDs
+            let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
+            let io_result = maybe_sync_file(file, *writable, File::sync_data);
+            this.try_unwrap_io_result(io_result)
+        } else {
+            this.handle_not_found()
+        }
+    }
+
+    fn sync_file_range(
+        &mut self,
+        fd_op: &OpTy<'tcx, Provenance>,
+        offset_op: &OpTy<'tcx, Provenance>,
+        nbytes_op: &OpTy<'tcx, Provenance>,
+        flags_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_mut();
+
+        let fd = this.read_scalar(fd_op)?.to_i32()?;
+        let offset = this.read_scalar(offset_op)?.to_i64()?;
+        let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
+        let flags = this.read_scalar(flags_op)?.to_i32()?;
+
+        if offset < 0 || nbytes < 0 {
+            let einval = this.eval_libc("EINVAL")?;
+            this.set_last_error(einval)?;
+            return Ok(Scalar::from_i32(-1));
+        }
+        let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")?
+            | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")?
+            | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER")?;
+        if flags & allowed_flags != flags {
+            let einval = this.eval_libc("EINVAL")?;
+            this.set_last_error(einval)?;
+            return Ok(Scalar::from_i32(-1));
+        }
+
+        // Reject if isolation is enabled.
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`sync_file_range`", reject_with)?;
+            // Set error code as "EBADF" (bad fd)
+            return Ok(Scalar::from_i32(this.handle_not_found()?));
+        }
+
+        if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
+            // FIXME: Support sync_data_range for all FDs
+            let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
+            let io_result = maybe_sync_file(file, *writable, File::sync_data);
+            Ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
+        } else {
+            Ok(Scalar::from_i32(this.handle_not_found()?))
+        }
+    }
+
+    fn readlink(
+        &mut self,
+        pathname_op: &OpTy<'tcx, Provenance>,
+        buf_op: &OpTy<'tcx, Provenance>,
+        bufsize_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i64> {
+        let this = self.eval_context_mut();
+
+        let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
+        let buf = this.read_pointer(buf_op)?;
+        let bufsize = this.read_scalar(bufsize_op)?.to_machine_usize(this)?;
+
+        // Reject if isolation is enabled.
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`readlink`", reject_with)?;
+            let eacc = this.eval_libc("EACCES")?;
+            this.set_last_error(eacc)?;
+            return Ok(-1);
+        }
+
+        let result = std::fs::read_link(pathname);
+        match result {
+            Ok(resolved) => {
+                // 'readlink' truncates the resolved path if the provided buffer is not large
+                // enough, and does *not* add a null terminator. That means we cannot use the usual
+                // `write_path_to_c_str` and have to re-implement parts of it ourselves.
+                let resolved = this.convert_path_separator(
+                    Cow::Borrowed(resolved.as_ref()),
+                    crate::shims::os_str::PathConversion::HostToTarget,
+                );
+                let mut path_bytes = crate::shims::os_str::os_str_to_bytes(resolved.as_ref())?;
+                let bufsize: usize = bufsize.try_into().unwrap();
+                if path_bytes.len() > bufsize {
+                    path_bytes = &path_bytes[..bufsize]
+                }
+                this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
+                Ok(path_bytes.len().try_into().unwrap())
+            }
+            Err(e) => {
+                this.set_last_error_from_io_error(e.kind())?;
+                Ok(-1)
+            }
+        }
+    }
+
+    #[cfg_attr(not(unix), allow(unused))]
+    fn isatty(&mut self, miri_fd: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+        #[cfg(unix)]
+        if matches!(this.machine.isolated_op, IsolatedOp::Allow) {
+            let miri_fd = this.read_scalar(miri_fd)?.to_i32()?;
+            if let Some(host_fd) =
+                this.machine.file_handler.handles.get(&miri_fd).and_then(|fd| fd.as_unix_host_fd())
+            {
+                // "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"
+                // SAFETY: isatty has no preconditions
+                let is_tty = unsafe { libc::isatty(host_fd) };
+                if is_tty == 0 {
+                    let errno = std::io::Error::last_os_error()
+                        .raw_os_error()
+                        .map(Scalar::from_i32)
+                        .unwrap();
+                    this.set_last_error(errno)?;
+                }
+                return Ok(is_tty);
+            }
+        }
+        // We are attemping to use a Unix interface on a non-Unix platform, or we are on a Unix
+        // platform and the passed file descriptor is not open, or isolation is enabled
+        // FIXME: It should be possible to emulate this at least on Windows by using
+        // GetConsoleMode.
+        let enotty = this.eval_libc("ENOTTY")?;
+        this.set_last_error(enotty)?;
+        Ok(0)
+    }
+
+    fn realpath(
+        &mut self,
+        path_op: &OpTy<'tcx, Provenance>,
+        processed_path_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_mut();
+        this.assert_target_os_is_unix("realpath");
+
+        let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
+        let processed_ptr = this.read_pointer(processed_path_op)?;
+
+        // Reject if isolation is enabled.
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`realpath`", reject_with)?;
+            let eacc = this.eval_libc("EACCES")?;
+            this.set_last_error(eacc)?;
+            return Ok(Scalar::from_machine_usize(0, this));
+        }
+
+        let result = std::fs::canonicalize(pathname);
+        match result {
+            Ok(resolved) => {
+                let path_max = this
+                    .eval_libc_i32("PATH_MAX")?
+                    .try_into()
+                    .expect("PATH_MAX does not fit in u64");
+                let dest = if this.ptr_is_null(processed_ptr)? {
+                    // POSIX says behavior when passing a null pointer is implementation-defined,
+                    // but GNU/linux, freebsd, netbsd, bionic/android, and macos all treat a null pointer
+                    // similarly to:
+                    //
+                    // "If resolved_path is specified as NULL, then realpath() uses
+                    // malloc(3) to allocate a buffer of up to PATH_MAX bytes to hold
+                    // the resolved pathname, and returns a pointer to this buffer.  The
+                    // caller should deallocate this buffer using free(3)."
+                    // <https://man7.org/linux/man-pages/man3/realpath.3.html>
+                    this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
+                } else {
+                    let (wrote_path, _) =
+                        this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
+
+                    if !wrote_path {
+                        // Note that we do not explicitly handle `FILENAME_MAX`
+                        // (different from `PATH_MAX` above) as it is Linux-specific and
+                        // seems like a bit of a mess anyway: <https://eklitzke.org/path-max-is-tricky>.
+                        let enametoolong = this.eval_libc("ENAMETOOLONG")?;
+                        this.set_last_error(enametoolong)?;
+                        return Ok(Scalar::from_machine_usize(0, this));
+                    }
+                    processed_ptr
+                };
+
+                Ok(Scalar::from_maybe_pointer(dest, this))
+            }
+            Err(e) => {
+                this.set_last_error_from_io_error(e.kind())?;
+                Ok(Scalar::from_machine_usize(0, this))
+            }
+        }
+    }
+    fn mkstemp(&mut self, template_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
+        use rand::seq::SliceRandom;
+
+        // POSIX defines the template string.
+        const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
+
+        let this = self.eval_context_mut();
+        this.assert_target_os_is_unix("mkstemp");
+
+        // POSIX defines the maximum number of attempts before failure.
+        //
+        // `mkstemp()` relies on `tmpnam()` which in turn relies on `TMP_MAX`.
+        // POSIX says this about `TMP_MAX`:
+        // * Minimum number of unique filenames generated by `tmpnam()`.
+        // * Maximum number of times an application can call `tmpnam()` reliably.
+        //   * The value of `TMP_MAX` is at least 25.
+        //   * On XSI-conformant systems, the value of `TMP_MAX` is at least 10000.
+        // See <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdio.h.html>.
+        let max_attempts = this.eval_libc("TMP_MAX")?.to_u32()?;
+
+        // Get the raw bytes from the template -- as a byte slice, this is a string in the target
+        // (and the target is unix, so a byte slice is the right representation).
+        let template_ptr = this.read_pointer(template_op)?;
+        let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
+        let template_bytes = template.as_mut_slice();
+
+        // Reject if isolation is enabled.
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`mkstemp`", reject_with)?;
+            let eacc = this.eval_libc("EACCES")?;
+            this.set_last_error(eacc)?;
+            return Ok(-1);
+        }
+
+        // Get the bytes of the suffix we expect in _target_ encoding.
+        let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
+
+        // At this point we have one `&[u8]` that represents the template and one `&[u8]`
+        // that represents the expected suffix.
+
+        // Now we figure out the index of the slice we expect to contain the suffix.
+        let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
+        let end_pos = template_bytes.len();
+        let last_six_char_bytes = &template_bytes[start_pos..end_pos];
+
+        // If we don't find the suffix, it is an error.
+        if last_six_char_bytes != suffix_bytes {
+            let einval = this.eval_libc("EINVAL")?;
+            this.set_last_error(einval)?;
+            return Ok(-1);
+        }
+
+        // At this point we know we have 6 ASCII 'X' characters as a suffix.
+
+        // From <https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/sysdeps/posix/tempname.c#L175>
+        const SUBSTITUTIONS: &[char; 62] = &[
+            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
+            'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+            'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
+            'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+        ];
+
+        // The file is opened with specific options, which Rust does not expose in a portable way.
+        // So we use specific APIs depending on the host OS.
+        let mut fopts = OpenOptions::new();
+        fopts.read(true).write(true).create_new(true);
+
+        #[cfg(unix)]
+        {
+            use std::os::unix::fs::OpenOptionsExt;
+            fopts.mode(0o600);
+            // Do not allow others to read or modify this file.
+            fopts.custom_flags(libc::O_EXCL);
+        }
+        #[cfg(windows)]
+        {
+            use std::os::windows::fs::OpenOptionsExt;
+            // Do not allow others to read or modify this file.
+            fopts.share_mode(0);
+        }
+
+        // If the generated file already exists, we will try again `max_attempts` many times.
+        for _ in 0..max_attempts {
+            let rng = this.machine.rng.get_mut();
+
+            // Generate a random unique suffix.
+            let unique_suffix = SUBSTITUTIONS.choose_multiple(rng, 6).collect::<String>();
+
+            // Replace the template string with the random string.
+            template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
+
+            // Write the modified template back to the passed in pointer to maintain POSIX semantics.
+            this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
+
+            // To actually open the file, turn this into a host OsString.
+            let p = bytes_to_os_str(template_bytes)?.to_os_string();
+
+            let possibly_unique = std::env::temp_dir().join::<PathBuf>(p.into());
+
+            let file = fopts.open(&possibly_unique);
+
+            match file {
+                Ok(f) => {
+                    let fh = &mut this.machine.file_handler;
+                    let fd = fh.insert_fd(Box::new(FileHandle { file: f, writable: true }));
+                    return Ok(fd);
+                }
+                Err(e) =>
+                    match e.kind() {
+                        // If the random file already exists, keep trying.
+                        ErrorKind::AlreadyExists => continue,
+                        // Any other errors are returned to the caller.
+                        _ => {
+                            // "On error, -1 is returned, and errno is set to
+                            // indicate the error"
+                            this.set_last_error_from_io_error(e.kind())?;
+                            return Ok(-1);
+                        }
+                    },
+            }
+        }
+
+        // We ran out of attempts to create the file, return an error.
+        let eexist = this.eval_libc("EEXIST")?;
+        this.set_last_error(eexist)?;
+        Ok(-1)
+    }
+}
+
+/// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when
+/// `time` is Ok. Returns `None` if `time` is an error. Fails if `time` happens before the unix
+/// epoch.
+fn extract_sec_and_nsec<'tcx>(
+    time: std::io::Result<SystemTime>,
+) -> InterpResult<'tcx, Option<(u64, u32)>> {
+    time.ok()
+        .map(|time| {
+            let duration = system_time_to_duration(&time)?;
+            Ok((duration.as_secs(), duration.subsec_nanos()))
+        })
+        .transpose()
+}
+
+/// Stores a file's metadata in order to avoid code duplication in the different metadata related
+/// shims.
+struct FileMetadata {
+    mode: Scalar<Provenance>,
+    size: u64,
+    created: Option<(u64, u32)>,
+    accessed: Option<(u64, u32)>,
+    modified: Option<(u64, u32)>,
+}
+
+impl FileMetadata {
+    fn from_path<'tcx, 'mir>(
+        ecx: &mut MiriInterpCx<'mir, 'tcx>,
+        path: &Path,
+        follow_symlink: bool,
+    ) -> InterpResult<'tcx, Option<FileMetadata>> {
+        let metadata =
+            if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
+
+        FileMetadata::from_meta(ecx, metadata)
+    }
+
+    fn from_fd<'tcx, 'mir>(
+        ecx: &mut MiriInterpCx<'mir, 'tcx>,
+        fd: i32,
+    ) -> InterpResult<'tcx, Option<FileMetadata>> {
+        let option = ecx.machine.file_handler.handles.get(&fd);
+        let file = match option {
+            Some(file_descriptor) => &file_descriptor.as_file_handle()?.file,
+            None => return ecx.handle_not_found().map(|_: i32| None),
+        };
+        let metadata = file.metadata();
+
+        FileMetadata::from_meta(ecx, metadata)
+    }
+
+    fn from_meta<'tcx, 'mir>(
+        ecx: &mut MiriInterpCx<'mir, 'tcx>,
+        metadata: Result<std::fs::Metadata, std::io::Error>,
+    ) -> InterpResult<'tcx, Option<FileMetadata>> {
+        let metadata = match metadata {
+            Ok(metadata) => metadata,
+            Err(e) => {
+                ecx.set_last_error_from_io_error(e.kind())?;
+                return Ok(None);
+            }
+        };
+
+        let file_type = metadata.file_type();
+
+        let mode_name = if file_type.is_file() {
+            "S_IFREG"
+        } else if file_type.is_dir() {
+            "S_IFDIR"
+        } else {
+            "S_IFLNK"
+        };
+
+        let mode = ecx.eval_libc(mode_name)?;
+
+        let size = metadata.len();
+
+        let created = extract_sec_and_nsec(metadata.created())?;
+        let accessed = extract_sec_and_nsec(metadata.accessed())?;
+        let modified = extract_sec_and_nsec(metadata.modified())?;
+
+        // FIXME: Provide more fields using platform specific methods.
+        Ok(Some(FileMetadata { mode, size, created, accessed, modified }))
+    }
+}
diff --git a/src/tools/miri/src/shims/unix/linux/dlsym.rs b/src/tools/miri/src/shims/unix/linux/dlsym.rs
new file mode 100644
index 0000000000000..a96c14c142b25
--- /dev/null
+++ b/src/tools/miri/src/shims/unix/linux/dlsym.rs
@@ -0,0 +1,40 @@
+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
new file mode 100644
index 0000000000000..5d000f9d121d4
--- /dev/null
+++ b/src/tools/miri/src/shims/unix/linux/foreign_items.rs
@@ -0,0 +1,187 @@
+use rustc_span::Symbol;
+use rustc_target::spec::abi::Abi;
+
+use crate::*;
+use shims::foreign_items::EmulateByNameResult;
+use shims::unix::fs::EvalContextExt as _;
+use shims::unix::linux::sync::futex;
+use shims::unix::sync::EvalContextExt as _;
+use shims::unix::thread::EvalContextExt as _;
+
+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(
+        &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();
+
+        // See `fn emulate_foreign_item_by_name` in `shims/foreign_items.rs` for the general pattern.
+
+        match link_name.as_str() {
+            // errno
+            "__errno_location" => {
+                let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let errno_place = this.last_error_place()?;
+                this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?;
+            }
+
+            // File related shims (but also see "syscall" below for statx)
+            "readdir64" => {
+                let [dirp] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.linux_readdir64(dirp)?;
+                this.write_scalar(result, dest)?;
+            }
+            // Linux-only
+            "sync_file_range" => {
+                let [fd, offset, nbytes, flags] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.sync_file_range(fd, offset, nbytes, flags)?;
+                this.write_scalar(result, dest)?;
+            }
+
+            // Time related shims
+            "clock_gettime" => {
+                // This is a POSIX function but it has only been tested on linux.
+                let [clk_id, tp] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.clock_gettime(clk_id, tp)?;
+                this.write_scalar(result, dest)?;
+            }
+
+            // Threading
+            "pthread_condattr_setclock" => {
+                let [attr, clock_id] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.pthread_condattr_setclock(attr, clock_id)?;
+                this.write_scalar(result, dest)?;
+            }
+            "pthread_condattr_getclock" => {
+                let [attr, clock_id] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.pthread_condattr_getclock(attr, clock_id)?;
+                this.write_scalar(result, dest)?;
+            }
+            "pthread_setname_np" => {
+                let [thread, name] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let res =
+                    this.pthread_setname_np(this.read_scalar(thread)?, this.read_scalar(name)?)?;
+                this.write_scalar(res, dest)?;
+            }
+
+            // Dynamically invoked syscalls
+            "syscall" => {
+                // We do not use `check_shim` here because `syscall` is variadic. The argument
+                // count is checked bellow.
+                this.check_abi_and_shim_symbol_clash(abi, Abi::C { unwind: false }, link_name)?;
+                // The syscall variadic function is legal to call with more arguments than needed,
+                // extra arguments are simply ignored. The important check is that when we use an
+                // argument, we have to also check all arguments *before* it to ensure that they
+                // have the right type.
+
+                let sys_getrandom = this.eval_libc("SYS_getrandom")?.to_machine_usize(this)?;
+
+                let sys_statx = this.eval_libc("SYS_statx")?.to_machine_usize(this)?;
+
+                let sys_futex = this.eval_libc("SYS_futex")?.to_machine_usize(this)?;
+
+                if args.is_empty() {
+                    throw_ub_format!(
+                        "incorrect number of arguments for syscall: got 0, expected at least 1"
+                    );
+                }
+                match this.read_scalar(&args[0])?.to_machine_usize(this)? {
+                    // `libc::syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), GRND_NONBLOCK)`
+                    // is called if a `HashMap` is created the regular way (e.g. HashMap<K, V>).
+                    id if id == sys_getrandom => {
+                        // The first argument is the syscall id, so skip over it.
+                        if args.len() < 4 {
+                            throw_ub_format!(
+                                "incorrect number of arguments for `getrandom` syscall: got {}, expected at least 4",
+                                args.len()
+                            );
+                        }
+                        getrandom(this, &args[1], &args[2], &args[3], dest)?;
+                    }
+                    // `statx` is used by `libstd` to retrieve metadata information on `linux`
+                    // instead of using `stat`,`lstat` or `fstat` as on `macos`.
+                    id if id == sys_statx => {
+                        // The first argument is the syscall id, so skip over it.
+                        if args.len() < 6 {
+                            throw_ub_format!(
+                                "incorrect number of arguments for `statx` syscall: got {}, expected at least 6",
+                                args.len()
+                            );
+                        }
+                        let result =
+                            this.linux_statx(&args[1], &args[2], &args[3], &args[4], &args[5])?;
+                        this.write_scalar(Scalar::from_machine_isize(result.into(), this), dest)?;
+                    }
+                    // `futex` is used by some synchonization primitives.
+                    id if id == sys_futex => {
+                        futex(this, &args[1..], dest)?;
+                    }
+                    id => {
+                        this.handle_unsupported(format!("can't execute syscall with ID {}", id))?;
+                        return Ok(EmulateByNameResult::AlreadyJumped);
+                    }
+                }
+            }
+
+            // Miscelanneous
+            "getrandom" => {
+                let [ptr, len, flags] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                getrandom(this, ptr, len, flags, dest)?;
+            }
+            "sched_getaffinity" => {
+                let [pid, cpusetsize, mask] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                this.read_scalar(pid)?.to_i32()?;
+                this.read_scalar(cpusetsize)?.to_machine_usize(this)?;
+                this.deref_operand(mask)?;
+                // FIXME: we just return an error; `num_cpus` then falls back to `sysconf`.
+                let einval = this.eval_libc("EINVAL")?;
+                this.set_last_error(einval)?;
+                this.write_scalar(Scalar::from_i32(-1), 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.
+            "pthread_getattr_np" if this.frame_in_std() => {
+                let [_thread, _attr] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                this.write_null(dest)?;
+            }
+
+            _ => return Ok(EmulateByNameResult::NotSupported),
+        };
+
+        Ok(EmulateByNameResult::NeedsJumping)
+    }
+}
+
+// Shims the linux `getrandom` syscall.
+fn getrandom<'tcx>(
+    this: &mut MiriInterpCx<'_, 'tcx>,
+    ptr: &OpTy<'tcx, Provenance>,
+    len: &OpTy<'tcx, Provenance>,
+    flags: &OpTy<'tcx, Provenance>,
+    dest: &PlaceTy<'tcx, Provenance>,
+) -> InterpResult<'tcx> {
+    let ptr = this.read_pointer(ptr)?;
+    let len = this.read_scalar(len)?.to_machine_usize(this)?;
+
+    // The only supported flags are GRND_RANDOM and GRND_NONBLOCK,
+    // neither of which have any effect on our current PRNG.
+    // See <https://github.com/rust-lang/rust/pull/79196> for a discussion of argument sizes.
+    let _flags = this.read_scalar(flags)?.to_i32();
+
+    this.gen_random(ptr, len)?;
+    this.write_scalar(Scalar::from_machine_usize(len, this), dest)?;
+    Ok(())
+}
diff --git a/src/tools/miri/src/shims/unix/linux/mod.rs b/src/tools/miri/src/shims/unix/linux/mod.rs
new file mode 100644
index 0000000000000..498eb57c57fe0
--- /dev/null
+++ b/src/tools/miri/src/shims/unix/linux/mod.rs
@@ -0,0 +1,3 @@
+pub mod dlsym;
+pub mod foreign_items;
+pub mod sync;
diff --git a/src/tools/miri/src/shims/unix/linux/sync.rs b/src/tools/miri/src/shims/unix/linux/sync.rs
new file mode 100644
index 0000000000000..5a6ce28d25c9f
--- /dev/null
+++ b/src/tools/miri/src/shims/unix/linux/sync.rs
@@ -0,0 +1,261 @@
+use crate::concurrency::thread::Time;
+use crate::*;
+use rustc_target::abi::{Align, Size};
+use std::time::SystemTime;
+
+/// Implementation of the SYS_futex syscall.
+/// `args` is the arguments *after* the syscall number.
+pub fn futex<'tcx>(
+    this: &mut MiriInterpCx<'_, 'tcx>,
+    args: &[OpTy<'tcx, Provenance>],
+    dest: &PlaceTy<'tcx, Provenance>,
+) -> InterpResult<'tcx> {
+    // The amount of arguments used depends on the type of futex operation.
+    // The full futex syscall takes six arguments (excluding the syscall
+    // number), which is also the maximum amount of arguments a linux syscall
+    // can take on most architectures.
+    // However, not all futex operations use all six arguments. The unused ones
+    // may or may not be left out from the `syscall()` call.
+    // Therefore we don't use `check_arg_count` here, but only check for the
+    // number of arguments to fall within a range.
+    if args.len() < 3 {
+        throw_ub_format!(
+            "incorrect number of arguments for `futex` syscall: got {}, expected at least 3",
+            args.len()
+        );
+    }
+
+    // The first three arguments (after the syscall number itself) are the same to all futex operations:
+    //     (int *addr, int op, int val).
+    // We checked above that these definitely exist.
+    let addr = this.read_immediate(&args[0])?;
+    let op = this.read_scalar(&args[1])?.to_i32()?;
+    let val = this.read_scalar(&args[2])?.to_i32()?;
+
+    let thread = this.get_active_thread();
+    let addr_scalar = addr.to_scalar();
+    let addr_usize = addr_scalar.to_machine_usize(this)?;
+
+    let futex_private = this.eval_libc_i32("FUTEX_PRIVATE_FLAG")?;
+    let futex_wait = this.eval_libc_i32("FUTEX_WAIT")?;
+    let futex_wait_bitset = this.eval_libc_i32("FUTEX_WAIT_BITSET")?;
+    let futex_wake = this.eval_libc_i32("FUTEX_WAKE")?;
+    let futex_wake_bitset = this.eval_libc_i32("FUTEX_WAKE_BITSET")?;
+    let futex_realtime = this.eval_libc_i32("FUTEX_CLOCK_REALTIME")?;
+
+    // FUTEX_PRIVATE enables an optimization that stops it from working across processes.
+    // Miri doesn't support that anyway, so we ignore that flag.
+    match op & !futex_private {
+        // FUTEX_WAIT: (int *addr, int op = FUTEX_WAIT, int val, const timespec *timeout)
+        // Blocks the thread if *addr still equals val. Wakes up when FUTEX_WAKE is called on the same address,
+        // or *timeout expires. `timeout == null` for an infinite timeout.
+        //
+        // FUTEX_WAIT_BITSET: (int *addr, int op = FUTEX_WAIT_BITSET, int val, const timespec *timeout, int *_ignored, unsigned int bitset)
+        // This is identical to FUTEX_WAIT, except:
+        //  - The timeout is absolute rather than relative.
+        //  - You can specify the bitset to selecting what WAKE operations to respond to.
+        op if op & !futex_realtime == futex_wait || op & !futex_realtime == futex_wait_bitset => {
+            let wait_bitset = op & !futex_realtime == futex_wait_bitset;
+
+            let bitset = if wait_bitset {
+                if args.len() < 6 {
+                    throw_ub_format!(
+                        "incorrect number of arguments for `futex` syscall with `op=FUTEX_WAIT_BITSET`: got {}, expected at least 6",
+                        args.len()
+                    );
+                }
+                let _timeout = this.read_pointer(&args[3])?;
+                let _uaddr2 = this.read_pointer(&args[4])?;
+                this.read_scalar(&args[5])?.to_u32()?
+            } else {
+                if args.len() < 4 {
+                    throw_ub_format!(
+                        "incorrect number of arguments for `futex` syscall with `op=FUTEX_WAIT`: got {}, expected at least 4",
+                        args.len()
+                    );
+                }
+                u32::MAX
+            };
+
+            if bitset == 0 {
+                let einval = this.eval_libc("EINVAL")?;
+                this.set_last_error(einval)?;
+                this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?;
+                return Ok(());
+            }
+
+            // `deref_operand` but not actually dereferencing the ptr yet (it might be NULL!).
+            let timeout = this.ref_to_mplace(&this.read_immediate(&args[3])?)?;
+            let timeout_time = if this.ptr_is_null(timeout.ptr)? {
+                None
+            } else {
+                this.check_no_isolation(
+                    "`futex` syscall with `op=FUTEX_WAIT` and non-null timeout",
+                )?;
+                let duration = match this.read_timespec(&timeout)? {
+                    Some(duration) => duration,
+                    None => {
+                        let einval = this.eval_libc("EINVAL")?;
+                        this.set_last_error(einval)?;
+                        this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?;
+                        return Ok(());
+                    }
+                };
+                Some(if wait_bitset {
+                    // FUTEX_WAIT_BITSET uses an absolute timestamp.
+                    if op & futex_realtime != 0 {
+                        Time::RealTime(SystemTime::UNIX_EPOCH.checked_add(duration).unwrap())
+                    } else {
+                        Time::Monotonic(this.machine.clock.anchor().checked_add(duration).unwrap())
+                    }
+                } else {
+                    // FUTEX_WAIT uses a relative timestamp.
+                    if op & futex_realtime != 0 {
+                        Time::RealTime(SystemTime::now().checked_add(duration).unwrap())
+                    } else {
+                        Time::Monotonic(this.machine.clock.now().checked_add(duration).unwrap())
+                    }
+                })
+            };
+            // Check the pointer for alignment and validity.
+            // The API requires `addr` to be a 4-byte aligned pointer, and will
+            // use the 4 bytes at the given address as an (atomic) i32.
+            this.check_ptr_access_align(
+                addr_scalar.to_pointer(this)?,
+                Size::from_bytes(4),
+                Align::from_bytes(4).unwrap(),
+                CheckInAllocMsg::MemoryAccessTest,
+            )?;
+            // There may be a concurrent thread changing the value of addr
+            // and then invoking the FUTEX_WAKE syscall. It is critical that the
+            // effects of this and the other thread are correctly observed,
+            // otherwise we will deadlock.
+            //
+            // There are two scenarios to consider:
+            // 1. If we (FUTEX_WAIT) execute first, we'll push ourselves into
+            //    the waiters queue and go to sleep. They (addr write & FUTEX_WAKE)
+            //    will see us in the queue and wake us up.
+            // 2. If they (addr write & FUTEX_WAKE) execute first, we must observe
+            //    addr's new value. If we see an outdated value that happens to equal
+            //    the expected val, then we'll put ourselves to sleep with no one to wake us
+            //    up, so we end up with a deadlock. This is prevented by having a SeqCst
+            //    fence inside FUTEX_WAKE syscall, and another SeqCst fence
+            //    below, the atomic read on addr after the SeqCst fence is guaranteed
+            //    not to see any value older than the addr write immediately before
+            //    calling FUTEX_WAKE. We'll see futex_val != val and return without
+            //    sleeping.
+            //
+            //    Note that the fences do not create any happens-before relationship.
+            //    The read sees the write immediately before the fence not because
+            //    one happens after the other, but is instead due to a guarantee unique
+            //    to SeqCst fences that restricts what an atomic read placed AFTER the
+            //    fence can see. The read still has to be atomic, otherwise it's a data
+            //    race. This guarantee cannot be achieved with acquire-release fences
+            //    since they only talk about reads placed BEFORE a fence - and places
+            //    no restrictions on what the read itself can see, only that there is
+            //    a happens-before between the fences IF the read happens to see the
+            //    right value. This is useless to us, since we need the read itself
+            //    to see an up-to-date value.
+            //
+            // The above case distinction is valid since both FUTEX_WAIT and FUTEX_WAKE
+            // contain a SeqCst fence, therefore inducting a total order between the operations.
+            // It is also critical that the fence, the atomic load, and the comparison in FUTEX_WAIT
+            // altogether happen atomically. If the other thread's fence in FUTEX_WAKE
+            // gets interleaved after our fence, then we lose the guarantee on the
+            // atomic load being up-to-date; if the other thread's write on addr and FUTEX_WAKE
+            // call are interleaved after the load but before the comparison, then we get a TOCTOU
+            // race condition, and go to sleep thinking the other thread will wake us up,
+            // even though they have already finished.
+            //
+            // Thankfully, preemptions cannot happen inside a Miri shim, so we do not need to
+            // do anything special to guarantee fence-load-comparison atomicity.
+            this.atomic_fence(AtomicFenceOrd::SeqCst)?;
+            // Read an `i32` through the pointer, regardless of any wrapper types.
+            // It's not uncommon for `addr` to be passed as another type than `*mut i32`, such as `*const AtomicI32`.
+            let futex_val = this
+                .read_scalar_at_offset_atomic(
+                    &addr.into(),
+                    0,
+                    this.machine.layouts.i32,
+                    AtomicReadOrd::Relaxed,
+                )?
+                .to_i32()?;
+            if val == futex_val {
+                // The value still matches, so we block the thread make it wait for FUTEX_WAKE.
+                this.block_thread(thread);
+                this.futex_wait(addr_usize, thread, bitset);
+                // Succesfully waking up from FUTEX_WAIT always returns zero.
+                this.write_scalar(Scalar::from_machine_isize(0, this), dest)?;
+                // Register a timeout callback if a timeout was specified.
+                // This callback will override the return value when the timeout triggers.
+                if let Some(timeout_time) = timeout_time {
+                    let dest = dest.clone();
+                    this.register_timeout_callback(
+                        thread,
+                        timeout_time,
+                        Box::new(move |this| {
+                            this.unblock_thread(thread);
+                            this.futex_remove_waiter(addr_usize, thread);
+                            let etimedout = this.eval_libc("ETIMEDOUT")?;
+                            this.set_last_error(etimedout)?;
+                            this.write_scalar(Scalar::from_machine_isize(-1, this), &dest)?;
+                            Ok(())
+                        }),
+                    );
+                }
+            } else {
+                // The futex value doesn't match the expected value, so we return failure
+                // right away without sleeping: -1 and errno set to EAGAIN.
+                let eagain = this.eval_libc("EAGAIN")?;
+                this.set_last_error(eagain)?;
+                this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?;
+            }
+        }
+        // FUTEX_WAKE: (int *addr, int op = FUTEX_WAKE, int val)
+        // Wakes at most `val` threads waiting on the futex at `addr`.
+        // Returns the amount of threads woken up.
+        // Does not access the futex value at *addr.
+        // FUTEX_WAKE_BITSET: (int *addr, int op = FUTEX_WAKE, int val, const timespect *_unused, int *_unused, unsigned int bitset)
+        // Same as FUTEX_WAKE, but allows you to specify a bitset to select which threads to wake up.
+        op if op == futex_wake || op == futex_wake_bitset => {
+            let bitset = if op == futex_wake_bitset {
+                if args.len() < 6 {
+                    throw_ub_format!(
+                        "incorrect number of arguments for `futex` syscall with `op=FUTEX_WAKE_BITSET`: got {}, expected at least 6",
+                        args.len()
+                    );
+                }
+                let _timeout = this.read_pointer(&args[3])?;
+                let _uaddr2 = this.read_pointer(&args[4])?;
+                this.read_scalar(&args[5])?.to_u32()?
+            } else {
+                u32::MAX
+            };
+            if bitset == 0 {
+                let einval = this.eval_libc("EINVAL")?;
+                this.set_last_error(einval)?;
+                this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?;
+                return Ok(());
+            }
+            // Together with the SeqCst fence in futex_wait, this makes sure that futex_wait
+            // will see the latest value on addr which could be changed by our caller
+            // before doing the syscall.
+            this.atomic_fence(AtomicFenceOrd::SeqCst)?;
+            let mut n = 0;
+            #[allow(clippy::integer_arithmetic)]
+            for _ in 0..val {
+                if let Some(thread) = this.futex_wake(addr_usize, bitset) {
+                    this.unblock_thread(thread);
+                    this.unregister_timeout_callback_if_exists(thread);
+                    n += 1;
+                } else {
+                    break;
+                }
+            }
+            this.write_scalar(Scalar::from_machine_isize(n, this), dest)?;
+        }
+        op => throw_unsup_format!("Miri does not support `futex` syscall with op={}", op),
+    }
+
+    Ok(())
+}
diff --git a/src/tools/miri/src/shims/unix/macos/dlsym.rs b/src/tools/miri/src/shims/unix/macos/dlsym.rs
new file mode 100644
index 0000000000000..18804b45efca9
--- /dev/null
+++ b/src/tools/miri/src/shims/unix/macos/dlsym.rs
@@ -0,0 +1,52 @@
+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_scalar(len)?.to_machine_usize(this)?;
+                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
new file mode 100644
index 0000000000000..38d791fba98a4
--- /dev/null
+++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs
@@ -0,0 +1,197 @@
+use rustc_span::Symbol;
+use rustc_target::spec::abi::Abi;
+
+use crate::*;
+use shims::foreign_items::EmulateByNameResult;
+use shims::unix::fs::EvalContextExt as _;
+use shims::unix::thread::EvalContextExt as _;
+
+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(
+        &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();
+
+        // See `fn emulate_foreign_item_by_name` in `shims/foreign_items.rs` for the general pattern.
+
+        match link_name.as_str() {
+            // errno
+            "__error" => {
+                let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let errno_place = this.last_error_place()?;
+                this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?;
+            }
+
+            // File related shims
+            "close$NOCANCEL" => {
+                let [result] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.close(result)?;
+                this.write_scalar(result, dest)?;
+            }
+            "stat" | "stat64" | "stat$INODE64" => {
+                let [path, buf] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.macos_stat(path, buf)?;
+                this.write_scalar(result, dest)?;
+            }
+            "lstat" | "lstat64" | "lstat$INODE64" => {
+                let [path, buf] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.macos_lstat(path, buf)?;
+                this.write_scalar(result, dest)?;
+            }
+            "fstat" | "fstat64" | "fstat$INODE64" => {
+                let [fd, buf] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.macos_fstat(fd, buf)?;
+                this.write_scalar(result, dest)?;
+            }
+            "opendir$INODE64" => {
+                let [name] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.opendir(name)?;
+                this.write_scalar(result, dest)?;
+            }
+            "readdir_r" | "readdir_r$INODE64" => {
+                let [dirp, entry, result] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.macos_readdir_r(dirp, entry, result)?;
+                this.write_scalar(result, dest)?;
+            }
+            "lseek" => {
+                let [fd, offset, whence] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                // macOS is 64bit-only, so this is lseek64
+                let result = this.lseek64(fd, offset, whence)?;
+                this.write_scalar(result, dest)?;
+            }
+            "ftruncate" => {
+                let [fd, length] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                // macOS is 64bit-only, so this is ftruncate64
+                let result = this.ftruncate64(fd, length)?;
+                this.write_scalar(result, dest)?;
+            }
+            "realpath$DARWIN_EXTSN" => {
+                let [path, resolved_path] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.realpath(path, resolved_path)?;
+                this.write_scalar(result, dest)?;
+            }
+
+            // Environment related shims
+            "_NSGetEnviron" => {
+                let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                this.write_pointer(
+                    this.machine.env_vars.environ.expect("machine must be initialized").ptr,
+                    dest,
+                )?;
+            }
+
+            // Time related shims
+            "mach_absolute_time" => {
+                let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.mach_absolute_time()?;
+                this.write_scalar(result, dest)?;
+            }
+
+            "mach_timebase_info" => {
+                let [info] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let result = this.mach_timebase_info(info)?;
+                this.write_scalar(result, dest)?;
+            }
+
+            // Access to command-line arguments
+            "_NSGetArgc" => {
+                let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                this.write_pointer(
+                    this.machine.argc.expect("machine must be initialized").ptr,
+                    dest,
+                )?;
+            }
+            "_NSGetArgv" => {
+                let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                this.write_pointer(
+                    this.machine.argv.expect("machine must be initialized").ptr,
+                    dest,
+                )?;
+            }
+            "_NSGetExecutablePath" => {
+                let [buf, bufsize] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                this.check_no_isolation("`_NSGetExecutablePath`")?;
+
+                let buf_ptr = this.read_pointer(buf)?;
+                let bufsize = this.deref_operand(bufsize)?;
+
+                // Using the host current_exe is a bit off, but consistent with Linux
+                // (where stdlib reads /proc/self/exe).
+                let path = std::env::current_exe().unwrap();
+                let (written, size_needed) = this.write_path_to_c_str(
+                    &path,
+                    buf_ptr,
+                    this.read_scalar(&bufsize.into())?.to_u32()?.into(),
+                )?;
+
+                if written {
+                    this.write_null(dest)?;
+                } else {
+                    this.write_scalar(
+                        Scalar::from_u32(size_needed.try_into().unwrap()),
+                        &bufsize.into(),
+                    )?;
+                    this.write_int(-1, dest)?;
+                }
+            }
+
+            // Thread-local storage
+            "_tlv_atexit" => {
+                let [dtor, data] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let dtor = this.read_pointer(dtor)?;
+                let dtor = this.get_ptr_fn(dtor)?.as_instance()?;
+                let data = this.read_scalar(data)?;
+                let active_thread = this.get_active_thread();
+                this.machine.tls.set_macos_thread_dtor(active_thread, dtor, data)?;
+            }
+
+            // Querying system information
+            "pthread_get_stackaddr_np" => {
+                let [thread] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                this.read_scalar(thread)?.to_machine_usize(this)?;
+                let stack_addr = Scalar::from_uint(STACK_ADDR, this.pointer_size());
+                this.write_scalar(stack_addr, dest)?;
+            }
+            "pthread_get_stacksize_np" => {
+                let [thread] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                this.read_scalar(thread)?.to_machine_usize(this)?;
+                let stack_size = Scalar::from_uint(STACK_SIZE, this.pointer_size());
+                this.write_scalar(stack_size, dest)?;
+            }
+
+            // Threading
+            "pthread_setname_np" => {
+                let [name] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let thread = this.pthread_self()?;
+                this.pthread_setname_np(thread, this.read_scalar(name)?)?;
+            }
+
+            // 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.
+            "mmap" if this.frame_in_std() => {
+                // This is a horrible hack, but since the guard page mechanism calls mmap and expects a particular return value, we just give it that value.
+                let [addr, _, _, _, _, _] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let addr = this.read_scalar(addr)?;
+                this.write_scalar(addr, dest)?;
+            }
+
+            _ => return Ok(EmulateByNameResult::NotSupported),
+        };
+
+        Ok(EmulateByNameResult::NeedsJumping)
+    }
+}
diff --git a/src/tools/miri/src/shims/unix/macos/mod.rs b/src/tools/miri/src/shims/unix/macos/mod.rs
new file mode 100644
index 0000000000000..434f5f30b5a56
--- /dev/null
+++ b/src/tools/miri/src/shims/unix/macos/mod.rs
@@ -0,0 +1,2 @@
+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
new file mode 100644
index 0000000000000..6fefb054f3c04
--- /dev/null
+++ b/src/tools/miri/src/shims/unix/mod.rs
@@ -0,0 +1,16 @@
+pub mod dlsym;
+pub mod foreign_items;
+
+mod fs;
+mod sync;
+mod thread;
+
+mod android;
+mod freebsd;
+mod linux;
+mod macos;
+
+pub use fs::{DirHandler, FileHandler};
+
+// Make up some constants.
+const UID: u32 = 1000;
diff --git a/src/tools/miri/src/shims/unix/sync.rs b/src/tools/miri/src/shims/unix/sync.rs
new file mode 100644
index 0000000000000..2e972a27ffebe
--- /dev/null
+++ b/src/tools/miri/src/shims/unix/sync.rs
@@ -0,0 +1,907 @@
+use std::time::SystemTime;
+
+use rustc_hir::LangItem;
+use rustc_middle::ty::{layout::TyAndLayout, query::TyCtxtAt, Ty};
+
+use crate::concurrency::thread::Time;
+use crate::*;
+
+// pthread_mutexattr_t is either 4 or 8 bytes, depending on the platform.
+
+// Our chosen memory layout for emulation (does not have to match the platform layout!):
+// store an i32 in the first four bytes equal to the corresponding libc mutex kind constant
+// (e.g. PTHREAD_MUTEX_NORMAL).
+
+/// A flag that allows to distinguish `PTHREAD_MUTEX_NORMAL` from
+/// `PTHREAD_MUTEX_DEFAULT`. Since in `glibc` they have the same numeric values,
+/// but different behaviour, we need a way to distinguish them. We do this by
+/// setting this bit flag to the `PTHREAD_MUTEX_NORMAL` mutexes. See the comment
+/// in `pthread_mutexattr_settype` function.
+const PTHREAD_MUTEX_NORMAL_FLAG: i32 = 0x8000000;
+
+fn is_mutex_kind_default<'mir, 'tcx: 'mir>(
+    ecx: &mut MiriInterpCx<'mir, 'tcx>,
+    kind: Scalar<Provenance>,
+) -> InterpResult<'tcx, bool> {
+    Ok(kind == ecx.eval_libc("PTHREAD_MUTEX_DEFAULT")?)
+}
+
+fn is_mutex_kind_normal<'mir, 'tcx: 'mir>(
+    ecx: &mut MiriInterpCx<'mir, 'tcx>,
+    kind: Scalar<Provenance>,
+) -> InterpResult<'tcx, bool> {
+    let kind = kind.to_i32()?;
+    let mutex_normal_kind = ecx.eval_libc("PTHREAD_MUTEX_NORMAL")?.to_i32()?;
+    Ok(kind == (mutex_normal_kind | PTHREAD_MUTEX_NORMAL_FLAG))
+}
+
+fn mutexattr_get_kind<'mir, 'tcx: 'mir>(
+    ecx: &MiriInterpCx<'mir, 'tcx>,
+    attr_op: &OpTy<'tcx, Provenance>,
+) -> InterpResult<'tcx, Scalar<Provenance>> {
+    ecx.read_scalar_at_offset(attr_op, 0, ecx.machine.layouts.i32)
+}
+
+fn mutexattr_set_kind<'mir, 'tcx: 'mir>(
+    ecx: &mut MiriInterpCx<'mir, 'tcx>,
+    attr_op: &OpTy<'tcx, Provenance>,
+    kind: impl Into<Scalar<Provenance>>,
+) -> InterpResult<'tcx, ()> {
+    ecx.write_scalar_at_offset(attr_op, 0, kind, layout_of_maybe_uninit(ecx.tcx, ecx.tcx.types.i32))
+}
+
+// pthread_mutex_t is between 24 and 48 bytes, depending on the platform.
+
+// Our chosen memory layout for the emulated mutex (does not have to match the platform layout!):
+// bytes 0-3: reserved for signature on macOS
+// (need to avoid this because it is set by static initializer macros)
+// bytes 4-7: mutex id as u32 or 0 if id is not assigned yet.
+// bytes 12-15 or 16-19 (depending on platform): mutex kind, as an i32
+// (the kind has to be at its offset for compatibility with static initializer macros)
+
+fn mutex_get_kind<'mir, 'tcx: 'mir>(
+    ecx: &MiriInterpCx<'mir, 'tcx>,
+    mutex_op: &OpTy<'tcx, Provenance>,
+) -> InterpResult<'tcx, Scalar<Provenance>> {
+    let offset = if ecx.pointer_size().bytes() == 8 { 16 } else { 12 };
+    ecx.read_scalar_at_offset_atomic(
+        mutex_op,
+        offset,
+        ecx.machine.layouts.i32,
+        AtomicReadOrd::Relaxed,
+    )
+}
+
+fn mutex_set_kind<'mir, 'tcx: 'mir>(
+    ecx: &mut MiriInterpCx<'mir, 'tcx>,
+    mutex_op: &OpTy<'tcx, Provenance>,
+    kind: impl Into<Scalar<Provenance>>,
+) -> InterpResult<'tcx, ()> {
+    let offset = if ecx.pointer_size().bytes() == 8 { 16 } else { 12 };
+    ecx.write_scalar_at_offset_atomic(
+        mutex_op,
+        offset,
+        kind,
+        layout_of_maybe_uninit(ecx.tcx, ecx.tcx.types.i32),
+        AtomicWriteOrd::Relaxed,
+    )
+}
+
+fn mutex_get_id<'mir, 'tcx: 'mir>(
+    ecx: &MiriInterpCx<'mir, 'tcx>,
+    mutex_op: &OpTy<'tcx, Provenance>,
+) -> InterpResult<'tcx, Scalar<Provenance>> {
+    ecx.read_scalar_at_offset_atomic(mutex_op, 4, ecx.machine.layouts.u32, AtomicReadOrd::Relaxed)
+}
+
+fn mutex_set_id<'mir, 'tcx: 'mir>(
+    ecx: &mut MiriInterpCx<'mir, 'tcx>,
+    mutex_op: &OpTy<'tcx, Provenance>,
+    id: impl Into<Scalar<Provenance>>,
+) -> InterpResult<'tcx, ()> {
+    ecx.write_scalar_at_offset_atomic(
+        mutex_op,
+        4,
+        id,
+        layout_of_maybe_uninit(ecx.tcx, ecx.tcx.types.u32),
+        AtomicWriteOrd::Relaxed,
+    )
+}
+
+fn mutex_get_or_create_id<'mir, 'tcx: 'mir>(
+    ecx: &mut MiriInterpCx<'mir, 'tcx>,
+    mutex_op: &OpTy<'tcx, Provenance>,
+) -> InterpResult<'tcx, MutexId> {
+    let value_place = ecx.deref_operand_and_offset(mutex_op, 4, ecx.machine.layouts.u32)?;
+
+    ecx.mutex_get_or_create(|ecx, next_id| {
+        let (old, success) = ecx
+            .atomic_compare_exchange_scalar(
+                &value_place,
+                &ImmTy::from_uint(0u32, ecx.machine.layouts.u32),
+                next_id.to_u32_scalar(),
+                AtomicRwOrd::Relaxed,
+                AtomicReadOrd::Relaxed,
+                false,
+            )?
+            .to_scalar_pair();
+
+        Ok(if success.to_bool().expect("compare_exchange's second return value is a bool") {
+            // Caller of the closure needs to allocate next_id
+            None
+        } else {
+            Some(MutexId::from_u32(old.to_u32().expect("layout is u32")))
+        })
+    })
+}
+
+// pthread_rwlock_t is between 32 and 56 bytes, depending on the platform.
+
+// Our chosen memory layout for the emulated rwlock (does not have to match the platform layout!):
+// bytes 0-3: reserved for signature on macOS
+// (need to avoid this because it is set by static initializer macros)
+// bytes 4-7: rwlock id as u32 or 0 if id is not assigned yet.
+
+fn rwlock_get_id<'mir, 'tcx: 'mir>(
+    ecx: &MiriInterpCx<'mir, 'tcx>,
+    rwlock_op: &OpTy<'tcx, Provenance>,
+) -> InterpResult<'tcx, Scalar<Provenance>> {
+    ecx.read_scalar_at_offset_atomic(rwlock_op, 4, ecx.machine.layouts.u32, AtomicReadOrd::Relaxed)
+}
+
+fn rwlock_get_or_create_id<'mir, 'tcx: 'mir>(
+    ecx: &mut MiriInterpCx<'mir, 'tcx>,
+    rwlock_op: &OpTy<'tcx, Provenance>,
+) -> InterpResult<'tcx, RwLockId> {
+    let value_place = ecx.deref_operand_and_offset(rwlock_op, 4, ecx.machine.layouts.u32)?;
+
+    ecx.rwlock_get_or_create(|ecx, next_id| {
+        let (old, success) = ecx
+            .atomic_compare_exchange_scalar(
+                &value_place,
+                &ImmTy::from_uint(0u32, ecx.machine.layouts.u32),
+                next_id.to_u32_scalar(),
+                AtomicRwOrd::Relaxed,
+                AtomicReadOrd::Relaxed,
+                false,
+            )?
+            .to_scalar_pair();
+
+        Ok(if success.to_bool().expect("compare_exchange's second return value is a bool") {
+            // Caller of the closure needs to allocate next_id
+            None
+        } else {
+            Some(RwLockId::from_u32(old.to_u32().expect("layout is u32")))
+        })
+    })
+}
+
+// pthread_condattr_t
+
+// Our chosen memory layout for emulation (does not have to match the platform layout!):
+// store an i32 in the first four bytes equal to the corresponding libc clock id constant
+// (e.g. CLOCK_REALTIME).
+
+fn condattr_get_clock_id<'mir, 'tcx: 'mir>(
+    ecx: &MiriInterpCx<'mir, 'tcx>,
+    attr_op: &OpTy<'tcx, Provenance>,
+) -> InterpResult<'tcx, Scalar<Provenance>> {
+    ecx.read_scalar_at_offset(attr_op, 0, ecx.machine.layouts.i32)
+}
+
+fn condattr_set_clock_id<'mir, 'tcx: 'mir>(
+    ecx: &mut MiriInterpCx<'mir, 'tcx>,
+    attr_op: &OpTy<'tcx, Provenance>,
+    clock_id: impl Into<Scalar<Provenance>>,
+) -> InterpResult<'tcx, ()> {
+    ecx.write_scalar_at_offset(
+        attr_op,
+        0,
+        clock_id,
+        layout_of_maybe_uninit(ecx.tcx, ecx.machine.layouts.i32.ty),
+    )
+}
+
+// pthread_cond_t
+
+// Our chosen memory layout for the emulated conditional variable (does not have
+// to match the platform layout!):
+
+// bytes 0-3: reserved for signature on macOS
+// bytes 4-7: the conditional variable id as u32 or 0 if id is not assigned yet.
+// bytes 8-11: the clock id constant as i32
+
+fn cond_get_id<'mir, 'tcx: 'mir>(
+    ecx: &MiriInterpCx<'mir, 'tcx>,
+    cond_op: &OpTy<'tcx, Provenance>,
+) -> InterpResult<'tcx, Scalar<Provenance>> {
+    ecx.read_scalar_at_offset_atomic(cond_op, 4, ecx.machine.layouts.u32, AtomicReadOrd::Relaxed)
+}
+
+fn cond_set_id<'mir, 'tcx: 'mir>(
+    ecx: &mut MiriInterpCx<'mir, 'tcx>,
+    cond_op: &OpTy<'tcx, Provenance>,
+    id: impl Into<Scalar<Provenance>>,
+) -> InterpResult<'tcx, ()> {
+    ecx.write_scalar_at_offset_atomic(
+        cond_op,
+        4,
+        id,
+        layout_of_maybe_uninit(ecx.tcx, ecx.tcx.types.u32),
+        AtomicWriteOrd::Relaxed,
+    )
+}
+
+fn cond_get_or_create_id<'mir, 'tcx: 'mir>(
+    ecx: &mut MiriInterpCx<'mir, 'tcx>,
+    cond_op: &OpTy<'tcx, Provenance>,
+) -> InterpResult<'tcx, CondvarId> {
+    let value_place = ecx.deref_operand_and_offset(cond_op, 4, ecx.machine.layouts.u32)?;
+
+    ecx.condvar_get_or_create(|ecx, next_id| {
+        let (old, success) = ecx
+            .atomic_compare_exchange_scalar(
+                &value_place,
+                &ImmTy::from_uint(0u32, ecx.machine.layouts.u32),
+                next_id.to_u32_scalar(),
+                AtomicRwOrd::Relaxed,
+                AtomicReadOrd::Relaxed,
+                false,
+            )?
+            .to_scalar_pair();
+
+        Ok(if success.to_bool().expect("compare_exchange's second return value is a bool") {
+            // Caller of the closure needs to allocate next_id
+            None
+        } else {
+            Some(CondvarId::from_u32(old.to_u32().expect("layout is u32")))
+        })
+    })
+}
+
+fn cond_get_clock_id<'mir, 'tcx: 'mir>(
+    ecx: &MiriInterpCx<'mir, 'tcx>,
+    cond_op: &OpTy<'tcx, Provenance>,
+) -> InterpResult<'tcx, Scalar<Provenance>> {
+    ecx.read_scalar_at_offset(cond_op, 8, ecx.machine.layouts.i32)
+}
+
+fn cond_set_clock_id<'mir, 'tcx: 'mir>(
+    ecx: &mut MiriInterpCx<'mir, 'tcx>,
+    cond_op: &OpTy<'tcx, Provenance>,
+    clock_id: impl Into<Scalar<Provenance>>,
+) -> InterpResult<'tcx, ()> {
+    ecx.write_scalar_at_offset(
+        cond_op,
+        8,
+        clock_id,
+        layout_of_maybe_uninit(ecx.tcx, ecx.tcx.types.i32),
+    )
+}
+
+/// Try to reacquire the mutex associated with the condition variable after we
+/// were signaled.
+fn reacquire_cond_mutex<'mir, 'tcx: 'mir>(
+    ecx: &mut MiriInterpCx<'mir, 'tcx>,
+    thread: ThreadId,
+    mutex: MutexId,
+) -> InterpResult<'tcx> {
+    ecx.unblock_thread(thread);
+    if ecx.mutex_is_locked(mutex) {
+        ecx.mutex_enqueue_and_block(mutex, thread);
+    } else {
+        ecx.mutex_lock(mutex, thread);
+    }
+    Ok(())
+}
+
+/// After a thread waiting on a condvar was signalled:
+/// Reacquire the conditional variable and remove the timeout callback if any
+/// was registered.
+fn post_cond_signal<'mir, 'tcx: 'mir>(
+    ecx: &mut MiriInterpCx<'mir, 'tcx>,
+    thread: ThreadId,
+    mutex: MutexId,
+) -> InterpResult<'tcx> {
+    reacquire_cond_mutex(ecx, thread, mutex)?;
+    // Waiting for the mutex is not included in the waiting time because we need
+    // to acquire the mutex always even if we get a timeout.
+    ecx.unregister_timeout_callback_if_exists(thread);
+    Ok(())
+}
+
+/// Release the mutex associated with the condition variable because we are
+/// entering the waiting state.
+fn release_cond_mutex_and_block<'mir, 'tcx: 'mir>(
+    ecx: &mut MiriInterpCx<'mir, 'tcx>,
+    active_thread: ThreadId,
+    mutex: MutexId,
+) -> InterpResult<'tcx> {
+    if let Some(old_locked_count) = ecx.mutex_unlock(mutex, active_thread) {
+        if old_locked_count != 1 {
+            throw_unsup_format!("awaiting on a lock acquired multiple times is not supported");
+        }
+    } else {
+        throw_ub_format!("awaiting on unlocked or owned by a different thread mutex");
+    }
+    ecx.block_thread(active_thread);
+    Ok(())
+}
+
+impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    fn pthread_mutexattr_init(
+        &mut self,
+        attr_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        let default_kind = this.eval_libc("PTHREAD_MUTEX_DEFAULT")?;
+        mutexattr_set_kind(this, attr_op, default_kind)?;
+
+        Ok(0)
+    }
+
+    fn pthread_mutexattr_settype(
+        &mut self,
+        attr_op: &OpTy<'tcx, Provenance>,
+        kind_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        let kind = this.read_scalar(kind_op)?;
+        if kind == this.eval_libc("PTHREAD_MUTEX_NORMAL")? {
+            // In `glibc` implementation, the numeric values of
+            // `PTHREAD_MUTEX_NORMAL` and `PTHREAD_MUTEX_DEFAULT` are equal.
+            // However, a mutex created by explicitly passing
+            // `PTHREAD_MUTEX_NORMAL` type has in some cases different behaviour
+            // from the default mutex for which the type was not explicitly
+            // specified. For a more detailed discussion, please see
+            // https://github.com/rust-lang/miri/issues/1419.
+            //
+            // To distinguish these two cases in already constructed mutexes, we
+            // use the same trick as glibc: for the case when
+            // `pthread_mutexattr_settype` is caled explicitly, we set the
+            // `PTHREAD_MUTEX_NORMAL_FLAG` flag.
+            let normal_kind = kind.to_i32()? | PTHREAD_MUTEX_NORMAL_FLAG;
+            // Check that after setting the flag, the kind is distinguishable
+            // from all other kinds.
+            assert_ne!(normal_kind, this.eval_libc("PTHREAD_MUTEX_DEFAULT")?.to_i32()?);
+            assert_ne!(normal_kind, this.eval_libc("PTHREAD_MUTEX_ERRORCHECK")?.to_i32()?);
+            assert_ne!(normal_kind, this.eval_libc("PTHREAD_MUTEX_RECURSIVE")?.to_i32()?);
+            mutexattr_set_kind(this, attr_op, Scalar::from_i32(normal_kind))?;
+        } else if kind == this.eval_libc("PTHREAD_MUTEX_DEFAULT")?
+            || kind == this.eval_libc("PTHREAD_MUTEX_ERRORCHECK")?
+            || kind == this.eval_libc("PTHREAD_MUTEX_RECURSIVE")?
+        {
+            mutexattr_set_kind(this, attr_op, kind)?;
+        } else {
+            let einval = this.eval_libc_i32("EINVAL")?;
+            return Ok(einval);
+        }
+
+        Ok(0)
+    }
+
+    fn pthread_mutexattr_destroy(
+        &mut self,
+        attr_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        // Destroying an uninit pthread_mutexattr is UB, so check to make sure it's not uninit.
+        mutexattr_get_kind(this, attr_op)?;
+
+        // To catch double-destroys, we de-initialize the mutexattr.
+        // This is technically not right and might lead to false positives. For example, the below
+        // code is *likely* sound, even assuming uninit numbers are UB, but Miri complains.
+        //
+        // let mut x: MaybeUninit<libc::pthread_mutexattr_t> = MaybeUninit::zeroed();
+        // libc::pthread_mutexattr_init(x.as_mut_ptr());
+        // libc::pthread_mutexattr_destroy(x.as_mut_ptr());
+        // x.assume_init();
+        //
+        // However, the way libstd uses the pthread APIs works in our favor here, so we can get away with this.
+        // This can always be revisited to have some external state to catch double-destroys
+        // but not complain about the above code. See https://github.com/rust-lang/miri/pull/1933
+        this.write_uninit(&this.deref_operand(attr_op)?.into())?;
+
+        Ok(0)
+    }
+
+    fn pthread_mutex_init(
+        &mut self,
+        mutex_op: &OpTy<'tcx, Provenance>,
+        attr_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        let attr = this.read_pointer(attr_op)?;
+        let kind = if this.ptr_is_null(attr)? {
+            this.eval_libc("PTHREAD_MUTEX_DEFAULT")?
+        } else {
+            mutexattr_get_kind(this, attr_op)?
+        };
+
+        // Write 0 to use the same code path as the static initializers.
+        mutex_set_id(this, mutex_op, Scalar::from_i32(0))?;
+
+        mutex_set_kind(this, mutex_op, kind)?;
+
+        Ok(0)
+    }
+
+    fn pthread_mutex_lock(&mut self, mutex_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        let kind = mutex_get_kind(this, mutex_op)?;
+        let id = mutex_get_or_create_id(this, mutex_op)?;
+        let active_thread = this.get_active_thread();
+
+        if this.mutex_is_locked(id) {
+            let owner_thread = this.mutex_get_owner(id);
+            if owner_thread != active_thread {
+                // Enqueue the active thread.
+                this.mutex_enqueue_and_block(id, active_thread);
+                Ok(0)
+            } else {
+                // Trying to acquire the same mutex again.
+                if is_mutex_kind_default(this, kind)? {
+                    throw_ub_format!("trying to acquire already locked default mutex");
+                } else if is_mutex_kind_normal(this, kind)? {
+                    throw_machine_stop!(TerminationInfo::Deadlock);
+                } else if kind == this.eval_libc("PTHREAD_MUTEX_ERRORCHECK")? {
+                    this.eval_libc_i32("EDEADLK")
+                } else if kind == this.eval_libc("PTHREAD_MUTEX_RECURSIVE")? {
+                    this.mutex_lock(id, active_thread);
+                    Ok(0)
+                } else {
+                    throw_unsup_format!(
+                        "called pthread_mutex_lock on an unsupported type of mutex"
+                    );
+                }
+            }
+        } else {
+            // The mutex is unlocked. Let's lock it.
+            this.mutex_lock(id, active_thread);
+            Ok(0)
+        }
+    }
+
+    fn pthread_mutex_trylock(
+        &mut self,
+        mutex_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        let kind = mutex_get_kind(this, mutex_op)?;
+        let id = mutex_get_or_create_id(this, mutex_op)?;
+        let active_thread = this.get_active_thread();
+
+        if this.mutex_is_locked(id) {
+            let owner_thread = this.mutex_get_owner(id);
+            if owner_thread != active_thread {
+                this.eval_libc_i32("EBUSY")
+            } else {
+                if is_mutex_kind_default(this, kind)?
+                    || is_mutex_kind_normal(this, kind)?
+                    || kind == this.eval_libc("PTHREAD_MUTEX_ERRORCHECK")?
+                {
+                    this.eval_libc_i32("EBUSY")
+                } else if kind == this.eval_libc("PTHREAD_MUTEX_RECURSIVE")? {
+                    this.mutex_lock(id, active_thread);
+                    Ok(0)
+                } else {
+                    throw_unsup_format!(
+                        "called pthread_mutex_trylock on an unsupported type of mutex"
+                    );
+                }
+            }
+        } else {
+            // The mutex is unlocked. Let's lock it.
+            this.mutex_lock(id, active_thread);
+            Ok(0)
+        }
+    }
+
+    fn pthread_mutex_unlock(
+        &mut self,
+        mutex_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        let kind = mutex_get_kind(this, mutex_op)?;
+        let id = mutex_get_or_create_id(this, mutex_op)?;
+        let active_thread = this.get_active_thread();
+
+        if let Some(_old_locked_count) = this.mutex_unlock(id, active_thread) {
+            // The mutex was locked by the current thread.
+            Ok(0)
+        } else {
+            // The mutex was locked by another thread or not locked at all. See
+            // the “Unlock When Not Owner” column in
+            // https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_mutex_unlock.html.
+            if is_mutex_kind_default(this, kind)? {
+                throw_ub_format!(
+                    "unlocked a default mutex that was not locked by the current thread"
+                );
+            } else if is_mutex_kind_normal(this, kind)? {
+                throw_ub_format!(
+                    "unlocked a PTHREAD_MUTEX_NORMAL mutex that was not locked by the current thread"
+                );
+            } else if kind == this.eval_libc("PTHREAD_MUTEX_ERRORCHECK")?
+                || kind == this.eval_libc("PTHREAD_MUTEX_RECURSIVE")?
+            {
+                this.eval_libc_i32("EPERM")
+            } else {
+                throw_unsup_format!("called pthread_mutex_unlock on an unsupported type of mutex");
+            }
+        }
+    }
+
+    fn pthread_mutex_destroy(
+        &mut self,
+        mutex_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        let id = mutex_get_or_create_id(this, mutex_op)?;
+
+        if this.mutex_is_locked(id) {
+            throw_ub_format!("destroyed a locked mutex");
+        }
+
+        // Destroying an uninit pthread_mutex is UB, so check to make sure it's not uninit.
+        mutex_get_kind(this, mutex_op)?;
+        mutex_get_id(this, mutex_op)?;
+
+        // This might lead to false positives, see comment in pthread_mutexattr_destroy
+        this.write_uninit(&this.deref_operand(mutex_op)?.into())?;
+        // FIXME: delete interpreter state associated with this mutex.
+
+        Ok(0)
+    }
+
+    fn pthread_rwlock_rdlock(
+        &mut self,
+        rwlock_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        let id = rwlock_get_or_create_id(this, rwlock_op)?;
+        let active_thread = this.get_active_thread();
+
+        if this.rwlock_is_write_locked(id) {
+            this.rwlock_enqueue_and_block_reader(id, active_thread);
+            Ok(0)
+        } else {
+            this.rwlock_reader_lock(id, active_thread);
+            Ok(0)
+        }
+    }
+
+    fn pthread_rwlock_tryrdlock(
+        &mut self,
+        rwlock_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        let id = rwlock_get_or_create_id(this, rwlock_op)?;
+        let active_thread = this.get_active_thread();
+
+        if this.rwlock_is_write_locked(id) {
+            this.eval_libc_i32("EBUSY")
+        } else {
+            this.rwlock_reader_lock(id, active_thread);
+            Ok(0)
+        }
+    }
+
+    fn pthread_rwlock_wrlock(
+        &mut self,
+        rwlock_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        let id = rwlock_get_or_create_id(this, rwlock_op)?;
+        let active_thread = this.get_active_thread();
+
+        if this.rwlock_is_locked(id) {
+            // Note: this will deadlock if the lock is already locked by this
+            // thread in any way.
+            //
+            // Relevant documentation:
+            // https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_rwlock_wrlock.html
+            // An in-depth discussion on this topic:
+            // https://github.com/rust-lang/rust/issues/53127
+            //
+            // FIXME: Detect and report the deadlock proactively. (We currently
+            // report the deadlock only when no thread can continue execution,
+            // but we could detect that this lock is already locked and report
+            // an error.)
+            this.rwlock_enqueue_and_block_writer(id, active_thread);
+        } else {
+            this.rwlock_writer_lock(id, active_thread);
+        }
+
+        Ok(0)
+    }
+
+    fn pthread_rwlock_trywrlock(
+        &mut self,
+        rwlock_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        let id = rwlock_get_or_create_id(this, rwlock_op)?;
+        let active_thread = this.get_active_thread();
+
+        if this.rwlock_is_locked(id) {
+            this.eval_libc_i32("EBUSY")
+        } else {
+            this.rwlock_writer_lock(id, active_thread);
+            Ok(0)
+        }
+    }
+
+    fn pthread_rwlock_unlock(
+        &mut self,
+        rwlock_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        let id = rwlock_get_or_create_id(this, rwlock_op)?;
+        let active_thread = this.get_active_thread();
+
+        #[allow(clippy::if_same_then_else)]
+        if this.rwlock_reader_unlock(id, active_thread) {
+            Ok(0)
+        } else if this.rwlock_writer_unlock(id, active_thread) {
+            Ok(0)
+        } else {
+            throw_ub_format!("unlocked an rwlock that was not locked by the active thread");
+        }
+    }
+
+    fn pthread_rwlock_destroy(
+        &mut self,
+        rwlock_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        let id = rwlock_get_or_create_id(this, rwlock_op)?;
+
+        if this.rwlock_is_locked(id) {
+            throw_ub_format!("destroyed a locked rwlock");
+        }
+
+        // Destroying an uninit pthread_rwlock is UB, so check to make sure it's not uninit.
+        rwlock_get_id(this, rwlock_op)?;
+
+        // This might lead to false positives, see comment in pthread_mutexattr_destroy
+        this.write_uninit(&this.deref_operand(rwlock_op)?.into())?;
+        // FIXME: delete interpreter state associated with this rwlock.
+
+        Ok(0)
+    }
+
+    fn pthread_condattr_init(
+        &mut self,
+        attr_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        // The default value of the clock attribute shall refer to the system
+        // clock.
+        // https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_condattr_setclock.html
+        let default_clock_id = this.eval_libc("CLOCK_REALTIME")?;
+        condattr_set_clock_id(this, attr_op, default_clock_id)?;
+
+        Ok(0)
+    }
+
+    fn pthread_condattr_setclock(
+        &mut self,
+        attr_op: &OpTy<'tcx, Provenance>,
+        clock_id_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_mut();
+
+        let clock_id = this.read_scalar(clock_id_op)?;
+        if clock_id == this.eval_libc("CLOCK_REALTIME")?
+            || clock_id == this.eval_libc("CLOCK_MONOTONIC")?
+        {
+            condattr_set_clock_id(this, attr_op, clock_id)?;
+        } else {
+            let einval = this.eval_libc_i32("EINVAL")?;
+            return Ok(Scalar::from_i32(einval));
+        }
+
+        Ok(Scalar::from_i32(0))
+    }
+
+    fn pthread_condattr_getclock(
+        &mut self,
+        attr_op: &OpTy<'tcx, Provenance>,
+        clk_id_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_mut();
+
+        let clock_id = condattr_get_clock_id(this, attr_op)?;
+        this.write_scalar(clock_id, &this.deref_operand(clk_id_op)?.into())?;
+
+        Ok(Scalar::from_i32(0))
+    }
+
+    fn pthread_condattr_destroy(
+        &mut self,
+        attr_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        // Destroying an uninit pthread_condattr is UB, so check to make sure it's not uninit.
+        condattr_get_clock_id(this, attr_op)?;
+
+        // This might lead to false positives, see comment in pthread_mutexattr_destroy
+        this.write_uninit(&this.deref_operand(attr_op)?.into())?;
+
+        Ok(0)
+    }
+
+    fn pthread_cond_init(
+        &mut self,
+        cond_op: &OpTy<'tcx, Provenance>,
+        attr_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        let attr = this.read_pointer(attr_op)?;
+        let clock_id = if this.ptr_is_null(attr)? {
+            this.eval_libc("CLOCK_REALTIME")?
+        } else {
+            condattr_get_clock_id(this, attr_op)?
+        };
+
+        // Write 0 to use the same code path as the static initializers.
+        cond_set_id(this, cond_op, Scalar::from_i32(0))?;
+
+        cond_set_clock_id(this, cond_op, clock_id)?;
+
+        Ok(0)
+    }
+
+    fn pthread_cond_signal(&mut self, cond_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+        let id = cond_get_or_create_id(this, cond_op)?;
+        if let Some((thread, mutex)) = this.condvar_signal(id) {
+            post_cond_signal(this, thread, mutex)?;
+        }
+
+        Ok(0)
+    }
+
+    fn pthread_cond_broadcast(
+        &mut self,
+        cond_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+        let id = cond_get_or_create_id(this, cond_op)?;
+
+        while let Some((thread, mutex)) = this.condvar_signal(id) {
+            post_cond_signal(this, thread, mutex)?;
+        }
+
+        Ok(0)
+    }
+
+    fn pthread_cond_wait(
+        &mut self,
+        cond_op: &OpTy<'tcx, Provenance>,
+        mutex_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        let id = cond_get_or_create_id(this, cond_op)?;
+        let mutex_id = mutex_get_or_create_id(this, mutex_op)?;
+        let active_thread = this.get_active_thread();
+
+        release_cond_mutex_and_block(this, active_thread, mutex_id)?;
+        this.condvar_wait(id, active_thread, mutex_id);
+
+        Ok(0)
+    }
+
+    fn pthread_cond_timedwait(
+        &mut self,
+        cond_op: &OpTy<'tcx, Provenance>,
+        mutex_op: &OpTy<'tcx, Provenance>,
+        abstime_op: &OpTy<'tcx, Provenance>,
+        dest: &PlaceTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+
+        this.check_no_isolation("`pthread_cond_timedwait`")?;
+
+        let id = cond_get_or_create_id(this, cond_op)?;
+        let mutex_id = mutex_get_or_create_id(this, mutex_op)?;
+        let active_thread = this.get_active_thread();
+
+        // Extract the timeout.
+        let clock_id = cond_get_clock_id(this, cond_op)?.to_i32()?;
+        let duration = match this.read_timespec(&this.deref_operand(abstime_op)?)? {
+            Some(duration) => duration,
+            None => {
+                let einval = this.eval_libc("EINVAL")?;
+                this.write_scalar(einval, dest)?;
+                return Ok(());
+            }
+        };
+
+        let timeout_time = if clock_id == this.eval_libc_i32("CLOCK_REALTIME")? {
+            Time::RealTime(SystemTime::UNIX_EPOCH.checked_add(duration).unwrap())
+        } else if clock_id == this.eval_libc_i32("CLOCK_MONOTONIC")? {
+            Time::Monotonic(this.machine.clock.anchor().checked_add(duration).unwrap())
+        } else {
+            throw_unsup_format!("unsupported clock id: {}", clock_id);
+        };
+
+        release_cond_mutex_and_block(this, active_thread, mutex_id)?;
+        this.condvar_wait(id, active_thread, mutex_id);
+
+        // We return success for now and override it in the timeout callback.
+        this.write_scalar(Scalar::from_i32(0), dest)?;
+
+        // Register the timeout callback.
+        let dest = dest.clone();
+        this.register_timeout_callback(
+            active_thread,
+            timeout_time,
+            Box::new(move |ecx| {
+                // We are not waiting for the condvar any more, wait for the
+                // mutex instead.
+                reacquire_cond_mutex(ecx, active_thread, mutex_id)?;
+
+                // Remove the thread from the conditional variable.
+                ecx.condvar_remove_waiter(id, active_thread);
+
+                // Set the return value: we timed out.
+                let etimedout = ecx.eval_libc("ETIMEDOUT")?;
+                ecx.write_scalar(etimedout, &dest)?;
+
+                Ok(())
+            }),
+        );
+
+        Ok(())
+    }
+
+    fn pthread_cond_destroy(
+        &mut self,
+        cond_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        let id = cond_get_or_create_id(this, cond_op)?;
+        if this.condvar_is_awaited(id) {
+            throw_ub_format!("destroying an awaited conditional variable");
+        }
+
+        // Destroying an uninit pthread_cond is UB, so check to make sure it's not uninit.
+        cond_get_id(this, cond_op)?;
+        cond_get_clock_id(this, cond_op)?;
+
+        // This might lead to false positives, see comment in pthread_mutexattr_destroy
+        this.write_uninit(&this.deref_operand(cond_op)?.into())?;
+        // FIXME: delete interpreter state associated with this condvar.
+
+        Ok(0)
+    }
+}
+
+fn layout_of_maybe_uninit<'tcx>(tcx: TyCtxtAt<'tcx>, param: Ty<'tcx>) -> TyAndLayout<'tcx> {
+    let def_id = tcx.require_lang_item(LangItem::MaybeUninit, None);
+    let ty = tcx.bound_type_of(def_id).subst(*tcx, &[param.into()]);
+
+    let param_env = tcx.param_env(def_id);
+    tcx.layout_of(param_env.and(ty)).unwrap()
+}
diff --git a/src/tools/miri/src/shims/unix/thread.rs b/src/tools/miri/src/shims/unix/thread.rs
new file mode 100644
index 0000000000000..59474d8d10ad7
--- /dev/null
+++ b/src/tools/miri/src/shims/unix/thread.rs
@@ -0,0 +1,93 @@
+use crate::*;
+use rustc_middle::ty::layout::LayoutOf;
+use rustc_target::spec::abi::Abi;
+
+impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    fn pthread_create(
+        &mut self,
+        thread: &OpTy<'tcx, Provenance>,
+        _attr: &OpTy<'tcx, Provenance>,
+        start_routine: &OpTy<'tcx, Provenance>,
+        arg: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        let thread_info_place = this.deref_operand(thread)?;
+
+        let start_routine = this.read_pointer(start_routine)?;
+
+        let func_arg = this.read_immediate(arg)?;
+
+        this.start_thread(
+            Some(thread_info_place),
+            start_routine,
+            Abi::C { unwind: false },
+            func_arg,
+            this.layout_of(this.tcx.types.usize)?,
+        )?;
+
+        Ok(0)
+    }
+
+    fn pthread_join(
+        &mut self,
+        thread: &OpTy<'tcx, Provenance>,
+        retval: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        if !this.ptr_is_null(this.read_pointer(retval)?)? {
+            // FIXME: implement reading the thread function's return place.
+            throw_unsup_format!("Miri supports pthread_join only with retval==NULL");
+        }
+
+        let thread_id = this.read_scalar(thread)?.to_machine_usize(this)?;
+        this.join_thread_exclusive(thread_id.try_into().expect("thread ID should fit in u32"))?;
+
+        Ok(0)
+    }
+
+    fn pthread_detach(&mut self, thread: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        let thread_id = this.read_scalar(thread)?.to_machine_usize(this)?;
+        this.detach_thread(
+            thread_id.try_into().expect("thread ID should fit in u32"),
+            /*allow_terminated_joined*/ false,
+        )?;
+
+        Ok(0)
+    }
+
+    fn pthread_self(&mut self) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_mut();
+
+        let thread_id = this.get_active_thread();
+        Ok(Scalar::from_machine_usize(thread_id.into(), this))
+    }
+
+    fn pthread_setname_np(
+        &mut self,
+        thread: Scalar<Provenance>,
+        name: Scalar<Provenance>,
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_mut();
+
+        let thread = ThreadId::try_from(thread.to_machine_usize(this)?).unwrap();
+        let name = name.to_pointer(this)?;
+
+        let name = this.read_c_str(name)?.to_owned();
+        this.set_thread_name(thread, name);
+
+        Ok(Scalar::from_u32(0))
+    }
+
+    fn sched_yield(&mut self) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        this.yield_active_thread();
+
+        Ok(0)
+    }
+}
diff --git a/src/tools/miri/src/shims/windows/dlsym.rs b/src/tools/miri/src/shims/windows/dlsym.rs
new file mode 100644
index 0000000000000..41b9473f81fef
--- /dev/null
+++ b/src/tools/miri/src/shims/windows/dlsym.rs
@@ -0,0 +1,136 @@
+use rustc_middle::mir;
+use rustc_target::abi::Size;
+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::*;
+
+#[derive(Debug, Copy, Clone)]
+pub enum Dlsym {
+    NtWriteFile,
+    SetThreadDescription,
+}
+
+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,
+            "NtWriteFile" => Some(Dlsym::NtWriteFile),
+            "SetThreadDescription" => Some(Dlsym::SetThreadDescription),
+            _ => 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::NtWriteFile => {
+                if !this.frame_in_std() {
+                    throw_unsup_format!(
+                        "`NtWriteFile` support is crude and just enough for stdout to work"
+                    );
+                }
+
+                let [
+                    handle,
+                    _event,
+                    _apc_routine,
+                    _apc_context,
+                    io_status_block,
+                    buf,
+                    n,
+                    byte_offset,
+                    _key,
+                ] = check_arg_count(args)?;
+                let handle = this.read_scalar(handle)?.to_machine_isize(this)?;
+                let buf = this.read_pointer(buf)?;
+                let n = this.read_scalar(n)?.to_u32()?;
+                let byte_offset = this.read_scalar(byte_offset)?.to_machine_usize(this)?; // is actually a pointer
+                let io_status_block = this.deref_operand(io_status_block)?;
+
+                if byte_offset != 0 {
+                    throw_unsup_format!(
+                        "`NtWriteFile` `ByteOffset` paremeter is non-null, which is unsupported"
+                    );
+                }
+
+                let written = if handle == -11 || handle == -12 {
+                    // stdout/stderr
+                    use std::io::{self, Write};
+
+                    let buf_cont =
+                        this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(u64::from(n)))?;
+                    let res = if this.machine.mute_stdout_stderr {
+                        Ok(buf_cont.len())
+                    } else if handle == -11 {
+                        io::stdout().write(buf_cont)
+                    } else {
+                        io::stderr().write(buf_cont)
+                    };
+                    // We write at most `n` bytes, which is a `u32`, so we cannot have written more than that.
+                    res.ok().map(|n| u32::try_from(n).unwrap())
+                } else {
+                    throw_unsup_format!(
+                        "on Windows, writing to anything except stdout/stderr is not supported"
+                    )
+                };
+                // We have to put the result into io_status_block.
+                if let Some(n) = written {
+                    let io_status_information =
+                        this.mplace_field_named(&io_status_block, "Information")?;
+                    this.write_scalar(
+                        Scalar::from_machine_usize(n.into(), this),
+                        &io_status_information.into(),
+                    )?;
+                }
+                // Return whether this was a success. >= 0 is success.
+                // For the error code we arbitrarily pick 0xC0000185, STATUS_IO_DEVICE_ERROR.
+                this.write_scalar(
+                    Scalar::from_u32(if written.is_some() { 0 } else { 0xC0000185u32 }),
+                    dest,
+                )?;
+            }
+            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)?;
+            }
+        }
+
+        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
new file mode 100644
index 0000000000000..53ab97b255e56
--- /dev/null
+++ b/src/tools/miri/src/shims/windows/foreign_items.rs
@@ -0,0 +1,442 @@
+use std::iter;
+
+use rustc_span::Symbol;
+use rustc_target::abi::Size;
+use rustc_target::spec::abi::Abi;
+
+use crate::*;
+use shims::foreign_items::EmulateByNameResult;
+use shims::windows::handle::{EvalContextExt as _, Handle, PseudoHandle};
+use shims::windows::sync::EvalContextExt as _;
+use shims::windows::thread::EvalContextExt as _;
+
+use smallvec::SmallVec;
+
+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(
+        &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();
+
+        // See `fn emulate_foreign_item_by_name` in `shims/foreign_items.rs` for the general pattern.
+
+        // Windows API stubs.
+        // HANDLE = isize
+        // NTSTATUS = LONH = i32
+        // DWORD = ULONG = u32
+        // BOOL = i32
+        // BOOLEAN = u8
+        match link_name.as_str() {
+            // Environment related shims
+            "GetEnvironmentVariableW" => {
+                let [name, buf, size] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                let result = this.GetEnvironmentVariableW(name, buf, size)?;
+                this.write_scalar(Scalar::from_u32(result), dest)?;
+            }
+            "SetEnvironmentVariableW" => {
+                let [name, value] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                let result = this.SetEnvironmentVariableW(name, value)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "GetEnvironmentStringsW" => {
+                let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                let result = this.GetEnvironmentStringsW()?;
+                this.write_pointer(result, dest)?;
+            }
+            "FreeEnvironmentStringsW" => {
+                let [env_block] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                let result = this.FreeEnvironmentStringsW(env_block)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "GetCurrentDirectoryW" => {
+                let [size, buf] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                let result = this.GetCurrentDirectoryW(size, buf)?;
+                this.write_scalar(Scalar::from_u32(result), dest)?;
+            }
+            "SetCurrentDirectoryW" => {
+                let [path] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                let result = this.SetCurrentDirectoryW(path)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+
+            // Allocation
+            "HeapAlloc" => {
+                let [handle, flags, size] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                this.read_scalar(handle)?.to_machine_isize(this)?;
+                let flags = this.read_scalar(flags)?.to_u32()?;
+                let size = this.read_scalar(size)?.to_machine_usize(this)?;
+                let zero_init = (flags & 0x00000008) != 0; // HEAP_ZERO_MEMORY
+                let res = this.malloc(size, zero_init, MiriMemoryKind::WinHeap)?;
+                this.write_pointer(res, dest)?;
+            }
+            "HeapFree" => {
+                let [handle, flags, ptr] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                this.read_scalar(handle)?.to_machine_isize(this)?;
+                this.read_scalar(flags)?.to_u32()?;
+                let ptr = this.read_pointer(ptr)?;
+                this.free(ptr, MiriMemoryKind::WinHeap)?;
+                this.write_scalar(Scalar::from_i32(1), dest)?;
+            }
+            "HeapReAlloc" => {
+                let [handle, flags, ptr, size] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                this.read_scalar(handle)?.to_machine_isize(this)?;
+                this.read_scalar(flags)?.to_u32()?;
+                let ptr = this.read_pointer(ptr)?;
+                let size = this.read_scalar(size)?.to_machine_usize(this)?;
+                let res = this.realloc(ptr, size, MiriMemoryKind::WinHeap)?;
+                this.write_pointer(res, dest)?;
+            }
+
+            // errno
+            "SetLastError" => {
+                let [error] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                let error = this.read_scalar(error)?;
+                this.set_last_error(error)?;
+            }
+            "GetLastError" => {
+                let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                let last_error = this.get_last_error()?;
+                this.write_scalar(last_error, dest)?;
+            }
+
+            // Querying system information
+            "GetSystemInfo" => {
+                // Also called from `page_size` crate.
+                let [system_info] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                let system_info = this.deref_operand(system_info)?;
+                // Initialize with `0`.
+                this.write_bytes_ptr(
+                    system_info.ptr,
+                    iter::repeat(0u8).take(system_info.layout.size.bytes_usize()),
+                )?;
+                // Set selected fields.
+                let word_layout = this.machine.layouts.u16;
+                let dword_layout = this.machine.layouts.u32;
+                let usize_layout = this.machine.layouts.usize;
+
+                // Using `mplace_field` is error-prone, see: https://github.com/rust-lang/miri/issues/2136.
+                // Pointer fields have different sizes on different targets.
+                // To avoid all these issue we calculate the offsets ourselves.
+                let field_sizes = [
+                    word_layout.size,  // 0,  wProcessorArchitecture      : WORD
+                    word_layout.size,  // 1,  wReserved                   : WORD
+                    dword_layout.size, // 2,  dwPageSize                  : DWORD
+                    usize_layout.size, // 3,  lpMinimumApplicationAddress : LPVOID
+                    usize_layout.size, // 4,  lpMaximumApplicationAddress : LPVOID
+                    usize_layout.size, // 5,  dwActiveProcessorMask       : DWORD_PTR
+                    dword_layout.size, // 6,  dwNumberOfProcessors        : DWORD
+                    dword_layout.size, // 7,  dwProcessorType             : DWORD
+                    dword_layout.size, // 8,  dwAllocationGranularity     : DWORD
+                    word_layout.size,  // 9,  wProcessorLevel             : WORD
+                    word_layout.size,  // 10, wProcessorRevision          : WORD
+                ];
+                let field_offsets: SmallVec<[Size; 11]> = field_sizes
+                    .iter()
+                    .copied()
+                    .scan(Size::ZERO, |a, x| {
+                        let res = Some(*a);
+                        *a += x;
+                        res
+                    })
+                    .collect();
+
+                // Set page size.
+                let page_size = system_info.offset(field_offsets[2], dword_layout, &this.tcx)?;
+                this.write_scalar(
+                    Scalar::from_int(PAGE_SIZE, dword_layout.size),
+                    &page_size.into(),
+                )?;
+                // Set number of processors.
+                let num_cpus = system_info.offset(field_offsets[6], dword_layout, &this.tcx)?;
+                this.write_scalar(Scalar::from_int(NUM_CPUS, dword_layout.size), &num_cpus.into())?;
+            }
+
+            // Thread-local storage
+            "TlsAlloc" => {
+                // This just creates a key; Windows does not natively support TLS destructors.
+
+                // Create key and return it.
+                let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                let key = this.machine.tls.create_tls_key(None, dest.layout.size)?;
+                this.write_scalar(Scalar::from_uint(key, dest.layout.size), dest)?;
+            }
+            "TlsGetValue" => {
+                let [key] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                let key = u128::from(this.read_scalar(key)?.to_u32()?);
+                let active_thread = this.get_active_thread();
+                let ptr = this.machine.tls.load_tls(key, active_thread, this)?;
+                this.write_scalar(ptr, dest)?;
+            }
+            "TlsSetValue" => {
+                let [key, new_ptr] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                let key = u128::from(this.read_scalar(key)?.to_u32()?);
+                let active_thread = this.get_active_thread();
+                let new_data = this.read_scalar(new_ptr)?;
+                this.machine.tls.store_tls(key, active_thread, new_data, &*this.tcx)?;
+
+                // Return success (`1`).
+                this.write_scalar(Scalar::from_i32(1), dest)?;
+            }
+
+            // Access to command-line arguments
+            "GetCommandLineW" => {
+                let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                this.write_pointer(
+                    this.machine.cmd_line.expect("machine must be initialized").ptr,
+                    dest,
+                )?;
+            }
+
+            // Time related shims
+            "GetSystemTimeAsFileTime" => {
+                #[allow(non_snake_case)]
+                let [LPFILETIME] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                this.GetSystemTimeAsFileTime(LPFILETIME)?;
+            }
+            "QueryPerformanceCounter" => {
+                #[allow(non_snake_case)]
+                let [lpPerformanceCount] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                let result = this.QueryPerformanceCounter(lpPerformanceCount)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "QueryPerformanceFrequency" => {
+                #[allow(non_snake_case)]
+                let [lpFrequency] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                let result = this.QueryPerformanceFrequency(lpFrequency)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
+            "Sleep" => {
+                let [timeout] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+
+                this.Sleep(timeout)?;
+            }
+
+            // Synchronization primitives
+            "AcquireSRWLockExclusive" => {
+                let [ptr] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                this.AcquireSRWLockExclusive(ptr)?;
+            }
+            "ReleaseSRWLockExclusive" => {
+                let [ptr] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                this.ReleaseSRWLockExclusive(ptr)?;
+            }
+            "TryAcquireSRWLockExclusive" => {
+                let [ptr] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                let ret = this.TryAcquireSRWLockExclusive(ptr)?;
+                this.write_scalar(Scalar::from_u8(ret), dest)?;
+            }
+            "AcquireSRWLockShared" => {
+                let [ptr] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                this.AcquireSRWLockShared(ptr)?;
+            }
+            "ReleaseSRWLockShared" => {
+                let [ptr] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                this.ReleaseSRWLockShared(ptr)?;
+            }
+            "TryAcquireSRWLockShared" => {
+                let [ptr] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                let ret = this.TryAcquireSRWLockShared(ptr)?;
+                this.write_scalar(Scalar::from_u8(ret), dest)?;
+            }
+
+            // Dynamic symbol loading
+            "GetProcAddress" => {
+                #[allow(non_snake_case)]
+                let [hModule, lpProcName] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                this.read_scalar(hModule)?.to_machine_isize(this)?;
+                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.create_fn_alloc_ptr(FnVal::Other(dlsym));
+                    this.write_pointer(ptr, dest)?;
+                } else {
+                    this.write_null(dest)?;
+                }
+            }
+
+            // Miscellaneous
+            "SystemFunction036" => {
+                // This is really 'RtlGenRandom'.
+                let [ptr, len] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                let ptr = this.read_pointer(ptr)?;
+                let len = this.read_scalar(len)?.to_u32()?;
+                this.gen_random(ptr, len.into())?;
+                this.write_scalar(Scalar::from_bool(true), dest)?;
+            }
+            "BCryptGenRandom" => {
+                let [algorithm, ptr, len, flags] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                let algorithm = this.read_scalar(algorithm)?;
+                let algorithm = algorithm.to_machine_usize(this)?;
+                let ptr = this.read_pointer(ptr)?;
+                let len = this.read_scalar(len)?.to_u32()?;
+                let flags = this.read_scalar(flags)?.to_u32()?;
+                match flags {
+                    0 => {
+                        if algorithm != 0x81 {
+                            // BCRYPT_RNG_ALG_HANDLE
+                            throw_unsup_format!(
+                                "BCryptGenRandom algorithm must be BCRYPT_RNG_ALG_HANDLE when the flag is 0"
+                            );
+                        }
+                    }
+                    2 => {
+                        // BCRYPT_USE_SYSTEM_PREFERRED_RNG
+                        if algorithm != 0 {
+                            throw_unsup_format!(
+                                "BCryptGenRandom algorithm must be NULL when the flag is BCRYPT_USE_SYSTEM_PREFERRED_RNG"
+                            );
+                        }
+                    }
+                    _ => {
+                        throw_unsup_format!(
+                            "BCryptGenRandom is only supported with BCRYPT_USE_SYSTEM_PREFERRED_RNG or BCRYPT_RNG_ALG_HANDLE"
+                        );
+                    }
+                }
+                this.gen_random(ptr, len.into())?;
+                this.write_null(dest)?; // STATUS_SUCCESS
+            }
+            "GetConsoleScreenBufferInfo" => {
+                // `term` needs this, so we fake it.
+                let [console, buffer_info] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                this.read_scalar(console)?.to_machine_isize(this)?;
+                this.deref_operand(buffer_info)?;
+                // Indicate an error.
+                // FIXME: we should set last_error, but to what?
+                this.write_null(dest)?;
+            }
+            "GetConsoleMode" => {
+                // Windows "isatty" (in libtest) needs this, so we fake it.
+                let [console, mode] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                this.read_scalar(console)?.to_machine_isize(this)?;
+                this.deref_operand(mode)?;
+                // Indicate an error.
+                // FIXME: we should set last_error, but to what?
+                this.write_null(dest)?;
+            }
+            "GetStdHandle" => {
+                let [which] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                let which = this.read_scalar(which)?.to_i32()?;
+                // We just make this the identity function, so we know later in `NtWriteFile` which
+                // one it is. This is very fake, but libtest needs it so we cannot make it a
+                // std-only shim.
+                // FIXME: this should return real HANDLEs when io support is added
+                this.write_scalar(Scalar::from_machine_isize(which.into(), this), dest)?;
+            }
+            "CloseHandle" => {
+                let [handle] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+
+                this.CloseHandle(handle)?;
+
+                this.write_scalar(Scalar::from_u32(1), 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,
+                )?;
+            }
+
+            // Incomplete shims that we "stub out" just to get pre-main initialization code to work.
+            // These shims are enabled only when the caller is in the standard library.
+            "GetProcessHeap" if this.frame_in_std() => {
+                let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                // Just fake a HANDLE
+                // It's fine to not use the Handle type here because its a stub
+                this.write_scalar(Scalar::from_machine_isize(1, this), dest)?;
+            }
+            "GetModuleHandleA" if this.frame_in_std() => {
+                #[allow(non_snake_case)]
+                let [_lpModuleName] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                // We need to return something non-null here to make `compat_fn!` work.
+                this.write_scalar(Scalar::from_machine_isize(1, this), dest)?;
+            }
+            "SetConsoleTextAttribute" if this.frame_in_std() => {
+                #[allow(non_snake_case)]
+                let [_hConsoleOutput, _wAttribute] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                // Pretend these does not exist / nothing happened, by returning zero.
+                this.write_null(dest)?;
+            }
+            "AddVectoredExceptionHandler" if this.frame_in_std() => {
+                #[allow(non_snake_case)]
+                let [_First, _Handler] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                // Any non zero value works for the stdlib. This is just used for stack overflows anyway.
+                this.write_scalar(Scalar::from_machine_usize(1, this), dest)?;
+            }
+            "SetThreadStackGuarantee" if this.frame_in_std() => {
+                #[allow(non_snake_case)]
+                let [_StackSizeInBytes] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                // Any non zero value works for the stdlib. This is just used for stack overflows anyway.
+                this.write_scalar(Scalar::from_u32(1), dest)?;
+            }
+            "GetCurrentProcessId" if this.frame_in_std() => {
+                let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                let result = this.GetCurrentProcessId()?;
+                this.write_scalar(Scalar::from_u32(result), dest)?;
+            }
+            // this is only callable from std because we know that std ignores the return value
+            "SwitchToThread" if this.frame_in_std() => {
+                let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+
+                this.yield_active_thread();
+
+                // FIXME: this should return a nonzero value if this call does result in switching to another thread.
+                this.write_null(dest)?;
+            }
+
+            _ => return Ok(EmulateByNameResult::NotSupported),
+        }
+
+        Ok(EmulateByNameResult::NeedsJumping)
+    }
+}
diff --git a/src/tools/miri/src/shims/windows/handle.rs b/src/tools/miri/src/shims/windows/handle.rs
new file mode 100644
index 0000000000000..5b22c4bd73584
--- /dev/null
+++ b/src/tools/miri/src/shims/windows/handle.rs
@@ -0,0 +1,171 @@
+use rustc_target::abi::HasDataLayout;
+use std::mem::variant_count;
+
+use crate::*;
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub enum PseudoHandle {
+    CurrentThread,
+}
+
+/// Miri representation of a Windows `HANDLE`
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub enum Handle {
+    Null,
+    Pseudo(PseudoHandle),
+    Thread(ThreadId),
+}
+
+impl PseudoHandle {
+    const CURRENT_THREAD_VALUE: u32 = 0;
+
+    fn value(self) -> u32 {
+        match self {
+            Self::CurrentThread => Self::CURRENT_THREAD_VALUE,
+        }
+    }
+
+    fn from_value(value: u32) -> Option<Self> {
+        match value {
+            Self::CURRENT_THREAD_VALUE => Some(Self::CurrentThread),
+            _ => None,
+        }
+    }
+}
+
+impl Handle {
+    const NULL_DISCRIMINANT: u32 = 0;
+    const PSEUDO_DISCRIMINANT: u32 = 1;
+    const THREAD_DISCRIMINANT: u32 = 2;
+
+    fn discriminant(self) -> u32 {
+        match self {
+            Self::Null => Self::NULL_DISCRIMINANT,
+            Self::Pseudo(_) => Self::PSEUDO_DISCRIMINANT,
+            Self::Thread(_) => Self::THREAD_DISCRIMINANT,
+        }
+    }
+
+    fn data(self) -> u32 {
+        match self {
+            Self::Null => 0,
+            Self::Pseudo(pseudo_handle) => pseudo_handle.value(),
+            Self::Thread(thread) => thread.to_u32(),
+        }
+    }
+
+    fn packed_disc_size() -> u32 {
+        // ceil(log2(x)) is how many bits it takes to store x numbers
+        let variant_count = variant_count::<Self>();
+
+        // however, std's ilog2 is floor(log2(x))
+        let floor_log2 = variant_count.ilog2();
+
+        // we need to add one for non powers of two to compensate for the difference
+        #[allow(clippy::integer_arithmetic)] // cannot overflow
+        if variant_count.is_power_of_two() { floor_log2 } else { floor_log2 + 1 }
+    }
+
+    /// Converts a handle into its machine representation.
+    ///
+    /// The upper [`Self::packed_disc_size()`] bits are used to store a discriminant corresponding to the handle variant.
+    /// The remaining bits are used for the variant's field.
+    ///
+    /// None of this layout is guaranteed to applications by Windows or Miri.
+    fn to_packed(self) -> u32 {
+        let disc_size = Self::packed_disc_size();
+        let data_size = u32::BITS.checked_sub(disc_size).unwrap();
+
+        let discriminant = self.discriminant();
+        let data = self.data();
+
+        // make sure the discriminant fits into `disc_size` bits
+        assert!(discriminant < 2u32.pow(disc_size));
+
+        // make sure the data fits into `data_size` bits
+        assert!(data < 2u32.pow(data_size));
+
+        // packs the data into the lower `data_size` bits
+        // and packs the discriminant right above the data
+        #[allow(clippy::integer_arithmetic)] // cannot overflow
+        return discriminant << data_size | data;
+    }
+
+    fn new(discriminant: u32, data: u32) -> Option<Self> {
+        match discriminant {
+            Self::NULL_DISCRIMINANT if data == 0 => Some(Self::Null),
+            Self::PSEUDO_DISCRIMINANT => Some(Self::Pseudo(PseudoHandle::from_value(data)?)),
+            Self::THREAD_DISCRIMINANT => Some(Self::Thread(data.into())),
+            _ => None,
+        }
+    }
+
+    /// see docs for `to_packed`
+    fn from_packed(handle: u32) -> Option<Self> {
+        let disc_size = Self::packed_disc_size();
+        let data_size = u32::BITS.checked_sub(disc_size).unwrap();
+
+        // the lower `data_size` bits of this mask are 1
+        #[allow(clippy::integer_arithmetic)] // cannot overflow
+        let data_mask = 2u32.pow(data_size) - 1;
+
+        // the discriminant is stored right above the lower `data_size` bits
+        #[allow(clippy::integer_arithmetic)] // cannot overflow
+        let discriminant = handle >> data_size;
+
+        // the data is stored in the lower `data_size` bits
+        let data = handle & data_mask;
+
+        Self::new(discriminant, data)
+    }
+
+    pub fn to_scalar(self, cx: &impl HasDataLayout) -> Scalar<Provenance> {
+        // 64-bit handles are sign extended 32-bit handles
+        // see https://docs.microsoft.com/en-us/windows/win32/winprog64/interprocess-communication
+        #[allow(clippy::cast_possible_wrap)] // we want it to wrap
+        let signed_handle = self.to_packed() as i32;
+        Scalar::from_machine_isize(signed_handle.into(), cx)
+    }
+
+    pub fn from_scalar<'tcx>(
+        handle: Scalar<Provenance>,
+        cx: &impl HasDataLayout,
+    ) -> InterpResult<'tcx, Option<Self>> {
+        let sign_extended_handle = handle.to_machine_isize(cx)?;
+
+        #[allow(clippy::cast_sign_loss)] // we want to lose the sign
+        let handle = if let Ok(signed_handle) = i32::try_from(sign_extended_handle) {
+            signed_handle as u32
+        } else {
+            // if a handle doesn't fit in an i32, it isn't valid.
+            return Ok(None);
+        };
+
+        Ok(Self::from_packed(handle))
+    }
+}
+
+impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+
+#[allow(non_snake_case)]
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    fn invalid_handle(&mut self, function_name: &str) -> InterpResult<'tcx, !> {
+        throw_machine_stop!(TerminationInfo::Abort(format!(
+            "invalid handle passed to `{function_name}`"
+        )))
+    }
+
+    fn CloseHandle(&mut self, handle_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+
+        let handle = this.read_scalar(handle_op)?;
+
+        match Handle::from_scalar(handle, this)? {
+            Some(Handle::Thread(thread)) =>
+                this.detach_thread(thread, /*allow_terminated_joined*/ true)?,
+            _ => this.invalid_handle("CloseHandle")?,
+        }
+
+        Ok(())
+    }
+}
diff --git a/src/tools/miri/src/shims/windows/mod.rs b/src/tools/miri/src/shims/windows/mod.rs
new file mode 100644
index 0000000000000..40fe71b2dbd02
--- /dev/null
+++ b/src/tools/miri/src/shims/windows/mod.rs
@@ -0,0 +1,6 @@
+pub mod dlsym;
+pub mod foreign_items;
+
+mod handle;
+mod sync;
+mod thread;
diff --git a/src/tools/miri/src/shims/windows/sync.rs b/src/tools/miri/src/shims/windows/sync.rs
new file mode 100644
index 0000000000000..dc1052a824def
--- /dev/null
+++ b/src/tools/miri/src/shims/windows/sync.rs
@@ -0,0 +1,138 @@
+use crate::*;
+
+// Locks are pointer-sized pieces of data, initialized to 0.
+// We use the first 4 bytes to store the RwLockId.
+
+fn srwlock_get_or_create_id<'mir, 'tcx: 'mir>(
+    ecx: &mut MiriInterpCx<'mir, 'tcx>,
+    lock_op: &OpTy<'tcx, Provenance>,
+) -> InterpResult<'tcx, RwLockId> {
+    let value_place = ecx.deref_operand_and_offset(lock_op, 0, ecx.machine.layouts.u32)?;
+
+    ecx.rwlock_get_or_create(|ecx, next_id| {
+        let (old, success) = ecx
+            .atomic_compare_exchange_scalar(
+                &value_place,
+                &ImmTy::from_uint(0u32, ecx.machine.layouts.u32),
+                next_id.to_u32_scalar(),
+                AtomicRwOrd::Relaxed,
+                AtomicReadOrd::Relaxed,
+                false,
+            )?
+            .to_scalar_pair();
+
+        Ok(if success.to_bool().expect("compare_exchange's second return value is a bool") {
+            // Caller of the closure needs to allocate next_id
+            None
+        } else {
+            Some(RwLockId::from_u32(old.to_u32().expect("layout is u32")))
+        })
+    })
+}
+
+impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    #[allow(non_snake_case)]
+    fn AcquireSRWLockExclusive(&mut self, lock_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        let id = srwlock_get_or_create_id(this, lock_op)?;
+        let active_thread = this.get_active_thread();
+
+        if this.rwlock_is_locked(id) {
+            // Note: this will deadlock if the lock is already locked by this
+            // thread in any way.
+            //
+            // FIXME: Detect and report the deadlock proactively. (We currently
+            // report the deadlock only when no thread can continue execution,
+            // but we could detect that this lock is already locked and report
+            // an error.)
+            this.rwlock_enqueue_and_block_writer(id, active_thread);
+        } else {
+            this.rwlock_writer_lock(id, active_thread);
+        }
+
+        Ok(())
+    }
+
+    #[allow(non_snake_case)]
+    fn TryAcquireSRWLockExclusive(
+        &mut self,
+        lock_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, u8> {
+        let this = self.eval_context_mut();
+        let id = srwlock_get_or_create_id(this, lock_op)?;
+        let active_thread = this.get_active_thread();
+
+        if this.rwlock_is_locked(id) {
+            // Lock is already held.
+            Ok(0)
+        } else {
+            this.rwlock_writer_lock(id, active_thread);
+            Ok(1)
+        }
+    }
+
+    #[allow(non_snake_case)]
+    fn ReleaseSRWLockExclusive(&mut self, lock_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        let id = srwlock_get_or_create_id(this, lock_op)?;
+        let active_thread = this.get_active_thread();
+
+        if !this.rwlock_writer_unlock(id, active_thread) {
+            // The docs do not say anything about this case, but it seems better to not allow it.
+            throw_ub_format!(
+                "calling ReleaseSRWLockExclusive on an SRWLock that is not exclusively locked by the current thread"
+            );
+        }
+
+        Ok(())
+    }
+
+    #[allow(non_snake_case)]
+    fn AcquireSRWLockShared(&mut self, lock_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        let id = srwlock_get_or_create_id(this, lock_op)?;
+        let active_thread = this.get_active_thread();
+
+        if this.rwlock_is_write_locked(id) {
+            this.rwlock_enqueue_and_block_reader(id, active_thread);
+        } else {
+            this.rwlock_reader_lock(id, active_thread);
+        }
+
+        Ok(())
+    }
+
+    #[allow(non_snake_case)]
+    fn TryAcquireSRWLockShared(
+        &mut self,
+        lock_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, u8> {
+        let this = self.eval_context_mut();
+        let id = srwlock_get_or_create_id(this, lock_op)?;
+        let active_thread = this.get_active_thread();
+
+        if this.rwlock_is_write_locked(id) {
+            Ok(0)
+        } else {
+            this.rwlock_reader_lock(id, active_thread);
+            Ok(1)
+        }
+    }
+
+    #[allow(non_snake_case)]
+    fn ReleaseSRWLockShared(&mut self, lock_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        let id = srwlock_get_or_create_id(this, lock_op)?;
+        let active_thread = this.get_active_thread();
+
+        if !this.rwlock_reader_unlock(id, active_thread) {
+            // The docs do not say anything about this case, but it seems better to not allow it.
+            throw_ub_format!(
+                "calling ReleaseSRWLockShared on an SRWLock that is not locked by the current thread"
+            );
+        }
+
+        Ok(())
+    }
+}
diff --git a/src/tools/miri/src/shims/windows/thread.rs b/src/tools/miri/src/shims/windows/thread.rs
new file mode 100644
index 0000000000000..5ed0cb92f9e34
--- /dev/null
+++ b/src/tools/miri/src/shims/windows/thread.rs
@@ -0,0 +1,84 @@
+use rustc_middle::ty::layout::LayoutOf;
+use rustc_target::spec::abi::Abi;
+
+use crate::*;
+use shims::windows::handle::{EvalContextExt as _, Handle, PseudoHandle};
+
+impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+
+#[allow(non_snake_case)]
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    fn CreateThread(
+        &mut self,
+        security_op: &OpTy<'tcx, Provenance>,
+        stacksize_op: &OpTy<'tcx, Provenance>,
+        start_op: &OpTy<'tcx, Provenance>,
+        arg_op: &OpTy<'tcx, Provenance>,
+        flags_op: &OpTy<'tcx, Provenance>,
+        thread_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, ThreadId> {
+        let this = self.eval_context_mut();
+
+        let security = this.read_pointer(security_op)?;
+        // stacksize is ignored, but still needs to be a valid usize
+        this.read_scalar(stacksize_op)?.to_machine_usize(this)?;
+        let start_routine = this.read_pointer(start_op)?;
+        let func_arg = this.read_immediate(arg_op)?;
+        let flags = this.read_scalar(flags_op)?.to_u32()?;
+
+        let thread = if this.ptr_is_null(this.read_pointer(thread_op)?)? {
+            None
+        } else {
+            let thread_info_place = this.deref_operand(thread_op)?;
+            Some(thread_info_place)
+        };
+
+        let stack_size_param_is_a_reservation =
+            this.eval_windows("c", "STACK_SIZE_PARAM_IS_A_RESERVATION")?.to_u32()?;
+
+        // We ignore the stack size, so we also ignore the
+        // `STACK_SIZE_PARAM_IS_A_RESERVATION` flag.
+        if flags != 0 && flags != stack_size_param_is_a_reservation {
+            throw_unsup_format!("unsupported `dwCreationFlags` {} in `CreateThread`", flags)
+        }
+
+        if !this.ptr_is_null(security)? {
+            throw_unsup_format!("non-null `lpThreadAttributes` in `CreateThread`")
+        }
+
+        this.start_thread(
+            thread,
+            start_routine,
+            Abi::System { unwind: false },
+            func_arg,
+            this.layout_of(this.tcx.types.u32)?,
+        )
+    }
+
+    fn WaitForSingleObject(
+        &mut self,
+        handle_op: &OpTy<'tcx, Provenance>,
+        timeout_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, u32> {
+        let this = self.eval_context_mut();
+
+        let handle = this.read_scalar(handle_op)?;
+        let timeout = this.read_scalar(timeout_op)?.to_u32()?;
+
+        let thread = match Handle::from_scalar(handle, this)? {
+            Some(Handle::Thread(thread)) => thread,
+            // Unlike on posix, the outcome of joining the current thread is not documented.
+            // On current Windows, it just deadlocks.
+            Some(Handle::Pseudo(PseudoHandle::CurrentThread)) => this.get_active_thread(),
+            _ => this.invalid_handle("WaitForSingleObject")?,
+        };
+
+        if timeout != this.eval_windows("c", "INFINITE")?.to_u32()? {
+            throw_unsup_format!("`WaitForSingleObject` with non-infinite timeout");
+        }
+
+        this.join_thread(thread)?;
+
+        Ok(0)
+    }
+}
diff --git a/src/tools/miri/src/stacked_borrows/diagnostics.rs b/src/tools/miri/src/stacked_borrows/diagnostics.rs
new file mode 100644
index 0000000000000..0d76ed4e30878
--- /dev/null
+++ b/src/tools/miri/src/stacked_borrows/diagnostics.rs
@@ -0,0 +1,509 @@
+use smallvec::SmallVec;
+use std::fmt;
+
+use rustc_middle::mir::interpret::{alloc_range, AllocId, AllocRange};
+use rustc_span::{Span, SpanData};
+use rustc_target::abi::Size;
+
+use crate::helpers::CurrentSpan;
+use crate::stacked_borrows::{err_sb_ub, AccessKind, GlobalStateInner, Permission};
+use crate::*;
+
+use rustc_middle::mir::interpret::InterpError;
+
+#[derive(Clone, Debug)]
+pub struct AllocHistory {
+    id: AllocId,
+    base: (Item, Span),
+    creations: smallvec::SmallVec<[Creation; 1]>,
+    invalidations: smallvec::SmallVec<[Invalidation; 1]>,
+    protectors: smallvec::SmallVec<[Protection; 1]>,
+}
+
+#[derive(Clone, Debug)]
+struct Creation {
+    retag: RetagOp,
+    span: Span,
+}
+
+impl Creation {
+    fn generate_diagnostic(&self) -> (String, SpanData) {
+        let tag = self.retag.new_tag;
+        if let Some(perm) = self.retag.permission {
+            (
+                format!(
+                    "{tag:?} was created by a {:?} retag at offsets {:?}",
+                    perm, self.retag.range,
+                ),
+                self.span.data(),
+            )
+        } else {
+            assert!(self.retag.range.size == Size::ZERO);
+            (
+                format!(
+                    "{tag:?} would have been created here, but this is a zero-size retag ({:?}) so the tag in question does not exist anywhere",
+                    self.retag.range,
+                ),
+                self.span.data(),
+            )
+        }
+    }
+}
+
+#[derive(Clone, Debug)]
+struct Invalidation {
+    tag: SbTag,
+    range: AllocRange,
+    span: Span,
+    cause: InvalidationCause,
+}
+
+#[derive(Clone, Debug)]
+enum InvalidationCause {
+    Access(AccessKind),
+    Retag(Permission, RetagCause),
+}
+
+impl Invalidation {
+    fn generate_diagnostic(&self) -> (String, SpanData) {
+        (
+            format!(
+                "{:?} was later invalidated at offsets {:?} by a {}",
+                self.tag, self.range, self.cause
+            ),
+            self.span.data(),
+        )
+    }
+}
+
+impl fmt::Display for InvalidationCause {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            InvalidationCause::Access(kind) => write!(f, "{}", kind),
+            InvalidationCause::Retag(perm, kind) =>
+                if *kind == RetagCause::FnEntry {
+                    write!(f, "{:?} FnEntry retag", perm)
+                } else {
+                    write!(f, "{:?} retag", perm)
+                },
+        }
+    }
+}
+
+#[derive(Clone, Debug)]
+struct Protection {
+    tag: SbTag,
+    span: Span,
+}
+
+#[derive(Clone)]
+pub struct TagHistory {
+    pub created: (String, SpanData),
+    pub invalidated: Option<(String, SpanData)>,
+    pub protected: Option<(String, SpanData)>,
+}
+
+pub struct DiagnosticCxBuilder<'span, 'ecx, 'mir, 'tcx> {
+    operation: Operation,
+    // 'span cannot be merged with any other lifetime since they appear invariantly, under the
+    // mutable ref.
+    current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>,
+    threads: &'ecx ThreadManager<'mir, 'tcx>,
+}
+
+pub struct DiagnosticCx<'span, 'history, 'ecx, 'mir, 'tcx> {
+    operation: Operation,
+    // 'span and 'history cannot be merged, since when we call `unbuild` we need
+    // to return the exact 'span that was used when calling `build`.
+    current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>,
+    threads: &'ecx ThreadManager<'mir, 'tcx>,
+    history: &'history mut AllocHistory,
+    offset: Size,
+}
+
+impl<'span, 'ecx, 'mir, 'tcx> DiagnosticCxBuilder<'span, 'ecx, 'mir, 'tcx> {
+    pub fn build<'history>(
+        self,
+        history: &'history mut AllocHistory,
+        offset: Size,
+    ) -> DiagnosticCx<'span, 'history, 'ecx, 'mir, 'tcx> {
+        DiagnosticCx {
+            operation: self.operation,
+            current_span: self.current_span,
+            threads: self.threads,
+            history,
+            offset,
+        }
+    }
+
+    pub fn retag(
+        current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>,
+        threads: &'ecx ThreadManager<'mir, 'tcx>,
+        cause: RetagCause,
+        new_tag: SbTag,
+        orig_tag: ProvenanceExtra,
+        range: AllocRange,
+    ) -> Self {
+        let operation =
+            Operation::Retag(RetagOp { cause, new_tag, orig_tag, range, permission: None });
+
+        DiagnosticCxBuilder { current_span, threads, operation }
+    }
+
+    pub fn read(
+        current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>,
+        threads: &'ecx ThreadManager<'mir, 'tcx>,
+        tag: ProvenanceExtra,
+        range: AllocRange,
+    ) -> Self {
+        let operation = Operation::Access(AccessOp { kind: AccessKind::Read, tag, range });
+        DiagnosticCxBuilder { current_span, threads, operation }
+    }
+
+    pub fn write(
+        current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>,
+        threads: &'ecx ThreadManager<'mir, 'tcx>,
+        tag: ProvenanceExtra,
+        range: AllocRange,
+    ) -> Self {
+        let operation = Operation::Access(AccessOp { kind: AccessKind::Write, tag, range });
+        DiagnosticCxBuilder { current_span, threads, operation }
+    }
+
+    pub fn dealloc(
+        current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>,
+        threads: &'ecx ThreadManager<'mir, 'tcx>,
+        tag: ProvenanceExtra,
+    ) -> Self {
+        let operation = Operation::Dealloc(DeallocOp { tag });
+        DiagnosticCxBuilder { current_span, threads, operation }
+    }
+}
+
+impl<'span, 'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'span, 'history, 'ecx, 'mir, 'tcx> {
+    pub fn unbuild(self) -> DiagnosticCxBuilder<'span, 'ecx, 'mir, 'tcx> {
+        DiagnosticCxBuilder {
+            operation: self.operation,
+            current_span: self.current_span,
+            threads: self.threads,
+        }
+    }
+}
+
+#[derive(Debug, Clone)]
+enum Operation {
+    Retag(RetagOp),
+    Access(AccessOp),
+    Dealloc(DeallocOp),
+}
+
+#[derive(Debug, Clone)]
+struct RetagOp {
+    cause: RetagCause,
+    new_tag: SbTag,
+    orig_tag: ProvenanceExtra,
+    range: AllocRange,
+    permission: Option<Permission>,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum RetagCause {
+    Normal,
+    FnReturn,
+    FnEntry,
+    TwoPhase,
+}
+
+#[derive(Debug, Clone)]
+struct AccessOp {
+    kind: AccessKind,
+    tag: ProvenanceExtra,
+    range: AllocRange,
+}
+
+#[derive(Debug, Clone)]
+struct DeallocOp {
+    tag: ProvenanceExtra,
+}
+
+impl AllocHistory {
+    pub fn new(id: AllocId, item: Item, current_span: &mut CurrentSpan<'_, '_, '_>) -> Self {
+        Self {
+            id,
+            base: (item, current_span.get()),
+            creations: SmallVec::new(),
+            invalidations: SmallVec::new(),
+            protectors: SmallVec::new(),
+        }
+    }
+}
+
+impl<'span, 'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'span, 'history, 'ecx, 'mir, 'tcx> {
+    pub fn start_grant(&mut self, perm: Permission) {
+        let Operation::Retag(op) = &mut self.operation else {
+            unreachable!("start_grant must only be called during a retag, this is: {:?}", self.operation)
+        };
+        op.permission = Some(perm);
+
+        let last_creation = &mut self.history.creations.last_mut().unwrap();
+        match last_creation.retag.permission {
+            None => {
+                last_creation.retag.permission = Some(perm);
+            }
+            Some(previous) =>
+                if previous != perm {
+                    // 'Split up' the creation event.
+                    let previous_range = last_creation.retag.range;
+                    last_creation.retag.range = alloc_range(previous_range.start, self.offset);
+                    let mut new_event = last_creation.clone();
+                    new_event.retag.range = alloc_range(self.offset, previous_range.end());
+                    new_event.retag.permission = Some(perm);
+                    self.history.creations.push(new_event);
+                },
+        }
+    }
+
+    pub fn log_creation(&mut self) {
+        let Operation::Retag(op) = &self.operation else {
+            unreachable!("log_creation must only be called during a retag")
+        };
+        self.history.creations.push(Creation { retag: op.clone(), span: self.current_span.get() });
+    }
+
+    pub fn log_invalidation(&mut self, tag: SbTag) {
+        let mut span = self.current_span.get();
+        let (range, cause) = match &self.operation {
+            Operation::Retag(RetagOp { cause, range, permission, .. }) => {
+                if *cause == RetagCause::FnEntry {
+                    span = self.current_span.get_parent();
+                }
+                (*range, InvalidationCause::Retag(permission.unwrap(), *cause))
+            }
+            Operation::Access(AccessOp { kind, range, .. }) =>
+                (*range, InvalidationCause::Access(*kind)),
+            _ => unreachable!("Tags can only be invalidated during a retag or access"),
+        };
+        self.history.invalidations.push(Invalidation { tag, range, span, cause });
+    }
+
+    pub fn log_protector(&mut self) {
+        let Operation::Retag(op) = &self.operation else {
+            unreachable!("Protectors can only be created during a retag")
+        };
+        self.history.protectors.push(Protection { tag: op.new_tag, span: self.current_span.get() });
+    }
+
+    pub fn get_logs_relevant_to(
+        &self,
+        tag: SbTag,
+        protector_tag: Option<SbTag>,
+    ) -> Option<TagHistory> {
+        let Some(created) = self.history
+            .creations
+            .iter()
+            .rev()
+            .find_map(|event| {
+                // First, look for a Creation event where the tag and the offset matches. This
+                // ensrues that we pick the right Creation event when a retag isn't uniform due to
+                // Freeze.
+                let range = event.retag.range;
+                if event.retag.new_tag == tag
+                    && self.offset >= range.start
+                    && self.offset < (range.start + range.size)
+                {
+                    Some(event.generate_diagnostic())
+                } else {
+                    None
+                }
+            })
+            .or_else(|| {
+                // If we didn't find anything with a matching offset, just return the event where
+                // the tag was created. This branch is hit when we use a tag at an offset that
+                // doesn't have the tag.
+                self.history.creations.iter().rev().find_map(|event| {
+                    if event.retag.new_tag == tag {
+                        Some(event.generate_diagnostic())
+                    } else {
+                        None
+                    }
+                })
+            }).or_else(|| {
+                // If we didn't find a retag that created this tag, it might be the base tag of
+                // this allocation.
+                if self.history.base.0.tag() == tag {
+                    Some((
+                        format!("{:?} was created here, as the base tag for {:?}", tag, self.history.id),
+                        self.history.base.1.data()
+                    ))
+                } else {
+                    None
+                }
+            }) else {
+                // But if we don't have a creation event, this is related to a wildcard, and there
+                // is really nothing we can do to help.
+                return None;
+            };
+
+        let invalidated = self.history.invalidations.iter().rev().find_map(|event| {
+            if event.tag == tag { Some(event.generate_diagnostic()) } else { None }
+        });
+
+        let protected = protector_tag
+            .and_then(|protector| {
+                self.history.protectors.iter().find(|protection| protection.tag == protector)
+            })
+            .map(|protection| {
+                let protected_tag = protection.tag;
+                (format!("{protected_tag:?} is this argument"), protection.span.data())
+            });
+
+        Some(TagHistory { created, invalidated, protected })
+    }
+
+    /// Report a descriptive error when `new` could not be granted from `derived_from`.
+    #[inline(never)] // This is only called on fatal code paths
+    pub fn grant_error(&self, perm: Permission, stack: &Stack) -> InterpError<'tcx> {
+        let Operation::Retag(op) = &self.operation else {
+            unreachable!("grant_error should only be called during a retag")
+        };
+        let action = format!(
+            "trying to retag from {:?} for {:?} permission at {:?}[{:#x}]",
+            op.orig_tag,
+            perm,
+            self.history.id,
+            self.offset.bytes(),
+        );
+        err_sb_ub(
+            format!("{}{}", action, error_cause(stack, op.orig_tag)),
+            Some(operation_summary(&op.cause.summary(), self.history.id, op.range)),
+            op.orig_tag.and_then(|orig_tag| self.get_logs_relevant_to(orig_tag, None)),
+        )
+    }
+
+    /// Report a descriptive error when `access` is not permitted based on `tag`.
+    #[inline(never)] // This is only called on fatal code paths
+    pub fn access_error(&self, stack: &Stack) -> InterpError<'tcx> {
+        let Operation::Access(op) = &self.operation  else {
+            unreachable!("access_error should only be called during an access")
+        };
+        let action = format!(
+            "attempting a {access} using {tag:?} at {alloc_id:?}[{offset:#x}]",
+            access = op.kind,
+            tag = op.tag,
+            alloc_id = self.history.id,
+            offset = self.offset.bytes(),
+        );
+        err_sb_ub(
+            format!("{}{}", action, error_cause(stack, op.tag)),
+            Some(operation_summary("an access", self.history.id, op.range)),
+            op.tag.and_then(|tag| self.get_logs_relevant_to(tag, None)),
+        )
+    }
+
+    #[inline(never)] // This is only called on fatal code paths
+    pub fn protector_error(&self, item: &Item) -> InterpError<'tcx> {
+        let call_id = self
+            .threads
+            .all_stacks()
+            .flatten()
+            .map(|frame| {
+                frame.extra.stacked_borrows.as_ref().expect("we should have Stacked Borrows data")
+            })
+            .find(|frame| frame.protected_tags.contains(&item.tag()))
+            .map(|frame| frame.call_id)
+            .unwrap(); // FIXME: Surely we should find something, but a panic seems wrong here?
+        match self.operation {
+            Operation::Dealloc(_) =>
+                err_sb_ub(
+                    format!(
+                        "deallocating while item {:?} is protected by call {:?}",
+                        item, call_id
+                    ),
+                    None,
+                    None,
+                ),
+            Operation::Retag(RetagOp { orig_tag: tag, .. })
+            | Operation::Access(AccessOp { tag, .. }) =>
+                err_sb_ub(
+                    format!(
+                        "not granting access to tag {:?} because that would remove {:?} which is protected because it is an argument of call {:?}",
+                        tag, item, call_id
+                    ),
+                    None,
+                    tag.and_then(|tag| self.get_logs_relevant_to(tag, Some(item.tag()))),
+                ),
+        }
+    }
+
+    #[inline(never)] // This is only called on fatal code paths
+    pub fn dealloc_error(&self) -> InterpError<'tcx> {
+        let Operation::Dealloc(op) = &self.operation else {
+            unreachable!("dealloc_error should only be called during a deallocation")
+        };
+        err_sb_ub(
+            format!(
+                "no item granting write access for deallocation to tag {:?} at {:?} found in borrow stack",
+                op.tag, self.history.id,
+            ),
+            None,
+            op.tag.and_then(|tag| self.get_logs_relevant_to(tag, None)),
+        )
+    }
+
+    #[inline(never)]
+    pub fn check_tracked_tag_popped(&self, item: &Item, global: &GlobalStateInner) {
+        if !global.tracked_pointer_tags.contains(&item.tag()) {
+            return;
+        }
+        let summary = match self.operation {
+            Operation::Dealloc(_) => None,
+            Operation::Access(AccessOp { kind, tag, .. }) => Some((tag, kind)),
+            Operation::Retag(RetagOp { orig_tag, permission, .. }) => {
+                let kind = match permission
+                    .expect("start_grant should set the current permission before popping a tag")
+                {
+                    Permission::SharedReadOnly => AccessKind::Read,
+                    Permission::Unique => AccessKind::Write,
+                    Permission::SharedReadWrite | Permission::Disabled => {
+                        panic!("Only SharedReadOnly and Unique retags can pop tags");
+                    }
+                };
+                Some((orig_tag, kind))
+            }
+        };
+        self.current_span
+            .machine()
+            .emit_diagnostic(NonHaltingDiagnostic::PoppedPointerTag(*item, summary));
+    }
+}
+
+fn operation_summary(operation: &str, alloc_id: AllocId, alloc_range: AllocRange) -> String {
+    format!("this error occurs as part of {operation} at {alloc_id:?}{alloc_range:?}")
+}
+
+fn error_cause(stack: &Stack, prov_extra: ProvenanceExtra) -> &'static str {
+    if let ProvenanceExtra::Concrete(tag) = prov_extra {
+        if (0..stack.len())
+            .map(|i| stack.get(i).unwrap())
+            .any(|item| item.tag() == tag && item.perm() != Permission::Disabled)
+        {
+            ", but that tag only grants SharedReadOnly permission for this location"
+        } else {
+            ", but that tag does not exist in the borrow stack for this location"
+        }
+    } else {
+        ", but no exposed tags have suitable permission in the borrow stack for this location"
+    }
+}
+
+impl RetagCause {
+    fn summary(&self) -> String {
+        match self {
+            RetagCause::Normal => "retag",
+            RetagCause::FnEntry => "FnEntry retag",
+            RetagCause::FnReturn => "FnReturn retag",
+            RetagCause::TwoPhase => "two-phase retag",
+        }
+        .to_string()
+    }
+}
diff --git a/src/tools/miri/src/stacked_borrows/item.rs b/src/tools/miri/src/stacked_borrows/item.rs
new file mode 100644
index 0000000000000..709b27d191b26
--- /dev/null
+++ b/src/tools/miri/src/stacked_borrows/item.rs
@@ -0,0 +1,104 @@
+use crate::stacked_borrows::SbTag;
+use std::fmt;
+use std::num::NonZeroU64;
+
+/// An item in the per-location borrow stack.
+#[derive(Copy, Clone, Hash, PartialEq, Eq)]
+pub struct Item(u64);
+
+// An Item contains 3 bitfields:
+// * Bits 0-61 store an SbTag
+// * Bits 61-63 store a Permission
+// * Bit 64 stores a flag which indicates if we have a protector
+const TAG_MASK: u64 = u64::MAX >> 3;
+const PERM_MASK: u64 = 0x3 << 61;
+const PROTECTED_MASK: u64 = 0x1 << 63;
+
+const PERM_SHIFT: u64 = 61;
+const PROTECTED_SHIFT: u64 = 63;
+
+impl Item {
+    pub fn new(tag: SbTag, perm: Permission, protected: bool) -> Self {
+        assert!(tag.0.get() <= TAG_MASK);
+        let packed_tag = tag.0.get();
+        let packed_perm = perm.to_bits() << PERM_SHIFT;
+        let packed_protected = u64::from(protected) << PROTECTED_SHIFT;
+
+        let new = Self(packed_tag | packed_perm | packed_protected);
+
+        debug_assert!(new.tag() == tag);
+        debug_assert!(new.perm() == perm);
+        debug_assert!(new.protected() == protected);
+
+        new
+    }
+
+    /// The pointers the permission is granted to.
+    pub fn tag(self) -> SbTag {
+        SbTag(NonZeroU64::new(self.0 & TAG_MASK).unwrap())
+    }
+
+    /// The permission this item grants.
+    pub fn perm(self) -> Permission {
+        Permission::from_bits((self.0 & PERM_MASK) >> PERM_SHIFT)
+    }
+
+    /// Whether or not there is a protector for this tag
+    pub fn protected(self) -> bool {
+        self.0 & PROTECTED_MASK > 0
+    }
+
+    /// Set the Permission stored in this Item
+    pub fn set_permission(&mut self, perm: Permission) {
+        // Clear the current set permission
+        self.0 &= !PERM_MASK;
+        // Write Permission::Disabled to the Permission bits
+        self.0 |= perm.to_bits() << PERM_SHIFT;
+    }
+}
+
+impl fmt::Debug for Item {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "[{:?} for {:?}]", self.perm(), self.tag())
+    }
+}
+
+/// Indicates which permission is granted (by this item to some pointers)
+#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
+pub enum Permission {
+    /// Grants unique mutable access.
+    Unique,
+    /// Grants shared mutable access.
+    SharedReadWrite,
+    /// Grants shared read-only access.
+    SharedReadOnly,
+    /// Grants no access, but separates two groups of SharedReadWrite so they are not
+    /// all considered mutually compatible.
+    Disabled,
+}
+
+impl Permission {
+    const UNIQUE: u64 = 0;
+    const SHARED_READ_WRITE: u64 = 1;
+    const SHARED_READ_ONLY: u64 = 2;
+    const DISABLED: u64 = 3;
+
+    fn to_bits(self) -> u64 {
+        match self {
+            Permission::Unique => Self::UNIQUE,
+            Permission::SharedReadWrite => Self::SHARED_READ_WRITE,
+            Permission::SharedReadOnly => Self::SHARED_READ_ONLY,
+            Permission::Disabled => Self::DISABLED,
+        }
+    }
+
+    fn from_bits(perm: u64) -> Self {
+        match perm {
+            Self::UNIQUE => Permission::Unique,
+            Self::SHARED_READ_WRITE => Permission::SharedReadWrite,
+            Self::SHARED_READ_ONLY => Permission::SharedReadOnly,
+            Self::DISABLED => Permission::Disabled,
+            _ => unreachable!(),
+        }
+    }
+}
diff --git a/src/tools/miri/src/stacked_borrows/mod.rs b/src/tools/miri/src/stacked_borrows/mod.rs
new file mode 100644
index 0000000000000..f7f4b1357f106
--- /dev/null
+++ b/src/tools/miri/src/stacked_borrows/mod.rs
@@ -0,0 +1,1087 @@
+//! Implements "Stacked Borrows".  See <https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md>
+//! for further information.
+
+use log::trace;
+use std::cell::RefCell;
+use std::cmp;
+use std::fmt;
+use std::num::NonZeroU64;
+
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_hir::Mutability;
+use rustc_middle::mir::RetagKind;
+use rustc_middle::ty::{
+    self,
+    layout::{HasParamEnv, LayoutOf},
+    Ty,
+};
+use rustc_span::DUMMY_SP;
+use rustc_target::abi::Size;
+use smallvec::SmallVec;
+
+use crate::*;
+
+pub mod diagnostics;
+use diagnostics::{AllocHistory, DiagnosticCx, DiagnosticCxBuilder, RetagCause, TagHistory};
+
+mod item;
+pub use item::{Item, Permission};
+mod stack;
+pub use stack::Stack;
+
+pub type CallId = NonZeroU64;
+
+// Even reading memory can have effects on the stack, so we need a `RefCell` here.
+pub type AllocExtra = RefCell<Stacks>;
+
+/// Tracking pointer provenance
+#[derive(Copy, Clone, Hash, PartialEq, Eq)]
+pub struct SbTag(NonZeroU64);
+
+impl SbTag {
+    pub fn new(i: u64) -> Option<Self> {
+        NonZeroU64::new(i).map(SbTag)
+    }
+
+    // The default to be used when SB is disabled
+    pub fn default() -> Self {
+        Self::new(1).unwrap()
+    }
+}
+
+impl fmt::Debug for SbTag {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "<{}>", self.0)
+    }
+}
+
+#[derive(Debug)]
+pub struct FrameExtra {
+    /// The ID of the call this frame corresponds to.
+    call_id: CallId,
+
+    /// If this frame is protecting any tags, they are listed here. We use this list to do
+    /// incremental updates of the global list of protected tags stored in the
+    /// `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.
+    ///
+    /// This will contain one tag per reference passed to the function, so
+    /// a size of 2 is enough for the vast majority of functions.
+    protected_tags: SmallVec<[SbTag; 2]>,
+}
+
+/// Extra per-allocation state.
+#[derive(Clone, Debug)]
+pub struct Stacks {
+    // Even reading memory can have effects on the stack, so we need a `RefCell` here.
+    stacks: RangeMap<Stack>,
+    /// Stores past operations on this allocation
+    history: AllocHistory,
+    /// The set of tags that have been exposed inside this allocation.
+    exposed_tags: FxHashSet<SbTag>,
+    /// Whether this memory has been modified since the last time the tag GC ran
+    modified_since_last_gc: bool,
+}
+
+/// Extra global state, available to the memory access hooks.
+#[derive(Debug)]
+pub struct GlobalStateInner {
+    /// Next unused pointer ID (tag).
+    next_ptr_tag: SbTag,
+    /// Table storing the "base" tag for each allocation.
+    /// The base tag is the one used for the initial pointer.
+    /// We need this in a separate table to handle cyclic statics.
+    base_ptr_tags: FxHashMap<AllocId, SbTag>,
+    /// Next unused call ID (for protectors).
+    next_call_id: CallId,
+    /// All currently protected tags.
+    /// An item is protected if its tag is in this set, *and* it has the "protected" bit set.
+    /// We add tags to this when they are created with a protector in `reborrow`, and
+    /// we remove tags from this when the call which is protecting them returns, in
+    /// `GlobalStateInner::end_call`. See `Stack::item_popped` for more details.
+    protected_tags: FxHashSet<SbTag>,
+    /// The pointer ids to trace
+    tracked_pointer_tags: FxHashSet<SbTag>,
+    /// The call ids to trace
+    tracked_call_ids: FxHashSet<CallId>,
+    /// Whether to recurse into datatypes when searching for pointers to retag.
+    retag_fields: bool,
+}
+
+/// We need interior mutable access to the global state.
+pub type GlobalState = RefCell<GlobalStateInner>;
+
+/// Indicates which kind of access is being performed.
+#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)]
+pub enum AccessKind {
+    Read,
+    Write,
+}
+
+impl fmt::Display for AccessKind {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            AccessKind::Read => write!(f, "read access"),
+            AccessKind::Write => write!(f, "write access"),
+        }
+    }
+}
+
+/// Indicates which kind of reference is being created.
+/// Used by high-level `reborrow` to compute which permissions to grant to the
+/// new pointer.
+#[derive(Copy, Clone, Hash, PartialEq, Eq)]
+pub enum RefKind {
+    /// `&mut` and `Box`.
+    Unique { two_phase: bool },
+    /// `&` with or without interior mutability.
+    Shared,
+    /// `*mut`/`*const` (raw pointers).
+    Raw { mutable: bool },
+}
+
+impl fmt::Display for RefKind {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            RefKind::Unique { two_phase: false } => write!(f, "unique reference"),
+            RefKind::Unique { two_phase: true } => write!(f, "unique reference (two-phase)"),
+            RefKind::Shared => write!(f, "shared reference"),
+            RefKind::Raw { mutable: true } => write!(f, "raw (mutable) pointer"),
+            RefKind::Raw { mutable: false } => write!(f, "raw (constant) pointer"),
+        }
+    }
+}
+
+/// Utilities for initialization and ID generation
+impl GlobalStateInner {
+    pub fn new(
+        tracked_pointer_tags: FxHashSet<SbTag>,
+        tracked_call_ids: FxHashSet<CallId>,
+        retag_fields: bool,
+    ) -> Self {
+        GlobalStateInner {
+            next_ptr_tag: SbTag(NonZeroU64::new(1).unwrap()),
+            base_ptr_tags: FxHashMap::default(),
+            next_call_id: NonZeroU64::new(1).unwrap(),
+            protected_tags: FxHashSet::default(),
+            tracked_pointer_tags,
+            tracked_call_ids,
+            retag_fields,
+        }
+    }
+
+    /// Generates a new pointer tag. Remember to also check track_pointer_tags and log its creation!
+    fn new_ptr(&mut self) -> SbTag {
+        let id = self.next_ptr_tag;
+        self.next_ptr_tag = SbTag(NonZeroU64::new(id.0.get() + 1).unwrap());
+        id
+    }
+
+    pub fn new_frame(&mut self, machine: &MiriMachine<'_, '_>) -> FrameExtra {
+        let call_id = self.next_call_id;
+        trace!("new_frame: Assigning call ID {}", call_id);
+        if self.tracked_call_ids.contains(&call_id) {
+            machine.emit_diagnostic(NonHaltingDiagnostic::CreatedCallId(call_id));
+        }
+        self.next_call_id = NonZeroU64::new(call_id.get() + 1).unwrap();
+        FrameExtra { call_id, protected_tags: SmallVec::new() }
+    }
+
+    pub fn end_call(&mut self, frame: &machine::FrameData<'_>) {
+        for tag in &frame
+            .stacked_borrows
+            .as_ref()
+            .expect("we should have Stacked Borrows data")
+            .protected_tags
+        {
+            self.protected_tags.remove(tag);
+        }
+    }
+
+    pub fn base_ptr_tag(&mut self, id: AllocId, machine: &MiriMachine<'_, '_>) -> SbTag {
+        self.base_ptr_tags.get(&id).copied().unwrap_or_else(|| {
+            let tag = self.new_ptr();
+            if self.tracked_pointer_tags.contains(&tag) {
+                machine.emit_diagnostic(NonHaltingDiagnostic::CreatedPointerTag(tag.0, None));
+            }
+            trace!("New allocation {:?} has base tag {:?}", id, tag);
+            self.base_ptr_tags.try_insert(id, tag).unwrap();
+            tag
+        })
+    }
+}
+
+/// Error reporting
+pub fn err_sb_ub<'tcx>(
+    msg: String,
+    help: Option<String>,
+    history: Option<TagHistory>,
+) -> InterpError<'tcx> {
+    err_machine_stop!(TerminationInfo::StackedBorrowsUb { msg, help, history })
+}
+
+// # Stacked Borrows Core Begin
+
+/// We need to make at least the following things true:
+///
+/// U1: After creating a `Uniq`, it is at the top.
+/// U2: If the top is `Uniq`, accesses must be through that `Uniq` or remove it it.
+/// U3: If an access happens with a `Uniq`, it requires the `Uniq` to be in the stack.
+///
+/// F1: After creating a `&`, the parts outside `UnsafeCell` have our `SharedReadOnly` on top.
+/// F2: If a write access happens, it pops the `SharedReadOnly`.  This has three pieces:
+///     F2a: If a write happens granted by an item below our `SharedReadOnly`, the `SharedReadOnly`
+///          gets popped.
+///     F2b: No `SharedReadWrite` or `Unique` will ever be added on top of our `SharedReadOnly`.
+/// F3: If an access happens with an `&` outside `UnsafeCell`,
+///     it requires the `SharedReadOnly` to still be in the stack.
+
+/// Core relation on `Permission` to define which accesses are allowed
+impl Permission {
+    /// This defines for a given permission, whether it permits the given kind of access.
+    fn grants(self, access: AccessKind) -> bool {
+        // Disabled grants nothing. Otherwise, all items grant read access, and except for SharedReadOnly they grant write access.
+        self != Permission::Disabled
+            && (access == AccessKind::Read || self != Permission::SharedReadOnly)
+    }
+}
+
+/// Core per-location operations: access, dealloc, reborrow.
+impl<'tcx> Stack {
+    /// Find the first write-incompatible item above the given one --
+    /// i.e, find the height to which the stack will be truncated when writing to `granting`.
+    fn find_first_write_incompatible(&self, granting: usize) -> usize {
+        let perm = self.get(granting).unwrap().perm();
+        match perm {
+            Permission::SharedReadOnly => bug!("Cannot use SharedReadOnly for writing"),
+            Permission::Disabled => bug!("Cannot use Disabled for anything"),
+            Permission::Unique => {
+                // On a write, everything above us is incompatible.
+                granting + 1
+            }
+            Permission::SharedReadWrite => {
+                // The SharedReadWrite *just* above us are compatible, to skip those.
+                let mut idx = granting + 1;
+                while let Some(item) = self.get(idx) {
+                    if item.perm() == Permission::SharedReadWrite {
+                        // Go on.
+                        idx += 1;
+                    } else {
+                        // Found first incompatible!
+                        break;
+                    }
+                }
+                idx
+            }
+        }
+    }
+
+    /// Check if the given item is protected.
+    ///
+    /// The `provoking_access` argument is only used to produce diagnostics.
+    /// It is `Some` when we are granting the contained access for said tag, and it is
+    /// `None` during a deallocation.
+    /// Within `provoking_access, the `AllocRange` refers the entire operation, and
+    /// the `Size` refers to the specific location in the `AllocRange` that we are
+    /// currently checking.
+    fn item_popped(
+        item: &Item,
+        global: &GlobalStateInner,
+        dcx: &mut DiagnosticCx<'_, '_, '_, '_, 'tcx>,
+    ) -> InterpResult<'tcx> {
+        if !global.tracked_pointer_tags.is_empty() {
+            dcx.check_tracked_tag_popped(item, global);
+        }
+
+        if !item.protected() {
+            return Ok(());
+        }
+
+        // We store tags twice, once in global.protected_tags and once in each call frame.
+        // We do this because consulting a single global set in this function is faster
+        // than attempting to search all call frames in the program for the `FrameExtra`
+        // (if any) which is protecting the popped tag.
+        //
+        // This duplication trades off making `end_call` slower to make this function faster. This
+        // trade-off is profitable in practice for a combination of two reasons.
+        // 1. A single protected tag can (and does in some programs) protect thousands of `Item`s.
+        //    Therefore, adding overhead in function call/return is profitable even if it only
+        //    saves a little work in this function.
+        // 2. Most frames protect only one or two tags. So this duplicative global turns a search
+        //    which ends up about linear in the number of protected tags in the program into a
+        //    constant time check (and a slow linear, because the tags in the frames aren't contiguous).
+        if global.protected_tags.contains(&item.tag()) {
+            return Err(dcx.protector_error(item).into());
+        }
+        Ok(())
+    }
+
+    /// Test if a memory `access` using pointer tagged `tag` is granted.
+    /// If yes, return the index of the item that granted it.
+    /// `range` refers the entire operation, and `offset` refers to the specific offset into the
+    /// allocation that we are currently checking.
+    fn access(
+        &mut self,
+        access: AccessKind,
+        tag: ProvenanceExtra,
+        global: &mut GlobalStateInner,
+        dcx: &mut DiagnosticCx<'_, '_, '_, '_, 'tcx>,
+        exposed_tags: &FxHashSet<SbTag>,
+    ) -> InterpResult<'tcx> {
+        // Two main steps: Find granting item, remove incompatible items above.
+
+        // Step 1: Find granting item.
+        let granting_idx =
+            self.find_granting(access, tag, exposed_tags).map_err(|_| dcx.access_error(self))?;
+
+        // Step 2: Remove incompatible items above them.  Make sure we do not remove protected
+        // items.  Behavior differs for reads and writes.
+        // In case of wildcards/unknown matches, we remove everything that is *definitely* gone.
+        if access == AccessKind::Write {
+            // Remove everything above the write-compatible items, like a proper stack. This makes sure read-only and unique
+            // pointers become invalid on write accesses (ensures F2a, and ensures U2 for write accesses).
+            let first_incompatible_idx = if let Some(granting_idx) = granting_idx {
+                // The granting_idx *might* be approximate, but any lower idx would remove more
+                // things. Even if this is a Unique and the lower idx is an SRW (which removes
+                // less), there is an SRW group boundary here so strictly more would get removed.
+                self.find_first_write_incompatible(granting_idx)
+            } else {
+                // We are writing to something in the unknown part.
+                // There is a SRW group boundary between the unknown and the known, so everything is incompatible.
+                0
+            };
+            self.pop_items_after(first_incompatible_idx, |item| {
+                Stack::item_popped(&item, global, dcx)?;
+                dcx.log_invalidation(item.tag());
+                Ok(())
+            })?;
+        } else {
+            // On a read, *disable* all `Unique` above the granting item.  This ensures U2 for read accesses.
+            // The reason this is not following the stack discipline (by removing the first Unique and
+            // everything on top of it) is that in `let raw = &mut *x as *mut _; let _val = *x;`, the second statement
+            // would pop the `Unique` from the reborrow of the first statement, and subsequently also pop the
+            // `SharedReadWrite` for `raw`.
+            // This pattern occurs a lot in the standard library: create a raw pointer, then also create a shared
+            // reference and use that.
+            // We *disable* instead of removing `Unique` to avoid "connecting" two neighbouring blocks of SRWs.
+            let first_incompatible_idx = if let Some(granting_idx) = granting_idx {
+                // The granting_idx *might* be approximate, but any lower idx would disable more things.
+                granting_idx + 1
+            } else {
+                // We are reading from something in the unknown part. That means *all* `Unique` we know about are dead now.
+                0
+            };
+            self.disable_uniques_starting_at(first_incompatible_idx, |item| {
+                Stack::item_popped(&item, global, dcx)?;
+                dcx.log_invalidation(item.tag());
+                Ok(())
+            })?;
+        }
+
+        // If this was an approximate action, we now collapse everything into an unknown.
+        if granting_idx.is_none() || matches!(tag, ProvenanceExtra::Wildcard) {
+            // Compute the upper bound of the items that remain.
+            // (This is why we did all the work above: to reduce the items we have to consider here.)
+            let mut max = NonZeroU64::new(1).unwrap();
+            for i in 0..self.len() {
+                let item = self.get(i).unwrap();
+                // Skip disabled items, they cannot be matched anyway.
+                if !matches!(item.perm(), Permission::Disabled) {
+                    // We are looking for a strict upper bound, so add 1 to this tag.
+                    max = cmp::max(item.tag().0.checked_add(1).unwrap(), max);
+                }
+            }
+            if let Some(unk) = self.unknown_bottom() {
+                max = cmp::max(unk.0, max);
+            }
+            // Use `max` as new strict upper bound for everything.
+            trace!(
+                "access: forgetting stack to upper bound {max} due to wildcard or unknown access"
+            );
+            self.set_unknown_bottom(SbTag(max));
+        }
+
+        // Done.
+        Ok(())
+    }
+
+    /// Deallocate a location: Like a write access, but also there must be no
+    /// active protectors at all because we will remove all items.
+    fn dealloc(
+        &mut self,
+        tag: ProvenanceExtra,
+        global: &GlobalStateInner,
+        dcx: &mut DiagnosticCx<'_, '_, '_, '_, 'tcx>,
+        exposed_tags: &FxHashSet<SbTag>,
+    ) -> InterpResult<'tcx> {
+        // Step 1: Make sure there is a granting item.
+        self.find_granting(AccessKind::Write, tag, exposed_tags)
+            .map_err(|_| dcx.dealloc_error())?;
+
+        // Step 2: Consider all items removed. This checks for protectors.
+        for idx in (0..self.len()).rev() {
+            let item = self.get(idx).unwrap();
+            Stack::item_popped(&item, global, dcx)?;
+        }
+
+        Ok(())
+    }
+
+    /// Derive a new pointer from one with the given tag.
+    /// `weak` controls whether this operation is weak or strong: weak granting does not act as
+    /// an access, and they add the new item directly on top of the one it is derived
+    /// from instead of all the way at the top of the stack.
+    /// `range` refers the entire operation, and `offset` refers to the specific location in
+    /// `range` that we are currently checking.
+    fn grant(
+        &mut self,
+        derived_from: ProvenanceExtra,
+        new: Item,
+        global: &mut GlobalStateInner,
+        dcx: &mut DiagnosticCx<'_, '_, '_, '_, 'tcx>,
+        exposed_tags: &FxHashSet<SbTag>,
+    ) -> InterpResult<'tcx> {
+        dcx.start_grant(new.perm());
+
+        // Figure out which access `perm` corresponds to.
+        let access =
+            if new.perm().grants(AccessKind::Write) { AccessKind::Write } else { AccessKind::Read };
+
+        // Now we figure out which item grants our parent (`derived_from`) this kind of access.
+        // We use that to determine where to put the new item.
+        let granting_idx = self
+            .find_granting(access, derived_from, exposed_tags)
+            .map_err(|_| dcx.grant_error(new.perm(), self))?;
+
+        // Compute where to put the new item.
+        // Either way, we ensure that we insert the new item in a way such that between
+        // `derived_from` and the new one, there are only items *compatible with* `derived_from`.
+        let new_idx = if new.perm() == Permission::SharedReadWrite {
+            assert!(
+                access == AccessKind::Write,
+                "this case only makes sense for stack-like accesses"
+            );
+
+            let (Some(granting_idx), ProvenanceExtra::Concrete(_)) = (granting_idx, derived_from) else {
+                // The parent is a wildcard pointer or matched the unknown bottom.
+                // This is approximate. Nobody knows what happened, so forget everything.
+                // The new thing is SRW anyway, so we cannot push it "on top of the unkown part"
+                // (for all we know, it might join an SRW group inside the unknown).
+                trace!("reborrow: forgetting stack entirely due to SharedReadWrite reborrow from wildcard or unknown");
+                self.set_unknown_bottom(global.next_ptr_tag);
+                return Ok(());
+            };
+
+            // SharedReadWrite can coexist with "existing loans", meaning they don't act like a write
+            // access.  Instead of popping the stack, we insert the item at the place the stack would
+            // be popped to (i.e., we insert it above all the write-compatible items).
+            // This ensures F2b by adding the new item below any potentially existing `SharedReadOnly`.
+            self.find_first_write_incompatible(granting_idx)
+        } else {
+            // A "safe" reborrow for a pointer that actually expects some aliasing guarantees.
+            // Here, creating a reference actually counts as an access.
+            // This ensures F2b for `Unique`, by removing offending `SharedReadOnly`.
+            self.access(access, derived_from, global, dcx, exposed_tags)?;
+
+            // We insert "as far up as possible": We know only compatible items are remaining
+            // on top of `derived_from`, and we want the new item at the top so that we
+            // get the strongest possible guarantees.
+            // This ensures U1 and F1.
+            self.len()
+        };
+
+        // Put the new item there.
+        trace!("reborrow: adding item {:?}", new);
+        self.insert(new_idx, new);
+        Ok(())
+    }
+}
+// # Stacked Borrows Core End
+
+/// Integration with the SbTag garbage collector
+impl Stacks {
+    pub fn remove_unreachable_tags(&mut self, live_tags: &FxHashSet<SbTag>) {
+        if self.modified_since_last_gc {
+            for stack in self.stacks.iter_mut_all() {
+                if stack.len() > 64 {
+                    stack.retain(live_tags);
+                }
+            }
+            self.modified_since_last_gc = false;
+        }
+    }
+}
+
+/// Map per-stack operations to higher-level per-location-range operations.
+impl<'tcx> Stacks {
+    /// Creates a new stack with an initial tag. For diagnostic purposes, we also need to know
+    /// the [`AllocId`] of the allocation this is associated with.
+    fn new(
+        size: Size,
+        perm: Permission,
+        tag: SbTag,
+        id: AllocId,
+        current_span: &mut CurrentSpan<'_, '_, '_>,
+    ) -> Self {
+        let item = Item::new(tag, perm, false);
+        let stack = Stack::new(item);
+
+        Stacks {
+            stacks: RangeMap::new(size, stack),
+            history: AllocHistory::new(id, item, current_span),
+            exposed_tags: FxHashSet::default(),
+            modified_since_last_gc: false,
+        }
+    }
+
+    /// Call `f` on every stack in the range.
+    fn for_each(
+        &mut self,
+        range: AllocRange,
+        mut dcx_builder: DiagnosticCxBuilder<'_, '_, '_, 'tcx>,
+        mut f: impl FnMut(
+            &mut Stack,
+            &mut DiagnosticCx<'_, '_, '_, '_, 'tcx>,
+            &mut FxHashSet<SbTag>,
+        ) -> InterpResult<'tcx>,
+    ) -> InterpResult<'tcx> {
+        self.modified_since_last_gc = true;
+        for (offset, stack) in self.stacks.iter_mut(range.start, range.size) {
+            let mut dcx = dcx_builder.build(&mut self.history, offset);
+            f(stack, &mut dcx, &mut self.exposed_tags)?;
+            dcx_builder = dcx.unbuild();
+        }
+        Ok(())
+    }
+}
+
+/// Glue code to connect with Miri Machine Hooks
+impl Stacks {
+    pub fn new_allocation(
+        id: AllocId,
+        size: Size,
+        state: &GlobalState,
+        kind: MemoryKind<MiriMemoryKind>,
+        mut current_span: CurrentSpan<'_, '_, '_>,
+    ) -> Self {
+        let mut extra = state.borrow_mut();
+        let (base_tag, perm) = match kind {
+            // New unique borrow. This tag is not accessible by the program,
+            // so it will only ever be used when using the local directly (i.e.,
+            // not through a pointer). That is, whenever we directly write to a local, this will pop
+            // everything else off the stack, invalidating all previous pointers,
+            // and in particular, *all* raw pointers.
+            MemoryKind::Stack =>
+                (extra.base_ptr_tag(id, current_span.machine()), Permission::Unique),
+            // Everything else is shared by default.
+            _ => (extra.base_ptr_tag(id, current_span.machine()), Permission::SharedReadWrite),
+        };
+        Stacks::new(size, perm, base_tag, id, &mut current_span)
+    }
+
+    #[inline(always)]
+    pub fn before_memory_read<'tcx, 'mir, 'ecx>(
+        &mut self,
+        alloc_id: AllocId,
+        tag: ProvenanceExtra,
+        range: AllocRange,
+        state: &GlobalState,
+        mut current_span: CurrentSpan<'ecx, 'mir, 'tcx>,
+        threads: &'ecx ThreadManager<'mir, 'tcx>,
+    ) -> InterpResult<'tcx>
+    where
+        'tcx: 'ecx,
+    {
+        trace!(
+            "read access with tag {:?}: {:?}, size {}",
+            tag,
+            Pointer::new(alloc_id, range.start),
+            range.size.bytes()
+        );
+        let dcx = DiagnosticCxBuilder::read(&mut current_span, threads, tag, range);
+        let mut state = state.borrow_mut();
+        self.for_each(range, dcx, |stack, dcx, exposed_tags| {
+            stack.access(AccessKind::Read, tag, &mut state, dcx, exposed_tags)
+        })
+    }
+
+    #[inline(always)]
+    pub fn before_memory_write<'tcx, 'mir, 'ecx>(
+        &mut self,
+        alloc_id: AllocId,
+        tag: ProvenanceExtra,
+        range: AllocRange,
+        state: &GlobalState,
+        mut current_span: CurrentSpan<'ecx, 'mir, 'tcx>,
+        threads: &'ecx ThreadManager<'mir, 'tcx>,
+    ) -> InterpResult<'tcx> {
+        trace!(
+            "write access with tag {:?}: {:?}, size {}",
+            tag,
+            Pointer::new(alloc_id, range.start),
+            range.size.bytes()
+        );
+        let dcx = DiagnosticCxBuilder::write(&mut current_span, threads, tag, range);
+        let mut state = state.borrow_mut();
+        self.for_each(range, dcx, |stack, dcx, exposed_tags| {
+            stack.access(AccessKind::Write, tag, &mut state, dcx, exposed_tags)
+        })
+    }
+
+    #[inline(always)]
+    pub fn before_memory_deallocation<'tcx, 'mir, 'ecx>(
+        &mut self,
+        alloc_id: AllocId,
+        tag: ProvenanceExtra,
+        range: AllocRange,
+        state: &GlobalState,
+        mut current_span: CurrentSpan<'ecx, 'mir, 'tcx>,
+        threads: &'ecx ThreadManager<'mir, 'tcx>,
+    ) -> InterpResult<'tcx> {
+        trace!("deallocation with tag {:?}: {:?}, size {}", tag, alloc_id, range.size.bytes());
+        let dcx = DiagnosticCxBuilder::dealloc(&mut current_span, threads, tag);
+        let state = state.borrow();
+        self.for_each(range, dcx, |stack, dcx, exposed_tags| {
+            stack.dealloc(tag, &state, dcx, exposed_tags)
+        })?;
+        Ok(())
+    }
+}
+
+/// Retagging/reborrowing.  There is some policy in here, such as which permissions
+/// to grant for which references, and when to add protectors.
+impl<'mir: 'ecx, 'tcx: 'mir, 'ecx> EvalContextPrivExt<'mir, 'tcx, 'ecx>
+    for crate::MiriInterpCx<'mir, 'tcx>
+{
+}
+trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    /// Returns the `AllocId` the reborrow was done in, if some actual borrow stack manipulation
+    /// happened.
+    fn reborrow(
+        &mut self,
+        place: &MPlaceTy<'tcx, Provenance>,
+        size: Size,
+        kind: RefKind,
+        retag_cause: RetagCause, // What caused this retag, for diagnostics only
+        new_tag: SbTag,
+        protect: bool,
+    ) -> InterpResult<'tcx, Option<AllocId>> {
+        let this = self.eval_context_mut();
+
+        // It is crucial that this gets called on all code paths, to ensure we track tag creation.
+        let log_creation = |this: &MiriInterpCx<'mir, 'tcx>,
+                            loc: Option<(AllocId, Size, ProvenanceExtra)>| // alloc_id, base_offset, orig_tag
+         -> InterpResult<'tcx> {
+            let global = this.machine.stacked_borrows.as_ref().unwrap().borrow();
+            if global.tracked_pointer_tags.contains(&new_tag) {
+                this.emit_diagnostic(NonHaltingDiagnostic::CreatedPointerTag(
+                    new_tag.0,
+                    loc.map(|(alloc_id, base_offset, _)| (alloc_id, alloc_range(base_offset, size))),
+                ));
+            }
+            drop(global); // don't hold that reference any longer than we have to
+
+            let Some((alloc_id, base_offset, orig_tag)) = loc else {
+                return Ok(())
+            };
+
+            let (_size, _align, alloc_kind) = this.get_alloc_info(alloc_id);
+            match alloc_kind {
+                AllocKind::LiveData => {
+                    let current_span = &mut this.machine.current_span();
+                    // This should have alloc_extra data, but `get_alloc_extra` can still fail
+                    // if converting this alloc_id from a global to a local one
+                    // uncovers a non-supported `extern static`.
+                    let extra = this.get_alloc_extra(alloc_id)?;
+                    let mut stacked_borrows = extra
+                        .stacked_borrows
+                        .as_ref()
+                        .expect("we should have Stacked Borrows data")
+                        .borrow_mut();
+                    let threads = &this.machine.threads;
+                    // Note that we create a *second* `DiagnosticCxBuilder` below for the actual retag.
+                    // FIXME: can this be done cleaner?
+                    let dcx = DiagnosticCxBuilder::retag(
+                        current_span,
+                        threads,
+                        retag_cause,
+                        new_tag,
+                        orig_tag,
+                        alloc_range(base_offset, size),
+                    );
+                    let mut dcx = dcx.build(&mut stacked_borrows.history, base_offset);
+                    dcx.log_creation();
+                    if protect {
+                        dcx.log_protector();
+                    }
+                }
+                AllocKind::Function | AllocKind::VTable | AllocKind::Dead => {
+                    // No stacked borrows on these allocations.
+                }
+            }
+            Ok(())
+        };
+
+        if size == Size::ZERO {
+            trace!(
+                "reborrow of size 0: {} reference {:?} derived from {:?} (pointee {})",
+                kind,
+                new_tag,
+                place.ptr,
+                place.layout.ty,
+            );
+            // Don't update any stacks for a zero-sized access; borrow stacks are per-byte and this
+            // touches no bytes so there is no stack to put this tag in.
+            // However, if the pointer for this operation points at a real allocation we still
+            // record where it was created so that we can issue a helpful diagnostic if there is an
+            // attempt to use it for a non-zero-sized access.
+            // Dangling slices are a common case here; it's valid to get their length but with raw
+            // pointer tagging for example all calls to get_unchecked on them are invalid.
+            if let Ok((alloc_id, base_offset, orig_tag)) = this.ptr_try_get_alloc_id(place.ptr) {
+                log_creation(this, Some((alloc_id, base_offset, orig_tag)))?;
+                return Ok(Some(alloc_id));
+            }
+            // This pointer doesn't come with an AllocId. :shrug:
+            log_creation(this, None)?;
+            return Ok(None);
+        }
+
+        let (alloc_id, base_offset, orig_tag) = this.ptr_get_alloc_id(place.ptr)?;
+        log_creation(this, Some((alloc_id, base_offset, orig_tag)))?;
+
+        // Ensure we bail out if the pointer goes out-of-bounds (see miri#1050).
+        let (alloc_size, _) = this.get_live_alloc_size_and_align(alloc_id)?;
+        if base_offset + size > alloc_size {
+            throw_ub!(PointerOutOfBounds {
+                alloc_id,
+                alloc_size,
+                ptr_offset: this.machine_usize_to_isize(base_offset.bytes()),
+                ptr_size: size,
+                msg: CheckInAllocMsg::InboundsTest
+            });
+        }
+
+        trace!(
+            "reborrow: {} reference {:?} derived from {:?} (pointee {}): {:?}, size {}",
+            kind,
+            new_tag,
+            orig_tag,
+            place.layout.ty,
+            Pointer::new(alloc_id, base_offset),
+            size.bytes()
+        );
+
+        if protect {
+            // See comment in `Stack::item_popped` for why we store the tag twice.
+            this.frame_mut().extra.stacked_borrows.as_mut().unwrap().protected_tags.push(new_tag);
+            this.machine.stacked_borrows.as_mut().unwrap().get_mut().protected_tags.insert(new_tag);
+        }
+
+        // Update the stacks.
+        // Make sure that raw pointers and mutable shared references are reborrowed "weak":
+        // There could be existing unique pointers reborrowed from them that should remain valid!
+        let perm = match kind {
+            RefKind::Unique { two_phase: false }
+                if place.layout.ty.is_unpin(this.tcx.at(DUMMY_SP), this.param_env()) =>
+            {
+                // Only if the type is unpin do we actually enforce uniqueness
+                Permission::Unique
+            }
+            RefKind::Unique { .. } => {
+                // Two-phase references and !Unpin references are treated as SharedReadWrite
+                Permission::SharedReadWrite
+            }
+            RefKind::Raw { mutable: true } => Permission::SharedReadWrite,
+            RefKind::Shared | RefKind::Raw { mutable: false } => {
+                // Shared references and *const are a whole different kind of game, the
+                // permission is not uniform across the entire range!
+                // We need a frozen-sensitive reborrow.
+                // We have to use shared references to alloc/memory_extra here since
+                // `visit_freeze_sensitive` needs to access the global state.
+                let extra = this.get_alloc_extra(alloc_id)?;
+                let mut stacked_borrows = extra
+                    .stacked_borrows
+                    .as_ref()
+                    .expect("we should have Stacked Borrows data")
+                    .borrow_mut();
+                // FIXME: can't share this with the current_span inside log_creation
+                let mut current_span = this.machine.current_span();
+                this.visit_freeze_sensitive(place, size, |mut range, frozen| {
+                    // Adjust range.
+                    range.start += base_offset;
+                    // We are only ever `SharedReadOnly` inside the frozen bits.
+                    let perm = if frozen {
+                        Permission::SharedReadOnly
+                    } else {
+                        Permission::SharedReadWrite
+                    };
+                    let protected = if frozen {
+                        protect
+                    } else {
+                        // We do not protect inside UnsafeCell.
+                        // This fixes https://github.com/rust-lang/rust/issues/55005.
+                        false
+                    };
+                    let item = Item::new(new_tag, perm, protected);
+                    let mut global = this.machine.stacked_borrows.as_ref().unwrap().borrow_mut();
+                    let dcx = DiagnosticCxBuilder::retag(
+                        &mut current_span, // FIXME avoid this `clone`
+                        &this.machine.threads,
+                        retag_cause,
+                        new_tag,
+                        orig_tag,
+                        alloc_range(base_offset, size),
+                    );
+                    stacked_borrows.for_each(range, dcx, |stack, dcx, exposed_tags| {
+                        stack.grant(orig_tag, item, &mut global, dcx, exposed_tags)
+                    })
+                })?;
+                return Ok(Some(alloc_id));
+            }
+        };
+
+        // Here we can avoid `borrow()` calls because we have mutable references.
+        // Note that this asserts that the allocation is mutable -- but since we are creating a
+        // mutable pointer, that seems reasonable.
+        let (alloc_extra, machine) = this.get_alloc_extra_mut(alloc_id)?;
+        let mut stacked_borrows = alloc_extra
+            .stacked_borrows
+            .as_mut()
+            .expect("we should have Stacked Borrows data")
+            .borrow_mut();
+        let item = Item::new(new_tag, perm, protect);
+        let range = alloc_range(base_offset, size);
+        let mut global = machine.stacked_borrows.as_ref().unwrap().borrow_mut();
+        // FIXME: can't share this with the current_span inside log_creation
+        let current_span = &mut machine.current_span();
+        let dcx = DiagnosticCxBuilder::retag(
+            current_span,
+            &machine.threads,
+            retag_cause,
+            new_tag,
+            orig_tag,
+            alloc_range(base_offset, size),
+        );
+        stacked_borrows.for_each(range, dcx, |stack, dcx, exposed_tags| {
+            stack.grant(orig_tag, item, &mut global, dcx, exposed_tags)
+        })?;
+
+        Ok(Some(alloc_id))
+    }
+
+    /// Retags an indidual pointer, returning the retagged version.
+    /// `mutbl` can be `None` to make this a raw pointer.
+    fn retag_reference(
+        &mut self,
+        val: &ImmTy<'tcx, Provenance>,
+        kind: RefKind,
+        retag_cause: RetagCause, // What caused this retag, for diagnostics only
+        protect: bool,
+    ) -> InterpResult<'tcx, ImmTy<'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);
+        // 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()),
+        };
+
+        // Compute new borrow.
+        let new_tag = this.machine.stacked_borrows.as_mut().unwrap().get_mut().new_ptr();
+
+        // Reborrow.
+        let alloc_id = this.reborrow(&place, size, kind, retag_cause, new_tag, protect)?;
+
+        // Adjust pointer.
+        let new_place = place.map_provenance(|p| {
+            p.map(|prov| {
+                match alloc_id {
+                    Some(alloc_id) => {
+                        // If `reborrow` could figure out the AllocId of this ptr, hard-code it into the new one.
+                        // Even if we started out with a wildcard, this newly retagged pointer is tied to that allocation.
+                        Provenance::Concrete { alloc_id, sb: new_tag }
+                    }
+                    None => {
+                        // Looks like this has to stay a wildcard pointer.
+                        assert!(matches!(prov, Provenance::Wildcard));
+                        Provenance::Wildcard
+                    }
+                }
+            })
+        });
+
+        // Return new pointer.
+        Ok(ImmTy::from_immediate(new_place.to_ref(this), val.layout))
+    }
+}
+
+impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    fn retag(&mut self, kind: RetagKind, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        let retag_fields = this.machine.stacked_borrows.as_mut().unwrap().get_mut().retag_fields;
+        let retag_cause = match kind {
+            RetagKind::TwoPhase { .. } => RetagCause::TwoPhase,
+            RetagKind::FnEntry => RetagCause::FnEntry,
+            RetagKind::Raw | RetagKind::Default => RetagCause::Normal,
+        };
+        let mut visitor = RetagVisitor { ecx: this, kind, retag_cause, retag_fields };
+        return visitor.visit_value(place);
+
+        // Determine mutability and whether to add a protector.
+        // Cannot use `builtin_deref` because that reports *immutable* for `Box`,
+        // making it useless.
+        fn qualify(ty: Ty<'_>, kind: RetagKind) -> Option<(RefKind, bool)> {
+            match ty.kind() {
+                // References are simple.
+                ty::Ref(_, _, Mutability::Mut) =>
+                    Some((
+                        RefKind::Unique { two_phase: kind == RetagKind::TwoPhase },
+                        kind == RetagKind::FnEntry,
+                    )),
+                ty::Ref(_, _, Mutability::Not) =>
+                    Some((RefKind::Shared, kind == RetagKind::FnEntry)),
+                // Raw pointers need to be enabled.
+                ty::RawPtr(tym) if kind == RetagKind::Raw =>
+                    Some((RefKind::Raw { mutable: tym.mutbl == Mutability::Mut }, false)),
+                // Boxes are handled separately due to that allocator situation,
+                // see the visitor below.
+                _ => None,
+            }
+        }
+
+        // The actual visitor.
+        struct RetagVisitor<'ecx, 'mir, 'tcx> {
+            ecx: &'ecx mut MiriInterpCx<'mir, 'tcx>,
+            kind: RetagKind,
+            retag_cause: RetagCause,
+            retag_fields: bool,
+        }
+        impl<'ecx, 'mir, 'tcx> RetagVisitor<'ecx, 'mir, 'tcx> {
+            #[inline(always)] // yes this helps in our benchmarks
+            fn retag_place(
+                &mut self,
+                place: &PlaceTy<'tcx, Provenance>,
+                ref_kind: RefKind,
+                retag_cause: RetagCause,
+                protector: bool,
+            ) -> InterpResult<'tcx> {
+                let val = self.ecx.read_immediate(&self.ecx.place_to_op(place)?)?;
+                let val = self.ecx.retag_reference(&val, ref_kind, retag_cause, protector)?;
+                self.ecx.write_immediate(*val, place)?;
+                Ok(())
+            }
+        }
+        impl<'ecx, 'mir, 'tcx> MutValueVisitor<'mir, 'tcx, MiriMachine<'mir, 'tcx>>
+            for RetagVisitor<'ecx, 'mir, 'tcx>
+        {
+            type V = PlaceTy<'tcx, Provenance>;
+
+            #[inline(always)]
+            fn ecx(&mut self) -> &mut MiriInterpCx<'mir, 'tcx> {
+                self.ecx
+            }
+
+            fn visit_box(&mut self, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
+                // Boxes do not get a protector: protectors reflect that references outlive the call
+                // they were passed in to; that's just not the case for boxes.
+                self.retag_place(
+                    place,
+                    RefKind::Unique { two_phase: false },
+                    self.retag_cause,
+                    /*protector*/ false,
+                )
+            }
+
+            fn visit_value(&mut self, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
+                // If this place is smaller than a pointer, we know that it can't contain any
+                // pointers we need to retag, so we can stop recursion early.
+                // This optimization is crucial for ZSTs, because they can contain way more fields
+                // than we can ever visit.
+                if !place.layout.is_unsized() && place.layout.size < self.ecx.pointer_size() {
+                    return Ok(());
+                }
+
+                if let Some((ref_kind, protector)) = qualify(place.layout.ty, self.kind) {
+                    self.retag_place(place, ref_kind, self.retag_cause, protector)?;
+                } else if matches!(place.layout.ty.kind(), ty::RawPtr(..)) {
+                    // Wide raw pointers *do* have fields and their types are strange.
+                    // vtables have a type like `&[*const (); 3]` or so!
+                    // Do *not* recurse into them.
+                    // (No need to worry about wide references, those always "qualify". And Boxes
+                    // are handles specially by the visitor anyway.)
+                } else if self.retag_fields
+                    || place.layout.ty.ty_adt_def().is_some_and(|adt| adt.is_box())
+                {
+                    // Recurse deeper. Need to always recurse for `Box` to even hit `visit_box`.
+                    // (Yes this means we technically also recursively retag the allocator itself
+                    // even if field retagging is not enabled. *shrug*)
+                    self.walk_value(place)?;
+                }
+                Ok(())
+            }
+        }
+    }
+
+    /// After a stack frame got pushed, retag the return place so that we are sure
+    /// it does not alias with anything.
+    ///
+    /// This is a HACK because there is nothing in MIR that would make the retag
+    /// explicit. Also see <https://github.com/rust-lang/rust/issues/71117>.
+    fn retag_return_place(&mut self) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        let return_place = &this.frame().return_place;
+        if return_place.layout.is_zst() {
+            // There may not be any memory here, nothing to do.
+            return Ok(());
+        }
+        // We need this to be in-memory to use tagged pointers.
+        let return_place = this.force_allocation(&return_place.clone())?;
+
+        // We have to turn the place into a pointer to use the existing code.
+        // (The pointer type does not matter, so we use a raw pointer.)
+        let ptr_layout = this.layout_of(this.tcx.mk_mut_ptr(return_place.layout.ty))?;
+        let val = ImmTy::from_immediate(return_place.to_ref(this), ptr_layout);
+        // Reborrow it.
+        let val = this.retag_reference(
+            &val,
+            RefKind::Unique { two_phase: false },
+            RetagCause::FnReturn,
+            /*protector*/ true,
+        )?;
+        // And use reborrowed pointer for return place.
+        let return_place = this.ref_to_mplace(&val)?;
+        this.frame_mut().return_place = return_place.into();
+
+        Ok(())
+    }
+
+    /// Mark the given tag as exposed. It was found on a pointer with the given AllocId.
+    fn expose_tag(&mut self, alloc_id: AllocId, tag: SbTag) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+
+        // Function pointers and dead objects don't have an alloc_extra so we ignore them.
+        // This is okay because accessing them is UB anyway, no need for any Stacked Borrows checks.
+        // NOT using `get_alloc_extra_mut` since this might be a read-only allocation!
+        let (_size, _align, kind) = this.get_alloc_info(alloc_id);
+        match kind {
+            AllocKind::LiveData => {
+                // This should have alloc_extra data, but `get_alloc_extra` can still fail
+                // 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:?}");
+                alloc_extra.stacked_borrows.as_ref().unwrap().borrow_mut().exposed_tags.insert(tag);
+            }
+            AllocKind::Function | AllocKind::VTable | AllocKind::Dead => {
+                // No stacked borrows on these allocations.
+            }
+        }
+        Ok(())
+    }
+}
diff --git a/src/tools/miri/src/stacked_borrows/stack.rs b/src/tools/miri/src/stacked_borrows/stack.rs
new file mode 100644
index 0000000000000..494ea08b56e48
--- /dev/null
+++ b/src/tools/miri/src/stacked_borrows/stack.rs
@@ -0,0 +1,462 @@
+#[cfg(feature = "stack-cache")]
+use std::ops::Range;
+
+use rustc_data_structures::fx::FxHashSet;
+
+use crate::stacked_borrows::{AccessKind, Item, Permission, SbTag};
+use crate::ProvenanceExtra;
+
+/// Exactly what cache size we should use is a difficult tradeoff. There will always be some
+/// workload which has a `SbTag` working set which exceeds the size of the cache, and ends up
+/// falling back to linear searches of the borrow stack very often.
+/// The cost of making this value too large is that the loop in `Stack::insert` which ensures the
+/// entries in the cache stay correct after an insert becomes expensive.
+#[cfg(feature = "stack-cache")]
+const CACHE_LEN: usize = 32;
+
+/// Extra per-location state.
+#[derive(Clone, Debug)]
+pub struct Stack {
+    /// Used *mostly* as a stack; never empty.
+    /// Invariants:
+    /// * Above a `SharedReadOnly` there can only be more `SharedReadOnly`.
+    /// * Except for `Untagged`, no tag occurs in the stack more than once.
+    borrows: Vec<Item>,
+    /// If this is `Some(id)`, then the actual current stack is unknown. This can happen when
+    /// wildcard pointers are used to access this location. What we do know is that `borrows` are at
+    /// the top of the stack, and below it are arbitrarily many items whose `tag` is strictly less
+    /// than `id`.
+    /// When the bottom is unknown, `borrows` always has a `SharedReadOnly` or `Unique` at the bottom;
+    /// we never have the unknown-to-known boundary in an SRW group.
+    unknown_bottom: Option<SbTag>,
+
+    /// A small LRU cache of searches of the borrow stack.
+    #[cfg(feature = "stack-cache")]
+    cache: StackCache,
+    /// On a read, we need to disable all `Unique` above the granting item. We can avoid most of
+    /// this scan by keeping track of the region of the borrow stack that may contain `Unique`s.
+    #[cfg(feature = "stack-cache")]
+    unique_range: Range<usize>,
+}
+
+impl Stack {
+    pub fn retain(&mut self, tags: &FxHashSet<SbTag>) {
+        let mut first_removed = None;
+
+        let mut read_idx = 1;
+        let mut write_idx = 1;
+        while read_idx < self.borrows.len() {
+            let left = self.borrows[read_idx - 1];
+            let this = self.borrows[read_idx];
+            let should_keep = match this.perm() {
+                // SharedReadWrite is the simplest case, if it's unreachable we can just remove it.
+                Permission::SharedReadWrite => tags.contains(&this.tag()),
+                // Only retain a Disabled tag if it is terminating a SharedReadWrite block.
+                Permission::Disabled => left.perm() == Permission::SharedReadWrite,
+                // Unique and SharedReadOnly can terminate a SharedReadWrite block, so only remove
+                // them if they are both unreachable and not directly after a SharedReadWrite.
+                Permission::Unique | Permission::SharedReadOnly =>
+                    left.perm() == Permission::SharedReadWrite || tags.contains(&this.tag()),
+            };
+
+            if should_keep {
+                if read_idx != write_idx {
+                    self.borrows[write_idx] = self.borrows[read_idx];
+                }
+                write_idx += 1;
+            } else if first_removed.is_none() {
+                first_removed = Some(read_idx);
+            }
+
+            read_idx += 1;
+        }
+        self.borrows.truncate(write_idx);
+
+        #[cfg(not(feature = "stack-cache"))]
+        drop(first_removed); // This is only needed for the stack-cache
+
+        #[cfg(feature = "stack-cache")]
+        if let Some(first_removed) = first_removed {
+            // Either end of unique_range may have shifted, all we really know is that we can't
+            // have introduced a new Unique.
+            if !self.unique_range.is_empty() {
+                self.unique_range = 0..self.len();
+            }
+
+            // Replace any Items which have been collected with the base item, a known-good value.
+            for i in 0..CACHE_LEN {
+                if self.cache.idx[i] >= first_removed {
+                    self.cache.items[i] = self.borrows[0];
+                    self.cache.idx[i] = 0;
+                }
+            }
+        }
+    }
+}
+
+/// A very small cache of searches of a borrow stack, mapping `Item`s to their position in said stack.
+///
+/// It may seem like maintaining this cache is a waste for small stacks, but
+/// (a) iterating over small fixed-size arrays is super fast, and (b) empirically this helps *a lot*,
+/// probably because runtime is dominated by large stacks.
+#[cfg(feature = "stack-cache")]
+#[derive(Clone, Debug)]
+struct StackCache {
+    items: [Item; CACHE_LEN], // Hot in find_granting
+    idx: [usize; CACHE_LEN],  // Hot in grant
+}
+
+#[cfg(feature = "stack-cache")]
+impl StackCache {
+    /// When a tag is used, we call this function to add or refresh it in the cache.
+    ///
+    /// We use the position in the cache to represent how recently a tag was used; the first position
+    /// is the most recently used tag. So an add shifts every element towards the end, and inserts
+    /// the new element at the start. We lose the last element.
+    /// This strategy is effective at keeping the most-accessed items in the cache, but it costs a
+    /// linear shift across the entire cache when we add a new tag.
+    fn add(&mut self, idx: usize, item: Item) {
+        self.items.copy_within(0..CACHE_LEN - 1, 1);
+        self.items[0] = item;
+        self.idx.copy_within(0..CACHE_LEN - 1, 1);
+        self.idx[0] = idx;
+    }
+}
+
+impl PartialEq for Stack {
+    fn eq(&self, other: &Self) -> bool {
+        // All the semantics of Stack are in self.borrows, everything else is caching
+        self.borrows == other.borrows
+    }
+}
+
+impl Eq for Stack {}
+
+impl<'tcx> Stack {
+    /// Panics if any of the caching mechanisms have broken,
+    /// - The StackCache indices don't refer to the parallel items,
+    /// - There are no Unique items outside of first_unique..last_unique
+    #[cfg(all(feature = "stack-cache", debug_assertions))]
+    fn verify_cache_consistency(&self) {
+        // Only a full cache needs to be valid. Also see the comments in find_granting_cache
+        // and set_unknown_bottom.
+        if self.borrows.len() >= CACHE_LEN {
+            for (tag, stack_idx) in self.cache.items.iter().zip(self.cache.idx.iter()) {
+                assert_eq!(self.borrows[*stack_idx], *tag);
+            }
+        }
+
+        // Check that all Unique items fall within unique_range.
+        for (idx, item) in self.borrows.iter().enumerate() {
+            if item.perm() == Permission::Unique {
+                assert!(
+                    self.unique_range.contains(&idx),
+                    "{:?} {:?}",
+                    self.unique_range,
+                    self.borrows
+                );
+            }
+        }
+
+        // Check that the unique_range is a valid index into the borrow stack.
+        // This asserts that the unique_range's start <= end.
+        let _uniques = &self.borrows[self.unique_range.clone()];
+
+        // We cannot assert that the unique range is precise.
+        // Both ends may shift around when `Stack::retain` is called. Additionally,
+        // when we pop items within the unique range, setting the end of the range precisely
+        // requires doing a linear search of the borrow stack, which is exactly the kind of
+        // operation that all this caching exists to avoid.
+    }
+
+    /// Find the item granting the given kind of access to the given tag, and return where
+    /// it is on the stack. For wildcard tags, the given index is approximate, but if *no*
+    /// index is given it means the match was *not* in the known part of the stack.
+    /// `Ok(None)` indicates it matched the "unknown" part of the stack.
+    /// `Err` indicates it was not found.
+    pub(super) fn find_granting(
+        &mut self,
+        access: AccessKind,
+        tag: ProvenanceExtra,
+        exposed_tags: &FxHashSet<SbTag>,
+    ) -> Result<Option<usize>, ()> {
+        #[cfg(all(feature = "stack-cache", debug_assertions))]
+        self.verify_cache_consistency();
+
+        let ProvenanceExtra::Concrete(tag) = tag else {
+            // Handle the wildcard case.
+            // Go search the stack for an exposed tag.
+            if let Some(idx) =
+                self.borrows
+                    .iter()
+                    .enumerate() // we also need to know *where* in the stack
+                    .rev() // search top-to-bottom
+                    .find_map(|(idx, item)| {
+                        // If the item fits and *might* be this wildcard, use it.
+                        if item.perm().grants(access) && exposed_tags.contains(&item.tag()) {
+                            Some(idx)
+                        } else {
+                            None
+                        }
+                    })
+            {
+                return Ok(Some(idx));
+            }
+            // If we couldn't find it in the stack, check the unknown bottom.
+            return if self.unknown_bottom.is_some() { Ok(None) } else { Err(()) };
+        };
+
+        if let Some(idx) = self.find_granting_tagged(access, tag) {
+            return Ok(Some(idx));
+        }
+
+        // Couldn't find it in the stack; but if there is an unknown bottom it might be there.
+        let found = self.unknown_bottom.is_some_and(|&unknown_limit| {
+            tag.0 < unknown_limit.0 // unknown_limit is an upper bound for what can be in the unknown bottom.
+        });
+        if found { Ok(None) } else { Err(()) }
+    }
+
+    fn find_granting_tagged(&mut self, access: AccessKind, tag: SbTag) -> Option<usize> {
+        #[cfg(feature = "stack-cache")]
+        if let Some(idx) = self.find_granting_cache(access, tag) {
+            return Some(idx);
+        }
+
+        // If we didn't find the tag in the cache, fall back to a linear search of the
+        // whole stack, and add the tag to the cache.
+        for (stack_idx, item) in self.borrows.iter().enumerate().rev() {
+            if tag == item.tag() && item.perm().grants(access) {
+                #[cfg(feature = "stack-cache")]
+                self.cache.add(stack_idx, *item);
+                return Some(stack_idx);
+            }
+        }
+        None
+    }
+
+    #[cfg(feature = "stack-cache")]
+    fn find_granting_cache(&mut self, access: AccessKind, tag: SbTag) -> Option<usize> {
+        // This looks like a common-sense optimization; we're going to do a linear search of the
+        // cache or the borrow stack to scan the shorter of the two. This optimization is miniscule
+        // and this check actually ensures we do not access an invalid cache.
+        // When a stack is created and when items are removed from the top of the borrow stack, we
+        // need some valid value to populate the cache. In both cases, we try to use the bottom
+        // item. But when the stack is cleared in `set_unknown_bottom` there is nothing we could
+        // place in the cache that is correct. But due to the way we populate the cache in
+        // `StackCache::add`, we know that when the borrow stack has grown larger than the cache,
+        // every slot in the cache is valid.
+        if self.borrows.len() <= CACHE_LEN {
+            return None;
+        }
+        // Search the cache for the tag we're looking up
+        let cache_idx = self.cache.items.iter().position(|t| t.tag() == tag)?;
+        let stack_idx = self.cache.idx[cache_idx];
+        // If we found the tag, look up its position in the stack to see if it grants
+        // the required permission
+        if self.cache.items[cache_idx].perm().grants(access) {
+            // If it does, and it's not already in the most-recently-used position, re-insert it at
+            // the most-recently-used position. This technically reduces the efficiency of the
+            // cache by duplicating elements, but current benchmarks do not seem to benefit from
+            // avoiding this duplication.
+            // But if the tag is in position 1, avoiding the duplicating add is trivial.
+            // If it does, and it's not already in the most-recently-used position, move it there.
+            // Except if the tag is in position 1, this is equivalent to just a swap, so do that.
+            if cache_idx == 1 {
+                self.cache.items.swap(0, 1);
+                self.cache.idx.swap(0, 1);
+            } else if cache_idx > 1 {
+                self.cache.add(stack_idx, self.cache.items[cache_idx]);
+            }
+            Some(stack_idx)
+        } else {
+            // Tag is in the cache, but it doesn't grant the required permission
+            None
+        }
+    }
+
+    pub fn insert(&mut self, new_idx: usize, new: Item) {
+        self.borrows.insert(new_idx, new);
+
+        #[cfg(feature = "stack-cache")]
+        self.insert_cache(new_idx, new);
+    }
+
+    #[cfg(feature = "stack-cache")]
+    fn insert_cache(&mut self, new_idx: usize, new: Item) {
+        // Adjust the possibly-unique range if an insert occurs before or within it
+        if self.unique_range.start >= new_idx {
+            self.unique_range.start += 1;
+        }
+        if self.unique_range.end >= new_idx {
+            self.unique_range.end += 1;
+        }
+        if new.perm() == Permission::Unique {
+            // If this is the only Unique, set the range to contain just the new item.
+            if self.unique_range.is_empty() {
+                self.unique_range = new_idx..new_idx + 1;
+            } else {
+                // We already have other Unique items, expand the range to include the new item
+                self.unique_range.start = self.unique_range.start.min(new_idx);
+                self.unique_range.end = self.unique_range.end.max(new_idx + 1);
+            }
+        }
+
+        // The above insert changes the meaning of every index in the cache >= new_idx, so now
+        // we need to find every one of those indexes and increment it.
+        // But if the insert is at the end (equivalent to a push), we can skip this step because
+        // it didn't change the position of any other items.
+        if new_idx != self.borrows.len() - 1 {
+            for idx in &mut self.cache.idx {
+                if *idx >= new_idx {
+                    *idx += 1;
+                }
+            }
+        }
+
+        // This primes the cache for the next access, which is almost always the just-added tag.
+        self.cache.add(new_idx, new);
+
+        #[cfg(debug_assertions)]
+        self.verify_cache_consistency();
+    }
+
+    /// Construct a new `Stack` using the passed `Item` as the base tag.
+    pub fn new(item: Item) -> Self {
+        Stack {
+            borrows: vec![item],
+            unknown_bottom: None,
+            #[cfg(feature = "stack-cache")]
+            cache: StackCache { idx: [0; CACHE_LEN], items: [item; CACHE_LEN] },
+            #[cfg(feature = "stack-cache")]
+            unique_range: if item.perm() == Permission::Unique { 0..1 } else { 0..0 },
+        }
+    }
+
+    pub fn get(&self, idx: usize) -> Option<Item> {
+        self.borrows.get(idx).cloned()
+    }
+
+    #[allow(clippy::len_without_is_empty)] // Stacks are never empty
+    pub fn len(&self) -> usize {
+        self.borrows.len()
+    }
+
+    pub fn unknown_bottom(&self) -> Option<SbTag> {
+        self.unknown_bottom
+    }
+
+    pub fn set_unknown_bottom(&mut self, tag: SbTag) {
+        // We clear the borrow stack but the lookup cache doesn't support clearing per se. Instead,
+        // there is a check explained in `find_granting_cache` which protects against accessing the
+        // cache when it has been cleared and not yet refilled.
+        self.borrows.clear();
+        self.unknown_bottom = Some(tag);
+        #[cfg(feature = "stack-cache")]
+        {
+            self.unique_range = 0..0;
+        }
+    }
+
+    /// Find all `Unique` elements in this borrow stack above `granting_idx`, pass a copy of them
+    /// to the `visitor`, then set their `Permission` to `Disabled`.
+    pub fn disable_uniques_starting_at<V: FnMut(Item) -> crate::InterpResult<'tcx>>(
+        &mut self,
+        disable_start: usize,
+        mut visitor: V,
+    ) -> crate::InterpResult<'tcx> {
+        #[cfg(feature = "stack-cache")]
+        let unique_range = self.unique_range.clone();
+        #[cfg(not(feature = "stack-cache"))]
+        let unique_range = 0..self.len();
+
+        if disable_start <= unique_range.end {
+            let lower = unique_range.start.max(disable_start);
+            let upper = unique_range.end;
+            for item in &mut self.borrows[lower..upper] {
+                if item.perm() == Permission::Unique {
+                    log::trace!("access: disabling item {:?}", item);
+                    visitor(*item)?;
+                    item.set_permission(Permission::Disabled);
+                    // Also update all copies of this item in the cache.
+                    #[cfg(feature = "stack-cache")]
+                    for it in &mut self.cache.items {
+                        if it.tag() == item.tag() {
+                            it.set_permission(Permission::Disabled);
+                        }
+                    }
+                }
+            }
+        }
+
+        #[cfg(feature = "stack-cache")]
+        if disable_start <= self.unique_range.start {
+            // We disabled all Unique items
+            self.unique_range.start = 0;
+            self.unique_range.end = 0;
+        } else {
+            // Truncate the range to only include items up to the index that we started disabling
+            // at.
+            self.unique_range.end = self.unique_range.end.min(disable_start);
+        }
+
+        #[cfg(all(feature = "stack-cache", debug_assertions))]
+        self.verify_cache_consistency();
+
+        Ok(())
+    }
+
+    /// Produces an iterator which iterates over `range` in reverse, and when dropped removes that
+    /// range of `Item`s from this `Stack`.
+    pub fn pop_items_after<V: FnMut(Item) -> crate::InterpResult<'tcx>>(
+        &mut self,
+        start: usize,
+        mut visitor: V,
+    ) -> crate::InterpResult<'tcx> {
+        while self.borrows.len() > start {
+            let item = self.borrows.pop().unwrap();
+            visitor(item)?;
+        }
+
+        #[cfg(feature = "stack-cache")]
+        if !self.borrows.is_empty() {
+            // After we remove from the borrow stack, every aspect of our caching may be invalid, but it is
+            // also possible that the whole cache is still valid. So we call this method to repair what
+            // aspects of the cache are now invalid, instead of resetting the whole thing to a trivially
+            // valid default state.
+            let base_tag = self.borrows[0];
+            let mut removed = 0;
+            let mut cursor = 0;
+            // Remove invalid entries from the cache by rotating them to the end of the cache, then
+            // keep track of how many invalid elements there are and overwrite them with the base tag.
+            // The base tag here serves as a harmless default value.
+            for _ in 0..CACHE_LEN - 1 {
+                if self.cache.idx[cursor] >= start {
+                    self.cache.idx[cursor..CACHE_LEN - removed].rotate_left(1);
+                    self.cache.items[cursor..CACHE_LEN - removed].rotate_left(1);
+                    removed += 1;
+                } else {
+                    cursor += 1;
+                }
+            }
+            for i in CACHE_LEN - removed - 1..CACHE_LEN {
+                self.cache.idx[i] = 0;
+                self.cache.items[i] = base_tag;
+            }
+
+            if start <= self.unique_range.start {
+                // We removed all the Unique items
+                self.unique_range = 0..0;
+            } else {
+                // Ensure the range doesn't extend past the new top of the stack
+                self.unique_range.end = self.unique_range.end.min(start);
+            }
+        } else {
+            self.unique_range = 0..0;
+        }
+
+        #[cfg(all(feature = "stack-cache", debug_assertions))]
+        self.verify_cache_consistency();
+        Ok(())
+    }
+}
diff --git a/src/tools/miri/src/tag_gc.rs b/src/tools/miri/src/tag_gc.rs
new file mode 100644
index 0000000000000..e20a86711478a
--- /dev/null
+++ b/src/tools/miri/src/tag_gc.rs
@@ -0,0 +1,117 @@
+use crate::*;
+use rustc_data_structures::fx::FxHashSet;
+
+impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
+    fn garbage_collect_tags(&mut self) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        // No reason to do anything at all if stacked borrows is off.
+        if this.machine.stacked_borrows.is_none() {
+            return Ok(());
+        }
+
+        let mut tags = FxHashSet::default();
+
+        for thread in this.machine.threads.iter() {
+            if let Some(Scalar::Ptr(
+                Pointer { provenance: Provenance::Concrete { sb, .. }, .. },
+                _,
+            )) = thread.panic_payload
+            {
+                tags.insert(sb);
+            }
+        }
+
+        self.find_tags_in_tls(&mut tags);
+        self.find_tags_in_memory(&mut tags);
+        self.find_tags_in_locals(&mut tags)?;
+
+        self.remove_unreachable_tags(tags);
+
+        Ok(())
+    }
+
+    fn find_tags_in_tls(&mut self, tags: &mut FxHashSet<SbTag>) {
+        let this = self.eval_context_mut();
+        this.machine.tls.iter(|scalar| {
+            if let Scalar::Ptr(Pointer { provenance: Provenance::Concrete { sb, .. }, .. }, _) =
+                scalar
+            {
+                tags.insert(*sb);
+            }
+        });
+    }
+
+    fn find_tags_in_memory(&mut self, tags: &mut FxHashSet<SbTag>) {
+        let this = self.eval_context_mut();
+        this.memory.alloc_map().iter(|it| {
+            for (_id, (_kind, alloc)) in it {
+                for (_size, prov) in alloc.provenance().iter() {
+                    if let Provenance::Concrete { sb, .. } = prov {
+                        tags.insert(*sb);
+                    }
+                }
+            }
+        });
+    }
+
+    fn find_tags_in_locals(&mut self, tags: &mut FxHashSet<SbTag>) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        for frame in this.machine.threads.all_stacks().flatten() {
+            // Handle the return place of each frame
+            if let Ok(return_place) = frame.return_place.try_as_mplace() {
+                if let Some(Provenance::Concrete { sb, .. }) = return_place.ptr.provenance {
+                    tags.insert(sb);
+                }
+            }
+
+            for local in frame.locals.iter() {
+                let LocalValue::Live(value) = local.value else {
+                continue;
+            };
+                match value {
+                    Operand::Immediate(Immediate::Scalar(Scalar::Ptr(ptr, _))) =>
+                        if let Provenance::Concrete { sb, .. } = ptr.provenance {
+                            tags.insert(sb);
+                        },
+                    Operand::Immediate(Immediate::ScalarPair(s1, s2)) => {
+                        if let Scalar::Ptr(ptr, _) = s1 {
+                            if let Provenance::Concrete { sb, .. } = ptr.provenance {
+                                tags.insert(sb);
+                            }
+                        }
+                        if let Scalar::Ptr(ptr, _) = s2 {
+                            if let Provenance::Concrete { sb, .. } = ptr.provenance {
+                                tags.insert(sb);
+                            }
+                        }
+                    }
+                    Operand::Indirect(MemPlace { ptr, .. }) => {
+                        if let Some(Provenance::Concrete { sb, .. }) = ptr.provenance {
+                            tags.insert(sb);
+                        }
+                    }
+                    Operand::Immediate(Immediate::Uninit)
+                    | Operand::Immediate(Immediate::Scalar(Scalar::Int(_))) => {}
+                }
+            }
+        }
+
+        Ok(())
+    }
+
+    fn remove_unreachable_tags(&mut self, tags: FxHashSet<SbTag>) {
+        let this = self.eval_context_mut();
+        this.memory.alloc_map().iter(|it| {
+            for (_id, (_kind, alloc)) in it {
+                alloc
+                    .extra
+                    .stacked_borrows
+                    .as_ref()
+                    .unwrap()
+                    .borrow_mut()
+                    .remove_unreachable_tags(&tags);
+            }
+        });
+    }
+}
diff --git a/src/tools/miri/test-cargo-miri/.gitignore b/src/tools/miri/test-cargo-miri/.gitignore
new file mode 100644
index 0000000000000..af5854e0c3fd9
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/.gitignore
@@ -0,0 +1,4 @@
+*.real
+custom-run
+custom-test
+config-cli
diff --git a/src/tools/miri/test-cargo-miri/Cargo.lock b/src/tools/miri/test-cargo-miri/Cargo.lock
new file mode 100644
index 0000000000000..a297dd27dbc94
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/Cargo.lock
@@ -0,0 +1,135 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "byteorder"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855"
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "cargo-miri-test"
+version = "0.1.0"
+dependencies = [
+ "autocfg",
+ "byteorder 0.5.3",
+ "byteorder 1.4.3",
+ "cdylib",
+ "exported_symbol",
+ "issue_1567",
+ "issue_1691",
+ "issue_1705",
+ "issue_1760",
+ "issue_rust_86261",
+ "serde_derive",
+]
+
+[[package]]
+name = "cdylib"
+version = "0.1.0"
+dependencies = [
+ "byteorder 1.4.3",
+]
+
+[[package]]
+name = "exported_symbol"
+version = "0.1.0"
+dependencies = [
+ "exported_symbol_dep",
+]
+
+[[package]]
+name = "exported_symbol_dep"
+version = "0.1.0"
+
+[[package]]
+name = "issue_1567"
+version = "0.1.0"
+dependencies = [
+ "byteorder 1.4.3",
+]
+
+[[package]]
+name = "issue_1691"
+version = "0.1.0"
+
+[[package]]
+name = "issue_1705"
+version = "0.1.0"
+dependencies = [
+ "byteorder 1.4.3",
+]
+
+[[package]]
+name = "issue_1760"
+version = "0.1.0"
+
+[[package]]
+name = "issue_rust_86261"
+version = "0.1.0"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.137"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "subcrate"
+version = "0.1.0"
+dependencies = [
+ "byteorder 1.4.3",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.98"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
diff --git a/src/tools/miri/test-cargo-miri/Cargo.toml b/src/tools/miri/test-cargo-miri/Cargo.toml
new file mode 100644
index 0000000000000..5d9e5d143b3b3
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/Cargo.toml
@@ -0,0 +1,32 @@
+[workspace]
+members = ["subcrate", "issue-1567", "exported-symbol-dep"]
+
+[package]
+name = "cargo-miri-test"
+version = "0.1.0"
+authors = ["Miri Team"]
+edition = "2018"
+
+[dependencies]
+byteorder = "1.0"
+cdylib = { path = "cdylib" }
+exported_symbol = { path = "exported-symbol" }
+issue_1567 = { path = "issue-1567" }
+issue_1691 = { path = "issue-1691" }
+issue_1705 = { path = "issue-1705" }
+issue_1760 = { path = "issue-1760" }
+issue_rust_86261 = { path = "issue-rust-86261" }
+
+[dev-dependencies]
+byteorder_2 = { package = "byteorder", version = "0.5" } # to test dev-dependencies behave as expected, with renaming
+serde_derive = "1.0" # not actually used, but exercises some unique code path (`--extern` .so file)
+
+[build-dependencies]
+autocfg = "1"
+
+[lib]
+test = false # test that this is respected (will show in the output)
+
+[[test]]
+name = "main"
+harness = false
diff --git a/src/tools/miri/test-cargo-miri/build.rs b/src/tools/miri/test-cargo-miri/build.rs
new file mode 100644
index 0000000000000..6c1f4d80d3392
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/build.rs
@@ -0,0 +1,40 @@
+use std::env;
+
+#[cfg(miri)]
+compile_error!("`miri` cfg should not be set in build script");
+
+fn not_in_miri() -> i32 {
+    // Inline assembly definitely does not work in Miri.
+    let mut dummy = 42;
+    unsafe {
+        std::arch::asm!("/* {} */", in(reg) &mut dummy);
+    }
+    return dummy;
+}
+
+fn main() {
+    not_in_miri();
+    // Cargo calls `miri --print=cfg` to populate the `CARGO_CFG_*` env vars.
+    // Make sure that the "miri" flag is set.
+    assert!(env::var_os("CARGO_CFG_MIRI").is_some(), "cargo failed to tell us about `--cfg miri`");
+    println!("cargo:rerun-if-changed=build.rs");
+    println!("cargo:rerun-if-env-changed=MIRITESTVAR");
+    println!("cargo:rustc-env=MIRITESTVAR=testval");
+
+    // Test that autocfg works. This invokes RUSTC.
+    let a = autocfg::new();
+    assert!(a.probe_sysroot_crate("std"));
+    assert!(!a.probe_sysroot_crate("doesnotexist"));
+    assert!(a.probe_rustc_version(1, 0));
+    assert!(!a.probe_rustc_version(2, 0));
+    assert!(a.probe_type("i128"));
+    assert!(!a.probe_type("doesnotexist"));
+    assert!(a.probe_trait("Send"));
+    assert!(!a.probe_trait("doesnotexist"));
+    assert!(a.probe_path("std::num"));
+    assert!(!a.probe_path("doesnotexist"));
+    assert!(a.probe_constant("i32::MAX"));
+    assert!(!a.probe_constant("doesnotexist"));
+    assert!(a.probe_expression("Box::new(0)"));
+    assert!(!a.probe_expression("doesnotexist"));
+}
diff --git a/src/tools/miri/test-cargo-miri/cdylib/Cargo.toml b/src/tools/miri/test-cargo-miri/cdylib/Cargo.toml
new file mode 100644
index 0000000000000..527602e0a888f
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/cdylib/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "cdylib"
+version = "0.1.0"
+authors = ["Miri Team"]
+edition = "2018"
+
+[lib]
+# cargo-miri used to handle `cdylib` crate-type specially (https://github.com/rust-lang/miri/pull/1577).
+crate-type = ["cdylib"]
+
+[dependencies]
+byteorder = "1.0" # to test dependencies of sub-crates
diff --git a/src/tools/miri/test-cargo-miri/cdylib/src/lib.rs b/src/tools/miri/test-cargo-miri/cdylib/src/lib.rs
new file mode 100644
index 0000000000000..e47e588251e4e
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/cdylib/src/lib.rs
@@ -0,0 +1,6 @@
+use byteorder::{BigEndian, ByteOrder};
+
+#[no_mangle]
+extern "C" fn use_the_dependency() {
+    let _n = <BigEndian as ByteOrder>::read_u64(&[1, 2, 3, 4, 5, 6, 7, 8]);
+}
diff --git a/src/tools/miri/test-cargo-miri/exported-symbol-dep/Cargo.toml b/src/tools/miri/test-cargo-miri/exported-symbol-dep/Cargo.toml
new file mode 100644
index 0000000000000..00c41172c3af2
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/exported-symbol-dep/Cargo.toml
@@ -0,0 +1,5 @@
+[package]
+name = "exported_symbol_dep"
+version = "0.1.0"
+authors = ["Miri Team"]
+edition = "2018"
diff --git a/src/tools/miri/test-cargo-miri/exported-symbol-dep/src/lib.rs b/src/tools/miri/test-cargo-miri/exported-symbol-dep/src/lib.rs
new file mode 100644
index 0000000000000..5b8a314ae7324
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/exported-symbol-dep/src/lib.rs
@@ -0,0 +1,13 @@
+#[no_mangle]
+fn exported_symbol() -> i32 {
+    123456
+}
+
+struct AssocFn;
+
+impl AssocFn {
+    #[no_mangle]
+    fn assoc_fn_as_exported_symbol() -> i32 {
+        -123456
+    }
+}
diff --git a/src/tools/miri/test-cargo-miri/exported-symbol/Cargo.toml b/src/tools/miri/test-cargo-miri/exported-symbol/Cargo.toml
new file mode 100644
index 0000000000000..7c01be1a85f9c
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/exported-symbol/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "exported_symbol"
+version = "0.1.0"
+authors = ["Miri Team"]
+edition = "2018"
+
+[dependencies]
+# This will become a transitive dependency of doctests in `test-cargo-miri/src/lib.rs`,
+# and the purpose of the test is to make sure Miri can find a `#[no_mangle]` function in a
+# transitive dependency like `exported_symbol_dep`.
+exported_symbol_dep = { path = "../exported-symbol-dep" }
diff --git a/src/tools/miri/test-cargo-miri/exported-symbol/src/lib.rs b/src/tools/miri/test-cargo-miri/exported-symbol/src/lib.rs
new file mode 100644
index 0000000000000..de55eb2a1a5a0
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/exported-symbol/src/lib.rs
@@ -0,0 +1 @@
+extern crate exported_symbol_dep;
diff --git a/src/tools/miri/test-cargo-miri/issue-1567/Cargo.toml b/src/tools/miri/test-cargo-miri/issue-1567/Cargo.toml
new file mode 100644
index 0000000000000..6a6e09036a01d
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/issue-1567/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "issue_1567"
+version = "0.1.0"
+authors = ["Miri Team"]
+edition = "2018"
+
+[lib]
+# Regression test for https://github.com/rust-lang/miri/issues/1567: crate must have this crate-type set.
+# It must also depend on some other crate and use that dependency (we use byteorder).
+crate-type = ["cdylib", "rlib"]
+
+[dependencies]
+byteorder = "1.0"
diff --git a/src/tools/miri/test-cargo-miri/issue-1567/src/lib.rs b/src/tools/miri/test-cargo-miri/issue-1567/src/lib.rs
new file mode 100644
index 0000000000000..5479216832388
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/issue-1567/src/lib.rs
@@ -0,0 +1,5 @@
+use byteorder::{BigEndian, ByteOrder};
+
+pub fn use_the_dependency() {
+    let _n = <BigEndian as ByteOrder>::read_u32(&[1, 2, 3, 4]);
+}
diff --git a/src/tools/miri/test-cargo-miri/issue-1691/Cargo.toml b/src/tools/miri/test-cargo-miri/issue-1691/Cargo.toml
new file mode 100644
index 0000000000000..3100cc6a60b58
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/issue-1691/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "issue_1691"
+version = "0.1.0"
+authors = ["Miri Team"]
+edition = "2018"
+
+[lib]
+crate-type = ["rlib"]
diff --git a/src/tools/miri/test-cargo-miri/issue-1691/src/lib.rs b/src/tools/miri/test-cargo-miri/issue-1691/src/lib.rs
new file mode 100644
index 0000000000000..efde2b58e199e
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/issue-1691/src/lib.rs
@@ -0,0 +1,3 @@
+pub fn use_me() -> bool {
+    true
+}
diff --git a/src/tools/miri/test-cargo-miri/issue-1705/Cargo.toml b/src/tools/miri/test-cargo-miri/issue-1705/Cargo.toml
new file mode 100644
index 0000000000000..ae63647a88819
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/issue-1705/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "issue_1705"
+version = "0.1.0"
+authors = ["Miri Team"]
+edition = "2018"
+
+[lib]
+crate-type = ["lib", "staticlib", "cdylib"]
+
+[dependencies]
+byteorder = "1.0"
diff --git a/src/tools/miri/test-cargo-miri/issue-1705/src/lib.rs b/src/tools/miri/test-cargo-miri/issue-1705/src/lib.rs
new file mode 100644
index 0000000000000..64633490f84b9
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/issue-1705/src/lib.rs
@@ -0,0 +1,5 @@
+use byteorder::{ByteOrder, LittleEndian};
+
+pub fn use_the_dependency() {
+    let _n = <LittleEndian as ByteOrder>::read_u32(&[1, 2, 3, 4]);
+}
diff --git a/src/tools/miri/test-cargo-miri/issue-1760/Cargo.toml b/src/tools/miri/test-cargo-miri/issue-1760/Cargo.toml
new file mode 100644
index 0000000000000..80925c7474638
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/issue-1760/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "issue_1760"
+version = "0.1.0"
+authors = ["Miri Team"]
+edition = "2018"
+
+[lib]
+proc-macro = true
diff --git a/src/tools/miri/test-cargo-miri/issue-1760/build.rs b/src/tools/miri/test-cargo-miri/issue-1760/build.rs
new file mode 100644
index 0000000000000..08427fd7164f1
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/issue-1760/build.rs
@@ -0,0 +1,10 @@
+use std::env;
+
+#[cfg(miri)]
+compile_error!("`miri` cfg should not be set in build script");
+
+fn main() {
+    // Cargo calls `miri --print=cfg` to populate the `CARGO_CFG_*` env vars.
+    // Make sure that the "miri" flag is not set since we are building a procedural macro crate.
+    assert!(env::var_os("CARGO_CFG_MIRI").is_none());
+}
diff --git a/src/tools/miri/test-cargo-miri/issue-1760/src/lib.rs b/src/tools/miri/test-cargo-miri/issue-1760/src/lib.rs
new file mode 100644
index 0000000000000..b4f6274af4448
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/issue-1760/src/lib.rs
@@ -0,0 +1,9 @@
+use proc_macro::TokenStream;
+
+#[cfg(miri)]
+compile_error!("`miri` cfg should not be set in proc-macro");
+
+#[proc_macro]
+pub fn use_the_dependency(_: TokenStream) -> TokenStream {
+    TokenStream::new()
+}
diff --git a/src/tools/miri/test-cargo-miri/issue-rust-86261/Cargo.toml b/src/tools/miri/test-cargo-miri/issue-rust-86261/Cargo.toml
new file mode 100644
index 0000000000000..a6b65ebb5318d
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/issue-rust-86261/Cargo.toml
@@ -0,0 +1,5 @@
+[package]
+name = "issue_rust_86261"
+version = "0.1.0"
+authors = ["Miri Team"]
+edition = "2018"
diff --git a/src/tools/miri/test-cargo-miri/issue-rust-86261/src/lib.rs b/src/tools/miri/test-cargo-miri/issue-rust-86261/src/lib.rs
new file mode 100644
index 0000000000000..1947c38b77455
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/issue-rust-86261/src/lib.rs
@@ -0,0 +1,23 @@
+#![allow(unused_imports, unused_attributes, no_mangle_generic_items)]
+
+// Regression test for https://github.com/rust-lang/rust/issues/86261:
+// `#[no_mangle]` on a `use` item.
+#[no_mangle]
+use std::{any, boxed, io, panic, string, thread};
+
+// `#[no_mangle]` on a struct has a similar problem.
+#[no_mangle]
+pub struct NoMangleStruct;
+
+// If `#[no_mangle]` has effect on the `struct` above, calling `NoMangleStruct` will fail with
+// "multiple definitions of symbol `NoMangleStruct`" error.
+#[export_name = "NoMangleStruct"]
+fn no_mangle_struct() {}
+
+// `#[no_mangle]` on a generic function can also cause ICEs.
+#[no_mangle]
+fn no_mangle_generic<T>() {}
+
+// Same as `no_mangle_struct()` but for the `no_mangle_generic()` generic function.
+#[export_name = "no_mangle_generic"]
+fn no_mangle_generic2() {}
diff --git a/src/tools/miri/test-cargo-miri/run-test.py b/src/tools/miri/test-cargo-miri/run-test.py
new file mode 100755
index 0000000000000..4485d3252ccc2
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/run-test.py
@@ -0,0 +1,192 @@
+#!/usr/bin/env python3
+'''
+Test whether cargo-miri works properly.
+Assumes the `MIRI_SYSROOT` env var to be set appropriately,
+and the working directory to contain the cargo-miri-test project.
+'''
+
+import sys, subprocess, os, re, difflib
+
+CGREEN  = '\33[32m'
+CBOLD   = '\33[1m'
+CEND    = '\33[0m'
+
+def fail(msg):
+    print("\nTEST FAIL: {}".format(msg))
+    sys.exit(1)
+
+def cargo_miri(cmd, quiet = True):
+    args = ["cargo", "miri", cmd]
+    if quiet:
+        args += ["-q"]
+    if 'MIRI_TEST_TARGET' in os.environ:
+        args += ["--target", os.environ['MIRI_TEST_TARGET']]
+    return args
+
+def normalize_stdout(str):
+    str = str.replace("src\\", "src/") # normalize paths across platforms
+    str = re.sub("finished in \d+\.\d\ds", "finished in $TIME", str) # the time keeps changing, obviously
+    return str
+
+def normalize_stderr(str):
+    str = re.sub("Preparing a sysroot for Miri \(target: [a-z0-9_-]+\)\.\.\. done\n", "", str) # remove leading cargo-miri setup output
+    return str
+
+def check_output(actual, path, name):
+    expected = open(path).read()
+    if expected == actual:
+        return True
+    print(f"{path} did not match reference!")
+    print(f"--- BEGIN diff {name} ---")
+    for text in difflib.unified_diff(expected.split("\n"), actual.split("\n")):
+        print(text)
+    print(f"--- END diff {name} ---")
+    return False
+
+def test(name, cmd, stdout_ref, stderr_ref, stdin=b'', env={}):
+    print("Testing {}...".format(name))
+    ## Call `cargo miri`, capture all output
+    p_env = os.environ.copy()
+    p_env.update(env)
+    p = subprocess.Popen(
+        cmd,
+        stdin=subprocess.PIPE,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
+        env=p_env,
+    )
+    (stdout, stderr) = p.communicate(input=stdin)
+    stdout = normalize_stdout(stdout.decode("UTF-8"))
+    stderr = normalize_stderr(stderr.decode("UTF-8"))
+
+    stdout_matches = check_output(stdout, stdout_ref, "stdout")
+    stderr_matches = check_output(stderr, stderr_ref, "stderr")
+    
+    if p.returncode == 0 and stdout_matches and stderr_matches:
+        # All good!
+        return
+    fail("exit code was {}".format(p.returncode))
+
+def test_no_rebuild(name, cmd, env={}):
+    print("Testing {}...".format(name))
+    p_env = os.environ.copy()
+    p_env.update(env)
+    p = subprocess.Popen(
+        cmd,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
+        env=p_env,
+    )
+    (stdout, stderr) = p.communicate()
+    stdout = stdout.decode("UTF-8")
+    stderr = stderr.decode("UTF-8")
+    if p.returncode != 0:
+        fail("rebuild failed");
+    # Also check for 'Running' as a sanity check.
+    if stderr.count(" Compiling ") > 0 or stderr.count(" Running ") == 0:
+        print("--- BEGIN stderr ---")
+        print(stderr, end="")
+        print("--- END stderr ---")
+        fail("Something was being rebuilt when it should not be (or we got no output)");
+
+def test_cargo_miri_run():
+    test("`cargo miri run` (no isolation)",
+        cargo_miri("run"),
+        "run.default.stdout.ref", "run.default.stderr.ref",
+        stdin=b'12\n21\n',
+        env={
+            'MIRIFLAGS': "-Zmiri-disable-isolation",
+            'MIRITESTVAR': "wrongval", # make sure the build.rs value takes precedence
+        },
+    )
+    # Special test: run it again *without* `-q` to make sure nothing is being rebuilt (Miri issue #1722)
+    test_no_rebuild("`cargo miri run` (no rebuild)",
+        cargo_miri("run", quiet=False) + ["--", ""],
+        env={'MIRITESTVAR': "wrongval"}, # changing the env var causes a rebuild (re-runs build.rs),
+                                         # so keep it set
+    )
+    test("`cargo miri run` (with arguments and target)",
+        cargo_miri("run") + ["--bin", "cargo-miri-test", "--", "hello world", '"hello world"', r'he\\llo\"world'],
+        "run.args.stdout.ref", "run.args.stderr.ref",
+    )
+    test("`cargo miri r` (subcrate, no isolation)",
+        cargo_miri("r") + ["-p", "subcrate"],
+        "run.subcrate.stdout.ref", "run.subcrate.stderr.ref",
+        env={'MIRIFLAGS': "-Zmiri-disable-isolation"},
+    )
+    test("`cargo miri run` (custom target dir)",
+        # Attempt to confuse the argument parser.
+        cargo_miri("run") + ["--target-dir=custom-run", "--", "--target-dir=target/custom-run"],
+        "run.args.stdout.ref", "run.custom-target-dir.stderr.ref",
+    )
+
+def test_cargo_miri_test():
+    # rustdoc is not run on foreign targets
+    is_foreign = 'MIRI_TEST_TARGET' in os.environ
+    default_ref = "test.cross-target.stdout.ref" if is_foreign else "test.default.stdout.ref"
+    filter_ref = "test.filter.cross-target.stdout.ref" if is_foreign else "test.filter.stdout.ref"
+
+    # macOS needs permissive provenance inside getrandom_1.
+    test("`cargo miri test`",
+        cargo_miri("test"),
+        default_ref, "test.stderr-empty.ref",
+        env={'MIRIFLAGS': "-Zmiri-permissive-provenance -Zmiri-seed=feed"},
+    )
+    test("`cargo miri test` (no isolation, no doctests)",
+        cargo_miri("test") + ["--bins", "--tests"], # no `--lib`, we disabled that in `Cargo.toml`
+        "test.cross-target.stdout.ref", "test.stderr-empty.ref",
+        env={'MIRIFLAGS': "-Zmiri-permissive-provenance -Zmiri-disable-isolation"},
+    )
+    test("`cargo miri test` (with filter)",
+        cargo_miri("test") + ["--", "--format=pretty", "pl"],
+        filter_ref, "test.stderr-empty.ref",
+    )
+    test("`cargo miri test` (test target)",
+        cargo_miri("test") + ["--test", "test", "--", "--format=pretty"],
+        "test.test-target.stdout.ref", "test.stderr-empty.ref",
+        env={'MIRIFLAGS': "-Zmiri-permissive-provenance"},
+    )
+    test("`cargo miri test` (bin target)",
+        cargo_miri("test") + ["--bin", "cargo-miri-test", "--", "--format=pretty"],
+        "test.bin-target.stdout.ref", "test.stderr-empty.ref",
+    )
+    test("`cargo miri t` (subcrate, no isolation)",
+        cargo_miri("t") + ["-p", "subcrate"],
+        "test.subcrate.stdout.ref", "test.stderr-proc-macro.ref",
+        env={'MIRIFLAGS': "-Zmiri-disable-isolation"},
+    )
+    test("`cargo miri test` (subcrate, doctests)",
+        cargo_miri("test") + ["-p", "subcrate", "--doc"],
+        "test.stdout-empty.ref", "test.stderr-proc-macro-doctest.ref",
+    )
+    test("`cargo miri test` (custom target dir)",
+        cargo_miri("test") + ["--target-dir=custom-test"],
+        default_ref, "test.stderr-empty.ref",
+        env={'MIRIFLAGS': "-Zmiri-permissive-provenance"},
+    )
+    del os.environ["CARGO_TARGET_DIR"] # this overrides `build.target-dir` passed by `--config`, so unset it
+    test("`cargo miri test` (config-cli)",
+        cargo_miri("test") + ["--config=build.target-dir=\"config-cli\"", "-Zunstable-options"],
+        default_ref, "test.stderr-empty.ref",
+        env={'MIRIFLAGS': "-Zmiri-permissive-provenance"},
+    )
+
+os.chdir(os.path.dirname(os.path.realpath(__file__)))
+os.environ["CARGO_TARGET_DIR"] = "target" # this affects the location of the target directory that we need to check
+os.environ["RUST_TEST_NOCAPTURE"] = "0" # this affects test output, so make sure it is not set
+os.environ["RUST_TEST_THREADS"] = "1" # avoid non-deterministic output due to concurrent test runs
+
+target_str = " for target {}".format(os.environ['MIRI_TEST_TARGET']) if 'MIRI_TEST_TARGET' in os.environ else ""
+print(CGREEN + CBOLD + "## Running `cargo miri` tests{}".format(target_str) + CEND)
+
+test_cargo_miri_run()
+test_cargo_miri_test()
+# Ensure we did not create anything outside the expected target dir.
+for target_dir in ["target", "custom-run", "custom-test", "config-cli"]:
+    if os.listdir(target_dir) != ["miri"]:
+        fail(f"`{target_dir}` contains unexpected files")
+    # Ensure something exists inside that target dir.
+    os.access(os.path.join(target_dir, "miri", "debug", "deps"), os.F_OK)
+
+print("\nTEST SUCCESSFUL!")
+sys.exit(0)
diff --git a/src/tools/miri/test-cargo-miri/run.args.stderr.ref b/src/tools/miri/test-cargo-miri/run.args.stderr.ref
new file mode 100644
index 0000000000000..01bb8952322b6
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/run.args.stderr.ref
@@ -0,0 +1,4 @@
+main
+hello world
+"hello world"
+he\\llo\"world
diff --git a/src/tools/miri/test-cargo-miri/run.args.stdout.ref b/src/tools/miri/test-cargo-miri/run.args.stdout.ref
new file mode 100644
index 0000000000000..6710f307cb26d
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/run.args.stdout.ref
@@ -0,0 +1 @@
+0x01020304
diff --git a/src/tools/miri/test-cargo-miri/run.custom-target-dir.stderr.ref b/src/tools/miri/test-cargo-miri/run.custom-target-dir.stderr.ref
new file mode 100644
index 0000000000000..4395ff8879b96
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/run.custom-target-dir.stderr.ref
@@ -0,0 +1,2 @@
+main
+--target-dir=target/custom-run
diff --git a/src/tools/miri/test-cargo-miri/run.default.stderr.ref b/src/tools/miri/test-cargo-miri/run.default.stderr.ref
new file mode 100644
index 0000000000000..ba2906d0666cf
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/run.default.stderr.ref
@@ -0,0 +1 @@
+main
diff --git a/src/tools/miri/test-cargo-miri/run.default.stdout.ref b/src/tools/miri/test-cargo-miri/run.default.stdout.ref
new file mode 100644
index 0000000000000..2eab8df967d5f
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/run.default.stdout.ref
@@ -0,0 +1,3 @@
+0x01020304
+24
+42
diff --git a/src/tools/miri/test-cargo-miri/run.subcrate.stderr.ref b/src/tools/miri/test-cargo-miri/run.subcrate.stderr.ref
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/src/tools/miri/test-cargo-miri/run.subcrate.stdout.ref b/src/tools/miri/test-cargo-miri/run.subcrate.stdout.ref
new file mode 100644
index 0000000000000..53340a502381d
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/run.subcrate.stdout.ref
@@ -0,0 +1 @@
+subcrate running
diff --git a/src/tools/miri/test-cargo-miri/src/lib.rs b/src/tools/miri/test-cargo-miri/src/lib.rs
new file mode 100644
index 0000000000000..66c8aa2eac57e
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/src/lib.rs
@@ -0,0 +1,27 @@
+/// Doc-test test
+/// ```rust
+/// assert!(cargo_miri_test::make_true());
+/// ```
+/// ```rust,no_run
+/// assert!(!cargo_miri_test::make_true());
+/// ```
+/// ```rust,compile_fail
+/// assert!(cargo_miri_test::make_true() == 5);
+/// ```
+#[no_mangle]
+pub fn make_true() -> bool {
+    issue_1567::use_the_dependency();
+    issue_1705::use_the_dependency();
+    issue_1760::use_the_dependency!();
+    issue_1691::use_me()
+}
+
+/// ```rust
+/// cargo_miri_test::miri_only_fn();
+/// ```
+#[cfg(miri)]
+pub fn miri_only_fn() {}
+
+pub fn main() {
+    println!("imported main");
+}
diff --git a/src/tools/miri/test-cargo-miri/src/main.rs b/src/tools/miri/test-cargo-miri/src/main.rs
new file mode 100644
index 0000000000000..41c52b7017028
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/src/main.rs
@@ -0,0 +1,78 @@
+use byteorder::{BigEndian, ByteOrder};
+use std::env;
+#[cfg(unix)]
+use std::io::{self, BufRead};
+
+fn main() {
+    // Check env var set by `build.rs`.
+    assert_eq!(env!("MIRITESTVAR"), "testval");
+
+    // Exercise external crate, printing to stdout.
+    let buf = &[1, 2, 3, 4];
+    let n = <BigEndian as ByteOrder>::read_u32(buf);
+    assert_eq!(n, 0x01020304);
+    println!("{:#010x}", n);
+
+    // Access program arguments, printing to stderr.
+    for arg in std::env::args() {
+        eprintln!("{}", arg);
+    }
+
+    // If there were no arguments, access stdin and test working dir.
+    // (We rely on the test runner to always disable isolation when passing no arguments.)
+    if std::env::args().len() <= 1 {
+        // CWD should be crate root.
+        // We have to normalize slashes, as the env var might be set for a different target's conventions.
+        let env_dir = env::current_dir().unwrap();
+        let env_dir = env_dir.to_string_lossy().replace("\\", "/");
+        let crate_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
+        let crate_dir = crate_dir.to_string_lossy().replace("\\", "/");
+        assert_eq!(env_dir, crate_dir);
+
+        #[cfg(unix)]
+        for line in io::stdin().lock().lines() {
+            let num: i32 = line.unwrap().parse().unwrap();
+            println!("{}", 2 * num);
+        }
+        // On non-Unix, reading from stdin is not supported. So we hard-code the right answer.
+        #[cfg(not(unix))]
+        {
+            println!("24");
+            println!("42");
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use byteorder_2::{BigEndian, ByteOrder};
+
+    // Make sure in-crate tests with dev-dependencies work
+    #[test]
+    fn dev_dependency() {
+        let _n = <BigEndian as ByteOrder>::read_u64(&[1, 2, 3, 4, 5, 6, 7, 8]);
+    }
+
+    #[test]
+    fn exported_symbol() {
+        extern crate cargo_miri_test;
+        extern crate exported_symbol;
+        extern crate issue_rust_86261;
+        // Test calling exported symbols in (transitive) dependencies.
+        // Repeat calls to make sure the `Instance` cache is not broken.
+        for _ in 0..3 {
+            extern "Rust" {
+                fn exported_symbol() -> i32;
+                fn assoc_fn_as_exported_symbol() -> i32;
+                fn make_true() -> bool;
+                fn NoMangleStruct();
+                fn no_mangle_generic();
+            }
+            assert_eq!(unsafe { exported_symbol() }, 123456);
+            assert_eq!(unsafe { assoc_fn_as_exported_symbol() }, -123456);
+            assert!(unsafe { make_true() });
+            unsafe { NoMangleStruct() }
+            unsafe { no_mangle_generic() }
+        }
+    }
+}
diff --git a/src/tools/miri/test-cargo-miri/subcrate/Cargo.toml b/src/tools/miri/test-cargo-miri/subcrate/Cargo.toml
new file mode 100644
index 0000000000000..06b1ce1cba4b8
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/subcrate/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "subcrate"
+version = "0.1.0"
+authors = ["Miri Team"]
+edition = "2018"
+
+[lib]
+proc-macro = true
+doctest = false
+
+[[bin]]
+name = "subcrate"
+path = "main.rs"
+
+[[test]]
+name = "subtest"
+path = "test.rs"
+harness = false
+
+[dev-dependencies]
+byteorder = "1.0"
diff --git a/src/tools/miri/test-cargo-miri/subcrate/main.rs b/src/tools/miri/test-cargo-miri/subcrate/main.rs
new file mode 100644
index 0000000000000..4ce80b3707226
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/subcrate/main.rs
@@ -0,0 +1,16 @@
+use std::env;
+use std::path::PathBuf;
+
+fn main() {
+    println!("subcrate running");
+
+    // CWD should be workspace root, i.e., one level up from crate root.
+    // We have to normalize slashes, as the env var might be set for a different target's conventions.
+    let env_dir = env::current_dir().unwrap();
+    let env_dir = env_dir.to_string_lossy().replace("\\", "/");
+    let crate_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
+    let crate_dir = crate_dir.to_string_lossy().replace("\\", "/");
+    let crate_dir = PathBuf::from(crate_dir);
+    let crate_dir = crate_dir.parent().unwrap().to_string_lossy();
+    assert_eq!(env_dir, crate_dir);
+}
diff --git a/src/tools/miri/test-cargo-miri/subcrate/src/lib.rs b/src/tools/miri/test-cargo-miri/subcrate/src/lib.rs
new file mode 100644
index 0000000000000..2ccb6704b05e6
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/subcrate/src/lib.rs
@@ -0,0 +1,5 @@
+#[cfg(doctest)]
+compile_error!("rustdoc should not touch me");
+
+#[cfg(test)]
+compile_error!("Miri should not touch me");
diff --git a/src/tools/miri/test-cargo-miri/subcrate/test.rs b/src/tools/miri/test-cargo-miri/subcrate/test.rs
new file mode 100644
index 0000000000000..77e3c2878ca0e
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/subcrate/test.rs
@@ -0,0 +1,18 @@
+use std::env;
+
+use byteorder::{ByteOrder, LittleEndian};
+
+fn main() {
+    println!("subcrate testing");
+
+    // CWD should be crate root.
+    // We have to normalize slashes, as the env var might be set for a different target's conventions.
+    let env_dir = env::current_dir().unwrap();
+    let env_dir = env_dir.to_string_lossy().replace("\\", "/");
+    let crate_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
+    let crate_dir = crate_dir.to_string_lossy().replace("\\", "/");
+    assert_eq!(env_dir, crate_dir);
+
+    // Make sure we can call dev-dependencies.
+    let _n = <LittleEndian as ByteOrder>::read_u32(&[1, 2, 3, 4]);
+}
diff --git a/src/tools/miri/test-cargo-miri/test.bin-target.stdout.ref b/src/tools/miri/test-cargo-miri/test.bin-target.stdout.ref
new file mode 100644
index 0000000000000..5264530160bc5
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/test.bin-target.stdout.ref
@@ -0,0 +1,7 @@
+
+running 2 tests
+test test::dev_dependency ... ok
+test test::exported_symbol ... ok
+
+test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
+
diff --git a/src/tools/miri/test-cargo-miri/test.cross-target.stdout.ref b/src/tools/miri/test-cargo-miri/test.cross-target.stdout.ref
new file mode 100644
index 0000000000000..8c543e479f4e0
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/test.cross-target.stdout.ref
@@ -0,0 +1,11 @@
+
+running 2 tests
+..
+test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
+
+imported main
+
+running 6 tests
+...i..
+test result: ok. 5 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out
+
diff --git a/src/tools/miri/test-cargo-miri/test.default.stdout.ref b/src/tools/miri/test-cargo-miri/test.default.stdout.ref
new file mode 100644
index 0000000000000..9a17f3d61b6ac
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/test.default.stdout.ref
@@ -0,0 +1,16 @@
+
+running 2 tests
+..
+test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
+
+imported main
+
+running 6 tests
+...i..
+test result: ok. 5 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out
+
+
+running 4 tests
+....
+test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
+
diff --git a/src/tools/miri/test-cargo-miri/test.filter.cross-target.stdout.ref b/src/tools/miri/test-cargo-miri/test.filter.cross-target.stdout.ref
new file mode 100644
index 0000000000000..bb0282d6c9167
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/test.filter.cross-target.stdout.ref
@@ -0,0 +1,12 @@
+
+running 0 tests
+
+test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out
+
+imported main
+
+running 1 test
+test simple ... ok
+
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 5 filtered out
+
diff --git a/src/tools/miri/test-cargo-miri/test.filter.stdout.ref b/src/tools/miri/test-cargo-miri/test.filter.stdout.ref
new file mode 100644
index 0000000000000..c618956656a8a
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/test.filter.stdout.ref
@@ -0,0 +1,17 @@
+
+running 0 tests
+
+test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out
+
+imported main
+
+running 1 test
+test simple ... ok
+
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 5 filtered out
+
+
+running 0 tests
+
+test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 4 filtered out; finished in $TIME
+
diff --git a/src/tools/miri/test-cargo-miri/test.stderr-empty.ref b/src/tools/miri/test-cargo-miri/test.stderr-empty.ref
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/src/tools/miri/test-cargo-miri/test.stderr-proc-macro-doctest.ref b/src/tools/miri/test-cargo-miri/test.stderr-proc-macro-doctest.ref
new file mode 100644
index 0000000000000..ca5e3a2392db8
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/test.stderr-proc-macro-doctest.ref
@@ -0,0 +1 @@
+Running doctests of `proc-macro` crates is not currently supported by Miri.
diff --git a/src/tools/miri/test-cargo-miri/test.stderr-proc-macro.ref b/src/tools/miri/test-cargo-miri/test.stderr-proc-macro.ref
new file mode 100644
index 0000000000000..4983250917b59
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/test.stderr-proc-macro.ref
@@ -0,0 +1 @@
+Running unit tests of `proc-macro` crates is not currently supported by Miri.
diff --git a/src/tools/miri/test-cargo-miri/test.stdout-empty.ref b/src/tools/miri/test-cargo-miri/test.stdout-empty.ref
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/src/tools/miri/test-cargo-miri/test.subcrate.stdout.ref b/src/tools/miri/test-cargo-miri/test.subcrate.stdout.ref
new file mode 100644
index 0000000000000..67e5c7f8e920c
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/test.subcrate.stdout.ref
@@ -0,0 +1,6 @@
+
+running 0 tests
+
+test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
+
+subcrate testing
diff --git a/src/tools/miri/test-cargo-miri/test.test-target.stdout.ref b/src/tools/miri/test-cargo-miri/test.test-target.stdout.ref
new file mode 100644
index 0000000000000..dd59b32b780c8
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/test.test-target.stdout.ref
@@ -0,0 +1,11 @@
+
+running 6 tests
+test cargo_env ... ok
+test deps ... ok
+test do_panic - should panic ... ok
+test does_not_work_on_miri ... ignored
+test fail_index_check - should panic ... ok
+test simple ... ok
+
+test result: ok. 5 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out
+
diff --git a/src/tools/miri/test-cargo-miri/tests/main.rs b/src/tools/miri/test-cargo-miri/tests/main.rs
new file mode 100644
index 0000000000000..bb94c8f37876c
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/tests/main.rs
@@ -0,0 +1,3 @@
+#![feature(imported_main)]
+
+use cargo_miri_test::main;
diff --git a/src/tools/miri/test-cargo-miri/tests/test.rs b/src/tools/miri/test-cargo-miri/tests/test.rs
new file mode 100644
index 0000000000000..9ed2152893964
--- /dev/null
+++ b/src/tools/miri/test-cargo-miri/tests/test.rs
@@ -0,0 +1,49 @@
+#[test]
+fn simple() {
+    assert_eq!(4, 4);
+}
+
+// A test that won't work on miri (tests disabling tests).
+#[test]
+#[cfg_attr(miri, ignore)]
+fn does_not_work_on_miri() {
+    // Only do this where inline assembly is stable.
+    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+    unsafe {
+        std::arch::asm!("foo");
+    }
+}
+
+// Make sure integration tests can access both dependencies and dev-dependencies
+#[test]
+fn deps() {
+    {
+        use byteorder::{BigEndian, ByteOrder};
+        let _n = <BigEndian as ByteOrder>::read_u64(&[1, 2, 3, 4, 5, 6, 7, 8]);
+    }
+    {
+        use byteorder_2::{BigEndian, ByteOrder};
+        let _n = <BigEndian as ByteOrder>::read_u64(&[1, 2, 3, 4, 5, 6, 7, 8]);
+    }
+}
+
+#[test]
+fn cargo_env() {
+    assert_eq!(env!("CARGO_PKG_NAME"), "cargo-miri-test");
+    env!("CARGO_BIN_EXE_cargo-miri-test"); // Asserts that this exists.
+}
+
+#[test]
+#[should_panic(expected = "Explicit panic")]
+fn do_panic() // In large, friendly letters :)
+{
+    panic!("Explicit panic from test!");
+}
+
+// A different way of raising a panic
+#[test]
+#[allow(unconditional_panic)]
+#[should_panic(expected = "the len is 0 but the index is 42")]
+fn fail_index_check() {
+    [][42]
+}
diff --git a/src/tools/miri/test_dependencies/Cargo.lock b/src/tools/miri/test_dependencies/Cargo.lock
new file mode 100644
index 0000000000000..d4b32e2c29a22
--- /dev/null
+++ b/src/tools/miri/test_dependencies/Cargo.lock
@@ -0,0 +1,388 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bytes"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "getrandom"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.9.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
+
+[[package]]
+name = "lock_api"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
+[[package]]
+name = "mio"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
+dependencies = [
+ "libc",
+ "log",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+ "windows-sys",
+]
+
+[[package]]
+name = "miri-test-deps"
+version = "0.1.0"
+dependencies = [
+ "getrandom 0.1.16",
+ "getrandom 0.2.7",
+ "libc",
+ "num_cpus",
+ "page_size",
+ "rand",
+ "tokio",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
+
+[[package]]
+name = "page_size"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-sys",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
+dependencies = [
+ "getrandom 0.2.7",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
+
+[[package]]
+name = "socket2"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.98"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tokio"
+version = "1.19.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439"
+dependencies = [
+ "bytes",
+ "libc",
+ "memchr",
+ "mio",
+ "num_cpus",
+ "once_cell",
+ "parking_lot",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2",
+ "tokio-macros",
+ "winapi",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+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.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
+dependencies = [
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
diff --git a/src/tools/miri/test_dependencies/Cargo.toml b/src/tools/miri/test_dependencies/Cargo.toml
new file mode 100644
index 0000000000000..58f731f91d0f4
--- /dev/null
+++ b/src/tools/miri/test_dependencies/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+authors = ["Miri Team"]
+description = "dependencies that unit tests can have"
+license = "MIT OR Apache-2.0"
+name = "miri-test-deps"
+repository = "https://github.com/rust-lang/miri"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+# all dependencies (and their transitive ones) listed here can be used in `tests/`.
+tokio = { version = "1.0", features = ["full"] }
+libc = "0.2"
+page_size = "0.4.1"
+num_cpus = "1.10.1"
+
+getrandom_1 = { package = "getrandom", version = "0.1" }
+getrandom_2 = { package = "getrandom", version = "0.2" }
+rand = { version = "0.8", features = ["small_rng"] }
+
+[workspace]
diff --git a/src/tools/miri/test_dependencies/src/main.rs b/src/tools/miri/test_dependencies/src/main.rs
new file mode 100644
index 0000000000000..f328e4d9d04c3
--- /dev/null
+++ b/src/tools/miri/test_dependencies/src/main.rs
@@ -0,0 +1 @@
+fn main() {}
diff --git a/src/tools/miri/tests/compiletest.rs b/src/tools/miri/tests/compiletest.rs
new file mode 100644
index 0000000000000..6b5668e2d6c4c
--- /dev/null
+++ b/src/tools/miri/tests/compiletest.rs
@@ -0,0 +1,220 @@
+use colored::*;
+use regex::Regex;
+use std::path::{Path, PathBuf};
+use std::{env, process::Command};
+use ui_test::{color_eyre::Result, Config, Mode, OutputConflictHandling};
+
+fn miri_path() -> PathBuf {
+    PathBuf::from(option_env!("MIRI").unwrap_or(env!("CARGO_BIN_EXE_miri")))
+}
+
+// Build the shared object file for testing external C function calls.
+fn build_so_for_c_ffi_tests() -> PathBuf {
+    let cc = option_env!("CC").unwrap_or("cc");
+    // Target directory that we can write to.
+    let so_target_dir = Path::new(&env::var_os("CARGO_TARGET_DIR").unwrap()).join("miri-extern-so");
+    // Create the directory if it does not already exist.
+    std::fs::create_dir_all(&so_target_dir)
+        .expect("Failed to create directory for shared object file");
+    let so_file_path = so_target_dir.join("libtestlib.so");
+    let cc_output = Command::new(cc)
+        .args([
+            "-shared",
+            "-o",
+            so_file_path.to_str().unwrap(),
+            "tests/extern-so/test.c",
+            // Only add the functions specified in libcode.version to the shared object file.
+            // This is to avoid automatically adding `malloc`, etc.
+            // Source: https://anadoxin.org/blog/control-over-symbol-exports-in-gcc.html/
+            "-fPIC",
+            "-Wl,--version-script=tests/extern-so/libcode.version",
+        ])
+        .output()
+        .expect("failed to generate shared object file for testing external C function calls");
+    if !cc_output.status.success() {
+        panic!("error in generating shared object file for testing external C function calls");
+    }
+    so_file_path
+}
+
+fn run_tests(
+    mode: Mode,
+    path: &str,
+    target: Option<String>,
+    with_dependencies: bool,
+) -> Result<()> {
+    let mut config = Config {
+        target,
+        stderr_filters: STDERR.clone(),
+        stdout_filters: STDOUT.clone(),
+        root_dir: PathBuf::from(path),
+        mode,
+        program: miri_path(),
+        quiet: false,
+        ..Config::default()
+    };
+
+    let in_rustc_test_suite = option_env!("RUSTC_STAGE").is_some();
+
+    // Add some flags we always want.
+    config.args.push("--edition".into());
+    config.args.push("2018".into());
+    if in_rustc_test_suite {
+        // Less aggressive warnings to make the rustc toolstate management less painful.
+        // (We often get warnings when e.g. a feature gets stabilized or some lint gets added/improved.)
+        config.args.push("-Astable-features".into());
+        config.args.push("-Aunused".into());
+    } else {
+        config.args.push("-Dwarnings".into());
+        config.args.push("-Dunused".into());
+    }
+    if let Ok(extra_flags) = env::var("MIRIFLAGS") {
+        for flag in extra_flags.split_whitespace() {
+            config.args.push(flag.into());
+        }
+    }
+    config.args.push("-Zui-testing".into());
+    if let Some(target) = &config.target {
+        config.args.push("--target".into());
+        config.args.push(target.into());
+    }
+
+    // If we're on linux, and we're testing the extern-so functionality,
+    // then build the shared object file for testing external C function calls
+    // and push the relevant compiler flag.
+    if cfg!(target_os = "linux") && path.starts_with("tests/extern-so/") {
+        let so_file_path = build_so_for_c_ffi_tests();
+        let mut flag = std::ffi::OsString::from("-Zmiri-extern-so-file=");
+        flag.push(so_file_path.into_os_string());
+        config.args.push(flag);
+    }
+
+    let skip_ui_checks = env::var_os("MIRI_SKIP_UI_CHECKS").is_some();
+
+    config.output_conflict_handling = match (env::var_os("MIRI_BLESS").is_some(), skip_ui_checks) {
+        (false, false) => OutputConflictHandling::Error,
+        (true, false) => OutputConflictHandling::Bless,
+        (false, true) => OutputConflictHandling::Ignore,
+        (true, true) => panic!("cannot use MIRI_BLESS and MIRI_SKIP_UI_CHECKS at the same time"),
+    };
+
+    // Handle command-line arguments.
+    config.path_filter.extend(std::env::args().skip(1).filter(|arg| {
+        match &**arg {
+            "--quiet" => {
+                config.quiet = true;
+                false
+            }
+            _ => true,
+        }
+    }));
+
+    let use_std = env::var_os("MIRI_NO_STD").is_none();
+
+    if with_dependencies && use_std {
+        config.dependencies_crate_manifest_path =
+            Some(Path::new("test_dependencies").join("Cargo.toml"));
+        config.dependency_builder.args = vec![
+            "run".into(),
+            "--manifest-path".into(),
+            "cargo-miri/Cargo.toml".into(),
+            "--".into(),
+            "miri".into(),
+            "run".into(), // There is no `cargo miri build` so we just use `cargo miri run`.
+        ];
+    }
+    ui_test::run_tests(config)
+}
+
+macro_rules! regexes {
+    ($name:ident: $($regex:expr => $replacement:expr,)*) => {lazy_static::lazy_static! {
+        static ref $name: Vec<(Regex, &'static str)> = vec![
+            $((Regex::new($regex).unwrap(), $replacement),)*
+        ];
+    }};
+}
+
+regexes! {
+    STDOUT:
+    // Windows file paths
+    r"\\"                           => "/",
+}
+
+regexes! {
+    STDERR:
+    // erase line and column info
+    r"\.rs:[0-9]+:[0-9]+(: [0-9]+:[0-9]+)?" => ".rs:LL:CC",
+    // erase alloc ids
+    "alloc[0-9]+"                    => "ALLOC",
+    // erase Stacked Borrows tags
+    "<[0-9]+>"                       => "<TAG>",
+    // erase whitespace that differs between platforms
+    r" +at (.*\.rs)"                 => " at $1",
+    // erase generics in backtraces
+    "([0-9]+: .*)::<.*>"             => "$1",
+    // erase addresses in backtraces
+    "([0-9]+: ) +0x[0-9a-f]+ - (.*)" => "$1$2",
+    // erase long hexadecimals
+    r"0x[0-9a-fA-F]+[0-9a-fA-F]{2,2}" => "$$HEX",
+    // erase specific alignments
+    "alignment [0-9]+"               => "alignment ALIGN",
+    // erase thread caller ids
+    r"call [0-9]+"                  => "call ID",
+    // erase platform module paths
+    "sys::[a-z]+::"                  => "sys::PLATFORM::",
+    // Windows file paths
+    r"\\"                           => "/",
+    // erase Rust stdlib path
+    "[^ `]*/(rust[^/]*|checkout)/library/" => "RUSTLIB/",
+    // erase platform file paths
+    "sys/[a-z]+/"                    => "sys/PLATFORM/",
+    // erase paths into the crate registry
+    r"[^ ]*/\.?cargo/registry/.*/(.*\.rs)"  => "CARGO_REGISTRY/.../$1",
+}
+
+enum Dependencies {
+    WithDependencies,
+    WithoutDependencies,
+}
+
+use Dependencies::*;
+
+fn ui(mode: Mode, path: &str, with_dependencies: Dependencies) -> Result<()> {
+    let target = get_target();
+
+    let msg = format!(
+        "## Running ui tests in {path} against miri for {}",
+        target.as_deref().unwrap_or("host")
+    );
+    eprintln!("{}", msg.green().bold());
+
+    let with_dependencies = match with_dependencies {
+        WithDependencies => true,
+        WithoutDependencies => false,
+    };
+    run_tests(mode, path, target, with_dependencies)
+}
+
+fn get_target() -> Option<String> {
+    env::var("MIRI_TEST_TARGET").ok()
+}
+
+fn main() -> Result<()> {
+    ui_test::color_eyre::install()?;
+
+    // Add a test env var to do environment communication tests.
+    env::set_var("MIRI_ENV_VAR_TEST", "0");
+    // Let the tests know where to store temp files (they might run for a different target, which can make this hard to find).
+    env::set_var("MIRI_TEMP", env::temp_dir());
+
+    ui(Mode::Pass, "tests/pass", WithoutDependencies)?;
+    ui(Mode::Pass, "tests/pass-dep", WithDependencies)?;
+    ui(Mode::Panic, "tests/panic", WithDependencies)?;
+    ui(Mode::Fail { require_patterns: true }, "tests/fail", WithDependencies)?;
+    if cfg!(target_os = "linux") {
+        ui(Mode::Pass, "tests/extern-so/pass", WithoutDependencies)?;
+        ui(Mode::Fail { require_patterns: true }, "tests/extern-so/fail", WithDependencies)?;
+    }
+
+    Ok(())
+}
diff --git a/src/tools/miri/tests/extern-so/fail/function_not_in_so.rs b/src/tools/miri/tests/extern-so/fail/function_not_in_so.rs
new file mode 100644
index 0000000000000..3aaeb632cad7b
--- /dev/null
+++ b/src/tools/miri/tests/extern-so/fail/function_not_in_so.rs
@@ -0,0 +1,12 @@
+//@only-target-linux
+//@only-on-host
+
+extern "C" {
+    fn foo();
+}
+
+fn main() {
+    unsafe {
+        foo(); //~ ERROR: unsupported operation: can't call foreign function: foo
+    }
+}
diff --git a/src/tools/miri/tests/extern-so/fail/function_not_in_so.stderr b/src/tools/miri/tests/extern-so/fail/function_not_in_so.stderr
new file mode 100644
index 0000000000000..f649f0ae43e30
--- /dev/null
+++ b/src/tools/miri/tests/extern-so/fail/function_not_in_so.stderr
@@ -0,0 +1,14 @@
+error: unsupported operation: can't call foreign function: foo
+  --> $DIR/function_not_in_so.rs:LL:CC
+   |
+LL |         foo();
+   |         ^^^^^ can't call foreign function: foo
+   |
+   = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/function_not_in_so.rs:LL:CC
+
+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/extern-so/libcode.version b/src/tools/miri/tests/extern-so/libcode.version
new file mode 100644
index 0000000000000..0f04b9aaebb38
--- /dev/null
+++ b/src/tools/miri/tests/extern-so/libcode.version
@@ -0,0 +1,9 @@
+CODEABI_1.0 {
+    global: *add_one_int*; 
+        *printer*; 
+        *test_stack_spill*; 
+        *get_unsigned_int*; 
+        *add_int16*;
+        *add_short_to_long*;
+    local: *;
+};
diff --git a/src/tools/miri/tests/extern-so/pass/call_extern_c_fn.rs b/src/tools/miri/tests/extern-so/pass/call_extern_c_fn.rs
new file mode 100644
index 0000000000000..1e1d0b11e99ff
--- /dev/null
+++ b/src/tools/miri/tests/extern-so/pass/call_extern_c_fn.rs
@@ -0,0 +1,46 @@
+//@only-target-linux
+//@only-on-host
+
+extern "C" {
+    fn add_one_int(x: i32) -> i32;
+    fn add_int16(x: i16) -> i16;
+    fn test_stack_spill(
+        a: i32,
+        b: i32,
+        c: i32,
+        d: i32,
+        e: i32,
+        f: i32,
+        g: i32,
+        h: i32,
+        i: i32,
+        j: i32,
+        k: i32,
+        l: i32,
+    ) -> i32;
+    fn add_short_to_long(x: i16, y: i64) -> i64;
+    fn get_unsigned_int() -> u32;
+    fn printer();
+}
+
+fn main() {
+    unsafe {
+        // test function that adds 2 to a provided int
+        assert_eq!(add_one_int(1), 3);
+
+        // test function that takes the sum of its 12 arguments
+        assert_eq!(test_stack_spill(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), 78);
+
+        // test function that adds 3 to a 16 bit int
+        assert_eq!(add_int16(-1i16), 2i16);
+
+        // test function that adds an i16 to an i64
+        assert_eq!(add_short_to_long(-1i16, 123456789123i64), 123456789122i64);
+
+        // test function that returns -10 as an unsigned int
+        assert_eq!(get_unsigned_int(), (-10i32) as u32);
+
+        // test void function that prints from C
+        printer();
+    }
+}
diff --git a/src/tools/miri/tests/extern-so/pass/call_extern_c_fn.stdout b/src/tools/miri/tests/extern-so/pass/call_extern_c_fn.stdout
new file mode 100644
index 0000000000000..7ba13d2d7b02e
--- /dev/null
+++ b/src/tools/miri/tests/extern-so/pass/call_extern_c_fn.stdout
@@ -0,0 +1 @@
+printing from C
diff --git a/src/tools/miri/tests/extern-so/test.c b/src/tools/miri/tests/extern-so/test.c
new file mode 100644
index 0000000000000..68714f1743b6e
--- /dev/null
+++ b/src/tools/miri/tests/extern-so/test.c
@@ -0,0 +1,27 @@
+#include <stdio.h>
+
+int add_one_int(int x) {
+  return 2 + x;
+}
+
+void printer() {
+  printf("printing from C\n");
+}
+
+// function with many arguments, to test functionality when some args are stored
+// on the stack
+int test_stack_spill(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l) {
+  return a+b+c+d+e+f+g+h+i+j+k+l;
+}
+
+unsigned int get_unsigned_int() {
+  return -10;
+}
+
+short add_int16(short x) {
+  return x + 3;
+}
+
+long add_short_to_long(short x, long y) {
+  return x + y;
+}
diff --git a/src/tools/miri/tests/fail/abort-terminator.rs b/src/tools/miri/tests/fail/abort-terminator.rs
new file mode 100644
index 0000000000000..c954443a27629
--- /dev/null
+++ b/src/tools/miri/tests/fail/abort-terminator.rs
@@ -0,0 +1,10 @@
+#![feature(c_unwind)]
+
+extern "C" fn panic_abort() {
+    //~^ ERROR: the program aborted
+    panic!()
+}
+
+fn main() {
+    panic_abort();
+}
diff --git a/src/tools/miri/tests/fail/abort-terminator.stderr b/src/tools/miri/tests/fail/abort-terminator.stderr
new file mode 100644
index 0000000000000..ec9ce76685b55
--- /dev/null
+++ b/src/tools/miri/tests/fail/abort-terminator.stderr
@@ -0,0 +1,22 @@
+thread 'main' panicked at 'explicit panic', $DIR/abort-terminator.rs:LL:CC
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
+error: abnormal termination: the program aborted execution
+  --> $DIR/abort-terminator.rs:LL:CC
+   |
+LL | / extern "C" fn panic_abort() {
+LL | |
+LL | |     panic!()
+LL | | }
+   | |_^ the program aborted execution
+   |
+   = note: inside `panic_abort` at $DIR/abort-terminator.rs:LL:CC
+note: inside `main` at $DIR/abort-terminator.rs:LL:CC
+  --> $DIR/abort-terminator.rs:LL:CC
+   |
+LL |     panic_abort();
+   |     ^^^^^^^^^^^^^
+
+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/alloc/deallocate-bad-alignment.rs b/src/tools/miri/tests/fail/alloc/deallocate-bad-alignment.rs
new file mode 100644
index 0000000000000..a07d8254ad3d5
--- /dev/null
+++ b/src/tools/miri/tests/fail/alloc/deallocate-bad-alignment.rs
@@ -0,0 +1,10 @@
+use std::alloc::{alloc, dealloc, Layout};
+
+//@error-pattern: has size 1 and alignment 1, but gave size 1 and alignment 2
+
+fn main() {
+    unsafe {
+        let x = alloc(Layout::from_size_align_unchecked(1, 1));
+        dealloc(x, Layout::from_size_align_unchecked(1, 2));
+    }
+}
diff --git a/src/tools/miri/tests/fail/alloc/deallocate-bad-alignment.stderr b/src/tools/miri/tests/fail/alloc/deallocate-bad-alignment.stderr
new file mode 100644
index 0000000000000..28439b54b2908
--- /dev/null
+++ b/src/tools/miri/tests/fail/alloc/deallocate-bad-alignment.stderr
@@ -0,0 +1,20 @@
+error: Undefined Behavior: incorrect layout on deallocation: ALLOC has size 1 and alignment ALIGN, but gave size 1 and alignment ALIGN
+  --> RUSTLIB/alloc/src/alloc.rs:LL:CC
+   |
+LL |     unsafe { __rust_dealloc(ptr, layout.size(), layout.align()) }
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ incorrect layout on deallocation: ALLOC has size 1 and alignment ALIGN, but gave size 1 and alignment ALIGN
+   |
+   = 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 `std::alloc::dealloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC
+note: inside `main` at $DIR/deallocate-bad-alignment.rs:LL:CC
+  --> $DIR/deallocate-bad-alignment.rs:LL:CC
+   |
+LL |         dealloc(x, Layout::from_size_align_unchecked(1, 2));
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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/alloc/deallocate-bad-size.rs b/src/tools/miri/tests/fail/alloc/deallocate-bad-size.rs
new file mode 100644
index 0000000000000..47aaef1935e90
--- /dev/null
+++ b/src/tools/miri/tests/fail/alloc/deallocate-bad-size.rs
@@ -0,0 +1,10 @@
+use std::alloc::{alloc, dealloc, Layout};
+
+//@error-pattern: has size 1 and alignment 1, but gave size 2 and alignment 1
+
+fn main() {
+    unsafe {
+        let x = alloc(Layout::from_size_align_unchecked(1, 1));
+        dealloc(x, Layout::from_size_align_unchecked(2, 1));
+    }
+}
diff --git a/src/tools/miri/tests/fail/alloc/deallocate-bad-size.stderr b/src/tools/miri/tests/fail/alloc/deallocate-bad-size.stderr
new file mode 100644
index 0000000000000..a6ceab1f56f51
--- /dev/null
+++ b/src/tools/miri/tests/fail/alloc/deallocate-bad-size.stderr
@@ -0,0 +1,20 @@
+error: Undefined Behavior: incorrect layout on deallocation: ALLOC has size 1 and alignment ALIGN, but gave size 2 and alignment ALIGN
+  --> RUSTLIB/alloc/src/alloc.rs:LL:CC
+   |
+LL |     unsafe { __rust_dealloc(ptr, layout.size(), layout.align()) }
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ incorrect layout on deallocation: ALLOC has size 1 and alignment ALIGN, but gave size 2 and alignment ALIGN
+   |
+   = 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 `std::alloc::dealloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC
+note: inside `main` at $DIR/deallocate-bad-size.rs:LL:CC
+  --> $DIR/deallocate-bad-size.rs:LL:CC
+   |
+LL |         dealloc(x, Layout::from_size_align_unchecked(2, 1));
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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/alloc/deallocate-twice.rs b/src/tools/miri/tests/fail/alloc/deallocate-twice.rs
new file mode 100644
index 0000000000000..1eb9bbf91ca5c
--- /dev/null
+++ b/src/tools/miri/tests/fail/alloc/deallocate-twice.rs
@@ -0,0 +1,11 @@
+use std::alloc::{alloc, dealloc, Layout};
+
+//@error-pattern: dereferenced after this allocation got freed
+
+fn main() {
+    unsafe {
+        let x = alloc(Layout::from_size_align_unchecked(1, 1));
+        dealloc(x, Layout::from_size_align_unchecked(1, 1));
+        dealloc(x, Layout::from_size_align_unchecked(1, 1));
+    }
+}
diff --git a/src/tools/miri/tests/fail/alloc/deallocate-twice.stderr b/src/tools/miri/tests/fail/alloc/deallocate-twice.stderr
new file mode 100644
index 0000000000000..b6c5b6f97ee7b
--- /dev/null
+++ b/src/tools/miri/tests/fail/alloc/deallocate-twice.stderr
@@ -0,0 +1,20 @@
+error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed
+  --> RUSTLIB/alloc/src/alloc.rs:LL:CC
+   |
+LL |     unsafe { __rust_dealloc(ptr, layout.size(), layout.align()) }
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed
+   |
+   = 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 `std::alloc::dealloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC
+note: inside `main` at $DIR/deallocate-twice.rs:LL:CC
+  --> $DIR/deallocate-twice.rs:LL:CC
+   |
+LL |         dealloc(x, Layout::from_size_align_unchecked(1, 1));
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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/alloc/global_system_mixup.rs b/src/tools/miri/tests/fail/alloc/global_system_mixup.rs
new file mode 100644
index 0000000000000..47b098c71a2bf
--- /dev/null
+++ b/src/tools/miri/tests/fail/alloc/global_system_mixup.rs
@@ -0,0 +1,19 @@
+// Make sure we detect when the `Global` and `System` allocators are mixed
+// (even when the default `Global` uses `System`).
+//@error-pattern: /deallocating .*, which is Rust heap memory, using .* heap deallocation operation/
+
+//@normalize-stderr-test: "using [A-Za-z]+ heap deallocation operation" -> "using PLATFORM heap deallocation operation"
+//@normalize-stderr-test: "\| +\^+" -> "| ^"
+//@normalize-stderr-test: "libc::free\([^()]*\)|unsafe \{ HeapFree\([^()]*\) \};" -> "FREE();"
+
+#![feature(allocator_api, slice_ptr_get)]
+
+use std::alloc::{Allocator, Global, Layout, System};
+
+fn main() {
+    let l = Layout::from_size_align(1, 1).unwrap();
+    let ptr = Global.allocate(l).unwrap().as_non_null_ptr();
+    unsafe {
+        System.deallocate(ptr, l);
+    }
+}
diff --git a/src/tools/miri/tests/fail/alloc/global_system_mixup.stderr b/src/tools/miri/tests/fail/alloc/global_system_mixup.stderr
new file mode 100644
index 0000000000000..4ee85add6c228
--- /dev/null
+++ b/src/tools/miri/tests/fail/alloc/global_system_mixup.stderr
@@ -0,0 +1,21 @@
+error: Undefined Behavior: deallocating ALLOC, which is Rust heap memory, using PLATFORM heap deallocation operation
+  --> RUSTLIB/std/src/sys/PLATFORM/alloc.rs:LL:CC
+   |
+LL |         FREE();
+   | ^ deallocating ALLOC, which is Rust heap memory, using PLATFORM heap deallocation operation
+   |
+   = 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 `std::sys::PLATFORM::alloc::<impl std::alloc::GlobalAlloc for std::alloc::System>::dealloc` at RUSTLIB/std/src/sys/PLATFORM/alloc.rs:LL:CC
+   = note: inside `<std::alloc::System as std::alloc::Allocator>::deallocate` at RUSTLIB/std/src/alloc.rs:LL:CC
+note: inside `main` at $DIR/global_system_mixup.rs:LL:CC
+  --> $DIR/global_system_mixup.rs:LL:CC
+   |
+LL |         System.deallocate(ptr, l);
+   | ^
+
+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/alloc/no_global_allocator.rs b/src/tools/miri/tests/fail/alloc/no_global_allocator.rs
new file mode 100644
index 0000000000000..fb0e7986bb5e6
--- /dev/null
+++ b/src/tools/miri/tests/fail/alloc/no_global_allocator.rs
@@ -0,0 +1,25 @@
+// Make sure we pretend the allocation symbols don't exist when there is no allocator
+
+#![feature(lang_items, start)]
+#![no_std]
+
+extern "Rust" {
+    fn __rust_alloc(size: usize, align: usize) -> *mut u8;
+}
+
+#[start]
+fn start(_: isize, _: *const *const u8) -> isize {
+    unsafe {
+        __rust_alloc(1, 1); //~ERROR: unsupported operation: can't call foreign function: __rust_alloc
+    }
+
+    0
+}
+
+#[panic_handler]
+fn panic_handler(_: &core::panic::PanicInfo) -> ! {
+    loop {}
+}
+
+#[lang = "eh_personality"]
+fn eh_personality() {}
diff --git a/src/tools/miri/tests/fail/alloc/no_global_allocator.stderr b/src/tools/miri/tests/fail/alloc/no_global_allocator.stderr
new file mode 100644
index 0000000000000..ea70970ae0fef
--- /dev/null
+++ b/src/tools/miri/tests/fail/alloc/no_global_allocator.stderr
@@ -0,0 +1,12 @@
+error: unsupported operation: can't call foreign function: __rust_alloc
+  --> $DIR/no_global_allocator.rs:LL:CC
+   |
+LL |         __rust_alloc(1, 1);
+   |         ^^^^^^^^^^^^^^^^^^ can't call foreign function: __rust_alloc
+   |
+   = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
+   = note: BACKTRACE:
+   = note: inside `start` at $DIR/no_global_allocator.rs:LL:CC
+
+error: aborting due to previous error
+
diff --git a/src/tools/miri/tests/fail/alloc/reallocate-bad-size.rs b/src/tools/miri/tests/fail/alloc/reallocate-bad-size.rs
new file mode 100644
index 0000000000000..145c3393d677a
--- /dev/null
+++ b/src/tools/miri/tests/fail/alloc/reallocate-bad-size.rs
@@ -0,0 +1,10 @@
+use std::alloc::{alloc, realloc, Layout};
+
+//@error-pattern: has size 1 and alignment 1, but gave size 2 and alignment 1
+
+fn main() {
+    unsafe {
+        let x = alloc(Layout::from_size_align_unchecked(1, 1));
+        let _y = realloc(x, Layout::from_size_align_unchecked(2, 1), 1);
+    }
+}
diff --git a/src/tools/miri/tests/fail/alloc/reallocate-bad-size.stderr b/src/tools/miri/tests/fail/alloc/reallocate-bad-size.stderr
new file mode 100644
index 0000000000000..c11b5a851048f
--- /dev/null
+++ b/src/tools/miri/tests/fail/alloc/reallocate-bad-size.stderr
@@ -0,0 +1,20 @@
+error: Undefined Behavior: incorrect layout on deallocation: ALLOC has size 1 and alignment ALIGN, but gave size 2 and alignment ALIGN
+  --> RUSTLIB/alloc/src/alloc.rs:LL:CC
+   |
+LL |     unsafe { __rust_realloc(ptr, layout.size(), layout.align(), new_size) }
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ incorrect layout on deallocation: ALLOC has size 1 and alignment ALIGN, but gave size 2 and alignment ALIGN
+   |
+   = 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 `std::alloc::realloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC
+note: inside `main` at $DIR/reallocate-bad-size.rs:LL:CC
+  --> $DIR/reallocate-bad-size.rs:LL:CC
+   |
+LL |         let _y = realloc(x, Layout::from_size_align_unchecked(2, 1), 1);
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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/alloc/reallocate-change-alloc.rs b/src/tools/miri/tests/fail/alloc/reallocate-change-alloc.rs
new file mode 100644
index 0000000000000..3ad56da2c2fff
--- /dev/null
+++ b/src/tools/miri/tests/fail/alloc/reallocate-change-alloc.rs
@@ -0,0 +1,9 @@
+use std::alloc::{alloc, realloc, Layout};
+
+fn main() {
+    unsafe {
+        let x = alloc(Layout::from_size_align_unchecked(1, 1));
+        let _y = realloc(x, Layout::from_size_align_unchecked(1, 1), 1);
+        let _z = *x; //~ ERROR: dereferenced after this allocation got freed
+    }
+}
diff --git a/src/tools/miri/tests/fail/alloc/reallocate-change-alloc.stderr b/src/tools/miri/tests/fail/alloc/reallocate-change-alloc.stderr
new file mode 100644
index 0000000000000..5631dcb4cc084
--- /dev/null
+++ b/src/tools/miri/tests/fail/alloc/reallocate-change-alloc.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed
+  --> $DIR/reallocate-change-alloc.rs:LL:CC
+   |
+LL |         let _z = *x;
+   |                  ^^ pointer to ALLOC was dereferenced after this allocation got freed
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/reallocate-change-alloc.rs:LL:CC
+
+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/alloc/reallocate-dangling.rs b/src/tools/miri/tests/fail/alloc/reallocate-dangling.rs
new file mode 100644
index 0000000000000..34f1658344a5e
--- /dev/null
+++ b/src/tools/miri/tests/fail/alloc/reallocate-dangling.rs
@@ -0,0 +1,11 @@
+use std::alloc::{alloc, dealloc, realloc, Layout};
+
+//@error-pattern: dereferenced after this allocation got freed
+
+fn main() {
+    unsafe {
+        let x = alloc(Layout::from_size_align_unchecked(1, 1));
+        dealloc(x, Layout::from_size_align_unchecked(1, 1));
+        let _z = realloc(x, Layout::from_size_align_unchecked(1, 1), 1);
+    }
+}
diff --git a/src/tools/miri/tests/fail/alloc/reallocate-dangling.stderr b/src/tools/miri/tests/fail/alloc/reallocate-dangling.stderr
new file mode 100644
index 0000000000000..c7db5a729048c
--- /dev/null
+++ b/src/tools/miri/tests/fail/alloc/reallocate-dangling.stderr
@@ -0,0 +1,20 @@
+error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed
+  --> RUSTLIB/alloc/src/alloc.rs:LL:CC
+   |
+LL |     unsafe { __rust_realloc(ptr, layout.size(), layout.align(), new_size) }
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed
+   |
+   = 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 `std::alloc::realloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC
+note: inside `main` at $DIR/reallocate-dangling.rs:LL:CC
+  --> $DIR/reallocate-dangling.rs:LL:CC
+   |
+LL |         let _z = realloc(x, Layout::from_size_align_unchecked(1, 1), 1);
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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/alloc/stack_free.rs b/src/tools/miri/tests/fail/alloc/stack_free.rs
new file mode 100644
index 0000000000000..baf53decc4413
--- /dev/null
+++ b/src/tools/miri/tests/fail/alloc/stack_free.rs
@@ -0,0 +1,10 @@
+// Validation/SB changes why we fail
+//@compile-flags: -Zmiri-disable-validation -Zmiri-disable-stacked-borrows
+
+//@error-pattern: /deallocating .*, which is stack variable memory, using Rust heap deallocation operation/
+
+fn main() {
+    let x = 42;
+    let bad_box = unsafe { std::mem::transmute::<&i32, Box<i32>>(&x) };
+    drop(bad_box);
+}
diff --git a/src/tools/miri/tests/fail/alloc/stack_free.stderr b/src/tools/miri/tests/fail/alloc/stack_free.stderr
new file mode 100644
index 0000000000000..44991542b1350
--- /dev/null
+++ b/src/tools/miri/tests/fail/alloc/stack_free.stderr
@@ -0,0 +1,24 @@
+error: Undefined Behavior: deallocating ALLOC, which is stack variable memory, using Rust heap deallocation operation
+  --> RUSTLIB/alloc/src/alloc.rs:LL:CC
+   |
+LL |     unsafe { __rust_dealloc(ptr, layout.size(), layout.align()) }
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ deallocating ALLOC, which is stack variable memory, using Rust heap deallocation operation
+   |
+   = 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 `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
+   = note: inside `alloc::alloc::box_free::<i32, std::alloc::Global>` at RUSTLIB/alloc/src/alloc.rs:LL:CC
+   = note: inside `std::ptr::drop_in_place::<std::boxed::Box<i32>> - shim(Some(std::boxed::Box<i32>))` at RUSTLIB/core/src/ptr/mod.rs:LL:CC
+   = note: inside `std::mem::drop::<std::boxed::Box<i32>>` at RUSTLIB/core/src/mem/mod.rs:LL:CC
+note: inside `main` at $DIR/stack_free.rs:LL:CC
+  --> $DIR/stack_free.rs:LL:CC
+   |
+LL |     drop(bad_box);
+   |     ^^^^^^^^^^^^^
+
+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/box-cell-alias.rs b/src/tools/miri/tests/fail/box-cell-alias.rs
new file mode 100644
index 0000000000000..1a4a3b97ea3ff
--- /dev/null
+++ b/src/tools/miri/tests/fail/box-cell-alias.rs
@@ -0,0 +1,18 @@
+//@compile-flags: -Zmiri-strict-provenance
+
+// Taken from <https://github.com/rust-lang/unsafe-code-guidelines/issues/194#issuecomment-520934222>.
+
+use std::cell::Cell;
+
+fn helper(val: Box<Cell<u8>>, ptr: *const Cell<u8>) -> u8 {
+    val.set(10);
+    unsafe { (*ptr).set(20) }; //~ ERROR: does not exist in the borrow stack
+    val.get()
+}
+
+fn main() {
+    let val: Box<Cell<u8>> = Box::new(Cell::new(25));
+    let ptr: *const Cell<u8> = &*val;
+    let res = helper(val, ptr);
+    assert_eq!(res, 20);
+}
diff --git a/src/tools/miri/tests/fail/box-cell-alias.stderr b/src/tools/miri/tests/fail/box-cell-alias.stderr
new file mode 100644
index 0000000000000..8370163997687
--- /dev/null
+++ b/src/tools/miri/tests/fail/box-cell-alias.stderr
@@ -0,0 +1,33 @@
+error: Undefined Behavior: trying to retag from <TAG> for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/box-cell-alias.rs:LL:CC
+   |
+LL |     unsafe { (*ptr).set(20) };
+   |              ^^^^^^^^^^^^^^
+   |              |
+   |              trying to retag from <TAG> for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |              this error occurs as part of retag at ALLOC[0x0..0x1]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadWrite retag at offsets [0x0..0x1]
+  --> $DIR/box-cell-alias.rs:LL:CC
+   |
+LL |     let ptr: *const Cell<u8> = &*val;
+   |                                ^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x1] by a Unique retag
+  --> $DIR/box-cell-alias.rs:LL:CC
+   |
+LL |     let res = helper(val, ptr);
+   |                      ^^^
+   = note: BACKTRACE:
+   = note: inside `helper` at $DIR/box-cell-alias.rs:LL:CC
+note: inside `main` at $DIR/box-cell-alias.rs:LL:CC
+  --> $DIR/box-cell-alias.rs:LL:CC
+   |
+LL |     let res = helper(val, ptr);
+   |               ^^^^^^^^^^^^^^^^
+
+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/branchless-select-i128-pointer.rs b/src/tools/miri/tests/fail/branchless-select-i128-pointer.rs
new file mode 100644
index 0000000000000..2b861e5447b03
--- /dev/null
+++ b/src/tools/miri/tests/fail/branchless-select-i128-pointer.rs
@@ -0,0 +1,22 @@
+use std::mem::transmute;
+
+#[cfg(target_pointer_width = "32")]
+type TwoPtrs = i64;
+#[cfg(target_pointer_width = "64")]
+type TwoPtrs = i128;
+
+fn main() {
+    for &my_bool in &[true, false] {
+        let mask = -(my_bool as TwoPtrs); // false -> 0, true -> -1 aka !0
+        // This is branchless code to select one or the other pointer.
+        // However, it drops provenance when transmuting to TwoPtrs, so this is UB.
+        let val = unsafe {
+            transmute::<_, &str>(
+                //~^ ERROR: constructing invalid value: encountered a dangling reference
+                !mask & transmute::<_, TwoPtrs>("false !")
+                    | mask & transmute::<_, TwoPtrs>("true !"),
+            )
+        };
+        println!("{}", val);
+    }
+}
diff --git a/src/tools/miri/tests/fail/branchless-select-i128-pointer.stderr b/src/tools/miri/tests/fail/branchless-select-i128-pointer.stderr
new file mode 100644
index 0000000000000..96f2ff3282c82
--- /dev/null
+++ b/src/tools/miri/tests/fail/branchless-select-i128-pointer.stderr
@@ -0,0 +1,19 @@
+error: Undefined Behavior: constructing invalid value: encountered a dangling reference (address $HEX is unallocated)
+  --> $DIR/branchless-select-i128-pointer.rs:LL:CC
+   |
+LL | /             transmute::<_, &str>(
+LL | |
+LL | |                 !mask & transmute::<_, TwoPtrs>("false !")
+LL | |                     | mask & transmute::<_, TwoPtrs>("true !"),
+LL | |             )
+   | |_____________^ constructing invalid value: encountered a dangling reference (address $HEX is unallocated)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/branchless-select-i128-pointer.rs:LL:CC
+
+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/breakpoint.rs b/src/tools/miri/tests/fail/breakpoint.rs
new file mode 100644
index 0000000000000..fb1d4d958ee6b
--- /dev/null
+++ b/src/tools/miri/tests/fail/breakpoint.rs
@@ -0,0 +1,7 @@
+#![feature(core_intrinsics)]
+
+fn main() {
+    unsafe {
+        core::intrinsics::breakpoint() //~ ERROR: Trace/breakpoint trap
+    };
+}
diff --git a/src/tools/miri/tests/fail/breakpoint.stderr b/src/tools/miri/tests/fail/breakpoint.stderr
new file mode 100644
index 0000000000000..7b9bbdb382895
--- /dev/null
+++ b/src/tools/miri/tests/fail/breakpoint.stderr
@@ -0,0 +1,12 @@
+error: abnormal termination: Trace/breakpoint trap
+  --> $DIR/breakpoint.rs:LL:CC
+   |
+LL |         core::intrinsics::breakpoint()
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Trace/breakpoint trap
+   |
+   = note: inside `main` at $DIR/breakpoint.rs:LL:CC
+
+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/concurrency/libc_pthread_create_main_terminate.rs b/src/tools/miri/tests/fail/concurrency/libc_pthread_create_main_terminate.rs
new file mode 100644
index 0000000000000..065ad2d725f8f
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_create_main_terminate.rs
@@ -0,0 +1,19 @@
+//@ignore-target-windows: No libc on Windows
+//@error-pattern: the main thread terminated without waiting for all remaining threads
+
+// Check that we terminate the program when the main thread terminates.
+
+use std::{mem, ptr};
+
+extern "C" fn thread_start(_null: *mut libc::c_void) -> *mut libc::c_void {
+    loop {}
+}
+
+fn main() {
+    unsafe {
+        let mut native: libc::pthread_t = mem::zeroed();
+        let attr: libc::pthread_attr_t = mem::zeroed();
+        // assert_eq!(libc::pthread_attr_init(&mut attr), 0); FIXME: this function is not yet implemented.
+        assert_eq!(libc::pthread_create(&mut native, &attr, thread_start, ptr::null_mut()), 0);
+    }
+}
diff --git a/src/tools/miri/tests/fail/concurrency/libc_pthread_create_main_terminate.stderr b/src/tools/miri/tests/fail/concurrency/libc_pthread_create_main_terminate.stderr
new file mode 100644
index 0000000000000..c5093c0e60113
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_create_main_terminate.stderr
@@ -0,0 +1,6 @@
+error: the main thread terminated without waiting for all remaining threads
+
+note: pass `-Zmiri-ignore-leaks` to disable this check
+
+error: aborting due to previous error
+
diff --git a/src/tools/miri/tests/fail/concurrency/libc_pthread_create_too_few_args.rs b/src/tools/miri/tests/fail/concurrency/libc_pthread_create_too_few_args.rs
new file mode 100644
index 0000000000000..e1d3704af7c0b
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_create_too_few_args.rs
@@ -0,0 +1,22 @@
+//@ignore-target-windows: No libc on Windows
+
+//! The thread function must have exactly one argument.
+
+use std::{mem, ptr};
+
+extern "C" fn thread_start() -> *mut libc::c_void {
+    panic!() //~ ERROR: callee has fewer arguments than expected
+}
+
+fn main() {
+    unsafe {
+        let mut native: libc::pthread_t = mem::zeroed();
+        let attr: libc::pthread_attr_t = mem::zeroed();
+        // assert_eq!(libc::pthread_attr_init(&mut attr), 0); FIXME: this function is not yet implemented.
+        let thread_start: extern "C" fn() -> *mut libc::c_void = thread_start;
+        let thread_start: extern "C" fn(*mut libc::c_void) -> *mut libc::c_void =
+            mem::transmute(thread_start);
+        assert_eq!(libc::pthread_create(&mut native, &attr, thread_start, ptr::null_mut()), 0);
+        assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0);
+    }
+}
diff --git a/src/tools/miri/tests/fail/concurrency/libc_pthread_create_too_few_args.stderr b/src/tools/miri/tests/fail/concurrency/libc_pthread_create_too_few_args.stderr
new file mode 100644
index 0000000000000..94463bef8f0fe
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_create_too_few_args.stderr
@@ -0,0 +1,14 @@
+error: Undefined Behavior: callee has fewer arguments than expected
+  --> $DIR/libc_pthread_create_too_few_args.rs:LL:CC
+   |
+LL |     panic!()
+   |     ^^^^^^^^ callee has fewer arguments than expected
+   |
+   = 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 `thread_start` at RUSTLIB/std/src/panic.rs:LL:CC
+   = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: aborting due to previous error
+
diff --git a/src/tools/miri/tests/fail/concurrency/libc_pthread_create_too_many_args.rs b/src/tools/miri/tests/fail/concurrency/libc_pthread_create_too_many_args.rs
new file mode 100644
index 0000000000000..7408634db528f
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_create_too_many_args.rs
@@ -0,0 +1,22 @@
+//@ignore-target-windows: No libc on Windows
+
+//! The thread function must have exactly one argument.
+
+use std::{mem, ptr};
+
+extern "C" fn thread_start(_null: *mut libc::c_void, _x: i32) -> *mut libc::c_void {
+    panic!() //~ ERROR: callee has more arguments than expected
+}
+
+fn main() {
+    unsafe {
+        let mut native: libc::pthread_t = mem::zeroed();
+        let attr: libc::pthread_attr_t = mem::zeroed();
+        // assert_eq!(libc::pthread_attr_init(&mut attr), 0); FIXME: this function is not yet implemented.
+        let thread_start: extern "C" fn(*mut libc::c_void, i32) -> *mut libc::c_void = thread_start;
+        let thread_start: extern "C" fn(*mut libc::c_void) -> *mut libc::c_void =
+            mem::transmute(thread_start);
+        assert_eq!(libc::pthread_create(&mut native, &attr, thread_start, ptr::null_mut()), 0);
+        assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0);
+    }
+}
diff --git a/src/tools/miri/tests/fail/concurrency/libc_pthread_create_too_many_args.stderr b/src/tools/miri/tests/fail/concurrency/libc_pthread_create_too_many_args.stderr
new file mode 100644
index 0000000000000..fdbe91cc8a803
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_create_too_many_args.stderr
@@ -0,0 +1,14 @@
+error: Undefined Behavior: callee has more arguments than expected
+  --> $DIR/libc_pthread_create_too_many_args.rs:LL:CC
+   |
+LL |     panic!()
+   |     ^^^^^^^^ callee has more arguments than expected
+   |
+   = 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 `thread_start` at RUSTLIB/std/src/panic.rs:LL:CC
+   = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: aborting due to previous error
+
diff --git a/src/tools/miri/tests/fail/concurrency/libc_pthread_join_detached.rs b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_detached.rs
new file mode 100644
index 0000000000000..0b810dc8c7212
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_detached.rs
@@ -0,0 +1,20 @@
+//@ignore-target-windows: No libc on Windows
+
+// Joining a detached thread is undefined behavior.
+
+use std::{mem, ptr};
+
+extern "C" fn thread_start(_null: *mut libc::c_void) -> *mut libc::c_void {
+    ptr::null_mut()
+}
+
+fn main() {
+    unsafe {
+        let mut native: libc::pthread_t = mem::zeroed();
+        let attr: libc::pthread_attr_t = mem::zeroed();
+        // assert_eq!(libc::pthread_attr_init(&mut attr), 0); FIXME: this function is not yet implemented.
+        assert_eq!(libc::pthread_create(&mut native, &attr, thread_start, ptr::null_mut()), 0);
+        assert_eq!(libc::pthread_detach(native), 0);
+        assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0); //~ ERROR: Undefined Behavior: trying to join a detached thread
+    }
+}
diff --git a/src/tools/miri/tests/fail/concurrency/libc_pthread_join_detached.stderr b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_detached.stderr
new file mode 100644
index 0000000000000..763e0d3665d8f
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_detached.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: trying to join a detached thread
+  --> $DIR/libc_pthread_join_detached.rs:LL:CC
+   |
+LL |         assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0);
+   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to join a detached thread
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/libc_pthread_join_detached.rs:LL:CC
+
+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/concurrency/libc_pthread_join_joined.rs b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_joined.rs
new file mode 100644
index 0000000000000..04ca4bbb3f611
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_joined.rs
@@ -0,0 +1,20 @@
+//@ignore-target-windows: No libc on Windows
+
+// Joining an already joined thread is undefined behavior.
+
+use std::{mem, ptr};
+
+extern "C" fn thread_start(_null: *mut libc::c_void) -> *mut libc::c_void {
+    ptr::null_mut()
+}
+
+fn main() {
+    unsafe {
+        let mut native: libc::pthread_t = mem::zeroed();
+        let attr: libc::pthread_attr_t = mem::zeroed();
+        // assert_eq!(libc::pthread_attr_init(&mut attr), 0); FIXME: this function is not yet implemented.
+        assert_eq!(libc::pthread_create(&mut native, &attr, thread_start, ptr::null_mut()), 0);
+        assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0);
+        assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0); //~ ERROR: Undefined Behavior: trying to join an already joined thread
+    }
+}
diff --git a/src/tools/miri/tests/fail/concurrency/libc_pthread_join_joined.stderr b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_joined.stderr
new file mode 100644
index 0000000000000..a3253e2ef933b
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_joined.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: trying to join an already joined thread
+  --> $DIR/libc_pthread_join_joined.rs:LL:CC
+   |
+LL |         assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0);
+   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to join an already joined thread
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/libc_pthread_join_joined.rs:LL:CC
+
+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/concurrency/libc_pthread_join_main.rs b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_main.rs
new file mode 100644
index 0000000000000..7576518216372
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_main.rs
@@ -0,0 +1,16 @@
+//@ignore-target-windows: No libc on Windows
+
+// Joining the main thread is undefined behavior.
+
+use std::{ptr, thread};
+
+fn main() {
+    let thread_id: libc::pthread_t = unsafe { libc::pthread_self() };
+    let handle = thread::spawn(move || {
+        unsafe {
+            assert_eq!(libc::pthread_join(thread_id, ptr::null_mut()), 0); //~ ERROR: Undefined Behavior: trying to join a detached thread
+        }
+    });
+    thread::yield_now();
+    handle.join().unwrap();
+}
diff --git a/src/tools/miri/tests/fail/concurrency/libc_pthread_join_main.stderr b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_main.stderr
new file mode 100644
index 0000000000000..09e14d46a967f
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_main.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: trying to join a detached thread
+  --> $DIR/libc_pthread_join_main.rs:LL:CC
+   |
+LL |             assert_eq!(libc::pthread_join(thread_id, ptr::null_mut()), 0);
+   |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to join a detached thread
+   |
+   = 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 closure at $DIR/libc_pthread_join_main.rs:LL:CC
+
+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/concurrency/libc_pthread_join_multiple.rs b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_multiple.rs
new file mode 100644
index 0000000000000..966f416eeac7e
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_multiple.rs
@@ -0,0 +1,29 @@
+//@ignore-target-windows: No libc on Windows
+
+// Joining the same thread from multiple threads is undefined behavior.
+
+use std::thread;
+use std::{mem, ptr};
+
+extern "C" fn thread_start(_null: *mut libc::c_void) -> *mut libc::c_void {
+    // Yield the thread several times so that other threads can join it.
+    thread::yield_now();
+    thread::yield_now();
+    ptr::null_mut()
+}
+
+fn main() {
+    unsafe {
+        let mut native: libc::pthread_t = mem::zeroed();
+        let attr: libc::pthread_attr_t = mem::zeroed();
+        // assert_eq!(libc::pthread_attr_init(&mut attr), 0); FIXME: this function is not yet implemented.
+        assert_eq!(libc::pthread_create(&mut native, &attr, thread_start, ptr::null_mut()), 0);
+        let mut native_copy: libc::pthread_t = mem::zeroed();
+        ptr::copy_nonoverlapping(&native, &mut native_copy, 1);
+        let handle = thread::spawn(move || {
+            assert_eq!(libc::pthread_join(native_copy, ptr::null_mut()), 0); //~ ERROR: Undefined Behavior: trying to join an already joined thread
+        });
+        assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0);
+        handle.join().unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/fail/concurrency/libc_pthread_join_multiple.stderr b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_multiple.stderr
new file mode 100644
index 0000000000000..db5d7bfd5daef
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_multiple.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: trying to join an already joined thread
+  --> $DIR/libc_pthread_join_multiple.rs:LL:CC
+   |
+LL | ...   assert_eq!(libc::pthread_join(native_copy, ptr::null_mut()), 0);
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to join an already joined thread
+   |
+   = 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 closure at $DIR/libc_pthread_join_multiple.rs:LL:CC
+
+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/concurrency/libc_pthread_join_self.rs b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_self.rs
new file mode 100644
index 0000000000000..0c25c690f3721
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_self.rs
@@ -0,0 +1,18 @@
+//@ignore-target-windows: No libc on Windows
+// We are making scheduler assumptions here.
+//@compile-flags: -Zmiri-preemption-rate=0
+
+// Joining itself is undefined behavior.
+
+use std::{ptr, thread};
+
+fn main() {
+    let handle = thread::spawn(|| {
+        unsafe {
+            let native: libc::pthread_t = libc::pthread_self();
+            assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0); //~ ERROR: Undefined Behavior: trying to join itself
+        }
+    });
+    thread::yield_now();
+    handle.join().unwrap();
+}
diff --git a/src/tools/miri/tests/fail/concurrency/libc_pthread_join_self.stderr b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_self.stderr
new file mode 100644
index 0000000000000..8db4a83f9cebb
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_self.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: trying to join itself
+  --> $DIR/libc_pthread_join_self.rs:LL:CC
+   |
+LL |             assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0);
+   |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to join itself
+   |
+   = 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 closure at $DIR/libc_pthread_join_self.rs:LL:CC
+
+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/concurrency/read_only_atomic_cmpxchg.rs b/src/tools/miri/tests/fail/concurrency/read_only_atomic_cmpxchg.rs
new file mode 100644
index 0000000000000..cb6aeea665d39
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/read_only_atomic_cmpxchg.rs
@@ -0,0 +1,11 @@
+// Should not rely on the aliasing model for its failure.
+//@compile-flags: -Zmiri-disable-stacked-borrows
+
+use std::sync::atomic::{AtomicI32, Ordering};
+
+fn main() {
+    static X: i32 = 0;
+    let x = &X as *const i32 as *const AtomicI32;
+    let x = unsafe { &*x };
+    x.compare_exchange(1, 2, Ordering::Relaxed, Ordering::Relaxed).unwrap_err(); //~ERROR: atomic operations cannot be performed on read-only memory
+}
diff --git a/src/tools/miri/tests/fail/concurrency/read_only_atomic_cmpxchg.stderr b/src/tools/miri/tests/fail/concurrency/read_only_atomic_cmpxchg.stderr
new file mode 100644
index 0000000000000..d51fdee0b256f
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/read_only_atomic_cmpxchg.stderr
@@ -0,0 +1,21 @@
+error: Undefined Behavior: atomic operations cannot be performed on read-only memory
+       many platforms require atomic read-modify-write instructions to be performed on writeable memory, even if the operation fails (and is hence nominally read-only)
+       some platforms implement (some) atomic loads via compare-exchange, which means they do not work on read-only memory; it is possible that we could have an exception permitting this for specific kinds of loads
+       please report an issue at <https://github.com/rust-lang/miri/issues> if this is a problem for you
+  --> $DIR/read_only_atomic_cmpxchg.rs:LL:CC
+   |
+LL |     x.compare_exchange(1, 2, Ordering::Relaxed, Ordering::Relaxed).unwrap_err();
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ atomic operations cannot be performed on read-only memory
+many platforms require atomic read-modify-write instructions to be performed on writeable memory, even if the operation fails (and is hence nominally read-only)
+some platforms implement (some) atomic loads via compare-exchange, which means they do not work on read-only memory; it is possible that we could have an exception permitting this for specific kinds of loads
+please report an issue at <https://github.com/rust-lang/miri/issues> if this is a problem for you
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/read_only_atomic_cmpxchg.rs:LL:CC
+
+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/concurrency/read_only_atomic_load.rs b/src/tools/miri/tests/fail/concurrency/read_only_atomic_load.rs
new file mode 100644
index 0000000000000..6e92453e3c195
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/read_only_atomic_load.rs
@@ -0,0 +1,13 @@
+// Should not rely on the aliasing model for its failure.
+//@compile-flags: -Zmiri-disable-stacked-borrows
+
+use std::sync::atomic::{AtomicI32, Ordering};
+
+fn main() {
+    static X: i32 = 0;
+    let x = &X as *const i32 as *const AtomicI32;
+    let x = unsafe { &*x };
+    // Some targets can implement atomic loads via compare_exchange, so we cannot allow them on
+    // read-only memory.
+    x.load(Ordering::Relaxed); //~ERROR: atomic operations cannot be performed on read-only memory
+}
diff --git a/src/tools/miri/tests/fail/concurrency/read_only_atomic_load.stderr b/src/tools/miri/tests/fail/concurrency/read_only_atomic_load.stderr
new file mode 100644
index 0000000000000..17851d6b470b4
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/read_only_atomic_load.stderr
@@ -0,0 +1,21 @@
+error: Undefined Behavior: atomic operations cannot be performed on read-only memory
+       many platforms require atomic read-modify-write instructions to be performed on writeable memory, even if the operation fails (and is hence nominally read-only)
+       some platforms implement (some) atomic loads via compare-exchange, which means they do not work on read-only memory; it is possible that we could have an exception permitting this for specific kinds of loads
+       please report an issue at <https://github.com/rust-lang/miri/issues> if this is a problem for you
+  --> $DIR/read_only_atomic_load.rs:LL:CC
+   |
+LL |     x.load(Ordering::Relaxed);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^ atomic operations cannot be performed on read-only memory
+many platforms require atomic read-modify-write instructions to be performed on writeable memory, even if the operation fails (and is hence nominally read-only)
+some platforms implement (some) atomic loads via compare-exchange, which means they do not work on read-only memory; it is possible that we could have an exception permitting this for specific kinds of loads
+please report an issue at <https://github.com/rust-lang/miri/issues> if this is a problem for you
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/read_only_atomic_load.rs:LL:CC
+
+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/concurrency/thread_local_static_dealloc.rs b/src/tools/miri/tests/fail/concurrency/thread_local_static_dealloc.rs
new file mode 100644
index 0000000000000..d89c670b632e6
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/thread_local_static_dealloc.rs
@@ -0,0 +1,16 @@
+//! Ensure that thread-local statics get deallocated when the thread dies.
+
+#![feature(thread_local)]
+
+#[thread_local]
+static mut TLS: u8 = 0;
+
+struct SendRaw(*const u8);
+unsafe impl Send for SendRaw {}
+
+fn main() {
+    unsafe {
+        let dangling_ptr = std::thread::spawn(|| SendRaw(&TLS as *const u8)).join().unwrap();
+        let _val = *dangling_ptr.0; //~ ERROR: dereferenced after this allocation got freed
+    }
+}
diff --git a/src/tools/miri/tests/fail/concurrency/thread_local_static_dealloc.stderr b/src/tools/miri/tests/fail/concurrency/thread_local_static_dealloc.stderr
new file mode 100644
index 0000000000000..cc3e56398781b
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/thread_local_static_dealloc.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed
+  --> $DIR/thread_local_static_dealloc.rs:LL:CC
+   |
+LL |         let _val = *dangling_ptr.0;
+   |                    ^^^^^^^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/thread_local_static_dealloc.rs:LL:CC
+
+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/concurrency/unwind_top_of_stack.rs b/src/tools/miri/tests/fail/concurrency/unwind_top_of_stack.rs
new file mode 100644
index 0000000000000..4704cfed03938
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/unwind_top_of_stack.rs
@@ -0,0 +1,29 @@
+//@ignore-target-windows: No libc on Windows
+
+//@compile-flags: -Zmiri-disable-abi-check
+
+//! Unwinding past the top frame of a stack is Undefined Behavior.
+
+#![feature(c_unwind)]
+
+use std::{mem, ptr};
+
+extern "C-unwind" fn thread_start(_null: *mut libc::c_void) -> *mut libc::c_void {
+    //~^ ERROR: unwinding past the topmost frame of the stack
+    panic!()
+}
+
+fn main() {
+    unsafe {
+        let mut native: libc::pthread_t = mem::zeroed();
+        let attr: libc::pthread_attr_t = mem::zeroed();
+        // assert_eq!(libc::pthread_attr_init(&mut attr), 0); FIXME: this function is not yet implemented.
+        // Cast to avoid inserting abort-on-unwind.
+        let thread_start: extern "C-unwind" fn(*mut libc::c_void) -> *mut libc::c_void =
+            thread_start;
+        let thread_start: extern "C" fn(*mut libc::c_void) -> *mut libc::c_void =
+            mem::transmute(thread_start);
+        assert_eq!(libc::pthread_create(&mut native, &attr, thread_start, ptr::null_mut()), 0);
+        assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0);
+    }
+}
diff --git a/src/tools/miri/tests/fail/concurrency/unwind_top_of_stack.stderr b/src/tools/miri/tests/fail/concurrency/unwind_top_of_stack.stderr
new file mode 100644
index 0000000000000..98db33e3206bd
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/unwind_top_of_stack.stderr
@@ -0,0 +1,18 @@
+thread '<unnamed>' panicked at 'explicit panic', $DIR/unwind_top_of_stack.rs:LL:CC
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
+error: Undefined Behavior: unwinding past the topmost frame of the stack
+  --> $DIR/unwind_top_of_stack.rs:LL:CC
+   |
+LL | / extern "C-unwind" fn thread_start(_null: *mut libc::c_void) -> *mut libc::c_void {
+LL | |
+LL | |     panic!()
+LL | | }
+   | |_^ unwinding past the topmost frame of the stack
+   |
+   = 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 `thread_start` at $DIR/unwind_top_of_stack.rs:LL:CC
+
+error: aborting due to previous error
+
diff --git a/src/tools/miri/tests/fail/concurrency/windows_join_detached.rs b/src/tools/miri/tests/fail/concurrency/windows_join_detached.rs
new file mode 100644
index 0000000000000..548ed63534dbd
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/windows_join_detached.rs
@@ -0,0 +1,21 @@
+//@only-target-windows: Uses win32 api functions
+//@error-pattern: Undefined Behavior: trying to join a detached thread
+
+// Joining a detached thread is undefined behavior.
+
+use std::os::windows::io::{AsRawHandle, RawHandle};
+use std::thread;
+
+extern "system" {
+    fn CloseHandle(handle: RawHandle) -> u32;
+}
+
+fn main() {
+    let thread = thread::spawn(|| ());
+
+    unsafe {
+        assert_ne!(CloseHandle(thread.as_raw_handle()), 0);
+    }
+
+    thread.join().unwrap();
+}
diff --git a/src/tools/miri/tests/fail/concurrency/windows_join_detached.stderr b/src/tools/miri/tests/fail/concurrency/windows_join_detached.stderr
new file mode 100644
index 0000000000000..78c75611d3333
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/windows_join_detached.stderr
@@ -0,0 +1,22 @@
+error: Undefined Behavior: trying to join a detached thread
+  --> RUSTLIB/std/src/sys/PLATFORM/thread.rs:LL:CC
+   |
+LL |         let rc = unsafe { c::WaitForSingleObject(self.handle.as_raw_handle(), c::INFINITE) };
+   |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to join a detached thread
+   |
+   = 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 `std::sys::PLATFORM::thread::Thread::join` at RUSTLIB/std/src/sys/PLATFORM/thread.rs:LL:CC
+   = note: inside `std::thread::JoinInner::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
+   = note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
+note: inside `main` at $DIR/windows_join_detached.rs:LL:CC
+  --> $DIR/windows_join_detached.rs:LL:CC
+   |
+LL |     thread.join().unwrap();
+   |     ^^^^^^^^^^^^^
+
+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/concurrency/windows_join_main.rs b/src/tools/miri/tests/fail/concurrency/windows_join_main.rs
new file mode 100644
index 0000000000000..cde6d19ef25bb
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/windows_join_main.rs
@@ -0,0 +1,28 @@
+//@only-target-windows: Uses win32 api functions
+// We are making scheduler assumptions here.
+//@compile-flags: -Zmiri-preemption-rate=0
+
+// On windows, joining main is not UB, but it will block a thread forever.
+
+use std::thread;
+
+extern "system" {
+    fn WaitForSingleObject(handle: isize, timeout: u32) -> u32;
+}
+
+const INFINITE: u32 = u32::MAX;
+
+// XXX HACK: This is how miri represents the handle for thread 0.
+// This value can be "legitimately" obtained by using `GetCurrentThread` with `DuplicateHandle`
+// but miri does not implement `DuplicateHandle` yet.
+const MAIN_THREAD: isize = (2i32 << 30) as isize;
+
+fn main() {
+    thread::spawn(|| {
+        unsafe {
+            assert_eq!(WaitForSingleObject(MAIN_THREAD, INFINITE), 0); //~ ERROR: deadlock: the evaluated program deadlocked
+        }
+    })
+    .join()
+    .unwrap();
+}
diff --git a/src/tools/miri/tests/fail/concurrency/windows_join_main.stderr b/src/tools/miri/tests/fail/concurrency/windows_join_main.stderr
new file mode 100644
index 0000000000000..ff0d074fa7d26
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/windows_join_main.stderr
@@ -0,0 +1,13 @@
+error: deadlock: the evaluated program deadlocked
+  --> $DIR/windows_join_main.rs:LL:CC
+   |
+LL |             assert_eq!(WaitForSingleObject(MAIN_THREAD, INFINITE), 0);
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program deadlocked
+   |
+   = note: inside closure at RUSTLIB/core/src/macros/mod.rs:LL:CC
+   = note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+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/concurrency/windows_join_self.rs b/src/tools/miri/tests/fail/concurrency/windows_join_self.rs
new file mode 100644
index 0000000000000..d9bbf66a7dca5
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/windows_join_self.rs
@@ -0,0 +1,25 @@
+//@only-target-windows: Uses win32 api functions
+// We are making scheduler assumptions here.
+//@compile-flags: -Zmiri-preemption-rate=0
+
+// On windows, a thread joining itself is not UB, but it will deadlock.
+
+use std::thread;
+
+extern "system" {
+    fn GetCurrentThread() -> usize;
+    fn WaitForSingleObject(handle: usize, timeout: u32) -> u32;
+}
+
+const INFINITE: u32 = u32::MAX;
+
+fn main() {
+    thread::spawn(|| {
+        unsafe {
+            let native = GetCurrentThread();
+            assert_eq!(WaitForSingleObject(native, INFINITE), 0); //~ ERROR: deadlock: the evaluated program deadlocked
+        }
+    })
+    .join()
+    .unwrap();
+}
diff --git a/src/tools/miri/tests/fail/concurrency/windows_join_self.stderr b/src/tools/miri/tests/fail/concurrency/windows_join_self.stderr
new file mode 100644
index 0000000000000..bbec3f7257ec0
--- /dev/null
+++ b/src/tools/miri/tests/fail/concurrency/windows_join_self.stderr
@@ -0,0 +1,12 @@
+error: deadlock: the evaluated program deadlocked
+  --> $DIR/windows_join_self.rs:LL:CC
+   |
+LL |             assert_eq!(WaitForSingleObject(native, INFINITE), 0);
+   |                                                            ^ the evaluated program deadlocked
+   |
+   = note: inside closure at $DIR/windows_join_self.rs:LL:CC
+
+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/const-ub-checks.rs b/src/tools/miri/tests/fail/const-ub-checks.rs
new file mode 100644
index 0000000000000..fa522c30cbd0f
--- /dev/null
+++ b/src/tools/miri/tests/fail/const-ub-checks.rs
@@ -0,0 +1,11 @@
+#![feature(const_ptr_read)]
+
+const UNALIGNED_READ: () = unsafe {
+    let x = &[0u8; 4];
+    let ptr = x.as_ptr().cast::<u32>();
+    ptr.read(); //~ERROR: evaluation of constant value failed
+};
+
+fn main() {
+    let _x = UNALIGNED_READ;
+}
diff --git a/src/tools/miri/tests/fail/const-ub-checks.stderr b/src/tools/miri/tests/fail/const-ub-checks.stderr
new file mode 100644
index 0000000000000..a8b7ea242b970
--- /dev/null
+++ b/src/tools/miri/tests/fail/const-ub-checks.stderr
@@ -0,0 +1,9 @@
+error[E0080]: evaluation of constant value failed
+  --> $DIR/const-ub-checks.rs:LL:CC
+   |
+LL |     ptr.read();
+   |     ^^^^^^^^^^ accessing memory with alignment ALIGN, but alignment ALIGN is required
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0080`.
diff --git a/src/tools/miri/tests/fail/copy_half_a_pointer.rs b/src/tools/miri/tests/fail/copy_half_a_pointer.rs
new file mode 100644
index 0000000000000..e1dcdda7fdfe3
--- /dev/null
+++ b/src/tools/miri/tests/fail/copy_half_a_pointer.rs
@@ -0,0 +1,21 @@
+//@normalize-stderr-test: "\+0x[48]" -> "+HALF_PTR"
+#![allow(dead_code)]
+
+// We use packed structs to get around alignment restrictions
+#[repr(packed)]
+struct Data {
+    pad: u8,
+    ptr: &'static i32,
+}
+
+static G: i32 = 0;
+
+fn main() {
+    let mut d = Data { pad: 0, ptr: &G };
+
+    // Get a pointer to the beginning of the Data struct (one u8 byte, then the pointer bytes).
+    let d_alias = &mut d as *mut _ as *mut *const u8;
+    unsafe {
+        let _x = d_alias.read_unaligned(); //~ERROR: unable to copy parts of a pointer
+    }
+}
diff --git a/src/tools/miri/tests/fail/copy_half_a_pointer.stderr b/src/tools/miri/tests/fail/copy_half_a_pointer.stderr
new file mode 100644
index 0000000000000..21797757084ee
--- /dev/null
+++ b/src/tools/miri/tests/fail/copy_half_a_pointer.stderr
@@ -0,0 +1,14 @@
+error: unsupported operation: unable to copy parts of a pointer from memory at ALLOC+HALF_PTR
+  --> $DIR/copy_half_a_pointer.rs:LL:CC
+   |
+LL |         let _x = d_alias.read_unaligned();
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^^ unable to copy parts of a pointer from memory at ALLOC+HALF_PTR
+   |
+   = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/copy_half_a_pointer.rs:LL:CC
+
+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/crates/tokio_mvp.rs b/src/tools/miri/tests/fail/crates/tokio_mvp.rs
new file mode 100644
index 0000000000000..7cb42c09a9677
--- /dev/null
+++ b/src/tools/miri/tests/fail/crates/tokio_mvp.rs
@@ -0,0 +1,7 @@
+//@compile-flags: -Zmiri-disable-isolation
+//@error-pattern: can't call foreign function: epoll_create1
+//@normalize-stderr-test: "   = note: inside .*\n" -> ""
+//@only-target-linux: the errors differ too much between platforms
+
+#[tokio::main]
+async fn main() {}
diff --git a/src/tools/miri/tests/fail/crates/tokio_mvp.stderr b/src/tools/miri/tests/fail/crates/tokio_mvp.stderr
new file mode 100644
index 0000000000000..016081d90adf7
--- /dev/null
+++ b/src/tools/miri/tests/fail/crates/tokio_mvp.stderr
@@ -0,0 +1,19 @@
+error: unsupported operation: can't call foreign function: epoll_create1
+  --> CARGO_REGISTRY/.../epoll.rs:LL:CC
+   |
+LL |         syscall!(epoll_create1(flag)).map(|ep| Selector {
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ can't call foreign function: epoll_create1
+   |
+   = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
+   = note: BACKTRACE:
+note: inside `main` at $DIR/tokio_mvp.rs:LL:CC
+  --> $DIR/tokio_mvp.rs:LL:CC
+   |
+LL | #[tokio::main]
+   | ^^^^^^^^^^^^^^
+   = note: this error originates in the macro `syscall` which comes from the expansion of the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+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/dangling_pointers/dangling_pointer_addr_of.rs b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_addr_of.rs
new file mode 100644
index 0000000000000..4249c1cbf0177
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_addr_of.rs
@@ -0,0 +1,12 @@
+// Make sure we find these even with many checks disabled.
+//@compile-flags: -Zmiri-disable-alignment-check -Zmiri-disable-stacked-borrows -Zmiri-disable-validation
+use std::ptr;
+
+fn main() {
+    let p = {
+        let b = Box::new(42);
+        &*b as *const i32
+    };
+    let x = unsafe { ptr::addr_of!(*p) }; //~ ERROR: dereferenced after this allocation got freed
+    panic!("this should never print: {:?}", x);
+}
diff --git a/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_addr_of.stderr b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_addr_of.stderr
new file mode 100644
index 0000000000000..5f081afe68af8
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_addr_of.stderr
@@ -0,0 +1,16 @@
+error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed
+  --> $DIR/dangling_pointer_addr_of.rs:LL:CC
+   |
+LL |     let x = unsafe { ptr::addr_of!(*p) };
+   |                      ^^^^^^^^^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at RUSTLIB/core/src/ptr/mod.rs:LL:CC
+   = note: this error originates in the macro `ptr::addr_of` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+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/dangling_pointers/dangling_pointer_deref.rs b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_deref.rs
new file mode 100644
index 0000000000000..ad2a599b60b48
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_deref.rs
@@ -0,0 +1,11 @@
+// Make sure we find these even with many checks disabled.
+//@compile-flags: -Zmiri-disable-alignment-check -Zmiri-disable-stacked-borrows -Zmiri-disable-validation
+
+fn main() {
+    let p = {
+        let b = Box::new(42);
+        &*b as *const i32
+    };
+    let x = unsafe { *p }; //~ ERROR: dereferenced after this allocation got freed
+    panic!("this should never print: {}", x);
+}
diff --git a/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_deref.stderr b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_deref.stderr
new file mode 100644
index 0000000000000..cb323818845df
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_deref.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed
+  --> $DIR/dangling_pointer_deref.rs:LL:CC
+   |
+LL |     let x = unsafe { *p };
+   |                      ^^ pointer to ALLOC was dereferenced after this allocation got freed
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/dangling_pointer_deref.rs:LL:CC
+
+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/dangling_pointers/dangling_zst_deref.rs b/src/tools/miri/tests/fail/dangling_pointers/dangling_zst_deref.rs
new file mode 100644
index 0000000000000..534d7d5f42f32
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/dangling_zst_deref.rs
@@ -0,0 +1,11 @@
+// Make sure we find these even with many checks disabled.
+// Some optimizations remove ZST accesses, thus masking this UB.
+//@compile-flags: -Zmir-opt-level=0 -Zmiri-disable-alignment-check -Zmiri-disable-stacked-borrows -Zmiri-disable-validation
+
+fn main() {
+    let p = {
+        let b = Box::new(42);
+        &*b as *const i32 as *const ()
+    };
+    let _x = unsafe { *p }; //~ ERROR: dereferenced after this allocation got freed
+}
diff --git a/src/tools/miri/tests/fail/dangling_pointers/dangling_zst_deref.stderr b/src/tools/miri/tests/fail/dangling_pointers/dangling_zst_deref.stderr
new file mode 100644
index 0000000000000..02db6302a0a1e
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/dangling_zst_deref.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed
+  --> $DIR/dangling_zst_deref.rs:LL:CC
+   |
+LL |     let _x = unsafe { *p };
+   |                       ^^ pointer to ALLOC was dereferenced after this allocation got freed
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/dangling_zst_deref.rs:LL:CC
+
+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/dangling_pointers/deref-invalid-ptr.rs b/src/tools/miri/tests/fail/dangling_pointers/deref-invalid-ptr.rs
new file mode 100644
index 0000000000000..57e95ef19dc99
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/deref-invalid-ptr.rs
@@ -0,0 +1,7 @@
+// This should fail even without validation.
+//@compile-flags: -Zmiri-disable-validation -Zmiri-permissive-provenance
+
+fn main() {
+    let x = 16usize as *const u32;
+    let _y = unsafe { &*x as *const u32 }; //~ ERROR: is a dangling pointer
+}
diff --git a/src/tools/miri/tests/fail/dangling_pointers/deref-invalid-ptr.stderr b/src/tools/miri/tests/fail/dangling_pointers/deref-invalid-ptr.stderr
new file mode 100644
index 0000000000000..3e2c3903b7e47
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/deref-invalid-ptr.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: dereferencing pointer failed: 0x10[noalloc] is a dangling pointer (it has no provenance)
+  --> $DIR/deref-invalid-ptr.rs:LL:CC
+   |
+LL |     let _y = unsafe { &*x as *const u32 };
+   |                       ^^^ dereferencing pointer failed: 0x10[noalloc] is a dangling pointer (it has no provenance)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/deref-invalid-ptr.rs:LL:CC
+
+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/dangling_pointers/deref-partially-dangling.rs b/src/tools/miri/tests/fail/dangling_pointers/deref-partially-dangling.rs
new file mode 100644
index 0000000000000..27040c26dc212
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/deref-partially-dangling.rs
@@ -0,0 +1,8 @@
+// Deref a raw ptr to access a field of a large struct, where the field
+// is allocated but not the entire struct is.
+fn main() {
+    let x = (1, 13);
+    let xptr = &x as *const _ as *const (i32, i32, i32);
+    let val = unsafe { (*xptr).1 }; //~ ERROR: pointer to 12 bytes starting at offset 0 is out-of-bounds
+    assert_eq!(val, 13);
+}
diff --git a/src/tools/miri/tests/fail/dangling_pointers/deref-partially-dangling.stderr b/src/tools/miri/tests/fail/dangling_pointers/deref-partially-dangling.stderr
new file mode 100644
index 0000000000000..fe039ef3adaf9
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/deref-partially-dangling.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: dereferencing pointer failed: ALLOC has size 8, so pointer to 12 bytes starting at offset 0 is out-of-bounds
+  --> $DIR/deref-partially-dangling.rs:LL:CC
+   |
+LL |     let val = unsafe { (*xptr).1 };
+   |                        ^^^^^^^^^ dereferencing pointer failed: ALLOC has size 8, so pointer to 12 bytes starting at offset 0 is out-of-bounds
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/deref-partially-dangling.rs:LL:CC
+
+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/dangling_pointers/dyn_size.rs b/src/tools/miri/tests/fail/dangling_pointers/dyn_size.rs
new file mode 100644
index 0000000000000..54f353ebebeb1
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/dyn_size.rs
@@ -0,0 +1,13 @@
+// should find the bug even without these, but gets masked by optimizations
+//@compile-flags: -Zmiri-disable-validation -Zmiri-disable-stacked-borrows -Zmir-opt-level=0
+
+struct SliceWithHead(u8, [u8]);
+
+fn main() {
+    let buf = [0u32; 1];
+    // We craft a wide pointer `*const SliceWithHead` such that the unsized tail is only partially allocated.
+    // That should be UB, as the reference is not fully dereferencable.
+    let ptr: *const SliceWithHead = unsafe { std::mem::transmute((&buf, 4usize)) };
+    // Re-borrow that. This should be UB.
+    let _ptr = unsafe { &*ptr }; //~ ERROR: pointer to 5 bytes starting at offset 0 is out-of-bounds
+}
diff --git a/src/tools/miri/tests/fail/dangling_pointers/dyn_size.stderr b/src/tools/miri/tests/fail/dangling_pointers/dyn_size.stderr
new file mode 100644
index 0000000000000..33aa6c8441017
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/dyn_size.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: dereferencing pointer failed: ALLOC has size 4, so pointer to 5 bytes starting at offset 0 is out-of-bounds
+  --> $DIR/dyn_size.rs:LL:CC
+   |
+LL |     let _ptr = unsafe { &*ptr };
+   |                         ^^^^^ dereferencing pointer failed: ALLOC has size 4, so pointer to 5 bytes starting at offset 0 is out-of-bounds
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/dyn_size.rs:LL:CC
+
+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/dangling_pointers/maybe_null_pointer_deref_zst.rs b/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_deref_zst.rs
new file mode 100644
index 0000000000000..a48a3189db2e3
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_deref_zst.rs
@@ -0,0 +1,8 @@
+// Some optimizations remove ZST accesses, thus masking this UB.
+//@compile-flags: -Zmir-opt-level=0
+
+fn main() {
+    // This pointer *could* be NULL so we cannot load from it, not even at ZST
+    let ptr = (&0u8 as *const u8).wrapping_sub(0x800) as *const ();
+    let _x: () = unsafe { *ptr }; //~ ERROR: out-of-bounds
+}
diff --git a/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_deref_zst.stderr b/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_deref_zst.stderr
new file mode 100644
index 0000000000000..3e492a170c8b1
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_deref_zst.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: dereferencing pointer failed: ALLOC has size 1, so pointer at offset -2048 is out-of-bounds
+  --> $DIR/maybe_null_pointer_deref_zst.rs:LL:CC
+   |
+LL |     let _x: () = unsafe { *ptr };
+   |                           ^^^^ dereferencing pointer failed: ALLOC has size 1, so pointer at offset -2048 is out-of-bounds
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/maybe_null_pointer_deref_zst.rs:LL:CC
+
+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/dangling_pointers/maybe_null_pointer_write_zst.rs b/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_write_zst.rs
new file mode 100644
index 0000000000000..449c65d218a02
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_write_zst.rs
@@ -0,0 +1,11 @@
+// Some optimizations remove ZST accesses, thus masking this UB.
+//@compile-flags: -Zmir-opt-level=0
+
+fn main() {
+    // This pointer *could* be NULL so we cannot load from it, not even at ZST.
+    // Not using the () type here, as writes of that type do not even have MIR generated.
+    // Also not assigning directly as that's array initialization, not assignment.
+    let zst_val = [1u8; 0];
+    let ptr = (&0u8 as *const u8).wrapping_sub(0x800) as *mut [u8; 0];
+    unsafe { *ptr = zst_val }; //~ ERROR: out-of-bounds
+}
diff --git a/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_write_zst.stderr b/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_write_zst.stderr
new file mode 100644
index 0000000000000..c41c20aaf4a7b
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_write_zst.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: dereferencing pointer failed: ALLOC has size 1, so pointer at offset -2048 is out-of-bounds
+  --> $DIR/maybe_null_pointer_write_zst.rs:LL:CC
+   |
+LL |     unsafe { *ptr = zst_val };
+   |              ^^^^^^^^^^^^^^ dereferencing pointer failed: ALLOC has size 1, so pointer at offset -2048 is out-of-bounds
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/maybe_null_pointer_write_zst.rs:LL:CC
+
+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/dangling_pointers/null_pointer_deref.rs b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_deref.rs
new file mode 100644
index 0000000000000..a0773c63cf6bf
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_deref.rs
@@ -0,0 +1,5 @@
+#[allow(deref_nullptr)]
+fn main() {
+    let x: i32 = unsafe { *std::ptr::null() }; //~ ERROR: null pointer is a dangling pointer
+    panic!("this should never print: {}", x);
+}
diff --git a/src/tools/miri/tests/fail/dangling_pointers/null_pointer_deref.stderr b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_deref.stderr
new file mode 100644
index 0000000000000..64dcaa4548476
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_deref.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: dereferencing pointer failed: null pointer is a dangling pointer (it has no provenance)
+  --> $DIR/null_pointer_deref.rs:LL:CC
+   |
+LL |     let x: i32 = unsafe { *std::ptr::null() };
+   |                           ^^^^^^^^^^^^^^^^^ dereferencing pointer failed: null pointer is a dangling pointer (it has no provenance)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/null_pointer_deref.rs:LL:CC
+
+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/dangling_pointers/null_pointer_deref_zst.rs b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_deref_zst.rs
new file mode 100644
index 0000000000000..d6a607c61cbeb
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_deref_zst.rs
@@ -0,0 +1,8 @@
+// Some optimizations remove ZST accesses, thus masking this UB.
+//@compile-flags: -Zmir-opt-level=0
+
+#[allow(deref_nullptr)]
+fn main() {
+    let x: () = unsafe { *std::ptr::null() }; //~ ERROR: dereferencing pointer failed: null pointer is a dangling pointer
+    panic!("this should never print: {:?}", x);
+}
diff --git a/src/tools/miri/tests/fail/dangling_pointers/null_pointer_deref_zst.stderr b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_deref_zst.stderr
new file mode 100644
index 0000000000000..301578a4f5fb4
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_deref_zst.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: dereferencing pointer failed: null pointer is a dangling pointer (it has no provenance)
+  --> $DIR/null_pointer_deref_zst.rs:LL:CC
+   |
+LL |     let x: () = unsafe { *std::ptr::null() };
+   |                          ^^^^^^^^^^^^^^^^^ dereferencing pointer failed: null pointer is a dangling pointer (it has no provenance)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/null_pointer_deref_zst.rs:LL:CC
+
+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/dangling_pointers/null_pointer_write.rs b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write.rs
new file mode 100644
index 0000000000000..954596f57542e
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write.rs
@@ -0,0 +1,4 @@
+#[allow(deref_nullptr)]
+fn main() {
+    unsafe { *std::ptr::null_mut() = 0i32 }; //~ ERROR: null pointer is a dangling pointer
+}
diff --git a/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write.stderr b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write.stderr
new file mode 100644
index 0000000000000..0e5858a96f9d7
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: dereferencing pointer failed: null pointer is a dangling pointer (it has no provenance)
+  --> $DIR/null_pointer_write.rs:LL:CC
+   |
+LL |     unsafe { *std::ptr::null_mut() = 0i32 };
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ dereferencing pointer failed: null pointer is a dangling pointer (it has no provenance)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/null_pointer_write.rs:LL:CC
+
+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/dangling_pointers/null_pointer_write_zst.rs b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write_zst.rs
new file mode 100644
index 0000000000000..c00344b6de23a
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write_zst.rs
@@ -0,0 +1,11 @@
+// Some optimizations remove ZST accesses, thus masking this UB.
+//@compile-flags: -Zmir-opt-level=0
+
+#[allow(deref_nullptr)]
+fn main() {
+    // Not using the () type here, as writes of that type do not even have MIR generated.
+    // Also not assigning directly as that's array initialization, not assignment.
+    let zst_val = [1u8; 0];
+    unsafe { std::ptr::null_mut::<[u8; 0]>().write(zst_val) };
+    //~^ERROR: memory access failed: null pointer is a dangling pointer
+}
diff --git a/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write_zst.stderr b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write_zst.stderr
new file mode 100644
index 0000000000000..2953d85c25f3f
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write_zst.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: memory access failed: null pointer is a dangling pointer (it has no provenance)
+  --> $DIR/null_pointer_write_zst.rs:LL:CC
+   |
+LL |     unsafe { std::ptr::null_mut::<[u8; 0]>().write(zst_val) };
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ memory access failed: null pointer is a dangling pointer (it has no provenance)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/null_pointer_write_zst.rs:LL:CC
+
+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/dangling_pointers/out_of_bounds_read1.rs b/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read1.rs
new file mode 100644
index 0000000000000..58a64eecace8c
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read1.rs
@@ -0,0 +1,5 @@
+fn main() {
+    let v: Vec<u8> = vec![1, 2];
+    let x = unsafe { *v.as_ptr().wrapping_offset(5) }; //~ ERROR: out-of-bounds
+    panic!("this should never print: {}", x);
+}
diff --git a/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read1.stderr b/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read1.stderr
new file mode 100644
index 0000000000000..b2461ce4230ad
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read1.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: dereferencing pointer failed: ALLOC has size 2, so pointer to 1 byte starting at offset 5 is out-of-bounds
+  --> $DIR/out_of_bounds_read1.rs:LL:CC
+   |
+LL |     let x = unsafe { *v.as_ptr().wrapping_offset(5) };
+   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ dereferencing pointer failed: ALLOC has size 2, so pointer to 1 byte starting at offset 5 is out-of-bounds
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/out_of_bounds_read1.rs:LL:CC
+
+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/dangling_pointers/out_of_bounds_read2.rs b/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read2.rs
new file mode 100644
index 0000000000000..58a64eecace8c
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read2.rs
@@ -0,0 +1,5 @@
+fn main() {
+    let v: Vec<u8> = vec![1, 2];
+    let x = unsafe { *v.as_ptr().wrapping_offset(5) }; //~ ERROR: out-of-bounds
+    panic!("this should never print: {}", x);
+}
diff --git a/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read2.stderr b/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read2.stderr
new file mode 100644
index 0000000000000..b17058b406298
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read2.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: dereferencing pointer failed: ALLOC has size 2, so pointer to 1 byte starting at offset 5 is out-of-bounds
+  --> $DIR/out_of_bounds_read2.rs:LL:CC
+   |
+LL |     let x = unsafe { *v.as_ptr().wrapping_offset(5) };
+   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ dereferencing pointer failed: ALLOC has size 2, so pointer to 1 byte starting at offset 5 is out-of-bounds
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/out_of_bounds_read2.rs:LL:CC
+
+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/dangling_pointers/stack_temporary.rs b/src/tools/miri/tests/fail/dangling_pointers/stack_temporary.rs
new file mode 100644
index 0000000000000..1373773f68d59
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/stack_temporary.rs
@@ -0,0 +1,14 @@
+// This should fail even without validation, but some MIR opts mask the error
+//@compile-flags: -Zmiri-disable-validation -Zmir-opt-level=0
+
+unsafe fn make_ref<'a>(x: *mut i32) -> &'a mut i32 {
+    &mut *x
+}
+
+fn main() {
+    unsafe {
+        let x = make_ref(&mut 0); // The temporary storing "0" is deallocated at the ";"!
+        let val = *x; //~ ERROR: dereferenced after this allocation got freed
+        println!("{}", val);
+    }
+}
diff --git a/src/tools/miri/tests/fail/dangling_pointers/stack_temporary.stderr b/src/tools/miri/tests/fail/dangling_pointers/stack_temporary.stderr
new file mode 100644
index 0000000000000..679e4809ca663
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/stack_temporary.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed
+  --> $DIR/stack_temporary.rs:LL:CC
+   |
+LL |         let val = *x;
+   |                   ^^ pointer to ALLOC was dereferenced after this allocation got freed
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/stack_temporary.rs:LL:CC
+
+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/dangling_pointers/storage_dead_dangling.rs b/src/tools/miri/tests/fail/dangling_pointers/storage_dead_dangling.rs
new file mode 100644
index 0000000000000..03113585d1464
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/storage_dead_dangling.rs
@@ -0,0 +1,26 @@
+// This should fail even without validation, but some MIR opts mask the error
+//@compile-flags: -Zmiri-disable-validation -Zmir-opt-level=0 -Zmiri-permissive-provenance
+
+static mut LEAK: usize = 0;
+
+fn fill(v: &mut i32) {
+    unsafe {
+        LEAK = v as *mut _ as usize;
+    }
+}
+
+fn evil() {
+    unsafe { &mut *(LEAK as *mut i32) }; //~ ERROR: is a dangling pointer
+}
+
+fn main() {
+    let _y;
+    {
+        let mut x = 0i32;
+        fill(&mut x);
+        _y = x;
+    }
+    // Now we use a pointer to `x` which is no longer in scope, and thus dead (even though the
+    // `main` stack frame still exists). We even try going through a `usize` for extra sneakiness!
+    evil();
+}
diff --git a/src/tools/miri/tests/fail/dangling_pointers/storage_dead_dangling.stderr b/src/tools/miri/tests/fail/dangling_pointers/storage_dead_dangling.stderr
new file mode 100644
index 0000000000000..72e5f20f924a4
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/storage_dead_dangling.stderr
@@ -0,0 +1,20 @@
+error: Undefined Behavior: dereferencing pointer failed: $HEX[noalloc] is a dangling pointer (it has no provenance)
+  --> $DIR/storage_dead_dangling.rs:LL:CC
+   |
+LL |     unsafe { &mut *(LEAK as *mut i32) };
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^ dereferencing pointer failed: $HEX[noalloc] is a dangling pointer (it has no provenance)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `evil` at $DIR/storage_dead_dangling.rs:LL:CC
+note: inside `main` at $DIR/storage_dead_dangling.rs:LL:CC
+  --> $DIR/storage_dead_dangling.rs:LL:CC
+   |
+LL |     evil();
+   |     ^^^^^^
+
+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/dangling_pointers/wild_pointer_deref.rs b/src/tools/miri/tests/fail/dangling_pointers/wild_pointer_deref.rs
new file mode 100644
index 0000000000000..9ffc681465504
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/wild_pointer_deref.rs
@@ -0,0 +1,7 @@
+//@compile-flags: -Zmiri-permissive-provenance
+
+fn main() {
+    let p = 44 as *const i32;
+    let x = unsafe { *p }; //~ ERROR: is a dangling pointer
+    panic!("this should never print: {}", x);
+}
diff --git a/src/tools/miri/tests/fail/dangling_pointers/wild_pointer_deref.stderr b/src/tools/miri/tests/fail/dangling_pointers/wild_pointer_deref.stderr
new file mode 100644
index 0000000000000..658fb228174e5
--- /dev/null
+++ b/src/tools/miri/tests/fail/dangling_pointers/wild_pointer_deref.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: dereferencing pointer failed: 0x2c[noalloc] is a dangling pointer (it has no provenance)
+  --> $DIR/wild_pointer_deref.rs:LL:CC
+   |
+LL |     let x = unsafe { *p };
+   |                      ^^ dereferencing pointer failed: 0x2c[noalloc] is a dangling pointer (it has no provenance)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/wild_pointer_deref.rs:LL:CC
+
+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/data_race/alloc_read_race.rs b/src/tools/miri/tests/fail/data_race/alloc_read_race.rs
new file mode 100644
index 0000000000000..0bd3068af1ffe
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/alloc_read_race.rs
@@ -0,0 +1,49 @@
+//@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
+#![feature(new_uninit)]
+
+use std::mem::MaybeUninit;
+use std::ptr::null_mut;
+use std::sync::atomic::{AtomicPtr, Ordering};
+use std::thread::spawn;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+pub fn main() {
+    // Shared atomic pointer
+    let pointer = AtomicPtr::new(null_mut::<MaybeUninit<usize>>());
+    let ptr = EvilSend(&pointer as *const AtomicPtr<MaybeUninit<usize>>);
+
+    // Note: this is scheduler-dependent
+    // the operations need to occur in
+    // order, otherwise the allocation is
+    // not visible to the other-thread to
+    // detect the race:
+    //  1. alloc
+    //  2. write
+    unsafe {
+        let j1 = spawn(move || {
+            // Concurrent allocate the memory.
+            // Uses relaxed semantics to not generate
+            // a release sequence.
+            let pointer = &*ptr.0;
+            pointer.store(Box::into_raw(Box::new_uninit()), Ordering::Relaxed);
+        });
+
+        let j2 = spawn(move || {
+            let pointer = &*ptr.0;
+
+            // Note: could also error due to reading uninitialized memory, but the data-race detector triggers first.
+            *pointer.load(Ordering::Relaxed) //~ ERROR: Data race detected between Read on thread `<unnamed>` and Allocate on thread `<unnamed>`
+        });
+
+        j1.join().unwrap();
+        j2.join().unwrap();
+
+        // Clean up memory, will never be executed
+        drop(Box::from_raw(pointer.load(Ordering::Relaxed)));
+    }
+}
diff --git a/src/tools/miri/tests/fail/data_race/alloc_read_race.stderr b/src/tools/miri/tests/fail/data_race/alloc_read_race.stderr
new file mode 100644
index 0000000000000..c6bfd12b24110
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/alloc_read_race.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: Data race detected between Read on thread `<unnamed>` and Allocate on thread `<unnamed>` at ALLOC
+  --> $DIR/alloc_read_race.rs:LL:CC
+   |
+LL |             *pointer.load(Ordering::Relaxed)
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Data race detected between Read on thread `<unnamed>` and Allocate on thread `<unnamed>` at ALLOC
+   |
+   = 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 closure at $DIR/alloc_read_race.rs:LL:CC
+
+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/data_race/alloc_write_race.rs b/src/tools/miri/tests/fail/data_race/alloc_write_race.rs
new file mode 100644
index 0000000000000..7991280721e29
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/alloc_write_race.rs
@@ -0,0 +1,47 @@
+//@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
+#![feature(new_uninit)]
+
+use std::ptr::null_mut;
+use std::sync::atomic::{AtomicPtr, Ordering};
+use std::thread::spawn;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+pub fn main() {
+    // Shared atomic pointer
+    let pointer = AtomicPtr::new(null_mut::<usize>());
+    let ptr = EvilSend(&pointer as *const AtomicPtr<usize>);
+
+    // Note: this is scheduler-dependent
+    // the operations need to occur in
+    // order, otherwise the allocation is
+    // not visible to the other-thread to
+    // detect the race:
+    //  1. alloc
+    //  2. write
+    unsafe {
+        let j1 = spawn(move || {
+            // Concurrent allocate the memory.
+            // Uses relaxed semantics to not generate
+            // a release sequence.
+            let pointer = &*ptr.0;
+            pointer
+                .store(Box::into_raw(Box::<usize>::new_uninit()) as *mut usize, Ordering::Relaxed);
+        });
+
+        let j2 = spawn(move || {
+            let pointer = &*ptr.0;
+            *pointer.load(Ordering::Relaxed) = 2; //~ ERROR: Data race detected between Write on thread `<unnamed>` and Allocate on thread `<unnamed>`
+        });
+
+        j1.join().unwrap();
+        j2.join().unwrap();
+
+        // Clean up memory, will never be executed
+        drop(Box::from_raw(pointer.load(Ordering::Relaxed)));
+    }
+}
diff --git a/src/tools/miri/tests/fail/data_race/alloc_write_race.stderr b/src/tools/miri/tests/fail/data_race/alloc_write_race.stderr
new file mode 100644
index 0000000000000..c4efc175c2077
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/alloc_write_race.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: Data race detected between Write on thread `<unnamed>` and Allocate on thread `<unnamed>` at ALLOC
+  --> $DIR/alloc_write_race.rs:LL:CC
+   |
+LL |             *pointer.load(Ordering::Relaxed) = 2;
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Data race detected between Write on thread `<unnamed>` and Allocate on thread `<unnamed>` at ALLOC
+   |
+   = 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 closure at $DIR/alloc_write_race.rs:LL:CC
+
+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/data_race/atomic_read_na_write_race1.rs b/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race1.rs
new file mode 100644
index 0000000000000..2b0446d724a02
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race1.rs
@@ -0,0 +1,29 @@
+// We want to control preemption here.
+//@compile-flags: -Zmiri-preemption-rate=0
+
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::thread::spawn;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+pub fn main() {
+    let mut a = AtomicUsize::new(0);
+    let b = &mut a as *mut AtomicUsize;
+    let c = EvilSend(b);
+    unsafe {
+        let j1 = spawn(move || {
+            *(c.0 as *mut usize) = 32;
+        });
+
+        let j2 = spawn(move || {
+            (&*c.0).load(Ordering::SeqCst) //~ ERROR: Data race detected between Atomic Load on thread `<unnamed>` and Write on thread `<unnamed>`
+        });
+
+        j1.join().unwrap();
+        j2.join().unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race1.stderr b/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race1.stderr
new file mode 100644
index 0000000000000..04adf0a98b6c5
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race1.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: Data race detected between Atomic Load on thread `<unnamed>` and Write on thread `<unnamed>` at ALLOC
+  --> $DIR/atomic_read_na_write_race1.rs:LL:CC
+   |
+LL |             (&*c.0).load(Ordering::SeqCst)
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Data race detected between Atomic Load on thread `<unnamed>` and Write on thread `<unnamed>` at ALLOC
+   |
+   = 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 closure at $DIR/atomic_read_na_write_race1.rs:LL:CC
+
+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/data_race/atomic_read_na_write_race2.rs b/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race2.rs
new file mode 100644
index 0000000000000..ef5157515c64a
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race2.rs
@@ -0,0 +1,32 @@
+// We want to control preemption here.
+//@compile-flags: -Zmiri-preemption-rate=0
+
+use std::sync::atomic::AtomicUsize;
+use std::sync::atomic::Ordering;
+use std::thread::spawn;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+pub fn main() {
+    let mut a = AtomicUsize::new(0);
+    let b = &mut a as *mut AtomicUsize;
+    let c = EvilSend(b);
+    unsafe {
+        let j1 = spawn(move || {
+            let atomic_ref = &mut *c.0;
+            atomic_ref.load(Ordering::SeqCst)
+        });
+
+        let j2 = spawn(move || {
+            let atomic_ref = &mut *c.0;
+            *atomic_ref.get_mut() = 32; //~ ERROR: Data race detected between Write on thread `<unnamed>` and Atomic Load on thread `<unnamed>`
+        });
+
+        j1.join().unwrap();
+        j2.join().unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race2.stderr b/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race2.stderr
new file mode 100644
index 0000000000000..b48f927b8fcae
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race2.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: Data race detected between Write on thread `<unnamed>` and Atomic Load on thread `<unnamed>` at ALLOC
+  --> $DIR/atomic_read_na_write_race2.rs:LL:CC
+   |
+LL |             *atomic_ref.get_mut() = 32;
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^ Data race detected between Write on thread `<unnamed>` and Atomic Load on thread `<unnamed>` at ALLOC
+   |
+   = 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 closure at $DIR/atomic_read_na_write_race2.rs:LL:CC
+
+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/data_race/atomic_write_na_read_race1.rs b/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race1.rs
new file mode 100644
index 0000000000000..8c17e76748438
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race1.rs
@@ -0,0 +1,32 @@
+// We want to control preemption here.
+//@compile-flags: -Zmiri-preemption-rate=0
+
+use std::sync::atomic::AtomicUsize;
+use std::sync::atomic::Ordering;
+use std::thread::spawn;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+pub fn main() {
+    let mut a = AtomicUsize::new(0);
+    let b = &mut a as *mut AtomicUsize;
+    let c = EvilSend(b);
+    unsafe {
+        let j1 = spawn(move || {
+            let atomic_ref = &mut *c.0;
+            atomic_ref.store(32, Ordering::SeqCst)
+        });
+
+        let j2 = spawn(move || {
+            let atomic_ref = &mut *c.0;
+            *atomic_ref.get_mut() //~ ERROR: Data race detected between Read on thread `<unnamed>` and Atomic Store on thread `<unnamed>`
+        });
+
+        j1.join().unwrap();
+        j2.join().unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race1.stderr b/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race1.stderr
new file mode 100644
index 0000000000000..fdb9b353a67bf
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race1.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: Data race detected between Read on thread `<unnamed>` and Atomic Store on thread `<unnamed>` at ALLOC
+  --> $DIR/atomic_write_na_read_race1.rs:LL:CC
+   |
+LL |             *atomic_ref.get_mut()
+   |             ^^^^^^^^^^^^^^^^^^^^^ Data race detected between Read on thread `<unnamed>` and Atomic Store on thread `<unnamed>` at ALLOC
+   |
+   = 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 closure at $DIR/atomic_write_na_read_race1.rs:LL:CC
+
+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/data_race/atomic_write_na_read_race2.rs b/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race2.rs
new file mode 100644
index 0000000000000..f14d7c704dbb1
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race2.rs
@@ -0,0 +1,29 @@
+// We want to control preemption here.
+//@compile-flags: -Zmiri-preemption-rate=0
+
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::thread::spawn;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+pub fn main() {
+    let mut a = AtomicUsize::new(0);
+    let b = &mut a as *mut AtomicUsize;
+    let c = EvilSend(b);
+    unsafe {
+        let j1 = spawn(move || {
+            let _val = *(c.0 as *mut usize);
+        });
+
+        let j2 = spawn(move || {
+            (&*c.0).store(32, Ordering::SeqCst); //~ ERROR: Data race detected between Atomic Store on thread `<unnamed>` and Read on thread `<unnamed>`
+        });
+
+        j1.join().unwrap();
+        j2.join().unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race2.stderr b/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race2.stderr
new file mode 100644
index 0000000000000..ec581e322b7d1
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race2.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: Data race detected between Atomic Store on thread `<unnamed>` and Read on thread `<unnamed>` at ALLOC
+  --> $DIR/atomic_write_na_read_race2.rs:LL:CC
+   |
+LL |             (&*c.0).store(32, Ordering::SeqCst);
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Data race detected between Atomic Store on thread `<unnamed>` and Read on thread `<unnamed>` at ALLOC
+   |
+   = 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 closure at $DIR/atomic_write_na_read_race2.rs:LL:CC
+
+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/data_race/atomic_write_na_write_race1.rs b/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race1.rs
new file mode 100644
index 0000000000000..0804b33407580
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race1.rs
@@ -0,0 +1,29 @@
+// We want to control preemption here.
+//@compile-flags: -Zmiri-preemption-rate=0
+
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::thread::spawn;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+pub fn main() {
+    let mut a = AtomicUsize::new(0);
+    let b = &mut a as *mut AtomicUsize;
+    let c = EvilSend(b);
+    unsafe {
+        let j1 = spawn(move || {
+            *(c.0 as *mut usize) = 32;
+        });
+
+        let j2 = spawn(move || {
+            (&*c.0).store(64, Ordering::SeqCst); //~ ERROR: Data race detected between Atomic Store on thread `<unnamed>` and Write on thread `<unnamed>`
+        });
+
+        j1.join().unwrap();
+        j2.join().unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race1.stderr b/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race1.stderr
new file mode 100644
index 0000000000000..4c75f94d71cf5
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race1.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: Data race detected between Atomic Store on thread `<unnamed>` and Write on thread `<unnamed>` at ALLOC
+  --> $DIR/atomic_write_na_write_race1.rs:LL:CC
+   |
+LL |             (&*c.0).store(64, Ordering::SeqCst);
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Data race detected between Atomic Store on thread `<unnamed>` and Write on thread `<unnamed>` at ALLOC
+   |
+   = 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 closure at $DIR/atomic_write_na_write_race1.rs:LL:CC
+
+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/data_race/atomic_write_na_write_race2.rs b/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race2.rs
new file mode 100644
index 0000000000000..658cddcc9c5b6
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race2.rs
@@ -0,0 +1,32 @@
+// We want to control preemption here.
+//@compile-flags: -Zmiri-preemption-rate=0
+
+use std::sync::atomic::AtomicUsize;
+use std::sync::atomic::Ordering;
+use std::thread::spawn;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+pub fn main() {
+    let mut a = AtomicUsize::new(0);
+    let b = &mut a as *mut AtomicUsize;
+    let c = EvilSend(b);
+    unsafe {
+        let j1 = spawn(move || {
+            let atomic_ref = &mut *c.0;
+            atomic_ref.store(64, Ordering::SeqCst);
+        });
+
+        let j2 = spawn(move || {
+            let atomic_ref = &mut *c.0;
+            *atomic_ref.get_mut() = 32; //~ ERROR: Data race detected between Write on thread `<unnamed>` and Atomic Store on thread `<unnamed>`
+        });
+
+        j1.join().unwrap();
+        j2.join().unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race2.stderr b/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race2.stderr
new file mode 100644
index 0000000000000..8c7f14081c87b
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race2.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: Data race detected between Write on thread `<unnamed>` and Atomic Store on thread `<unnamed>` at ALLOC
+  --> $DIR/atomic_write_na_write_race2.rs:LL:CC
+   |
+LL |             *atomic_ref.get_mut() = 32;
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^ Data race detected between Write on thread `<unnamed>` and Atomic Store on thread `<unnamed>` at ALLOC
+   |
+   = 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 closure at $DIR/atomic_write_na_write_race2.rs:LL:CC
+
+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/data_race/dangling_thread_async_race.rs b/src/tools/miri/tests/fail/data_race/dangling_thread_async_race.rs
new file mode 100644
index 0000000000000..af2588e923240
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/dangling_thread_async_race.rs
@@ -0,0 +1,42 @@
+// We want to control preemption here.
+//@compile-flags: -Zmiri-disable-isolation -Zmiri-preemption-rate=0
+
+use std::mem;
+use std::thread::{sleep, spawn};
+use std::time::Duration;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+fn main() {
+    let mut a = 0u32;
+    let b = &mut a as *mut u32;
+    let c = EvilSend(b);
+
+    let join = unsafe {
+        spawn(move || {
+            *c.0 = 32;
+        })
+    };
+
+    // Detach the thread and sleep until it terminates
+    mem::drop(join);
+    sleep(Duration::from_millis(200));
+
+    // Spawn and immediately join a thread
+    // to execute the join code-path
+    // and ensure that data-race detection
+    // remains enabled nevertheless.
+    spawn(|| ()).join().unwrap();
+
+    let join2 = unsafe {
+        spawn(move || {
+            *c.0 = 64; //~ ERROR: Data race detected between Write on thread `<unnamed>` and Write on thread `<unnamed>`
+        })
+    };
+
+    join2.join().unwrap();
+}
diff --git a/src/tools/miri/tests/fail/data_race/dangling_thread_async_race.stderr b/src/tools/miri/tests/fail/data_race/dangling_thread_async_race.stderr
new file mode 100644
index 0000000000000..663bb8d4af512
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/dangling_thread_async_race.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: Data race detected between Write on thread `<unnamed>` and Write on thread `<unnamed>` at ALLOC
+  --> $DIR/dangling_thread_async_race.rs:LL:CC
+   |
+LL |             *c.0 = 64;
+   |             ^^^^^^^^^ Data race detected between Write on thread `<unnamed>` and Write on thread `<unnamed>` at ALLOC
+   |
+   = 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 closure at $DIR/dangling_thread_async_race.rs:LL:CC
+
+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/data_race/dangling_thread_race.rs b/src/tools/miri/tests/fail/data_race/dangling_thread_race.rs
new file mode 100644
index 0000000000000..1ee619c3f99d5
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/dangling_thread_race.rs
@@ -0,0 +1,38 @@
+// We want to control preemption here.
+//@compile-flags: -Zmiri-disable-isolation -Zmiri-preemption-rate=0
+
+use std::mem;
+use std::thread::{sleep, spawn};
+use std::time::Duration;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+fn main() {
+    let mut a = 0u32;
+    let b = &mut a as *mut u32;
+    let c = EvilSend(b);
+
+    let join = unsafe {
+        spawn(move || {
+            *c.0 = 32;
+        })
+    };
+
+    // Detach the thread and sleep until it terminates
+    mem::drop(join);
+    sleep(Duration::from_millis(200));
+
+    // Spawn and immediately join a thread
+    // to execute the join code-path
+    // and ensure that data-race detection
+    // remains enabled nevertheless.
+    spawn(|| ()).join().unwrap();
+
+    unsafe {
+        *c.0 = 64; //~ ERROR: Data race detected between Write on thread `main` and Write on thread `<unnamed>`
+    }
+}
diff --git a/src/tools/miri/tests/fail/data_race/dangling_thread_race.stderr b/src/tools/miri/tests/fail/data_race/dangling_thread_race.stderr
new file mode 100644
index 0000000000000..ad3e1735378f3
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/dangling_thread_race.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: Data race detected between Write on thread `main` and Write on thread `<unnamed>` at ALLOC
+  --> $DIR/dangling_thread_race.rs:LL:CC
+   |
+LL |         *c.0 = 64;
+   |         ^^^^^^^^^ Data race detected between Write on thread `main` and Write on thread `<unnamed>` at ALLOC
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/dangling_thread_race.rs:LL:CC
+
+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/data_race/dealloc_read_race1.rs b/src/tools/miri/tests/fail/data_race/dealloc_read_race1.rs
new file mode 100644
index 0000000000000..cbc02549a2541
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/dealloc_read_race1.rs
@@ -0,0 +1,38 @@
+// We want to control preemption here.
+//@compile-flags: -Zmiri-preemption-rate=0
+
+use std::thread::spawn;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+extern "Rust" {
+    fn __rust_dealloc(ptr: *mut u8, size: usize, align: usize);
+}
+
+pub fn main() {
+    // Shared atomic pointer
+    let pointer: *mut usize = Box::into_raw(Box::new(0usize));
+    let ptr = EvilSend(pointer);
+
+    unsafe {
+        let j1 = spawn(move || {
+            let _val = *ptr.0;
+        });
+
+        let j2 = spawn(move || {
+            __rust_dealloc(
+                //~^ ERROR: Data race detected between Deallocate on thread `<unnamed>` and Read on thread `<unnamed>`
+                ptr.0 as *mut _,
+                std::mem::size_of::<usize>(),
+                std::mem::align_of::<usize>(),
+            );
+        });
+
+        j1.join().unwrap();
+        j2.join().unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/fail/data_race/dealloc_read_race1.stderr b/src/tools/miri/tests/fail/data_race/dealloc_read_race1.stderr
new file mode 100644
index 0000000000000..194c2260baaab
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/dealloc_read_race1.stderr
@@ -0,0 +1,20 @@
+error: Undefined Behavior: Data race detected between Deallocate on thread `<unnamed>` and Read on thread `<unnamed>` at ALLOC
+  --> $DIR/dealloc_read_race1.rs:LL:CC
+   |
+LL | /             __rust_dealloc(
+LL | |
+LL | |                 ptr.0 as *mut _,
+LL | |                 std::mem::size_of::<usize>(),
+LL | |                 std::mem::align_of::<usize>(),
+LL | |             );
+   | |_____________^ Data race detected between Deallocate on thread `<unnamed>` and Read on thread `<unnamed>` at ALLOC
+   |
+   = 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 closure at $DIR/dealloc_read_race1.rs:LL:CC
+
+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/data_race/dealloc_read_race2.rs b/src/tools/miri/tests/fail/data_race/dealloc_read_race2.rs
new file mode 100644
index 0000000000000..24cce5d6fac1c
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/dealloc_read_race2.rs
@@ -0,0 +1,39 @@
+// We want to control preemption here.
+//@compile-flags: -Zmiri-preemption-rate=0
+
+use std::thread::spawn;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+extern "Rust" {
+    fn __rust_dealloc(ptr: *mut u8, size: usize, align: usize);
+}
+
+pub fn main() {
+    // Shared atomic pointer
+    let pointer: *mut usize = Box::into_raw(Box::new(0usize));
+    let ptr = EvilSend(pointer);
+
+    unsafe {
+        let j1 = spawn(move || {
+            __rust_dealloc(
+                ptr.0 as *mut _,
+                std::mem::size_of::<usize>(),
+                std::mem::align_of::<usize>(),
+            )
+        });
+
+        let j2 = spawn(move || {
+            // Also an error of the form: Data race detected between Read on thread `<unnamed>` and Deallocate on thread `<unnamed>`
+            // but the invalid allocation is detected first.
+            *ptr.0 //~ ERROR: dereferenced after this allocation got freed
+        });
+
+        j1.join().unwrap();
+        j2.join().unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/fail/data_race/dealloc_read_race2.stderr b/src/tools/miri/tests/fail/data_race/dealloc_read_race2.stderr
new file mode 100644
index 0000000000000..f303d57c8bd9c
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/dealloc_read_race2.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed
+  --> $DIR/dealloc_read_race2.rs:LL:CC
+   |
+LL |             *ptr.0
+   |             ^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed
+   |
+   = 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 closure at $DIR/dealloc_read_race2.rs:LL:CC
+
+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/data_race/dealloc_read_race_stack.rs b/src/tools/miri/tests/fail/data_race/dealloc_read_race_stack.rs
new file mode 100644
index 0000000000000..5484370f35c17
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/dealloc_read_race_stack.rs
@@ -0,0 +1,49 @@
+//@compile-flags: -Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
+
+use std::ptr::null_mut;
+use std::sync::atomic::{AtomicPtr, Ordering};
+use std::thread::{sleep, spawn};
+use std::time::Duration;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+pub fn main() {
+    // Shared atomic pointer
+    let pointer = AtomicPtr::new(null_mut::<usize>());
+    let ptr = EvilSend(&pointer as *const AtomicPtr<usize>);
+
+    // Note: this is scheduler-dependent
+    // the operations need to occur in
+    // order, otherwise the allocation is
+    // not visible to the other-thread to
+    // detect the race:
+    //  1. stack-allocate
+    //  2. read
+    //  3. stack-deallocate
+    unsafe {
+        let j1 = spawn(move || {
+            let pointer = &*ptr.0;
+            {
+                let mut stack_var = 0usize;
+
+                pointer.store(&mut stack_var as *mut _, Ordering::Release);
+
+                sleep(Duration::from_millis(200));
+
+                // Now `stack_var` gets deallocated.
+            } //~ ERROR: Data race detected between Deallocate on thread `<unnamed>` and Read on thread `<unnamed>`
+        });
+
+        let j2 = spawn(move || {
+            let pointer = &*ptr.0;
+            *pointer.load(Ordering::Acquire)
+        });
+
+        j1.join().unwrap();
+        j2.join().unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/fail/data_race/dealloc_read_race_stack.stderr b/src/tools/miri/tests/fail/data_race/dealloc_read_race_stack.stderr
new file mode 100644
index 0000000000000..c986e912f03ba
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/dealloc_read_race_stack.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: Data race detected between Deallocate on thread `<unnamed>` and Read on thread `<unnamed>` at ALLOC
+  --> $DIR/dealloc_read_race_stack.rs:LL:CC
+   |
+LL |             }
+   |             ^ Data race detected between Deallocate on thread `<unnamed>` and Read on thread `<unnamed>` at ALLOC
+   |
+   = 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 closure at $DIR/dealloc_read_race_stack.rs:LL:CC
+
+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/data_race/dealloc_write_race1.rs b/src/tools/miri/tests/fail/data_race/dealloc_write_race1.rs
new file mode 100644
index 0000000000000..23bf73fe8c5ad
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/dealloc_write_race1.rs
@@ -0,0 +1,37 @@
+// We want to control preemption here.
+//@compile-flags: -Zmiri-preemption-rate=0
+
+use std::thread::spawn;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+extern "Rust" {
+    fn __rust_dealloc(ptr: *mut u8, size: usize, align: usize);
+}
+pub fn main() {
+    // Shared atomic pointer
+    let pointer: *mut usize = Box::into_raw(Box::new(0usize));
+    let ptr = EvilSend(pointer);
+
+    unsafe {
+        let j1 = spawn(move || {
+            *ptr.0 = 2;
+        });
+
+        let j2 = spawn(move || {
+            __rust_dealloc(
+                //~^ ERROR: Data race detected between Deallocate on thread `<unnamed>` and Write on thread `<unnamed>`
+                ptr.0 as *mut _,
+                std::mem::size_of::<usize>(),
+                std::mem::align_of::<usize>(),
+            );
+        });
+
+        j1.join().unwrap();
+        j2.join().unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/fail/data_race/dealloc_write_race1.stderr b/src/tools/miri/tests/fail/data_race/dealloc_write_race1.stderr
new file mode 100644
index 0000000000000..56eb0b519c484
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/dealloc_write_race1.stderr
@@ -0,0 +1,20 @@
+error: Undefined Behavior: Data race detected between Deallocate on thread `<unnamed>` and Write on thread `<unnamed>` at ALLOC
+  --> $DIR/dealloc_write_race1.rs:LL:CC
+   |
+LL | /             __rust_dealloc(
+LL | |
+LL | |                 ptr.0 as *mut _,
+LL | |                 std::mem::size_of::<usize>(),
+LL | |                 std::mem::align_of::<usize>(),
+LL | |             );
+   | |_____________^ Data race detected between Deallocate on thread `<unnamed>` and Write on thread `<unnamed>` at ALLOC
+   |
+   = 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 closure at $DIR/dealloc_write_race1.rs:LL:CC
+
+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/data_race/dealloc_write_race2.rs b/src/tools/miri/tests/fail/data_race/dealloc_write_race2.rs
new file mode 100644
index 0000000000000..7c8033e2335e9
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/dealloc_write_race2.rs
@@ -0,0 +1,38 @@
+// We want to control preemption here.
+//@compile-flags: -Zmiri-preemption-rate=0
+
+use std::thread::spawn;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+extern "Rust" {
+    fn __rust_dealloc(ptr: *mut u8, size: usize, align: usize);
+}
+pub fn main() {
+    // Shared atomic pointer
+    let pointer: *mut usize = Box::into_raw(Box::new(0usize));
+    let ptr = EvilSend(pointer);
+
+    unsafe {
+        let j1 = spawn(move || {
+            __rust_dealloc(
+                ptr.0 as *mut _,
+                std::mem::size_of::<usize>(),
+                std::mem::align_of::<usize>(),
+            );
+        });
+
+        let j2 = spawn(move || {
+            // Also an error of the form: Data race detected between Write on thread `<unnamed>` and Deallocate on thread `<unnamed>`
+            // but the invalid allocation is detected first.
+            *ptr.0 = 2; //~ ERROR: dereferenced after this allocation got freed
+        });
+
+        j1.join().unwrap();
+        j2.join().unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/fail/data_race/dealloc_write_race2.stderr b/src/tools/miri/tests/fail/data_race/dealloc_write_race2.stderr
new file mode 100644
index 0000000000000..23b8e9ade0e0e
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/dealloc_write_race2.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed
+  --> $DIR/dealloc_write_race2.rs:LL:CC
+   |
+LL |             *ptr.0 = 2;
+   |             ^^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed
+   |
+   = 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 closure at $DIR/dealloc_write_race2.rs:LL:CC
+
+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/data_race/dealloc_write_race_stack.rs b/src/tools/miri/tests/fail/data_race/dealloc_write_race_stack.rs
new file mode 100644
index 0000000000000..1872abfe021b3
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/dealloc_write_race_stack.rs
@@ -0,0 +1,49 @@
+//@compile-flags: -Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
+
+use std::ptr::null_mut;
+use std::sync::atomic::{AtomicPtr, Ordering};
+use std::thread::{sleep, spawn};
+use std::time::Duration;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+pub fn main() {
+    // Shared atomic pointer
+    let pointer = AtomicPtr::new(null_mut::<usize>());
+    let ptr = EvilSend(&pointer as *const AtomicPtr<usize>);
+
+    // Note: this is scheduler-dependent
+    // the operations need to occur in
+    // order, otherwise the allocation is
+    // not visible to the other-thread to
+    // detect the race:
+    //  1. stack-allocate
+    //  2. read
+    //  3. stack-deallocate
+    unsafe {
+        let j1 = spawn(move || {
+            let pointer = &*ptr.0;
+            {
+                let mut stack_var = 0usize;
+
+                pointer.store(&mut stack_var as *mut _, Ordering::Release);
+
+                sleep(Duration::from_millis(200));
+
+                // Now `stack_var` gets deallocated.
+            } //~ ERROR: Data race detected between Deallocate on thread `<unnamed>` and Write on thread `<unnamed>`
+        });
+
+        let j2 = spawn(move || {
+            let pointer = &*ptr.0;
+            *pointer.load(Ordering::Acquire) = 3;
+        });
+
+        j1.join().unwrap();
+        j2.join().unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/fail/data_race/dealloc_write_race_stack.stderr b/src/tools/miri/tests/fail/data_race/dealloc_write_race_stack.stderr
new file mode 100644
index 0000000000000..7b77e2470a1ab
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/dealloc_write_race_stack.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: Data race detected between Deallocate on thread `<unnamed>` and Write on thread `<unnamed>` at ALLOC
+  --> $DIR/dealloc_write_race_stack.rs:LL:CC
+   |
+LL |             }
+   |             ^ Data race detected between Deallocate on thread `<unnamed>` and Write on thread `<unnamed>` at ALLOC
+   |
+   = 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 closure at $DIR/dealloc_write_race_stack.rs:LL:CC
+
+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/data_race/enable_after_join_to_main.rs b/src/tools/miri/tests/fail/data_race/enable_after_join_to_main.rs
new file mode 100644
index 0000000000000..c11239da7febb
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/enable_after_join_to_main.rs
@@ -0,0 +1,39 @@
+// We want to control preemption here.
+//@compile-flags: -Zmiri-preemption-rate=0
+
+use std::thread::spawn;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+pub fn main() {
+    // Enable and then join with multiple threads.
+    let t1 = spawn(|| ());
+    let t2 = spawn(|| ());
+    let t3 = spawn(|| ());
+    let t4 = spawn(|| ());
+    t1.join().unwrap();
+    t2.join().unwrap();
+    t3.join().unwrap();
+    t4.join().unwrap();
+
+    // Perform write-write data race detection.
+    let mut a = 0u32;
+    let b = &mut a as *mut u32;
+    let c = EvilSend(b);
+    unsafe {
+        let j1 = spawn(move || {
+            *c.0 = 32;
+        });
+
+        let j2 = spawn(move || {
+            *c.0 = 64; //~ ERROR: Data race detected between Write on thread `<unnamed>` and Write on thread `<unnamed>`
+        });
+
+        j1.join().unwrap();
+        j2.join().unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/fail/data_race/enable_after_join_to_main.stderr b/src/tools/miri/tests/fail/data_race/enable_after_join_to_main.stderr
new file mode 100644
index 0000000000000..26c07ae6962b5
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/enable_after_join_to_main.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: Data race detected between Write on thread `<unnamed>` and Write on thread `<unnamed>` at ALLOC
+  --> $DIR/enable_after_join_to_main.rs:LL:CC
+   |
+LL |             *c.0 = 64;
+   |             ^^^^^^^^^ Data race detected between Write on thread `<unnamed>` and Write on thread `<unnamed>` at ALLOC
+   |
+   = 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 closure at $DIR/enable_after_join_to_main.rs:LL:CC
+
+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/data_race/fence_after_load.rs b/src/tools/miri/tests/fail/data_race/fence_after_load.rs
new file mode 100644
index 0000000000000..ae443908598fe
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/fence_after_load.rs
@@ -0,0 +1,24 @@
+// We want to control preemption here.
+//@compile-flags: -Zmiri-disable-isolation -Zmiri-preemption-rate=0
+use std::sync::atomic::{fence, AtomicUsize, Ordering};
+use std::sync::Arc;
+use std::thread;
+use std::time::Duration;
+
+fn main() {
+    static mut V: u32 = 0;
+    let a = Arc::new(AtomicUsize::default());
+    let b = a.clone();
+    thread::spawn(move || {
+        unsafe { V = 1 }
+        b.store(1, Ordering::SeqCst);
+    });
+    thread::sleep(Duration::from_millis(100));
+    fence(Ordering::SeqCst);
+    // Imagine the other thread's actions happening here.
+    assert_eq!(a.load(Ordering::Relaxed), 1);
+    // The fence is useless, since it did not happen-after the `store` in the other thread.
+    // Hence this is a data race.
+    // Also see https://github.com/rust-lang/miri/issues/2192.
+    unsafe { V = 2 } //~ERROR: Data race detected between Write on thread `main` and Write on thread `<unnamed>`
+}
diff --git a/src/tools/miri/tests/fail/data_race/fence_after_load.stderr b/src/tools/miri/tests/fail/data_race/fence_after_load.stderr
new file mode 100644
index 0000000000000..0abfe213db17d
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/fence_after_load.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: Data race detected between Write on thread `main` and Write on thread `<unnamed>` at ALLOC
+  --> $DIR/fence_after_load.rs:LL:CC
+   |
+LL |     unsafe { V = 2 }
+   |              ^^^^^ Data race detected between Write on thread `main` and Write on thread `<unnamed>` at ALLOC
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/fence_after_load.rs:LL:CC
+
+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/data_race/read_write_race.rs b/src/tools/miri/tests/fail/data_race/read_write_race.rs
new file mode 100644
index 0000000000000..482dd2df7df91
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/read_write_race.rs
@@ -0,0 +1,28 @@
+// We want to control preemption here.
+//@compile-flags: -Zmiri-preemption-rate=0
+
+use std::thread::spawn;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+pub fn main() {
+    let mut a = 0u32;
+    let b = &mut a as *mut u32;
+    let c = EvilSend(b);
+    unsafe {
+        let j1 = spawn(move || {
+            let _val = *c.0;
+        });
+
+        let j2 = spawn(move || {
+            *c.0 = 64; //~ ERROR: Data race detected between Write on thread `<unnamed>` and Read on thread `<unnamed>`
+        });
+
+        j1.join().unwrap();
+        j2.join().unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/fail/data_race/read_write_race.stderr b/src/tools/miri/tests/fail/data_race/read_write_race.stderr
new file mode 100644
index 0000000000000..08a19537312cf
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/read_write_race.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: Data race detected between Write on thread `<unnamed>` and Read on thread `<unnamed>` at ALLOC
+  --> $DIR/read_write_race.rs:LL:CC
+   |
+LL |             *c.0 = 64;
+   |             ^^^^^^^^^ Data race detected between Write on thread `<unnamed>` and Read on thread `<unnamed>` at ALLOC
+   |
+   = 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 closure at $DIR/read_write_race.rs:LL:CC
+
+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/data_race/read_write_race_stack.rs b/src/tools/miri/tests/fail/data_race/read_write_race_stack.rs
new file mode 100644
index 0000000000000..1b4932439b010
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/read_write_race_stack.rs
@@ -0,0 +1,56 @@
+//@compile-flags: -Zmiri-disable-isolation -Zmir-opt-level=0 -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
+
+// Note: mir-opt-level set to 0 to prevent the read of stack_var in thread 1
+// from being optimized away and preventing the detection of the data-race.
+
+use std::ptr::null_mut;
+use std::sync::atomic::{AtomicPtr, Ordering};
+use std::thread::{sleep, spawn};
+use std::time::Duration;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+pub fn main() {
+    // Shared atomic pointer
+    let pointer = AtomicPtr::new(null_mut::<usize>());
+    let ptr = EvilSend(&pointer as *const AtomicPtr<usize>);
+
+    // Note: this is scheduler-dependent
+    // the operations need to occur in
+    // order, otherwise the allocation is
+    // not visible to the other-thread to
+    // detect the race:
+    //  1. stack-allocate
+    //  2. atomic_store
+    //  3. atomic_load
+    //  4. write-value
+    //  5. read-value
+    unsafe {
+        let j1 = spawn(move || {
+            // Concurrent allocate the memory.
+            // Uses relaxed semantics to not generate
+            // a release sequence.
+            let pointer = &*ptr.0;
+
+            let mut stack_var = 0usize;
+
+            pointer.store(&mut stack_var as *mut _, Ordering::Release);
+
+            sleep(Duration::from_millis(200));
+
+            stack_var //~ ERROR: Data race detected between Read on thread `<unnamed>` and Write on thread `<unnamed>`
+        });
+
+        let j2 = spawn(move || {
+            let pointer = &*ptr.0;
+            *pointer.load(Ordering::Acquire) = 3;
+        });
+
+        j1.join().unwrap();
+        j2.join().unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/fail/data_race/read_write_race_stack.stderr b/src/tools/miri/tests/fail/data_race/read_write_race_stack.stderr
new file mode 100644
index 0000000000000..20f137afe7329
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/read_write_race_stack.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: Data race detected between Read on thread `<unnamed>` and Write on thread `<unnamed>` at ALLOC
+  --> $DIR/read_write_race_stack.rs:LL:CC
+   |
+LL |             stack_var
+   |             ^^^^^^^^^ Data race detected between Read on thread `<unnamed>` and Write on thread `<unnamed>` at ALLOC
+   |
+   = 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 closure at $DIR/read_write_race_stack.rs:LL:CC
+
+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/data_race/relax_acquire_race.rs b/src/tools/miri/tests/fail/data_race/relax_acquire_race.rs
new file mode 100644
index 0000000000000..240b4c90eb225
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/relax_acquire_race.rs
@@ -0,0 +1,50 @@
+//@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
+
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::thread::spawn;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+static SYNC: AtomicUsize = AtomicUsize::new(0);
+
+pub fn main() {
+    let mut a = 0u32;
+    let b = &mut a as *mut u32;
+    let c = EvilSend(b);
+
+    // Note: this is scheduler-dependent
+    // the operations need to occur in
+    // order:
+    //  1. store release : 1
+    //  2. load acquire : 1
+    //  3. store relaxed : 2
+    //  4. load acquire : 2
+    unsafe {
+        let j1 = spawn(move || {
+            *c.0 = 1;
+            SYNC.store(1, Ordering::Release);
+        });
+
+        let j2 = spawn(move || {
+            if SYNC.load(Ordering::Acquire) == 1 {
+                SYNC.store(2, Ordering::Relaxed);
+            }
+        });
+
+        let j3 = spawn(move || {
+            if SYNC.load(Ordering::Acquire) == 2 {
+                *c.0 //~ ERROR: Data race detected between Read on thread `<unnamed>` and Write on thread `<unnamed>`
+            } else {
+                0
+            }
+        });
+
+        j1.join().unwrap();
+        j2.join().unwrap();
+        j3.join().unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/fail/data_race/relax_acquire_race.stderr b/src/tools/miri/tests/fail/data_race/relax_acquire_race.stderr
new file mode 100644
index 0000000000000..6121c25db22d7
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/relax_acquire_race.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: Data race detected between Read on thread `<unnamed>` and Write on thread `<unnamed>` at ALLOC
+  --> $DIR/relax_acquire_race.rs:LL:CC
+   |
+LL |                 *c.0
+   |                 ^^^^ Data race detected between Read on thread `<unnamed>` and Write on thread `<unnamed>` at ALLOC
+   |
+   = 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 closure at $DIR/relax_acquire_race.rs:LL:CC
+
+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/data_race/release_seq_race.rs b/src/tools/miri/tests/fail/data_race/release_seq_race.rs
new file mode 100644
index 0000000000000..5ae801278357b
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/release_seq_race.rs
@@ -0,0 +1,54 @@
+//@compile-flags: -Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
+
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::thread::{sleep, spawn};
+use std::time::Duration;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+static SYNC: AtomicUsize = AtomicUsize::new(0);
+
+pub fn main() {
+    let mut a = 0u32;
+    let b = &mut a as *mut u32;
+    let c = EvilSend(b);
+
+    // Note: this is scheduler-dependent
+    // the operations need to occur in
+    // order, the sleep operations currently
+    // force the desired ordering:
+    //  1. store release : 1
+    //  2. store relaxed : 2
+    //  3. store relaxed : 3
+    //  4. load acquire : 3
+    unsafe {
+        let j1 = spawn(move || {
+            *c.0 = 1;
+            SYNC.store(1, Ordering::Release);
+            sleep(Duration::from_millis(200));
+            SYNC.store(3, Ordering::Relaxed);
+        });
+
+        let j2 = spawn(move || {
+            // Blocks the acquire-release sequence
+            SYNC.store(2, Ordering::Relaxed);
+        });
+
+        let j3 = spawn(move || {
+            sleep(Duration::from_millis(500));
+            if SYNC.load(Ordering::Acquire) == 3 {
+                *c.0 //~ ERROR: Data race detected between Read on thread `<unnamed>` and Write on thread `<unnamed>`
+            } else {
+                0
+            }
+        });
+
+        j1.join().unwrap();
+        j2.join().unwrap();
+        j3.join().unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/fail/data_race/release_seq_race.stderr b/src/tools/miri/tests/fail/data_race/release_seq_race.stderr
new file mode 100644
index 0000000000000..777bc4adadc6d
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/release_seq_race.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: Data race detected between Read on thread `<unnamed>` and Write on thread `<unnamed>` at ALLOC
+  --> $DIR/release_seq_race.rs:LL:CC
+   |
+LL |                 *c.0
+   |                 ^^^^ Data race detected between Read on thread `<unnamed>` and Write on thread `<unnamed>` at ALLOC
+   |
+   = 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 closure at $DIR/release_seq_race.rs:LL:CC
+
+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/data_race/release_seq_race_same_thread.rs b/src/tools/miri/tests/fail/data_race/release_seq_race_same_thread.rs
new file mode 100644
index 0000000000000..63e6dc2dd71b9
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/release_seq_race_same_thread.rs
@@ -0,0 +1,49 @@
+//@compile-flags: -Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
+
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::thread::spawn;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+static SYNC: AtomicUsize = AtomicUsize::new(0);
+
+pub fn main() {
+    let mut a = 0u32;
+    let b = &mut a as *mut u32;
+    let c = EvilSend(b);
+
+    // Note: this is scheduler-dependent
+    // the operations need to occur in
+    // order, the sleep operations currently
+    // force the desired ordering:
+    //  1. store release : 1
+    //  2. store relaxed : 2
+    //  3. load acquire : 2
+    unsafe {
+        let j1 = spawn(move || {
+            *c.0 = 1;
+            SYNC.store(1, Ordering::Release);
+
+            // C++20 update to release sequences
+            // makes this block the release sequence
+            // despite the being on the same thread
+            // as the release store.
+            SYNC.store(2, Ordering::Relaxed);
+        });
+
+        let j2 = spawn(move || {
+            if SYNC.load(Ordering::Acquire) == 2 {
+                *c.0 //~ ERROR: Data race detected between Read on thread `<unnamed>` and Write on thread `<unnamed>`
+            } else {
+                0
+            }
+        });
+
+        j1.join().unwrap();
+        j2.join().unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/fail/data_race/release_seq_race_same_thread.stderr b/src/tools/miri/tests/fail/data_race/release_seq_race_same_thread.stderr
new file mode 100644
index 0000000000000..0fcb192d920fd
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/release_seq_race_same_thread.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: Data race detected between Read on thread `<unnamed>` and Write on thread `<unnamed>` at ALLOC
+  --> $DIR/release_seq_race_same_thread.rs:LL:CC
+   |
+LL |                 *c.0
+   |                 ^^^^ Data race detected between Read on thread `<unnamed>` and Write on thread `<unnamed>` at ALLOC
+   |
+   = 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 closure at $DIR/release_seq_race_same_thread.rs:LL:CC
+
+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/data_race/rmw_race.rs b/src/tools/miri/tests/fail/data_race/rmw_race.rs
new file mode 100644
index 0000000000000..122780d11aa1f
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/rmw_race.rs
@@ -0,0 +1,51 @@
+//@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
+
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::thread::spawn;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+static SYNC: AtomicUsize = AtomicUsize::new(0);
+
+pub fn main() {
+    let mut a = 0u32;
+    let b = &mut a as *mut u32;
+    let c = EvilSend(b);
+
+    // Note: this is scheduler-dependent
+    // the operations need to occur in
+    // order:
+    //  1. store release : 1
+    //  2. RMW relaxed : 1 -> 2
+    //  3. store relaxed : 3
+    //  4. load acquire : 3
+    unsafe {
+        let j1 = spawn(move || {
+            *c.0 = 1;
+            SYNC.store(1, Ordering::Release);
+        });
+
+        let j2 = spawn(move || {
+            if SYNC.swap(2, Ordering::Relaxed) == 1 {
+                // Blocks the acquire-release sequence
+                SYNC.store(3, Ordering::Relaxed);
+            }
+        });
+
+        let j3 = spawn(move || {
+            if SYNC.load(Ordering::Acquire) == 3 {
+                *c.0 //~ ERROR: Data race detected between Read on thread `<unnamed>` and Write on thread `<unnamed>`
+            } else {
+                0
+            }
+        });
+
+        j1.join().unwrap();
+        j2.join().unwrap();
+        j3.join().unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/fail/data_race/rmw_race.stderr b/src/tools/miri/tests/fail/data_race/rmw_race.stderr
new file mode 100644
index 0000000000000..3ae6f3b84fe12
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/rmw_race.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: Data race detected between Read on thread `<unnamed>` and Write on thread `<unnamed>` at ALLOC
+  --> $DIR/rmw_race.rs:LL:CC
+   |
+LL |                 *c.0
+   |                 ^^^^ Data race detected between Read on thread `<unnamed>` and Write on thread `<unnamed>` at ALLOC
+   |
+   = 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 closure at $DIR/rmw_race.rs:LL:CC
+
+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/data_race/stack_pop_race.rs b/src/tools/miri/tests/fail/data_race/stack_pop_race.rs
new file mode 100644
index 0000000000000..8f371a680f11d
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/stack_pop_race.rs
@@ -0,0 +1,25 @@
+//@ignore-target-windows: Concurrency on Windows is not supported yet.
+//@compile-flags: -Zmiri-preemption-rate=0
+use std::thread;
+
+#[derive(Copy, Clone)]
+struct MakeSend(*const i32);
+unsafe impl Send for MakeSend {}
+
+fn main() {
+    race(0);
+}
+
+// Using an argument for the ptr to point to, since those do not get StorageDead.
+fn race(local: i32) {
+    let ptr = MakeSend(&local as *const i32);
+    thread::spawn(move || {
+        let ptr = ptr;
+        let _val = unsafe { *ptr.0 };
+    });
+    // Make the other thread go first so that it does not UAF.
+    thread::yield_now();
+    // Deallocating the local (when `main` returns)
+    // races with the read in the other thread.
+    // Make sure the error points at this function's end, not just the call site.
+} //~ERROR: Data race detected between Deallocate on thread `main` and Read on thread `<unnamed>`
diff --git a/src/tools/miri/tests/fail/data_race/stack_pop_race.stderr b/src/tools/miri/tests/fail/data_race/stack_pop_race.stderr
new file mode 100644
index 0000000000000..5de27108ab633
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/stack_pop_race.stderr
@@ -0,0 +1,20 @@
+error: Undefined Behavior: Data race detected between Deallocate on thread `main` and Read on thread `<unnamed>` at ALLOC
+  --> $DIR/stack_pop_race.rs:LL:CC
+   |
+LL | }
+   |  ^ Data race detected between Deallocate on thread `main` and Read on thread `<unnamed>` at ALLOC
+   |
+   = 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 `race` at $DIR/stack_pop_race.rs:LL:CC
+note: inside `main` at $DIR/stack_pop_race.rs:LL:CC
+  --> $DIR/stack_pop_race.rs:LL:CC
+   |
+LL |     race(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/data_race/write_write_race.rs b/src/tools/miri/tests/fail/data_race/write_write_race.rs
new file mode 100644
index 0000000000000..13c31c87cbbae
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/write_write_race.rs
@@ -0,0 +1,28 @@
+// We want to control preemption here.
+//@compile-flags: -Zmiri-preemption-rate=0
+
+use std::thread::spawn;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+pub fn main() {
+    let mut a = 0u32;
+    let b = &mut a as *mut u32;
+    let c = EvilSend(b);
+    unsafe {
+        let j1 = spawn(move || {
+            *c.0 = 32;
+        });
+
+        let j2 = spawn(move || {
+            *c.0 = 64; //~ ERROR: Data race detected between Write on thread `<unnamed>` and Write on thread `<unnamed>`
+        });
+
+        j1.join().unwrap();
+        j2.join().unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/fail/data_race/write_write_race.stderr b/src/tools/miri/tests/fail/data_race/write_write_race.stderr
new file mode 100644
index 0000000000000..ee7072ccf5d17
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/write_write_race.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: Data race detected between Write on thread `<unnamed>` and Write on thread `<unnamed>` at ALLOC
+  --> $DIR/write_write_race.rs:LL:CC
+   |
+LL |             *c.0 = 64;
+   |             ^^^^^^^^^ Data race detected between Write on thread `<unnamed>` and Write on thread `<unnamed>` at ALLOC
+   |
+   = 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 closure at $DIR/write_write_race.rs:LL:CC
+
+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/data_race/write_write_race_stack.rs b/src/tools/miri/tests/fail/data_race/write_write_race_stack.rs
new file mode 100644
index 0000000000000..731ac8b26aa74
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/write_write_race_stack.rs
@@ -0,0 +1,56 @@
+//@compile-flags: -Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
+
+use std::ptr::null_mut;
+use std::sync::atomic::{AtomicPtr, Ordering};
+use std::thread::{sleep, spawn};
+use std::time::Duration;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+pub fn main() {
+    // Shared atomic pointer
+    let pointer = AtomicPtr::new(null_mut::<usize>());
+    let ptr = EvilSend(&pointer as *const AtomicPtr<usize>);
+
+    // Note: this is scheduler-dependent
+    // the operations need to occur in
+    // order, otherwise the allocation is
+    // not visible to the other-thread to
+    // detect the race:
+    //  1. stack-allocate
+    //  2. atomic_store
+    //  3. atomic_load
+    //  4. write-value
+    //  5. write-value
+    unsafe {
+        let j1 = spawn(move || {
+            // Concurrent allocate the memory.
+            // Uses relaxed semantics to not generate
+            // a release sequence.
+            let pointer = &*ptr.0;
+
+            let mut stack_var = 0usize;
+
+            pointer.store(&mut stack_var as *mut _, Ordering::Release);
+
+            sleep(Duration::from_millis(200));
+
+            stack_var = 1usize; //~ ERROR: Data race detected between Write on thread `<unnamed>` and Write on thread `<unnamed>`
+
+            // read to silence errors
+            stack_var
+        });
+
+        let j2 = spawn(move || {
+            let pointer = &*ptr.0;
+            *pointer.load(Ordering::Acquire) = 3;
+        });
+
+        j1.join().unwrap();
+        j2.join().unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/fail/data_race/write_write_race_stack.stderr b/src/tools/miri/tests/fail/data_race/write_write_race_stack.stderr
new file mode 100644
index 0000000000000..ceb473c2a4a41
--- /dev/null
+++ b/src/tools/miri/tests/fail/data_race/write_write_race_stack.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: Data race detected between Write on thread `<unnamed>` and Write on thread `<unnamed>` at ALLOC
+  --> $DIR/write_write_race_stack.rs:LL:CC
+   |
+LL |             stack_var = 1usize;
+   |             ^^^^^^^^^^^^^^^^^^ Data race detected between Write on thread `<unnamed>` and Write on thread `<unnamed>` at ALLOC
+   |
+   = 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 closure at $DIR/write_write_race_stack.rs:LL:CC
+
+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/dyn-call-trait-mismatch.rs b/src/tools/miri/tests/fail/dyn-call-trait-mismatch.rs
new file mode 100644
index 0000000000000..0e7c3dbcc040f
--- /dev/null
+++ b/src/tools/miri/tests/fail/dyn-call-trait-mismatch.rs
@@ -0,0 +1,16 @@
+trait T1 {
+    fn method1(self: Box<Self>);
+}
+trait T2 {
+    fn method2(self: Box<Self>);
+}
+
+impl T1 for i32 {
+    fn method1(self: Box<Self>) {}
+}
+
+fn main() {
+    let r = Box::new(0) as Box<dyn T1>;
+    let r2: Box<dyn T2> = unsafe { std::mem::transmute(r) };
+    r2.method2(); //~ERROR: call on a pointer whose vtable does not match its type
+}
diff --git a/src/tools/miri/tests/fail/dyn-call-trait-mismatch.stderr b/src/tools/miri/tests/fail/dyn-call-trait-mismatch.stderr
new file mode 100644
index 0000000000000..03272105c4146
--- /dev/null
+++ b/src/tools/miri/tests/fail/dyn-call-trait-mismatch.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `dyn` call on a pointer whose vtable does not match its type
+  --> $DIR/dyn-call-trait-mismatch.rs:LL:CC
+   |
+LL |     r2.method2();
+   |     ^^^^^^^^^^^^ `dyn` call on a pointer whose vtable does not match its type
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/dyn-call-trait-mismatch.rs:LL:CC
+
+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/dyn-upcast-trait-mismatch.rs b/src/tools/miri/tests/fail/dyn-upcast-trait-mismatch.rs
new file mode 100644
index 0000000000000..648ac07c43ec9
--- /dev/null
+++ b/src/tools/miri/tests/fail/dyn-upcast-trait-mismatch.rs
@@ -0,0 +1,57 @@
+#![feature(trait_upcasting)]
+#![allow(incomplete_features)]
+
+trait Foo: PartialEq<i32> + std::fmt::Debug + Send + Sync {
+    fn a(&self) -> i32 {
+        10
+    }
+
+    fn z(&self) -> i32 {
+        11
+    }
+
+    fn y(&self) -> i32 {
+        12
+    }
+}
+
+trait Bar: Foo {
+    fn b(&self) -> i32 {
+        20
+    }
+
+    fn w(&self) -> i32 {
+        21
+    }
+}
+
+trait Baz: Bar {
+    fn c(&self) -> i32 {
+        30
+    }
+}
+
+impl Foo for i32 {
+    fn a(&self) -> i32 {
+        100
+    }
+}
+
+impl Bar for i32 {
+    fn b(&self) -> i32 {
+        200
+    }
+}
+
+impl Baz for i32 {
+    fn c(&self) -> i32 {
+        300
+    }
+}
+
+fn main() {
+    let baz: &dyn Baz = &1;
+    let baz_fake: &dyn Bar = unsafe { std::mem::transmute(baz) };
+    let _err = baz_fake as &dyn Foo;
+    //~^ERROR: upcast on a pointer whose vtable does not match its type
+}
diff --git a/src/tools/miri/tests/fail/dyn-upcast-trait-mismatch.stderr b/src/tools/miri/tests/fail/dyn-upcast-trait-mismatch.stderr
new file mode 100644
index 0000000000000..21870ef3733e8
--- /dev/null
+++ b/src/tools/miri/tests/fail/dyn-upcast-trait-mismatch.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: upcast on a pointer whose vtable does not match its type
+  --> $DIR/dyn-upcast-trait-mismatch.rs:LL:CC
+   |
+LL |     let _err = baz_fake as &dyn Foo;
+   |                ^^^^^^^^ upcast on a pointer whose vtable does not match its type
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/dyn-upcast-trait-mismatch.rs:LL:CC
+
+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/environ-gets-deallocated.rs b/src/tools/miri/tests/fail/environ-gets-deallocated.rs
new file mode 100644
index 0000000000000..e4597140b8440
--- /dev/null
+++ b/src/tools/miri/tests/fail/environ-gets-deallocated.rs
@@ -0,0 +1,24 @@
+//@ignore-target-windows: Windows does not have a global environ list that the program can access directly
+
+#[cfg(any(target_os = "linux", target_os = "freebsd"))]
+fn get_environ() -> *const *const u8 {
+    extern "C" {
+        static mut environ: *const *const u8;
+    }
+    unsafe { environ }
+}
+
+#[cfg(target_os = "macos")]
+fn get_environ() -> *const *const u8 {
+    extern "C" {
+        fn _NSGetEnviron() -> *mut *const *const u8;
+    }
+    unsafe { *_NSGetEnviron() }
+}
+
+fn main() {
+    let pointer = get_environ();
+    let _x = unsafe { *pointer };
+    std::env::set_var("FOO", "BAR");
+    let _y = unsafe { *pointer }; //~ ERROR: dereferenced after this allocation got freed
+}
diff --git a/src/tools/miri/tests/fail/environ-gets-deallocated.stderr b/src/tools/miri/tests/fail/environ-gets-deallocated.stderr
new file mode 100644
index 0000000000000..a2d343bf8651c
--- /dev/null
+++ b/src/tools/miri/tests/fail/environ-gets-deallocated.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed
+  --> $DIR/environ-gets-deallocated.rs:LL:CC
+   |
+LL |     let _y = unsafe { *pointer };
+   |                       ^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/environ-gets-deallocated.rs:LL:CC
+
+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/erroneous_const.rs b/src/tools/miri/tests/fail/erroneous_const.rs
new file mode 100644
index 0000000000000..c35a905035932
--- /dev/null
+++ b/src/tools/miri/tests/fail/erroneous_const.rs
@@ -0,0 +1,20 @@
+//! Make sure we detect erroneous constants post-monomorphization even when they are unused.
+//! (https://github.com/rust-lang/miri/issues/1382)
+// Inlining changes the error location
+//@compile-flags: -Zmir-opt-level=0
+#![feature(never_type)]
+#![warn(warnings, const_err)]
+
+struct PrintName<T>(T);
+impl<T> PrintName<T> {
+    const VOID: ! = panic!(); //~ERROR: evaluation of `PrintName::<i32>::VOID` failed
+}
+
+fn no_codegen<T>() {
+    if false {
+        let _ = PrintName::<T>::VOID; //~ERROR: post-monomorphization error
+    }
+}
+fn main() {
+    no_codegen::<i32>();
+}
diff --git a/src/tools/miri/tests/fail/erroneous_const.stderr b/src/tools/miri/tests/fail/erroneous_const.stderr
new file mode 100644
index 0000000000000..8138d69f4031e
--- /dev/null
+++ b/src/tools/miri/tests/fail/erroneous_const.stderr
@@ -0,0 +1,26 @@
+error[E0080]: evaluation of `PrintName::<i32>::VOID` failed
+  --> $DIR/erroneous_const.rs:LL:CC
+   |
+LL |     const VOID: ! = panic!();
+   |                     ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/erroneous_const.rs:LL:CC
+   |
+   = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: post-monomorphization error: referenced constant has errors
+  --> $DIR/erroneous_const.rs:LL:CC
+   |
+LL |         let _ = PrintName::<T>::VOID;
+   |                 ^^^^^^^^^^^^^^^^^^^^ referenced constant has errors
+   |
+   = note: inside `no_codegen::<i32>` at $DIR/erroneous_const.rs:LL:CC
+note: inside `main` at $DIR/erroneous_const.rs:LL:CC
+  --> $DIR/erroneous_const.rs:LL:CC
+   |
+LL |     no_codegen::<i32>();
+   |     ^^^^^^^^^^^^^^^^^^^
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to 2 previous errors
+
+For more information about this error, try `rustc --explain E0080`.
diff --git a/src/tools/miri/tests/fail/erroneous_const2.rs b/src/tools/miri/tests/fail/erroneous_const2.rs
new file mode 100644
index 0000000000000..6628166cfacdf
--- /dev/null
+++ b/src/tools/miri/tests/fail/erroneous_const2.rs
@@ -0,0 +1,13 @@
+const X: u32 = 5;
+const Y: u32 = 6;
+const FOO: u32 = [X - Y, Y - X][(X < Y) as usize];
+//~^ERROR: any use of this value
+//~|WARN: previously accepted
+
+#[rustfmt::skip] // rustfmt bug: https://github.com/rust-lang/rustfmt/issues/5391
+fn main() {
+    println!("{}", FOO); //~ERROR: post-monomorphization error
+    //~|ERROR: evaluation of constant value failed
+    //~|ERROR: erroneous constant used
+    //~|WARN: previously accepted
+}
diff --git a/src/tools/miri/tests/fail/erroneous_const2.stderr b/src/tools/miri/tests/fail/erroneous_const2.stderr
new file mode 100644
index 0000000000000..05ed8ea1c14bc
--- /dev/null
+++ b/src/tools/miri/tests/fail/erroneous_const2.stderr
@@ -0,0 +1,39 @@
+error: any use of this value will cause an error
+  --> $DIR/erroneous_const2.rs:LL:CC
+   |
+LL | const FOO: u32 = [X - Y, Y - X][(X < Y) as usize];
+   | --------------    ^^^^^ attempt to compute `5_u32 - 6_u32`, which would overflow
+   |
+   = note: `#[deny(const_err)]` on by default
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
+
+error[E0080]: evaluation of constant value failed
+  --> $DIR/erroneous_const2.rs:LL:CC
+   |
+LL |     println!("{}", FOO);
+   |                    ^^^ referenced constant has errors
+
+error: erroneous constant used
+  --> $DIR/erroneous_const2.rs:LL:CC
+   |
+LL |     println!("{}", FOO);
+   |                    ^^^ referenced constant has errors
+   |
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
+   = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: post-monomorphization error: referenced constant has errors
+  --> $DIR/erroneous_const2.rs:LL:CC
+   |
+LL |     println!("{}", FOO);
+   |                    ^^^ referenced constant has errors
+   |
+   = note: inside `main` at $DIR/erroneous_const2.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to 4 previous errors
+
+For more information about this error, try `rustc --explain E0080`.
diff --git a/src/tools/miri/tests/fail/extern_static.rs b/src/tools/miri/tests/fail/extern_static.rs
new file mode 100644
index 0000000000000..f8805db8d1439
--- /dev/null
+++ b/src/tools/miri/tests/fail/extern_static.rs
@@ -0,0 +1,9 @@
+//! Even referencing an unknown `extern static` already triggers an error.
+
+extern "C" {
+    static mut FOO: i32;
+}
+
+fn main() {
+    let _val = unsafe { std::ptr::addr_of!(FOO) }; //~ ERROR: is not supported by Miri
+}
diff --git a/src/tools/miri/tests/fail/extern_static.stderr b/src/tools/miri/tests/fail/extern_static.stderr
new file mode 100644
index 0000000000000..fa0d55e5f6781
--- /dev/null
+++ b/src/tools/miri/tests/fail/extern_static.stderr
@@ -0,0 +1,14 @@
+error: unsupported operation: `extern` static `FOO` from crate `extern_static` is not supported by Miri
+  --> $DIR/extern_static.rs:LL:CC
+   |
+LL |     let _val = unsafe { std::ptr::addr_of!(FOO) };
+   |                                            ^^^ `extern` static `FOO` from crate `extern_static` is not supported by Miri
+   |
+   = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/extern_static.rs:LL:CC
+
+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/extern_static_in_const.rs b/src/tools/miri/tests/fail/extern_static_in_const.rs
new file mode 100644
index 0000000000000..31c192bf44bea
--- /dev/null
+++ b/src/tools/miri/tests/fail/extern_static_in_const.rs
@@ -0,0 +1,11 @@
+//! Even referencing an unknown `extern static` already triggers an error.
+
+extern "C" {
+    static E: [u8; 0];
+}
+
+static X: &'static [u8; 0] = unsafe { &E };
+
+fn main() {
+    let _val = X; //~ ERROR: is not supported by Miri
+}
diff --git a/src/tools/miri/tests/fail/extern_static_in_const.stderr b/src/tools/miri/tests/fail/extern_static_in_const.stderr
new file mode 100644
index 0000000000000..e4ee8f1acba29
--- /dev/null
+++ b/src/tools/miri/tests/fail/extern_static_in_const.stderr
@@ -0,0 +1,14 @@
+error: unsupported operation: `extern` static `E` from crate `extern_static_in_const` is not supported by Miri
+  --> $DIR/extern_static_in_const.rs:LL:CC
+   |
+LL |     let _val = X;
+   |                ^ `extern` static `E` from crate `extern_static_in_const` is not supported by Miri
+   |
+   = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/extern_static_in_const.rs:LL:CC
+
+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/extern_static_wrong_size.rs b/src/tools/miri/tests/fail/extern_static_wrong_size.rs
new file mode 100644
index 0000000000000..17061f0e5c81c
--- /dev/null
+++ b/src/tools/miri/tests/fail/extern_static_wrong_size.rs
@@ -0,0 +1,10 @@
+//@ only-target-linux: we need a specific extern supported on this target
+//@normalize-stderr-test: "[48] bytes" -> "N bytes"
+
+extern "C" {
+    static mut environ: i8;
+}
+
+fn main() {
+    let _val = unsafe { environ }; //~ ERROR: /has been declared with a size of 1 bytes and alignment of 1 bytes, but Miri emulates it via an extern static shim with a size of [48] bytes and alignment of [48] bytes/
+}
diff --git a/src/tools/miri/tests/fail/extern_static_wrong_size.stderr b/src/tools/miri/tests/fail/extern_static_wrong_size.stderr
new file mode 100644
index 0000000000000..a56eba09df98f
--- /dev/null
+++ b/src/tools/miri/tests/fail/extern_static_wrong_size.stderr
@@ -0,0 +1,14 @@
+error: unsupported operation: `extern` static `environ` from crate `extern_static_wrong_size` has been declared with a size of 1 bytes and alignment of 1 bytes, but Miri emulates it via an extern static shim with a size of N bytes and alignment of N bytes
+  --> $DIR/extern_static_wrong_size.rs:LL:CC
+   |
+LL |     let _val = unsafe { environ };
+   |                         ^^^^^^^ `extern` static `environ` from crate `extern_static_wrong_size` has been declared with a size of 1 bytes and alignment of 1 bytes, but Miri emulates it via an extern static shim with a size of N bytes and alignment of N bytes
+   |
+   = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/extern_static_wrong_size.rs:LL:CC
+
+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/fast_math_both.rs b/src/tools/miri/tests/fail/fast_math_both.rs
new file mode 100644
index 0000000000000..dd2787bf40f40
--- /dev/null
+++ b/src/tools/miri/tests/fail/fast_math_both.rs
@@ -0,0 +1,7 @@
+#![feature(core_intrinsics)]
+
+fn main() {
+    unsafe {
+        let _x: f32 = core::intrinsics::fsub_fast(f32::NAN, f32::NAN); //~ ERROR: `fsub_fast` intrinsic called with non-finite value as both parameters
+    }
+}
diff --git a/src/tools/miri/tests/fail/fast_math_both.stderr b/src/tools/miri/tests/fail/fast_math_both.stderr
new file mode 100644
index 0000000000000..2a0759f8a3ba7
--- /dev/null
+++ b/src/tools/miri/tests/fail/fast_math_both.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `fsub_fast` intrinsic called with non-finite value as both parameters
+  --> $DIR/fast_math_both.rs:LL:CC
+   |
+LL | ...: f32 = core::intrinsics::fsub_fast(f32::NAN, f32::NAN);
+   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `fsub_fast` intrinsic called with non-finite value as both parameters
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/fast_math_both.rs:LL:CC
+
+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/fast_math_first.rs b/src/tools/miri/tests/fail/fast_math_first.rs
new file mode 100644
index 0000000000000..e495498ab2869
--- /dev/null
+++ b/src/tools/miri/tests/fail/fast_math_first.rs
@@ -0,0 +1,7 @@
+#![feature(core_intrinsics)]
+
+fn main() {
+    unsafe {
+        let _x: f32 = core::intrinsics::frem_fast(f32::NAN, 3.2); //~ ERROR: `frem_fast` intrinsic called with non-finite value as first parameter
+    }
+}
diff --git a/src/tools/miri/tests/fail/fast_math_first.stderr b/src/tools/miri/tests/fail/fast_math_first.stderr
new file mode 100644
index 0000000000000..766662ca14ba9
--- /dev/null
+++ b/src/tools/miri/tests/fail/fast_math_first.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `frem_fast` intrinsic called with non-finite value as first parameter
+  --> $DIR/fast_math_first.rs:LL:CC
+   |
+LL | ...   let _x: f32 = core::intrinsics::frem_fast(f32::NAN, 3.2);
+   |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `frem_fast` intrinsic called with non-finite value as first parameter
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/fast_math_first.rs:LL:CC
+
+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/fast_math_second.rs b/src/tools/miri/tests/fail/fast_math_second.rs
new file mode 100644
index 0000000000000..408c461077fc5
--- /dev/null
+++ b/src/tools/miri/tests/fail/fast_math_second.rs
@@ -0,0 +1,7 @@
+#![feature(core_intrinsics)]
+
+fn main() {
+    unsafe {
+        let _x: f32 = core::intrinsics::fmul_fast(3.4f32, f32::INFINITY); //~ ERROR: `fmul_fast` intrinsic called with non-finite value as second parameter
+    }
+}
diff --git a/src/tools/miri/tests/fail/fast_math_second.stderr b/src/tools/miri/tests/fail/fast_math_second.stderr
new file mode 100644
index 0000000000000..ce93f9398f2cd
--- /dev/null
+++ b/src/tools/miri/tests/fail/fast_math_second.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `fmul_fast` intrinsic called with non-finite value as second parameter
+  --> $DIR/fast_math_second.rs:LL:CC
+   |
+LL | ...f32 = core::intrinsics::fmul_fast(3.4f32, f32::INFINITY);
+   |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `fmul_fast` intrinsic called with non-finite value as second parameter
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/fast_math_second.rs:LL:CC
+
+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/check_arg_abi.rs b/src/tools/miri/tests/fail/function_calls/check_arg_abi.rs
new file mode 100644
index 0000000000000..ffa0443ce507a
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/check_arg_abi.rs
@@ -0,0 +1,9 @@
+fn main() {
+    extern "Rust" {
+        fn malloc(size: usize) -> *mut std::ffi::c_void;
+    }
+
+    unsafe {
+        let _ = malloc(0); //~ ERROR: calling a function with ABI C using caller ABI Rust
+    };
+}
diff --git a/src/tools/miri/tests/fail/function_calls/check_arg_abi.stderr b/src/tools/miri/tests/fail/function_calls/check_arg_abi.stderr
new file mode 100644
index 0000000000000..406ccb070bab5
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/check_arg_abi.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: calling a function with ABI C using caller ABI Rust
+  --> $DIR/check_arg_abi.rs:LL:CC
+   |
+LL |         let _ = malloc(0);
+   |                 ^^^^^^^^^ calling a function with ABI C using caller ABI Rust
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/check_arg_abi.rs:LL:CC
+
+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/check_arg_count_abort.rs b/src/tools/miri/tests/fail/function_calls/check_arg_count_abort.rs
new file mode 100644
index 0000000000000..967a78bf83187
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/check_arg_count_abort.rs
@@ -0,0 +1,10 @@
+fn main() {
+    extern "C" {
+        fn abort(_: i32) -> !;
+    }
+
+    unsafe {
+        abort(1);
+        //~^ ERROR: Undefined Behavior: incorrect number of arguments: got 1, expected 0
+    }
+}
diff --git a/src/tools/miri/tests/fail/function_calls/check_arg_count_abort.stderr b/src/tools/miri/tests/fail/function_calls/check_arg_count_abort.stderr
new file mode 100644
index 0000000000000..d90a7e31d6ee9
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/check_arg_count_abort.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: incorrect number of arguments: got 1, expected 0
+  --> $DIR/check_arg_count_abort.rs:LL:CC
+   |
+LL |         abort(1);
+   |         ^^^^^^^^ incorrect number of arguments: got 1, expected 0
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/check_arg_count_abort.rs:LL:CC
+
+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/check_arg_count_too_few_args.rs b/src/tools/miri/tests/fail/function_calls/check_arg_count_too_few_args.rs
new file mode 100644
index 0000000000000..223c95ffca46b
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/check_arg_count_too_few_args.rs
@@ -0,0 +1,9 @@
+fn main() {
+    extern "C" {
+        fn malloc() -> *mut std::ffi::c_void;
+    }
+
+    unsafe {
+        let _ = malloc(); //~ ERROR: Undefined Behavior: incorrect number of arguments: got 0, expected 1
+    };
+}
diff --git a/src/tools/miri/tests/fail/function_calls/check_arg_count_too_few_args.stderr b/src/tools/miri/tests/fail/function_calls/check_arg_count_too_few_args.stderr
new file mode 100644
index 0000000000000..9e2751a216bcb
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/check_arg_count_too_few_args.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: incorrect number of arguments: got 0, expected 1
+  --> $DIR/check_arg_count_too_few_args.rs:LL:CC
+   |
+LL |         let _ = malloc();
+   |                 ^^^^^^^^ incorrect number of arguments: got 0, expected 1
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/check_arg_count_too_few_args.rs:LL:CC
+
+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/check_arg_count_too_many_args.rs b/src/tools/miri/tests/fail/function_calls/check_arg_count_too_many_args.rs
new file mode 100644
index 0000000000000..7ee9c40bf7a4b
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/check_arg_count_too_many_args.rs
@@ -0,0 +1,9 @@
+fn main() {
+    extern "C" {
+        fn malloc(_: i32, _: i32) -> *mut std::ffi::c_void;
+    }
+
+    unsafe {
+        let _ = malloc(1, 2); //~ ERROR: Undefined Behavior: incorrect number of arguments: got 2, expected 1
+    };
+}
diff --git a/src/tools/miri/tests/fail/function_calls/check_arg_count_too_many_args.stderr b/src/tools/miri/tests/fail/function_calls/check_arg_count_too_many_args.stderr
new file mode 100644
index 0000000000000..e9a38b5ae4218
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/check_arg_count_too_many_args.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: incorrect number of arguments: got 2, expected 1
+  --> $DIR/check_arg_count_too_many_args.rs:LL:CC
+   |
+LL |         let _ = malloc(1, 2);
+   |                 ^^^^^^^^^^^^ incorrect number of arguments: got 2, expected 1
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/check_arg_count_too_many_args.rs:LL:CC
+
+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/check_callback_abi.rs b/src/tools/miri/tests/fail/function_calls/check_callback_abi.rs
new file mode 100644
index 0000000000000..883d5ae8096bd
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/check_callback_abi.rs
@@ -0,0 +1,18 @@
+#![feature(core_intrinsics)]
+
+extern "C" fn try_fn(_: *mut u8) {
+    unreachable!();
+}
+
+fn main() {
+    unsafe {
+        // Make sure we check the ABI when Miri itself invokes a function
+        // as part of a shim implementation.
+        std::intrinsics::r#try(
+            //~^ ERROR: calling a function with ABI C using caller ABI Rust
+            std::mem::transmute::<extern "C" fn(*mut u8), _>(try_fn),
+            std::ptr::null_mut(),
+            |_, _| unreachable!(),
+        );
+    }
+}
diff --git a/src/tools/miri/tests/fail/function_calls/check_callback_abi.stderr b/src/tools/miri/tests/fail/function_calls/check_callback_abi.stderr
new file mode 100644
index 0000000000000..50afc10979749
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/check_callback_abi.stderr
@@ -0,0 +1,20 @@
+error: Undefined Behavior: calling a function with ABI C using caller ABI Rust
+  --> $DIR/check_callback_abi.rs:LL:CC
+   |
+LL | /         std::intrinsics::r#try(
+LL | |
+LL | |             std::mem::transmute::<extern "C" fn(*mut u8), _>(try_fn),
+LL | |             std::ptr::null_mut(),
+LL | |             |_, _| unreachable!(),
+LL | |         );
+   | |_________^ calling a function with ABI C using caller ABI Rust
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/check_callback_abi.rs:LL:CC
+
+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/exported_symbol_abi_mismatch.cache.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_abi_mismatch.cache.stderr
new file mode 100644
index 0000000000000..ae5c6cb72b3c9
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_abi_mismatch.cache.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: calling a function with calling convention Rust using calling convention C
+  --> $DIR/exported_symbol_abi_mismatch.rs:LL:CC
+   |
+LL |             foo();
+   |             ^^^^^ calling a function with calling convention Rust using calling convention C
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/exported_symbol_abi_mismatch.rs:LL:CC
+
+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/exported_symbol_abi_mismatch.fn_ptr.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_abi_mismatch.fn_ptr.stderr
new file mode 100644
index 0000000000000..17d56793ac5c6
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_abi_mismatch.fn_ptr.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: calling a function with calling convention Rust using calling convention C
+  --> $DIR/exported_symbol_abi_mismatch.rs:LL:CC
+   |
+LL |         std::mem::transmute::<unsafe fn(), unsafe extern "C" fn()>(foo)();
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ calling a function with calling convention Rust using calling convention C
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/exported_symbol_abi_mismatch.rs:LL:CC
+
+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/exported_symbol_abi_mismatch.no_cache.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_abi_mismatch.no_cache.stderr
new file mode 100644
index 0000000000000..ae5c6cb72b3c9
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_abi_mismatch.no_cache.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: calling a function with calling convention Rust using calling convention C
+  --> $DIR/exported_symbol_abi_mismatch.rs:LL:CC
+   |
+LL |             foo();
+   |             ^^^^^ calling a function with calling convention Rust using calling convention C
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/exported_symbol_abi_mismatch.rs:LL:CC
+
+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/exported_symbol_abi_mismatch.rs b/src/tools/miri/tests/fail/function_calls/exported_symbol_abi_mismatch.rs
new file mode 100644
index 0000000000000..dbf72b5b61ad9
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_abi_mismatch.rs
@@ -0,0 +1,35 @@
+//@revisions: no_cache cache fn_ptr
+
+#[no_mangle]
+fn foo() {}
+
+fn main() {
+    #[cfg(any(cache, fn_ptr))]
+    extern "Rust" {
+        fn foo();
+    }
+
+    #[cfg(fn_ptr)]
+    unsafe {
+        std::mem::transmute::<unsafe fn(), unsafe extern "C" fn()>(foo)();
+        //[fn_ptr]~^ ERROR: calling a function with calling convention Rust using calling convention C
+    }
+
+    // `Instance` caching should not suppress ABI check.
+    #[cfg(cache)]
+    unsafe {
+        foo();
+    }
+
+    {
+        #[cfg_attr(any(cache, fn_ptr), allow(clashing_extern_declarations))]
+        extern "C" {
+            fn foo();
+        }
+        unsafe {
+            foo();
+            //[no_cache]~^ ERROR: calling a function with calling convention Rust using calling convention C
+            //[cache]~| ERROR: calling a function with calling convention Rust using calling convention C
+        }
+    }
+}
diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind1.rs b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind1.rs
new file mode 100644
index 0000000000000..5f4df7c6a1ef3
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind1.rs
@@ -0,0 +1,15 @@
+//@compile-flags: -Zmiri-disable-abi-check
+#![feature(c_unwind)]
+
+#[no_mangle]
+extern "C-unwind" fn unwind() {
+    panic!();
+}
+
+fn main() {
+    extern "C" {
+        fn unwind();
+    }
+    unsafe { unwind() }
+    //~^ ERROR: unwinding past a stack frame that does not allow unwinding
+}
diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind1.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind1.stderr
new file mode 100644
index 0000000000000..7f87ec6f3bb69
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind1.stderr
@@ -0,0 +1,17 @@
+thread 'main' panicked at 'explicit panic', $DIR/exported_symbol_bad_unwind1.rs:LL:CC
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
+error: Undefined Behavior: unwinding past a stack frame that does not allow unwinding
+  --> $DIR/exported_symbol_bad_unwind1.rs:LL:CC
+   |
+LL |     unsafe { unwind() }
+   |              ^^^^^^^^ unwinding past a stack frame that does not allow unwinding
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/exported_symbol_bad_unwind1.rs:LL:CC
+
+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/exported_symbol_bad_unwind2.both.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.both.stderr
new file mode 100644
index 0000000000000..7d9302e3e3adc
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.both.stderr
@@ -0,0 +1,23 @@
+thread 'main' panicked at 'explicit panic', $DIR/exported_symbol_bad_unwind2.rs:LL:CC
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
+error: abnormal termination: the program aborted execution
+  --> $DIR/exported_symbol_bad_unwind2.rs:LL:CC
+   |
+LL | / extern "C-unwind" fn nounwind() {
+LL | |
+LL | |
+LL | |     panic!();
+LL | | }
+   | |_^ the program aborted execution
+   |
+   = note: inside `nounwind` at $DIR/exported_symbol_bad_unwind2.rs:LL:CC
+note: inside `main` at $DIR/exported_symbol_bad_unwind2.rs:LL:CC
+  --> $DIR/exported_symbol_bad_unwind2.rs:LL:CC
+   |
+LL |     unsafe { nounwind() }
+   |              ^^^^^^^^^^
+
+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/exported_symbol_bad_unwind2.definition.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.definition.stderr
new file mode 100644
index 0000000000000..7d9302e3e3adc
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.definition.stderr
@@ -0,0 +1,23 @@
+thread 'main' panicked at 'explicit panic', $DIR/exported_symbol_bad_unwind2.rs:LL:CC
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
+error: abnormal termination: the program aborted execution
+  --> $DIR/exported_symbol_bad_unwind2.rs:LL:CC
+   |
+LL | / extern "C-unwind" fn nounwind() {
+LL | |
+LL | |
+LL | |     panic!();
+LL | | }
+   | |_^ the program aborted execution
+   |
+   = note: inside `nounwind` at $DIR/exported_symbol_bad_unwind2.rs:LL:CC
+note: inside `main` at $DIR/exported_symbol_bad_unwind2.rs:LL:CC
+  --> $DIR/exported_symbol_bad_unwind2.rs:LL:CC
+   |
+LL |     unsafe { nounwind() }
+   |              ^^^^^^^^^^
+
+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/exported_symbol_bad_unwind2.extern_block.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.extern_block.stderr
new file mode 100644
index 0000000000000..b23c05a530357
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.extern_block.stderr
@@ -0,0 +1,17 @@
+thread 'main' panicked at 'explicit panic', $DIR/exported_symbol_bad_unwind2.rs:LL:CC
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
+error: Undefined Behavior: unwinding past a stack frame that does not allow unwinding
+  --> $DIR/exported_symbol_bad_unwind2.rs:LL:CC
+   |
+LL |     unsafe { nounwind() }
+   |              ^^^^^^^^^^ unwinding past a stack frame that does not allow unwinding
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/exported_symbol_bad_unwind2.rs:LL:CC
+
+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/exported_symbol_bad_unwind2.rs b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.rs
new file mode 100644
index 0000000000000..d9aacdb8aea4e
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.rs
@@ -0,0 +1,19 @@
+//@revisions: extern_block definition both
+#![feature(rustc_attrs, c_unwind)]
+
+#[cfg_attr(any(definition, both), rustc_allocator_nounwind)]
+#[no_mangle]
+extern "C-unwind" fn nounwind() {
+    //[definition]~^ ERROR: abnormal termination: the program aborted execution
+    //[both]~^^ ERROR: abnormal termination: the program aborted execution
+    panic!();
+}
+
+fn main() {
+    extern "C-unwind" {
+        #[cfg_attr(any(extern_block, both), rustc_allocator_nounwind)]
+        fn nounwind();
+    }
+    unsafe { nounwind() }
+    //[extern_block]~^ ERROR: unwinding past a stack frame that does not allow unwinding
+}
diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_clashing.rs b/src/tools/miri/tests/fail/function_calls/exported_symbol_clashing.rs
new file mode 100644
index 0000000000000..45ad412e7a55e
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_clashing.rs
@@ -0,0 +1,15 @@
+#[no_mangle]
+fn foo() {}
+//~^ HELP: it's first defined here, in crate `exported_symbol_clashing`
+
+#[export_name = "foo"]
+fn bar() {}
+//~^ HELP: then it's defined here again, in crate `exported_symbol_clashing`
+
+fn main() {
+    extern "Rust" {
+        fn foo();
+    }
+    unsafe { foo() }
+    //~^ ERROR: multiple definitions of symbol `foo`
+}
diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_clashing.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_clashing.stderr
new file mode 100644
index 0000000000000..8eb9fa4ff5c27
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_clashing.stderr
@@ -0,0 +1,23 @@
+error: multiple definitions of symbol `foo`
+  --> $DIR/exported_symbol_clashing.rs:LL:CC
+   |
+LL |     unsafe { foo() }
+   |              ^^^^^ multiple definitions of symbol `foo`
+   |
+help: it's first defined here, in crate `exported_symbol_clashing`
+  --> $DIR/exported_symbol_clashing.rs:LL:CC
+   |
+LL | fn foo() {}
+   | ^^^^^^^^
+help: then it's defined here again, in crate `exported_symbol_clashing`
+  --> $DIR/exported_symbol_clashing.rs:LL:CC
+   |
+LL | fn bar() {}
+   | ^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/exported_symbol_clashing.rs:LL:CC
+
+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/exported_symbol_shim_clashing.rs b/src/tools/miri/tests/fail/function_calls/exported_symbol_shim_clashing.rs
new file mode 100644
index 0000000000000..dffae7adbb972
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_shim_clashing.rs
@@ -0,0 +1,15 @@
+#[no_mangle]
+extern "C" fn malloc(_: usize) -> *mut std::ffi::c_void {
+    //~^ HELP: the `malloc` symbol is defined here
+    unreachable!()
+}
+
+fn main() {
+    extern "C" {
+        fn malloc(_: usize) -> *mut std::ffi::c_void;
+    }
+    unsafe {
+        malloc(0);
+        //~^ ERROR: found `malloc` symbol definition that clashes with a built-in shim
+    }
+}
diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_shim_clashing.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_shim_clashing.stderr
new file mode 100644
index 0000000000000..58a996e64530e
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_shim_clashing.stderr
@@ -0,0 +1,21 @@
+error: found `malloc` symbol definition that clashes with a built-in shim
+  --> $DIR/exported_symbol_shim_clashing.rs:LL:CC
+   |
+LL |         malloc(0);
+   |         ^^^^^^^^^ found `malloc` symbol definition that clashes with a built-in shim
+   |
+help: the `malloc` symbol is defined here
+  --> $DIR/exported_symbol_shim_clashing.rs:LL:CC
+   |
+LL | / extern "C" fn malloc(_: usize) -> *mut std::ffi::c_void {
+LL | |
+LL | |     unreachable!()
+LL | | }
+   | |_^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/exported_symbol_shim_clashing.rs:LL:CC
+
+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/exported_symbol_wrong_arguments.rs b/src/tools/miri/tests/fail/function_calls/exported_symbol_wrong_arguments.rs
new file mode 100644
index 0000000000000..a108944c5e434
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_wrong_arguments.rs
@@ -0,0 +1,9 @@
+#[no_mangle]
+fn foo() {}
+
+fn main() {
+    extern "Rust" {
+        fn foo(_: i32);
+    }
+    unsafe { foo(1) } //~ ERROR: calling a function with more arguments than it expected
+}
diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_wrong_arguments.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_wrong_arguments.stderr
new file mode 100644
index 0000000000000..1aa13ce438953
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_wrong_arguments.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: calling a function with more arguments than it expected
+  --> $DIR/exported_symbol_wrong_arguments.rs:LL:CC
+   |
+LL |     unsafe { foo(1) }
+   |              ^^^^^^ calling a function with more arguments than it expected
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/exported_symbol_wrong_arguments.rs:LL:CC
+
+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/exported_symbol_wrong_type.rs b/src/tools/miri/tests/fail/function_calls/exported_symbol_wrong_type.rs
new file mode 100644
index 0000000000000..e273e354334f8
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_wrong_type.rs
@@ -0,0 +1,9 @@
+#[no_mangle]
+static FOO: () = ();
+
+fn main() {
+    extern "C" {
+        fn FOO();
+    }
+    unsafe { FOO() } //~ ERROR: attempt to call an exported symbol that is not defined as a function
+}
diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_wrong_type.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_wrong_type.stderr
new file mode 100644
index 0000000000000..abfd7a9a6c4d9
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_wrong_type.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: attempt to call an exported symbol that is not defined as a function
+  --> $DIR/exported_symbol_wrong_type.rs:LL:CC
+   |
+LL |     unsafe { FOO() }
+   |              ^^^^^ attempt to call an exported symbol that is not defined as a function
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/exported_symbol_wrong_type.rs:LL:CC
+
+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_pointers/cast_box_int_to_fn_ptr.rs b/src/tools/miri/tests/fail/function_pointers/cast_box_int_to_fn_ptr.rs
new file mode 100644
index 0000000000000..9815569b607f2
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_pointers/cast_box_int_to_fn_ptr.rs
@@ -0,0 +1,9 @@
+// Validation makes this fail in the wrong place
+//@compile-flags: -Zmiri-disable-validation
+
+fn main() {
+    let b = Box::new(42);
+    let g = unsafe { std::mem::transmute::<&Box<usize>, &fn(i32)>(&b) };
+
+    (*g)(42) //~ ERROR: it does not point to a function
+}
diff --git a/src/tools/miri/tests/fail/function_pointers/cast_box_int_to_fn_ptr.stderr b/src/tools/miri/tests/fail/function_pointers/cast_box_int_to_fn_ptr.stderr
new file mode 100644
index 0000000000000..ad43c2c9d3fe7
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_pointers/cast_box_int_to_fn_ptr.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: using ALLOC as function pointer but it does not point to a function
+  --> $DIR/cast_box_int_to_fn_ptr.rs:LL:CC
+   |
+LL |     (*g)(42)
+   |     ^^^^^^^^ using ALLOC as function pointer but it does not point to a function
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/cast_box_int_to_fn_ptr.rs:LL:CC
+
+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_pointers/cast_fn_ptr1.rs b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr1.rs
new file mode 100644
index 0000000000000..c0e96a43cc522
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr1.rs
@@ -0,0 +1,7 @@
+fn main() {
+    fn f() {}
+
+    let g = unsafe { std::mem::transmute::<fn(), fn(i32)>(f) };
+
+    g(42) //~ ERROR: calling a function with more arguments than it expected
+}
diff --git a/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr1.stderr b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr1.stderr
new file mode 100644
index 0000000000000..bb2a263795980
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr1.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: calling a function with more arguments than it expected
+  --> $DIR/cast_fn_ptr1.rs:LL:CC
+   |
+LL |     g(42)
+   |     ^^^^^ calling a function with more arguments than it expected
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/cast_fn_ptr1.rs:LL:CC
+
+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_pointers/cast_fn_ptr2.rs b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr2.rs
new file mode 100644
index 0000000000000..20384f0965b82
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr2.rs
@@ -0,0 +1,7 @@
+fn main() {
+    fn f(_: (i32, i32)) {}
+
+    let g = unsafe { std::mem::transmute::<fn((i32, i32)), fn(i32)>(f) };
+
+    g(42) //~ ERROR: calling a function with argument of type (i32, i32) passing data of type i32
+}
diff --git a/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr2.stderr b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr2.stderr
new file mode 100644
index 0000000000000..086712e0d13bd
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr2.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: calling a function with argument of type (i32, i32) passing data of type i32
+  --> $DIR/cast_fn_ptr2.rs:LL:CC
+   |
+LL |     g(42)
+   |     ^^^^^ calling a function with argument of type (i32, i32) passing data of type i32
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/cast_fn_ptr2.rs:LL:CC
+
+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_pointers/cast_fn_ptr3.rs b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr3.rs
new file mode 100644
index 0000000000000..920fb51abb644
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr3.rs
@@ -0,0 +1,7 @@
+fn main() {
+    fn f(_: (i32, i32)) {}
+
+    let g = unsafe { std::mem::transmute::<fn((i32, i32)), fn()>(f) };
+
+    g() //~ ERROR: calling a function with fewer arguments than it requires
+}
diff --git a/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr3.stderr b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr3.stderr
new file mode 100644
index 0000000000000..55fd7d6072089
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr3.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: calling a function with fewer arguments than it requires
+  --> $DIR/cast_fn_ptr3.rs:LL:CC
+   |
+LL |     g()
+   |     ^^^ calling a function with fewer arguments than it requires
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/cast_fn_ptr3.rs:LL:CC
+
+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_pointers/cast_fn_ptr4.rs b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr4.rs
new file mode 100644
index 0000000000000..f0ea5ccfe0f5a
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr4.rs
@@ -0,0 +1,7 @@
+fn main() {
+    fn f(_: *const [i32]) {}
+
+    let g = unsafe { std::mem::transmute::<fn(*const [i32]), fn(*const i32)>(f) };
+
+    g(&42 as *const i32) //~ ERROR: calling a function with argument of type *const [i32] passing data of type *const i32
+}
diff --git a/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr4.stderr b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr4.stderr
new file mode 100644
index 0000000000000..610425658fe1f
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr4.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: calling a function with argument of type *const [i32] passing data of type *const i32
+  --> $DIR/cast_fn_ptr4.rs:LL:CC
+   |
+LL |     g(&42 as *const i32)
+   |     ^^^^^^^^^^^^^^^^^^^^ calling a function with argument of type *const [i32] passing data of type *const i32
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/cast_fn_ptr4.rs:LL:CC
+
+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_pointers/cast_fn_ptr5.rs b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr5.rs
new file mode 100644
index 0000000000000..0fdab49b94b6f
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr5.rs
@@ -0,0 +1,9 @@
+fn main() {
+    fn f() -> u32 {
+        42
+    }
+
+    let g = unsafe { std::mem::transmute::<fn() -> u32, fn()>(f) };
+
+    g() //~ ERROR: calling a function with return type u32 passing return place of type ()
+}
diff --git a/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr5.stderr b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr5.stderr
new file mode 100644
index 0000000000000..c4e08b58430a2
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr5.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: calling a function with return type u32 passing return place of type ()
+  --> $DIR/cast_fn_ptr5.rs:LL:CC
+   |
+LL |     g()
+   |     ^^^ calling a function with return type u32 passing return place of type ()
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/cast_fn_ptr5.rs:LL:CC
+
+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_pointers/cast_int_to_fn_ptr.rs b/src/tools/miri/tests/fail/function_pointers/cast_int_to_fn_ptr.rs
new file mode 100644
index 0000000000000..dbf8a560fb7af
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_pointers/cast_int_to_fn_ptr.rs
@@ -0,0 +1,8 @@
+// Validation makes this fail in the wrong place
+//@compile-flags: -Zmiri-disable-validation
+
+fn main() {
+    let g = unsafe { std::mem::transmute::<usize, fn(i32)>(42) };
+
+    g(42) //~ ERROR: is a dangling pointer
+}
diff --git a/src/tools/miri/tests/fail/function_pointers/cast_int_to_fn_ptr.stderr b/src/tools/miri/tests/fail/function_pointers/cast_int_to_fn_ptr.stderr
new file mode 100644
index 0000000000000..81fc9716a4156
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_pointers/cast_int_to_fn_ptr.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: out-of-bounds pointer use: 0x2a[noalloc] is a dangling pointer (it has no provenance)
+  --> $DIR/cast_int_to_fn_ptr.rs:LL:CC
+   |
+LL |     g(42)
+   |     ^^^^^ out-of-bounds pointer use: 0x2a[noalloc] is a dangling pointer (it has no provenance)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/cast_int_to_fn_ptr.rs:LL:CC
+
+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_pointers/deref_fn_ptr.rs b/src/tools/miri/tests/fail/function_pointers/deref_fn_ptr.rs
new file mode 100644
index 0000000000000..f071b63902fee
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_pointers/deref_fn_ptr.rs
@@ -0,0 +1,8 @@
+fn f() {}
+
+fn main() {
+    let x: u8 = unsafe {
+        *std::mem::transmute::<fn(), *const u8>(f) //~ ERROR: out-of-bounds
+    };
+    panic!("this should never print: {}", x);
+}
diff --git a/src/tools/miri/tests/fail/function_pointers/deref_fn_ptr.stderr b/src/tools/miri/tests/fail/function_pointers/deref_fn_ptr.stderr
new file mode 100644
index 0000000000000..7ce0b08695ebb
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_pointers/deref_fn_ptr.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: dereferencing pointer failed: ALLOC has size 0, so pointer to 1 byte starting at offset 0 is out-of-bounds
+  --> $DIR/deref_fn_ptr.rs:LL:CC
+   |
+LL |         *std::mem::transmute::<fn(), *const u8>(f)
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ dereferencing pointer failed: ALLOC has size 0, so pointer to 1 byte starting at offset 0 is out-of-bounds
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/deref_fn_ptr.rs:LL:CC
+
+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_pointers/execute_memory.rs b/src/tools/miri/tests/fail/function_pointers/execute_memory.rs
new file mode 100644
index 0000000000000..967933e769b8c
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_pointers/execute_memory.rs
@@ -0,0 +1,12 @@
+// Validation makes this fail in the wrong place
+//@compile-flags: -Zmiri-disable-validation
+
+#![feature(box_syntax)]
+
+fn main() {
+    let x = box 42;
+    unsafe {
+        let f = std::mem::transmute::<Box<i32>, fn()>(x);
+        f() //~ ERROR: function pointer but it does not point to a function
+    }
+}
diff --git a/src/tools/miri/tests/fail/function_pointers/execute_memory.stderr b/src/tools/miri/tests/fail/function_pointers/execute_memory.stderr
new file mode 100644
index 0000000000000..10c53ca2beaee
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_pointers/execute_memory.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: using ALLOC as function pointer but it does not point to a function
+  --> $DIR/execute_memory.rs:LL:CC
+   |
+LL |         f()
+   |         ^^^ using ALLOC as function pointer but it does not point to a function
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/execute_memory.rs:LL:CC
+
+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_pointers/fn_ptr_offset.rs b/src/tools/miri/tests/fail/function_pointers/fn_ptr_offset.rs
new file mode 100644
index 0000000000000..eba0953ac863f
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_pointers/fn_ptr_offset.rs
@@ -0,0 +1,14 @@
+// Validation makes this fail in the wrong place
+//@compile-flags: -Zmiri-disable-validation
+
+use std::mem;
+
+fn f() {}
+
+fn main() {
+    let x: fn() = f;
+    let y: *mut u8 = unsafe { mem::transmute(x) };
+    let y = y.wrapping_offset(1);
+    let x: fn() = unsafe { mem::transmute(y) };
+    x(); //~ ERROR: function pointer but it does not point to a function
+}
diff --git a/src/tools/miri/tests/fail/function_pointers/fn_ptr_offset.stderr b/src/tools/miri/tests/fail/function_pointers/fn_ptr_offset.stderr
new file mode 100644
index 0000000000000..f8c519c1b54b0
--- /dev/null
+++ b/src/tools/miri/tests/fail/function_pointers/fn_ptr_offset.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: using ALLOC+0x1 as function pointer but it does not point to a function
+  --> $DIR/fn_ptr_offset.rs:LL:CC
+   |
+LL |     x();
+   |     ^^^ using ALLOC+0x1 as function pointer but it does not point to a function
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/fn_ptr_offset.rs:LL:CC
+
+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/generator-pinned-moved.rs b/src/tools/miri/tests/fail/generator-pinned-moved.rs
new file mode 100644
index 0000000000000..240ae18cc45a6
--- /dev/null
+++ b/src/tools/miri/tests/fail/generator-pinned-moved.rs
@@ -0,0 +1,45 @@
+//@compile-flags: -Zmiri-disable-validation
+#![feature(generators, generator_trait)]
+
+use std::{
+    ops::{Generator, GeneratorState},
+    pin::Pin,
+};
+
+fn firstn() -> impl Generator<Yield = u64, Return = ()> {
+    static move || {
+        let mut num = 0;
+        let num = &mut num;
+
+        yield *num;
+        *num += 1; //~ ERROR: dereferenced after this allocation got freed
+    }
+}
+
+struct GeneratorIteratorAdapter<G>(G);
+
+impl<G> Iterator for GeneratorIteratorAdapter<G>
+where
+    G: Generator<Return = ()>,
+{
+    type Item = G::Yield;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let me = unsafe { Pin::new_unchecked(&mut self.0) };
+        match me.resume(()) {
+            GeneratorState::Yielded(x) => Some(x),
+            GeneratorState::Complete(_) => None,
+        }
+    }
+}
+
+fn main() {
+    let mut generator_iterator_2 = {
+        let mut generator_iterator = Box::new(GeneratorIteratorAdapter(firstn()));
+        generator_iterator.next(); // pin it
+
+        Box::new(*generator_iterator) // move it
+    }; // *deallocate* generator_iterator
+
+    generator_iterator_2.next(); // and use moved value
+}
diff --git a/src/tools/miri/tests/fail/generator-pinned-moved.stderr b/src/tools/miri/tests/fail/generator-pinned-moved.stderr
new file mode 100644
index 0000000000000..4f73671a78947
--- /dev/null
+++ b/src/tools/miri/tests/fail/generator-pinned-moved.stderr
@@ -0,0 +1,26 @@
+error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed
+  --> $DIR/generator-pinned-moved.rs:LL:CC
+   |
+LL |         *num += 1;
+   |         ^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed
+   |
+   = 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 closure at $DIR/generator-pinned-moved.rs:LL:CC
+note: inside `<GeneratorIteratorAdapter<[static generator@$DIR/generator-pinned-moved.rs:LL:CC]> as std::iter::Iterator>::next` at $DIR/generator-pinned-moved.rs:LL:CC
+  --> $DIR/generator-pinned-moved.rs:LL:CC
+   |
+LL |         match me.resume(()) {
+   |               ^^^^^^^^^^^^^
+   = note: inside `<std::boxed::Box<GeneratorIteratorAdapter<[static generator@$DIR/generator-pinned-moved.rs:LL:CC]>> as std::iter::Iterator>::next` at RUSTLIB/alloc/src/boxed.rs:LL:CC
+note: inside `main` at $DIR/generator-pinned-moved.rs:LL:CC
+  --> $DIR/generator-pinned-moved.rs:LL:CC
+   |
+LL |     generator_iterator_2.next(); // and use moved value
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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/intrinsics/assume.rs b/src/tools/miri/tests/fail/intrinsics/assume.rs
new file mode 100644
index 0000000000000..c34827427ef01
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/assume.rs
@@ -0,0 +1,10 @@
+#![feature(core_intrinsics)]
+
+fn main() {
+    let x = 5;
+    unsafe {
+        std::intrinsics::assume(x < 10);
+        std::intrinsics::assume(x > 1);
+        std::intrinsics::assume(x > 42); //~ ERROR: `assume` called with `false`
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/assume.stderr b/src/tools/miri/tests/fail/intrinsics/assume.stderr
new file mode 100644
index 0000000000000..c1909570d99f2
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/assume.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `assume` called with `false`
+  --> $DIR/assume.rs:LL:CC
+   |
+LL |         std::intrinsics::assume(x > 42);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `assume` called with `false`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/assume.rs:LL:CC
+
+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/intrinsics/copy_null.rs b/src/tools/miri/tests/fail/intrinsics/copy_null.rs
new file mode 100644
index 0000000000000..237e517f2875a
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/copy_null.rs
@@ -0,0 +1,15 @@
+#![feature(intrinsics)]
+
+// Directly call intrinsic to avoid debug assertions in libstd
+extern "rust-intrinsic" {
+    fn copy_nonoverlapping<T>(src: *const T, dst: *mut T, count: usize);
+}
+
+fn main() {
+    let mut data = [0u16; 4];
+    let ptr = &mut data[0] as *mut u16;
+    // Even copying 0 elements from NULL should error.
+    unsafe {
+        copy_nonoverlapping(std::ptr::null(), ptr, 0); //~ ERROR: memory access failed: null pointer is a dangling pointer
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/copy_null.stderr b/src/tools/miri/tests/fail/intrinsics/copy_null.stderr
new file mode 100644
index 0000000000000..6e3215d9f1c9c
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/copy_null.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: memory access failed: null pointer is a dangling pointer (it has no provenance)
+  --> $DIR/copy_null.rs:LL:CC
+   |
+LL |         copy_nonoverlapping(std::ptr::null(), ptr, 0);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ memory access failed: null pointer is a dangling pointer (it has no provenance)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/copy_null.rs:LL:CC
+
+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/intrinsics/copy_overflow.rs b/src/tools/miri/tests/fail/intrinsics/copy_overflow.rs
new file mode 100644
index 0000000000000..e64a1f9090214
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/copy_overflow.rs
@@ -0,0 +1,10 @@
+use std::mem;
+
+fn main() {
+    let x = 0;
+    let mut y = 0;
+    unsafe {
+        (&mut y as *mut i32).copy_from(&x, 1usize << (mem::size_of::<usize>() * 8 - 1));
+        //~^ERROR: overflow computing total size
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/copy_overflow.stderr b/src/tools/miri/tests/fail/intrinsics/copy_overflow.stderr
new file mode 100644
index 0000000000000..23a4adbd0ed6f
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/copy_overflow.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: overflow computing total size of `copy`
+  --> $DIR/copy_overflow.rs:LL:CC
+   |
+LL |         (&mut y as *mut i32).copy_from(&x, 1usize << (mem::size_of::<usize>() * 8 - 1));
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflow computing total size of `copy`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/copy_overflow.rs:LL:CC
+
+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/intrinsics/copy_overlapping.rs b/src/tools/miri/tests/fail/intrinsics/copy_overlapping.rs
new file mode 100644
index 0000000000000..3df881bd43ca4
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/copy_overlapping.rs
@@ -0,0 +1,15 @@
+#![feature(intrinsics)]
+
+// Directly call intrinsic to avoid debug assertions in libstd
+extern "rust-intrinsic" {
+    fn copy_nonoverlapping<T>(src: *const T, dst: *mut T, count: usize);
+}
+
+fn main() {
+    let mut data = [0u8; 16];
+    unsafe {
+        let a = data.as_mut_ptr();
+        let b = a.wrapping_offset(1) as *mut _;
+        copy_nonoverlapping(a, b, 2); //~ ERROR: copy_nonoverlapping called on overlapping ranges
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/copy_overlapping.stderr b/src/tools/miri/tests/fail/intrinsics/copy_overlapping.stderr
new file mode 100644
index 0000000000000..cdb3da74ca954
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/copy_overlapping.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: copy_nonoverlapping called on overlapping ranges
+  --> $DIR/copy_overlapping.rs:LL:CC
+   |
+LL |         copy_nonoverlapping(a, b, 2);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ copy_nonoverlapping called on overlapping ranges
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/copy_overlapping.rs:LL:CC
+
+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/intrinsics/copy_unaligned.rs b/src/tools/miri/tests/fail/intrinsics/copy_unaligned.rs
new file mode 100644
index 0000000000000..281217f06f516
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/copy_unaligned.rs
@@ -0,0 +1,15 @@
+#![feature(intrinsics)]
+
+// Directly call intrinsic to avoid debug assertions in libstd
+extern "rust-intrinsic" {
+    fn copy_nonoverlapping<T>(src: *const T, dst: *mut T, count: usize);
+}
+
+fn main() {
+    let mut data = [0u16; 8];
+    let ptr = (&mut data[0] as *mut u16 as *mut u8).wrapping_add(1) as *mut u16;
+    // Even copying 0 elements to something unaligned should error
+    unsafe {
+        copy_nonoverlapping(&data[5], ptr, 0); //~ ERROR: accessing memory with alignment 1, but alignment 2 is required
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/copy_unaligned.stderr b/src/tools/miri/tests/fail/intrinsics/copy_unaligned.stderr
new file mode 100644
index 0000000000000..a275979e6be13
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/copy_unaligned.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: accessing memory with alignment ALIGN, but alignment ALIGN is required
+  --> $DIR/copy_unaligned.rs:LL:CC
+   |
+LL |         copy_nonoverlapping(&data[5], ptr, 0);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ accessing memory with alignment ALIGN, but alignment ALIGN is required
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/copy_unaligned.rs:LL:CC
+
+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/intrinsics/ctlz_nonzero.rs b/src/tools/miri/tests/fail/intrinsics/ctlz_nonzero.rs
new file mode 100644
index 0000000000000..c26cd4cadb539
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/ctlz_nonzero.rs
@@ -0,0 +1,15 @@
+#![feature(intrinsics)]
+
+mod rusti {
+    extern "rust-intrinsic" {
+        pub fn ctlz_nonzero<T>(x: T) -> T;
+    }
+}
+
+pub fn main() {
+    unsafe {
+        use crate::rusti::*;
+
+        ctlz_nonzero(0u8); //~ ERROR: `ctlz_nonzero` called on 0
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/ctlz_nonzero.stderr b/src/tools/miri/tests/fail/intrinsics/ctlz_nonzero.stderr
new file mode 100644
index 0000000000000..5ae14472a8a63
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/ctlz_nonzero.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `ctlz_nonzero` called on 0
+  --> $DIR/ctlz_nonzero.rs:LL:CC
+   |
+LL |         ctlz_nonzero(0u8);
+   |         ^^^^^^^^^^^^^^^^^ `ctlz_nonzero` called on 0
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/ctlz_nonzero.rs:LL:CC
+
+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/intrinsics/cttz_nonzero.rs b/src/tools/miri/tests/fail/intrinsics/cttz_nonzero.rs
new file mode 100644
index 0000000000000..25a0501fdd80e
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/cttz_nonzero.rs
@@ -0,0 +1,15 @@
+#![feature(intrinsics)]
+
+mod rusti {
+    extern "rust-intrinsic" {
+        pub fn cttz_nonzero<T>(x: T) -> T;
+    }
+}
+
+pub fn main() {
+    unsafe {
+        use crate::rusti::*;
+
+        cttz_nonzero(0u8); //~ ERROR: `cttz_nonzero` called on 0
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/cttz_nonzero.stderr b/src/tools/miri/tests/fail/intrinsics/cttz_nonzero.stderr
new file mode 100644
index 0000000000000..ae013fb3d9794
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/cttz_nonzero.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `cttz_nonzero` called on 0
+  --> $DIR/cttz_nonzero.rs:LL:CC
+   |
+LL |         cttz_nonzero(0u8);
+   |         ^^^^^^^^^^^^^^^^^ `cttz_nonzero` called on 0
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/cttz_nonzero.rs:LL:CC
+
+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/intrinsics/div-by-zero.rs b/src/tools/miri/tests/fail/intrinsics/div-by-zero.rs
new file mode 100644
index 0000000000000..78c05c543a8f0
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/div-by-zero.rs
@@ -0,0 +1,9 @@
+#![feature(core_intrinsics)]
+
+use std::intrinsics::*;
+
+fn main() {
+    unsafe {
+        let _n = unchecked_div(1i64, 0); //~ERROR: dividing by zero
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/div-by-zero.stderr b/src/tools/miri/tests/fail/intrinsics/div-by-zero.stderr
new file mode 100644
index 0000000000000..8c2910de3eef4
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/div-by-zero.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: dividing by zero
+  --> $DIR/div-by-zero.rs:LL:CC
+   |
+LL |         let _n = unchecked_div(1i64, 0);
+   |                  ^^^^^^^^^^^^^^^^^^^^^^ dividing by zero
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/div-by-zero.rs:LL:CC
+
+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/intrinsics/exact_div1.rs b/src/tools/miri/tests/fail/intrinsics/exact_div1.rs
new file mode 100644
index 0000000000000..3dda9d1090de7
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/exact_div1.rs
@@ -0,0 +1,5 @@
+#![feature(core_intrinsics)]
+fn main() {
+    // divison by 0
+    unsafe { std::intrinsics::exact_div(2, 0) }; //~ ERROR: divisor of zero
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/exact_div1.stderr b/src/tools/miri/tests/fail/intrinsics/exact_div1.stderr
new file mode 100644
index 0000000000000..2c7bbc00e1b1b
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/exact_div1.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: calculating the remainder with a divisor of zero
+  --> $DIR/exact_div1.rs:LL:CC
+   |
+LL |     unsafe { std::intrinsics::exact_div(2, 0) };
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ calculating the remainder with a divisor of zero
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/exact_div1.rs:LL:CC
+
+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/intrinsics/exact_div2.rs b/src/tools/miri/tests/fail/intrinsics/exact_div2.rs
new file mode 100644
index 0000000000000..00064fa0b9c11
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/exact_div2.rs
@@ -0,0 +1,5 @@
+#![feature(core_intrinsics)]
+fn main() {
+    // divison with a remainder
+    unsafe { std::intrinsics::exact_div(2u16, 3) }; //~ ERROR: 2_u16 cannot be divided by 3_u16 without remainder
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/exact_div2.stderr b/src/tools/miri/tests/fail/intrinsics/exact_div2.stderr
new file mode 100644
index 0000000000000..6a264b8b4476f
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/exact_div2.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: exact_div: 2_u16 cannot be divided by 3_u16 without remainder
+  --> $DIR/exact_div2.rs:LL:CC
+   |
+LL |     unsafe { std::intrinsics::exact_div(2u16, 3) };
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ exact_div: 2_u16 cannot be divided by 3_u16 without remainder
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/exact_div2.rs:LL:CC
+
+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/intrinsics/exact_div3.rs b/src/tools/miri/tests/fail/intrinsics/exact_div3.rs
new file mode 100644
index 0000000000000..a61abcd137e17
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/exact_div3.rs
@@ -0,0 +1,5 @@
+#![feature(core_intrinsics)]
+fn main() {
+    // signed divison with a remainder
+    unsafe { std::intrinsics::exact_div(-19i8, 2) }; //~ ERROR: -19_i8 cannot be divided by 2_i8 without remainder
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/exact_div3.stderr b/src/tools/miri/tests/fail/intrinsics/exact_div3.stderr
new file mode 100644
index 0000000000000..1a73822c300f3
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/exact_div3.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: exact_div: -19_i8 cannot be divided by 2_i8 without remainder
+  --> $DIR/exact_div3.rs:LL:CC
+   |
+LL |     unsafe { std::intrinsics::exact_div(-19i8, 2) };
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ exact_div: -19_i8 cannot be divided by 2_i8 without remainder
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/exact_div3.rs:LL:CC
+
+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/intrinsics/exact_div4.rs b/src/tools/miri/tests/fail/intrinsics/exact_div4.rs
new file mode 100644
index 0000000000000..b5b60190b4ece
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/exact_div4.rs
@@ -0,0 +1,5 @@
+#![feature(core_intrinsics)]
+fn main() {
+    // divison of MIN by -1
+    unsafe { std::intrinsics::exact_div(i64::MIN, -1) }; //~ ERROR: overflow in signed remainder (dividing MIN by -1)
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/exact_div4.stderr b/src/tools/miri/tests/fail/intrinsics/exact_div4.stderr
new file mode 100644
index 0000000000000..27201d9c7cf65
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/exact_div4.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: overflow in signed remainder (dividing MIN by -1)
+  --> $DIR/exact_div4.rs:LL:CC
+   |
+LL |     unsafe { std::intrinsics::exact_div(i64::MIN, -1) };
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflow in signed remainder (dividing MIN by -1)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/exact_div4.rs:LL:CC
+
+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/intrinsics/float_to_int_32_inf1.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_inf1.rs
new file mode 100644
index 0000000000000..a57845426d582
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_inf1.rs
@@ -0,0 +1,12 @@
+#![feature(intrinsics)]
+
+// Directly call intrinsic to avoid debug assertions in libstd
+extern "rust-intrinsic" {
+    fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
+}
+
+fn main() {
+    unsafe {
+        float_to_int_unchecked::<f32, i32>(f32::INFINITY); //~ ERROR: cannot be represented in target type `i32`
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_32_inf1.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_inf1.stderr
new file mode 100644
index 0000000000000..c82d6b30224fc
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_inf1.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on +Inf which cannot be represented in target type `i32`
+  --> $DIR/float_to_int_32_inf1.rs:LL:CC
+   |
+LL |         float_to_int_unchecked::<f32, i32>(f32::INFINITY);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on +Inf which cannot be represented in target type `i32`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/float_to_int_32_inf1.rs:LL:CC
+
+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/intrinsics/float_to_int_32_infneg1.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_infneg1.rs
new file mode 100644
index 0000000000000..d383fc5b50ac1
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_infneg1.rs
@@ -0,0 +1,12 @@
+#![feature(intrinsics)]
+
+// Directly call intrinsic to avoid debug assertions in libstd
+extern "rust-intrinsic" {
+    fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
+}
+
+fn main() {
+    unsafe {
+        float_to_int_unchecked::<f32, i32>(f32::NEG_INFINITY); //~ ERROR: cannot be represented in target type `i32`
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_32_infneg1.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_infneg1.stderr
new file mode 100644
index 0000000000000..4ca41b676e9c3
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_infneg1.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on -Inf which cannot be represented in target type `i32`
+  --> $DIR/float_to_int_32_infneg1.rs:LL:CC
+   |
+LL |         float_to_int_unchecked::<f32, i32>(f32::NEG_INFINITY);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on -Inf which cannot be represented in target type `i32`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/float_to_int_32_infneg1.rs:LL:CC
+
+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/intrinsics/float_to_int_32_nan.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_nan.rs
new file mode 100644
index 0000000000000..a39a5066b6f8b
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_nan.rs
@@ -0,0 +1,12 @@
+#![feature(intrinsics)]
+
+// Directly call intrinsic to avoid debug assertions in libstd
+extern "rust-intrinsic" {
+    fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
+}
+
+fn main() {
+    unsafe {
+        float_to_int_unchecked::<f32, u32>(f32::NAN); //~ ERROR: cannot be represented in target type `u32`
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_32_nan.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_nan.stderr
new file mode 100644
index 0000000000000..88b8948b0c29e
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_nan.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on NaN which cannot be represented in target type `u32`
+  --> $DIR/float_to_int_32_nan.rs:LL:CC
+   |
+LL |         float_to_int_unchecked::<f32, u32>(f32::NAN);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on NaN which cannot be represented in target type `u32`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/float_to_int_32_nan.rs:LL:CC
+
+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/intrinsics/float_to_int_32_nanneg.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_nanneg.rs
new file mode 100644
index 0000000000000..71436eb3ba846
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_nanneg.rs
@@ -0,0 +1,12 @@
+#![feature(intrinsics)]
+
+// Directly call intrinsic to avoid debug assertions in libstd
+extern "rust-intrinsic" {
+    fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
+}
+
+fn main() {
+    unsafe {
+        float_to_int_unchecked::<f32, u32>(-f32::NAN); //~ ERROR: cannot be represented in target type `u32`
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_32_nanneg.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_nanneg.stderr
new file mode 100644
index 0000000000000..ca798dd391aa9
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_nanneg.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on NaN which cannot be represented in target type `u32`
+  --> $DIR/float_to_int_32_nanneg.rs:LL:CC
+   |
+LL |         float_to_int_unchecked::<f32, u32>(-f32::NAN);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on NaN which cannot be represented in target type `u32`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/float_to_int_32_nanneg.rs:LL:CC
+
+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/intrinsics/float_to_int_32_neg.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_neg.rs
new file mode 100644
index 0000000000000..98ba964e47c2a
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_neg.rs
@@ -0,0 +1,12 @@
+#![feature(intrinsics)]
+
+// Directly call intrinsic to avoid debug assertions in libstd
+extern "rust-intrinsic" {
+    fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
+}
+
+fn main() {
+    unsafe {
+        float_to_int_unchecked::<f32, u32>(-1.000000001f32); //~ ERROR: cannot be represented in target type `u32`
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_32_neg.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_neg.stderr
new file mode 100644
index 0000000000000..4ff6eb8098540
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_neg.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on -1 which cannot be represented in target type `u32`
+  --> $DIR/float_to_int_32_neg.rs:LL:CC
+   |
+LL |         float_to_int_unchecked::<f32, u32>(-1.000000001f32);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on -1 which cannot be represented in target type `u32`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/float_to_int_32_neg.rs:LL:CC
+
+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/intrinsics/float_to_int_32_too_big1.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_big1.rs
new file mode 100644
index 0000000000000..424b8fd965e9c
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_big1.rs
@@ -0,0 +1,12 @@
+#![feature(intrinsics)]
+
+// Directly call intrinsic to avoid debug assertions in libstd
+extern "rust-intrinsic" {
+    fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
+}
+
+fn main() {
+    unsafe {
+        float_to_int_unchecked::<f32, i32>(2147483648.0f32); //~ ERROR: cannot be represented in target type `i32`
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_big1.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_big1.stderr
new file mode 100644
index 0000000000000..fd17709d164b1
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_big1.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on 2.14748365E+9 which cannot be represented in target type `i32`
+  --> $DIR/float_to_int_32_too_big1.rs:LL:CC
+   |
+LL |         float_to_int_unchecked::<f32, i32>(2147483648.0f32);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on 2.14748365E+9 which cannot be represented in target type `i32`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/float_to_int_32_too_big1.rs:LL:CC
+
+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/intrinsics/float_to_int_32_too_big2.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_big2.rs
new file mode 100644
index 0000000000000..5c50926c4df3e
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_big2.rs
@@ -0,0 +1,12 @@
+#![feature(intrinsics)]
+
+// Directly call intrinsic to avoid debug assertions in libstd
+extern "rust-intrinsic" {
+    fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
+}
+
+fn main() {
+    unsafe {
+        float_to_int_unchecked::<f32, u32>((u32::MAX - 127) as f32); //~ ERROR: cannot be represented in target type `u32`
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_big2.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_big2.stderr
new file mode 100644
index 0000000000000..fdc1f65dc1485
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_big2.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on 4.2949673E+9 which cannot be represented in target type `u32`
+  --> $DIR/float_to_int_32_too_big2.rs:LL:CC
+   |
+LL |         float_to_int_unchecked::<f32, u32>((u32::MAX - 127) as f32);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on 4.2949673E+9 which cannot be represented in target type `u32`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/float_to_int_32_too_big2.rs:LL:CC
+
+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/intrinsics/float_to_int_32_too_small1.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_small1.rs
new file mode 100644
index 0000000000000..e0abd19d03fc9
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_small1.rs
@@ -0,0 +1,12 @@
+#![feature(intrinsics)]
+
+// Directly call intrinsic to avoid debug assertions in libstd
+extern "rust-intrinsic" {
+    fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
+}
+
+fn main() {
+    unsafe {
+        float_to_int_unchecked::<f32, i32>(-2147483904.0f32); //~ ERROR: cannot be represented in target type `i32`
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_small1.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_small1.stderr
new file mode 100644
index 0000000000000..9e743a3214449
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_small1.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on -2.1474839E+9 which cannot be represented in target type `i32`
+  --> $DIR/float_to_int_32_too_small1.rs:LL:CC
+   |
+LL |         float_to_int_unchecked::<f32, i32>(-2147483904.0f32);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on -2.1474839E+9 which cannot be represented in target type `i32`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/float_to_int_32_too_small1.rs:LL:CC
+
+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/intrinsics/float_to_int_64_inf1.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_inf1.rs
new file mode 100644
index 0000000000000..f5f842e58ece4
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_inf1.rs
@@ -0,0 +1,12 @@
+#![feature(intrinsics)]
+
+// Directly call intrinsic to avoid debug assertions in libstd
+extern "rust-intrinsic" {
+    fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
+}
+
+fn main() {
+    unsafe {
+        float_to_int_unchecked::<f64, u128>(f64::INFINITY); //~ ERROR: cannot be represented in target type `u128`
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_inf1.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_inf1.stderr
new file mode 100644
index 0000000000000..ee01143dc8dfc
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_inf1.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on +Inf which cannot be represented in target type `u128`
+  --> $DIR/float_to_int_64_inf1.rs:LL:CC
+   |
+LL |         float_to_int_unchecked::<f64, u128>(f64::INFINITY);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on +Inf which cannot be represented in target type `u128`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/float_to_int_64_inf1.rs:LL:CC
+
+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/intrinsics/float_to_int_64_infneg1.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_infneg1.rs
new file mode 100644
index 0000000000000..244c25b31cbfc
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_infneg1.rs
@@ -0,0 +1,12 @@
+#![feature(intrinsics)]
+
+// Directly call intrinsic to avoid debug assertions in libstd
+extern "rust-intrinsic" {
+    fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
+}
+
+fn main() {
+    unsafe {
+        float_to_int_unchecked::<f64, u128>(f64::NEG_INFINITY); //~ ERROR: cannot be represented in target type `u128`
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_infneg1.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_infneg1.stderr
new file mode 100644
index 0000000000000..f37b8ae550643
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_infneg1.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on -Inf which cannot be represented in target type `u128`
+  --> $DIR/float_to_int_64_infneg1.rs:LL:CC
+   |
+LL |         float_to_int_unchecked::<f64, u128>(f64::NEG_INFINITY);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on -Inf which cannot be represented in target type `u128`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/float_to_int_64_infneg1.rs:LL:CC
+
+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/intrinsics/float_to_int_64_infneg2.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_infneg2.rs
new file mode 100644
index 0000000000000..f7a663d12a56c
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_infneg2.rs
@@ -0,0 +1,12 @@
+#![feature(intrinsics)]
+
+// Directly call intrinsic to avoid debug assertions in libstd
+extern "rust-intrinsic" {
+    fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
+}
+
+fn main() {
+    unsafe {
+        float_to_int_unchecked::<f64, i128>(f64::NEG_INFINITY); //~ ERROR: cannot be represented in target type `i128`
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_infneg2.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_infneg2.stderr
new file mode 100644
index 0000000000000..05dcd5ebcf69a
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_infneg2.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on -Inf which cannot be represented in target type `i128`
+  --> $DIR/float_to_int_64_infneg2.rs:LL:CC
+   |
+LL |         float_to_int_unchecked::<f64, i128>(f64::NEG_INFINITY);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on -Inf which cannot be represented in target type `i128`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/float_to_int_64_infneg2.rs:LL:CC
+
+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/intrinsics/float_to_int_64_nan.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_nan.rs
new file mode 100644
index 0000000000000..171cbcc59344f
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_nan.rs
@@ -0,0 +1,12 @@
+#![feature(intrinsics)]
+
+// Directly call intrinsic to avoid debug assertions in libstd
+extern "rust-intrinsic" {
+    fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
+}
+
+fn main() {
+    unsafe {
+        float_to_int_unchecked::<f64, u32>(f64::NAN); //~ ERROR: cannot be represented in target type `u32`
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_nan.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_nan.stderr
new file mode 100644
index 0000000000000..0a914abb2ce78
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_nan.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on NaN which cannot be represented in target type `u32`
+  --> $DIR/float_to_int_64_nan.rs:LL:CC
+   |
+LL |         float_to_int_unchecked::<f64, u32>(f64::NAN);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on NaN which cannot be represented in target type `u32`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/float_to_int_64_nan.rs:LL:CC
+
+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/intrinsics/float_to_int_64_neg.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_neg.rs
new file mode 100644
index 0000000000000..40b67e173b97a
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_neg.rs
@@ -0,0 +1,12 @@
+#![feature(intrinsics)]
+
+// Directly call intrinsic to avoid debug assertions in libstd
+extern "rust-intrinsic" {
+    fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
+}
+
+fn main() {
+    unsafe {
+        float_to_int_unchecked::<f64, u128>(-1.0000000000001f64); //~ ERROR: cannot be represented in target type `u128`
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_neg.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_neg.stderr
new file mode 100644
index 0000000000000..7e24f45f653d1
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_neg.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on -1 which cannot be represented in target type `u128`
+  --> $DIR/float_to_int_64_neg.rs:LL:CC
+   |
+LL |         float_to_int_unchecked::<f64, u128>(-1.0000000000001f64);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on -1 which cannot be represented in target type `u128`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/float_to_int_64_neg.rs:LL:CC
+
+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/intrinsics/float_to_int_64_too_big1.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big1.rs
new file mode 100644
index 0000000000000..e785123c4ca91
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big1.rs
@@ -0,0 +1,12 @@
+#![feature(intrinsics)]
+
+// Directly call intrinsic to avoid debug assertions in libstd
+extern "rust-intrinsic" {
+    fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
+}
+
+fn main() {
+    unsafe {
+        float_to_int_unchecked::<f64, i32>(2147483648.0f64); //~ ERROR: cannot be represented in target type `i32`
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big1.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big1.stderr
new file mode 100644
index 0000000000000..42da33321f371
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big1.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on 2147483648 which cannot be represented in target type `i32`
+  --> $DIR/float_to_int_64_too_big1.rs:LL:CC
+   |
+LL |         float_to_int_unchecked::<f64, i32>(2147483648.0f64);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on 2147483648 which cannot be represented in target type `i32`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/float_to_int_64_too_big1.rs:LL:CC
+
+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/intrinsics/float_to_int_64_too_big2.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big2.rs
new file mode 100644
index 0000000000000..4bf31d8ac0271
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big2.rs
@@ -0,0 +1,12 @@
+#![feature(intrinsics)]
+
+// Directly call intrinsic to avoid debug assertions in libstd
+extern "rust-intrinsic" {
+    fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
+}
+
+fn main() {
+    unsafe {
+        float_to_int_unchecked::<f64, i64>(9223372036854775808.0f64); //~ ERROR: cannot be represented in target type `i64`
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big2.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big2.stderr
new file mode 100644
index 0000000000000..af4c4ceb3f73f
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big2.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on 9.2233720368547758E+18 which cannot be represented in target type `i64`
+  --> $DIR/float_to_int_64_too_big2.rs:LL:CC
+   |
+LL |         float_to_int_unchecked::<f64, i64>(9223372036854775808.0f64);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on 9.2233720368547758E+18 which cannot be represented in target type `i64`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/float_to_int_64_too_big2.rs:LL:CC
+
+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/intrinsics/float_to_int_64_too_big3.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big3.rs
new file mode 100644
index 0000000000000..9775a56724bc2
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big3.rs
@@ -0,0 +1,12 @@
+#![feature(intrinsics)]
+
+// Directly call intrinsic to avoid debug assertions in libstd
+extern "rust-intrinsic" {
+    fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
+}
+
+fn main() {
+    unsafe {
+        float_to_int_unchecked::<f64, u64>(18446744073709551616.0f64); //~ ERROR: cannot be represented in target type `u64`
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big3.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big3.stderr
new file mode 100644
index 0000000000000..6e384a6fbc7cb
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big3.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on 1.8446744073709552E+19 which cannot be represented in target type `u64`
+  --> $DIR/float_to_int_64_too_big3.rs:LL:CC
+   |
+LL |         float_to_int_unchecked::<f64, u64>(18446744073709551616.0f64);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on 1.8446744073709552E+19 which cannot be represented in target type `u64`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/float_to_int_64_too_big3.rs:LL:CC
+
+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/intrinsics/float_to_int_64_too_big4.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big4.rs
new file mode 100644
index 0000000000000..53ff06e1e4678
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big4.rs
@@ -0,0 +1,12 @@
+#![feature(intrinsics)]
+
+// Directly call intrinsic to avoid debug assertions in libstd
+extern "rust-intrinsic" {
+    fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
+}
+
+fn main() {
+    unsafe {
+        float_to_int_unchecked::<f64, u128>(u128::MAX as f64); //~ ERROR: cannot be represented in target type `u128`
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big4.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big4.stderr
new file mode 100644
index 0000000000000..77f05ff91e3b5
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big4.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on 3.4028236692093846E+38 which cannot be represented in target type `u128`
+  --> $DIR/float_to_int_64_too_big4.rs:LL:CC
+   |
+LL |         float_to_int_unchecked::<f64, u128>(u128::MAX as f64);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on 3.4028236692093846E+38 which cannot be represented in target type `u128`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/float_to_int_64_too_big4.rs:LL:CC
+
+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/intrinsics/float_to_int_64_too_big5.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big5.rs
new file mode 100644
index 0000000000000..44356ff1771b1
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big5.rs
@@ -0,0 +1,12 @@
+#![feature(intrinsics)]
+
+// Directly call intrinsic to avoid debug assertions in libstd
+extern "rust-intrinsic" {
+    fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
+}
+
+fn main() {
+    unsafe {
+        float_to_int_unchecked::<f64, i128>(240282366920938463463374607431768211455.0f64); //~ ERROR: cannot be represented in target type `i128`
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big5.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big5.stderr
new file mode 100644
index 0000000000000..cb5eba490b447
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big5.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on 2.4028236692093845E+38 which cannot be represented in target type `i128`
+  --> $DIR/float_to_int_64_too_big5.rs:LL:CC
+   |
+LL |         float_to_int_unchecked::<f64, i128>(240282366920938463463374607431768211455.0f64);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on 2.4028236692093845E+38 which cannot be represented in target type `i128`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/float_to_int_64_too_big5.rs:LL:CC
+
+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/intrinsics/float_to_int_64_too_big6.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big6.rs
new file mode 100644
index 0000000000000..66f5be96bfd0e
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big6.rs
@@ -0,0 +1,12 @@
+#![feature(intrinsics)]
+
+// Directly call intrinsic to avoid debug assertions in libstd
+extern "rust-intrinsic" {
+    fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
+}
+
+fn main() {
+    unsafe {
+        float_to_int_unchecked::<f64, u128>(f64::MAX); //~ ERROR: cannot be represented in target type `u128`
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big6.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big6.stderr
new file mode 100644
index 0000000000000..d899d2f808a5d
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big6.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on 1.7976931348623157E+308 which cannot be represented in target type `u128`
+  --> $DIR/float_to_int_64_too_big6.rs:LL:CC
+   |
+LL |         float_to_int_unchecked::<f64, u128>(f64::MAX);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on 1.7976931348623157E+308 which cannot be represented in target type `u128`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/float_to_int_64_too_big6.rs:LL:CC
+
+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/intrinsics/float_to_int_64_too_big7.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big7.rs
new file mode 100644
index 0000000000000..18b380e8575ee
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big7.rs
@@ -0,0 +1,12 @@
+#![feature(intrinsics)]
+
+// Directly call intrinsic to avoid debug assertions in libstd
+extern "rust-intrinsic" {
+    fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
+}
+
+fn main() {
+    unsafe {
+        float_to_int_unchecked::<f64, i128>(f64::MIN); //~ ERROR: cannot be represented in target type `i128`
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big7.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big7.stderr
new file mode 100644
index 0000000000000..443b2759c2606
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big7.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on -1.7976931348623157E+308 which cannot be represented in target type `i128`
+  --> $DIR/float_to_int_64_too_big7.rs:LL:CC
+   |
+LL |         float_to_int_unchecked::<f64, i128>(f64::MIN);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on -1.7976931348623157E+308 which cannot be represented in target type `i128`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/float_to_int_64_too_big7.rs:LL:CC
+
+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/intrinsics/float_to_int_64_too_small1.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small1.rs
new file mode 100644
index 0000000000000..2a23b1dc8a4b8
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small1.rs
@@ -0,0 +1,12 @@
+#![feature(intrinsics)]
+
+// Directly call intrinsic to avoid debug assertions in libstd
+extern "rust-intrinsic" {
+    fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
+}
+
+fn main() {
+    unsafe {
+        float_to_int_unchecked::<f64, i32>(-2147483649.0f64); //~ ERROR: cannot be represented in target type `i32`
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small1.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small1.stderr
new file mode 100644
index 0000000000000..f8d88c44aa80d
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small1.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on -2147483649 which cannot be represented in target type `i32`
+  --> $DIR/float_to_int_64_too_small1.rs:LL:CC
+   |
+LL |         float_to_int_unchecked::<f64, i32>(-2147483649.0f64);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on -2147483649 which cannot be represented in target type `i32`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/float_to_int_64_too_small1.rs:LL:CC
+
+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/intrinsics/float_to_int_64_too_small2.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small2.rs
new file mode 100644
index 0000000000000..7fc3effda5dff
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small2.rs
@@ -0,0 +1,12 @@
+#![feature(intrinsics)]
+
+// Directly call intrinsic to avoid debug assertions in libstd
+extern "rust-intrinsic" {
+    fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
+}
+
+fn main() {
+    unsafe {
+        float_to_int_unchecked::<f64, i64>(-9223372036854777856.0f64); //~ ERROR: cannot be represented in target type `i64`
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small2.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small2.stderr
new file mode 100644
index 0000000000000..d94e57b1e67c2
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small2.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on -9.2233720368547778E+18 which cannot be represented in target type `i64`
+  --> $DIR/float_to_int_64_too_small2.rs:LL:CC
+   |
+LL |         float_to_int_unchecked::<f64, i64>(-9223372036854777856.0f64);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on -9.2233720368547778E+18 which cannot be represented in target type `i64`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/float_to_int_64_too_small2.rs:LL:CC
+
+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/intrinsics/float_to_int_64_too_small3.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small3.rs
new file mode 100644
index 0000000000000..2a8f9c366425d
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small3.rs
@@ -0,0 +1,12 @@
+#![feature(intrinsics)]
+
+// Directly call intrinsic to avoid debug assertions in libstd
+extern "rust-intrinsic" {
+    fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
+}
+
+fn main() {
+    unsafe {
+        float_to_int_unchecked::<f64, i128>(-240282366920938463463374607431768211455.0f64); //~ ERROR: cannot be represented in target type `i128`
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small3.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small3.stderr
new file mode 100644
index 0000000000000..59b74f5f51f37
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small3.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on -2.4028236692093845E+38 which cannot be represented in target type `i128`
+  --> $DIR/float_to_int_64_too_small3.rs:LL:CC
+   |
+LL |         float_to_int_unchecked::<f64, i128>(-240282366920938463463374607431768211455.0f64);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on -2.4028236692093845E+38 which cannot be represented in target type `i128`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/float_to_int_64_too_small3.rs:LL:CC
+
+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/intrinsics/out_of_bounds_ptr_1.rs b/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_1.rs
new file mode 100644
index 0000000000000..b6a110ee84d2c
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_1.rs
@@ -0,0 +1,7 @@
+fn main() {
+    let v = [0i8; 4];
+    let x = &v as *const i8;
+    // The error is inside another function, so we cannot match it by line
+    let x = unsafe { x.offset(5) }; //~ERROR: pointer to 5 bytes starting at offset 0 is out-of-bounds
+    panic!("this should never print: {:?}", x);
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_1.stderr b/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_1.stderr
new file mode 100644
index 0000000000000..4422310870a64
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_1.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: out-of-bounds pointer arithmetic: ALLOC has size 4, so pointer to 5 bytes starting at offset 0 is out-of-bounds
+  --> $DIR/out_of_bounds_ptr_1.rs:LL:CC
+   |
+LL |     let x = unsafe { x.offset(5) };
+   |                      ^^^^^^^^^^^ out-of-bounds pointer arithmetic: ALLOC has size 4, so pointer to 5 bytes starting at offset 0 is out-of-bounds
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/out_of_bounds_ptr_1.rs:LL:CC
+
+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/intrinsics/out_of_bounds_ptr_2.rs b/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_2.rs
new file mode 100644
index 0000000000000..0d4eea9a5bdea
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_2.rs
@@ -0,0 +1,6 @@
+fn main() {
+    let v = [0i8; 4];
+    let x = &v as *const i8;
+    let x = unsafe { x.offset(isize::MIN) }; //~ERROR: overflowing in-bounds pointer arithmetic
+    panic!("this should never print: {:?}", x);
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_2.stderr b/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_2.stderr
new file mode 100644
index 0000000000000..6a11ebae108f0
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_2.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: overflowing in-bounds pointer arithmetic
+  --> $DIR/out_of_bounds_ptr_2.rs:LL:CC
+   |
+LL |     let x = unsafe { x.offset(isize::MIN) };
+   |                      ^^^^^^^^^^^^^^^^^^^^ overflowing in-bounds pointer arithmetic
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/out_of_bounds_ptr_2.rs:LL:CC
+
+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/intrinsics/out_of_bounds_ptr_3.rs b/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_3.rs
new file mode 100644
index 0000000000000..701bc33a645e1
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_3.rs
@@ -0,0 +1,6 @@
+fn main() {
+    let v = [0i8; 4];
+    let x = &v as *const i8;
+    let x = unsafe { x.offset(-1) }; //~ERROR: pointer to 1 byte starting at offset -1 is out-of-bounds
+    panic!("this should never print: {:?}", x);
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_3.stderr b/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_3.stderr
new file mode 100644
index 0000000000000..1364e0f9009d8
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_3.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: out-of-bounds pointer arithmetic: ALLOC has size 4, so pointer to 1 byte starting at offset -1 is out-of-bounds
+  --> $DIR/out_of_bounds_ptr_3.rs:LL:CC
+   |
+LL |     let x = unsafe { x.offset(-1) };
+   |                      ^^^^^^^^^^^^ out-of-bounds pointer arithmetic: ALLOC has size 4, so pointer to 1 byte starting at offset -1 is out-of-bounds
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/out_of_bounds_ptr_3.rs:LL:CC
+
+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/intrinsics/overflowing-unchecked-rsh.rs b/src/tools/miri/tests/fail/intrinsics/overflowing-unchecked-rsh.rs
new file mode 100644
index 0000000000000..fe2e85be69868
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/overflowing-unchecked-rsh.rs
@@ -0,0 +1,8 @@
+#![feature(unchecked_math)]
+
+fn main() {
+    unsafe {
+        let _n = 1i64.unchecked_shr(64);
+        //~^ ERROR: overflowing shift by 64 in `unchecked_shr`
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/overflowing-unchecked-rsh.stderr b/src/tools/miri/tests/fail/intrinsics/overflowing-unchecked-rsh.stderr
new file mode 100644
index 0000000000000..9c5d0d13108ce
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/overflowing-unchecked-rsh.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: overflowing shift by 64 in `unchecked_shr`
+  --> $DIR/overflowing-unchecked-rsh.rs:LL:CC
+   |
+LL |         let _n = 1i64.unchecked_shr(64);
+   |                  ^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by 64 in `unchecked_shr`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/overflowing-unchecked-rsh.rs:LL:CC
+
+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/intrinsics/ptr_offset_0_plus_0.rs b/src/tools/miri/tests/fail/intrinsics/ptr_offset_0_plus_0.rs
new file mode 100644
index 0000000000000..e2329c1313984
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_0_plus_0.rs
@@ -0,0 +1,9 @@
+//@compile-flags: -Zmiri-permissive-provenance
+
+#[rustfmt::skip] // fails with "left behind trailing whitespace"
+fn main() {
+    let x = 0 as *mut i32;
+    let _x = x.wrapping_offset(8); // ok, this has no inbounds tag
+    let _x = unsafe { x.offset(0) }; // UB despite offset 0, NULL is never inbounds
+    //~^ERROR: null pointer is a dangling pointer
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_0_plus_0.stderr b/src/tools/miri/tests/fail/intrinsics/ptr_offset_0_plus_0.stderr
new file mode 100644
index 0000000000000..9c1c387d54991
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_0_plus_0.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: out-of-bounds pointer arithmetic: null pointer is a dangling pointer (it has no provenance)
+  --> $DIR/ptr_offset_0_plus_0.rs:LL:CC
+   |
+LL |     let _x = unsafe { x.offset(0) }; // UB despite offset 0, NULL is never inbounds
+   |                       ^^^^^^^^^^^ out-of-bounds pointer arithmetic: null pointer is a dangling pointer (it has no provenance)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/ptr_offset_0_plus_0.rs:LL:CC
+
+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/intrinsics/ptr_offset_from_oob.rs b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_oob.rs
new file mode 100644
index 0000000000000..0e5acf08b2030
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_oob.rs
@@ -0,0 +1,7 @@
+fn main() {
+    let start_ptr = &4 as *const _ as *const u8;
+    let length = 10;
+    let end_ptr = start_ptr.wrapping_add(length);
+    // Even if the offset is 0, a dangling OOB pointer is not allowed.
+    unsafe { end_ptr.offset_from(end_ptr) }; //~ERROR: pointer at offset 10 is out-of-bounds
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_oob.stderr b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_oob.stderr
new file mode 100644
index 0000000000000..a31b929d7a7ae
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_oob.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: out-of-bounds offset_from: ALLOC has size 4, so pointer at offset 10 is out-of-bounds
+  --> $DIR/ptr_offset_from_oob.rs:LL:CC
+   |
+LL |     unsafe { end_ptr.offset_from(end_ptr) };
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds offset_from: ALLOC has size 4, so pointer at offset 10 is out-of-bounds
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/ptr_offset_from_oob.rs:LL:CC
+
+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/intrinsics/ptr_offset_from_unsigned_neg.rs b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_unsigned_neg.rs
new file mode 100644
index 0000000000000..06d13d9bdbaf3
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_unsigned_neg.rs
@@ -0,0 +1,8 @@
+#![feature(ptr_sub_ptr)]
+
+fn main() {
+    let arr = [0u8; 8];
+    let ptr1 = arr.as_ptr();
+    let ptr2 = ptr1.wrapping_add(4);
+    let _val = unsafe { ptr1.sub_ptr(ptr2) }; //~ERROR: first pointer has smaller offset than second: 0 < 4
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_unsigned_neg.stderr b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_unsigned_neg.stderr
new file mode 100644
index 0000000000000..803aaaa55c21e
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_unsigned_neg.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `ptr_offset_from_unsigned` called when first pointer has smaller offset than second: 0 < 4
+  --> $DIR/ptr_offset_from_unsigned_neg.rs:LL:CC
+   |
+LL |     let _val = unsafe { ptr1.sub_ptr(ptr2) };
+   |                         ^^^^^^^^^^^^^^^^^^ `ptr_offset_from_unsigned` called when first pointer has smaller offset than second: 0 < 4
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/ptr_offset_from_unsigned_neg.rs:LL:CC
+
+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/intrinsics/ptr_offset_int_plus_int.rs b/src/tools/miri/tests/fail/intrinsics/ptr_offset_int_plus_int.rs
new file mode 100644
index 0000000000000..19bd265c14351
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_int_plus_int.rs
@@ -0,0 +1,8 @@
+//@compile-flags: -Zmiri-permissive-provenance
+
+fn main() {
+    // Can't offset an integer pointer by non-zero offset.
+    unsafe {
+        let _val = (1 as *mut u8).offset(1); //~ERROR: is a dangling pointer
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_int_plus_int.stderr b/src/tools/miri/tests/fail/intrinsics/ptr_offset_int_plus_int.stderr
new file mode 100644
index 0000000000000..f76881011d079
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_int_plus_int.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: out-of-bounds pointer arithmetic: 0x1[noalloc] is a dangling pointer (it has no provenance)
+  --> $DIR/ptr_offset_int_plus_int.rs:LL:CC
+   |
+LL |         let _val = (1 as *mut u8).offset(1);
+   |                    ^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer arithmetic: 0x1[noalloc] is a dangling pointer (it has no provenance)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/ptr_offset_int_plus_int.rs:LL:CC
+
+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/intrinsics/ptr_offset_int_plus_ptr.rs b/src/tools/miri/tests/fail/intrinsics/ptr_offset_int_plus_ptr.rs
new file mode 100644
index 0000000000000..fd3c9b44615c2
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_int_plus_ptr.rs
@@ -0,0 +1,9 @@
+//@compile-flags: -Zmiri-permissive-provenance
+
+fn main() {
+    let ptr = Box::into_raw(Box::new(0u32));
+    // Can't start with an integer pointer and get to something usable
+    unsafe {
+        let _val = (1 as *mut u8).offset(ptr as isize); //~ERROR: is a dangling pointer
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_int_plus_ptr.stderr b/src/tools/miri/tests/fail/intrinsics/ptr_offset_int_plus_ptr.stderr
new file mode 100644
index 0000000000000..6e0744b7d5c39
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_int_plus_ptr.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: out-of-bounds pointer arithmetic: 0x1[noalloc] is a dangling pointer (it has no provenance)
+  --> $DIR/ptr_offset_int_plus_ptr.rs:LL:CC
+   |
+LL |         let _val = (1 as *mut u8).offset(ptr as isize);
+   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer arithmetic: 0x1[noalloc] is a dangling pointer (it has no provenance)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/ptr_offset_int_plus_ptr.rs:LL:CC
+
+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/intrinsics/ptr_offset_overflow.rs b/src/tools/miri/tests/fail/intrinsics/ptr_offset_overflow.rs
new file mode 100644
index 0000000000000..c3db1e23b9bfb
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_overflow.rs
@@ -0,0 +1,5 @@
+fn main() {
+    let v = [1i8, 2];
+    let x = &v[1] as *const i8;
+    let _val = unsafe { x.offset(isize::MIN) }; //~ERROR: overflowing in-bounds pointer arithmetic
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_overflow.stderr b/src/tools/miri/tests/fail/intrinsics/ptr_offset_overflow.stderr
new file mode 100644
index 0000000000000..6fb94cf5f8122
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_overflow.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: overflowing in-bounds pointer arithmetic
+  --> $DIR/ptr_offset_overflow.rs:LL:CC
+   |
+LL |     let _val = unsafe { x.offset(isize::MIN) };
+   |                         ^^^^^^^^^^^^^^^^^^^^ overflowing in-bounds pointer arithmetic
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/ptr_offset_overflow.rs:LL:CC
+
+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/intrinsics/ptr_offset_ptr_plus_0.rs b/src/tools/miri/tests/fail/intrinsics/ptr_offset_ptr_plus_0.rs
new file mode 100644
index 0000000000000..575e28854b1a9
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_ptr_plus_0.rs
@@ -0,0 +1,7 @@
+#[rustfmt::skip] // fails with "left behind trailing whitespace"
+fn main() {
+    let x = Box::into_raw(Box::new(0u32));
+    let x = x.wrapping_offset(8); // ok, this has no inbounds tag
+    let _x = unsafe { x.offset(0) }; // UB despite offset 0, the pointer is not inbounds of the only object it can point to
+    //~^ERROR: pointer at offset 32 is out-of-bounds
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_ptr_plus_0.stderr b/src/tools/miri/tests/fail/intrinsics/ptr_offset_ptr_plus_0.stderr
new file mode 100644
index 0000000000000..b18147ce379d7
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_ptr_plus_0.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: out-of-bounds pointer arithmetic: ALLOC has size 4, so pointer at offset 32 is out-of-bounds
+  --> $DIR/ptr_offset_ptr_plus_0.rs:LL:CC
+   |
+LL |     let _x = unsafe { x.offset(0) }; // UB despite offset 0, the pointer is not inbounds of the only object it can point to
+   |                       ^^^^^^^^^^^ out-of-bounds pointer arithmetic: ALLOC has size 4, so pointer at offset 32 is out-of-bounds
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/ptr_offset_ptr_plus_0.rs:LL:CC
+
+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/intrinsics/raw_eq_on_ptr.rs b/src/tools/miri/tests/fail/intrinsics/raw_eq_on_ptr.rs
new file mode 100644
index 0000000000000..c14f86147dbb9
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/raw_eq_on_ptr.rs
@@ -0,0 +1,10 @@
+#![feature(intrinsics)]
+
+extern "rust-intrinsic" {
+    fn raw_eq<T>(a: &T, b: &T) -> bool;
+}
+
+fn main() {
+    let x = &0;
+    unsafe { raw_eq(&x, &x) }; //~ERROR: `raw_eq` on bytes with provenance
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/raw_eq_on_ptr.stderr b/src/tools/miri/tests/fail/intrinsics/raw_eq_on_ptr.stderr
new file mode 100644
index 0000000000000..2236ad9839c5e
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/raw_eq_on_ptr.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `raw_eq` on bytes with provenance
+  --> $DIR/raw_eq_on_ptr.rs:LL:CC
+   |
+LL |     unsafe { raw_eq(&x, &x) };
+   |              ^^^^^^^^^^^^^^ `raw_eq` on bytes with provenance
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/raw_eq_on_ptr.rs:LL:CC
+
+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/intrinsics/rem-by-zero.rs b/src/tools/miri/tests/fail/intrinsics/rem-by-zero.rs
new file mode 100644
index 0000000000000..ac80852e8dcfc
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/rem-by-zero.rs
@@ -0,0 +1,9 @@
+#![feature(core_intrinsics)]
+
+use std::intrinsics::*;
+
+fn main() {
+    unsafe {
+        let _n = unchecked_rem(3u32, 0); //~ ERROR: calculating the remainder with a divisor of zero
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/rem-by-zero.stderr b/src/tools/miri/tests/fail/intrinsics/rem-by-zero.stderr
new file mode 100644
index 0000000000000..1fc39188e5a94
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/rem-by-zero.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: calculating the remainder with a divisor of zero
+  --> $DIR/rem-by-zero.rs:LL:CC
+   |
+LL |         let _n = unchecked_rem(3u32, 0);
+   |                  ^^^^^^^^^^^^^^^^^^^^^^ calculating the remainder with a divisor of zero
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/rem-by-zero.rs:LL:CC
+
+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/intrinsics/simd-div-by-zero.rs b/src/tools/miri/tests/fail/intrinsics/simd-div-by-zero.rs
new file mode 100644
index 0000000000000..5fa6f69d00593
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/simd-div-by-zero.rs
@@ -0,0 +1,17 @@
+#![feature(platform_intrinsics, repr_simd)]
+
+extern "platform-intrinsic" {
+    pub(crate) fn simd_div<T>(x: T, y: T) -> T;
+}
+
+#[repr(simd)]
+#[allow(non_camel_case_types)]
+struct i32x2(i32, i32);
+
+fn main() {
+    unsafe {
+        let x = i32x2(1, 1);
+        let y = i32x2(1, 0);
+        simd_div(x, y); //~ERROR: Undefined Behavior: dividing by zero
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/simd-div-by-zero.stderr b/src/tools/miri/tests/fail/intrinsics/simd-div-by-zero.stderr
new file mode 100644
index 0000000000000..ddab24d0c1639
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/simd-div-by-zero.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: dividing by zero
+  --> $DIR/simd-div-by-zero.rs:LL:CC
+   |
+LL |         simd_div(x, y);
+   |         ^^^^^^^^^^^^^^ dividing by zero
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/simd-div-by-zero.rs:LL:CC
+
+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/intrinsics/simd-div-overflow.rs b/src/tools/miri/tests/fail/intrinsics/simd-div-overflow.rs
new file mode 100644
index 0000000000000..57712b1b836b5
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/simd-div-overflow.rs
@@ -0,0 +1,17 @@
+#![feature(platform_intrinsics, repr_simd)]
+
+extern "platform-intrinsic" {
+    pub(crate) fn simd_div<T>(x: T, y: T) -> T;
+}
+
+#[repr(simd)]
+#[allow(non_camel_case_types)]
+struct i32x2(i32, i32);
+
+fn main() {
+    unsafe {
+        let x = i32x2(1, i32::MIN);
+        let y = i32x2(1, -1);
+        simd_div(x, y); //~ERROR: Undefined Behavior: overflow in signed division
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/simd-div-overflow.stderr b/src/tools/miri/tests/fail/intrinsics/simd-div-overflow.stderr
new file mode 100644
index 0000000000000..27d4dd9e3e73f
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/simd-div-overflow.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: overflow in signed division (dividing MIN by -1)
+  --> $DIR/simd-div-overflow.rs:LL:CC
+   |
+LL |         simd_div(x, y);
+   |         ^^^^^^^^^^^^^^ overflow in signed division (dividing MIN by -1)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/simd-div-overflow.rs:LL:CC
+
+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/intrinsics/simd-float-to-int.rs b/src/tools/miri/tests/fail/intrinsics/simd-float-to-int.rs
new file mode 100644
index 0000000000000..a5bae36d92a46
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/simd-float-to-int.rs
@@ -0,0 +1,9 @@
+//@error-pattern: cannot be represented in target type `i32`
+#![feature(portable_simd)]
+use std::simd::*;
+
+fn main() {
+    unsafe {
+        let _x: i32x2 = f32x2::from_array([f32::MAX, f32::MIN]).to_int_unchecked();
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/simd-float-to-int.stderr b/src/tools/miri/tests/fail/intrinsics/simd-float-to-int.stderr
new file mode 100644
index 0000000000000..36bb9643b48d4
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/simd-float-to-int.stderr
@@ -0,0 +1,20 @@
+error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on 3.40282347E+38 which cannot be represented in target type `i32`
+  --> RUSTLIB/core/src/../../portable-simd/crates/core_simd/src/vector.rs:LL:CC
+   |
+LL |         unsafe { intrinsics::simd_cast(self) }
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on 3.40282347E+38 which cannot be represented in target type `i32`
+   |
+   = 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 `std::simd::Simd::<f32, 2>::to_int_unchecked::<i32>` at RUSTLIB/core/src/../../portable-simd/crates/core_simd/src/vector.rs:LL:CC
+note: inside `main` at $DIR/simd-float-to-int.rs:LL:CC
+  --> $DIR/simd-float-to-int.rs:LL:CC
+   |
+LL |         let _x: i32x2 = f32x2::from_array([f32::MAX, f32::MIN]).to_int_unchecked();
+   |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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/intrinsics/simd-gather.rs b/src/tools/miri/tests/fail/intrinsics/simd-gather.rs
new file mode 100644
index 0000000000000..e394cce9a4fe3
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/simd-gather.rs
@@ -0,0 +1,11 @@
+//@error-pattern: pointer to 1 byte starting at offset 9 is out-of-bounds
+#![feature(portable_simd)]
+use std::simd::*;
+
+fn main() {
+    unsafe {
+        let vec: &[i8] = &[10, 11, 12, 13, 14, 15, 16, 17, 18];
+        let idxs = Simd::from_array([9, 3, 0, 17]);
+        let _result = Simd::gather_select_unchecked(&vec, Mask::splat(true), idxs, Simd::splat(0));
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/simd-gather.stderr b/src/tools/miri/tests/fail/intrinsics/simd-gather.stderr
new file mode 100644
index 0000000000000..29a4ef65705ab
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/simd-gather.stderr
@@ -0,0 +1,20 @@
+error: Undefined Behavior: dereferencing pointer failed: ALLOC has size 9, so pointer to 1 byte starting at offset 9 is out-of-bounds
+  --> RUSTLIB/core/src/../../portable-simd/crates/core_simd/src/vector.rs:LL:CC
+   |
+LL |         unsafe { intrinsics::simd_gather(or, ptrs, enable.to_int()) }
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ dereferencing pointer failed: ALLOC has size 9, so pointer to 1 byte starting at offset 9 is out-of-bounds
+   |
+   = 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 `std::simd::Simd::<i8, 4>::gather_select_unchecked` at RUSTLIB/core/src/../../portable-simd/crates/core_simd/src/vector.rs:LL:CC
+note: inside `main` at $DIR/simd-gather.rs:LL:CC
+  --> $DIR/simd-gather.rs:LL:CC
+   |
+LL |         let _result = Simd::gather_select_unchecked(&vec, Mask::splat(true), idxs, Simd::splat(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/intrinsics/simd-reduce-invalid-bool.rs b/src/tools/miri/tests/fail/intrinsics/simd-reduce-invalid-bool.rs
new file mode 100644
index 0000000000000..354f8213120a2
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/simd-reduce-invalid-bool.rs
@@ -0,0 +1,16 @@
+#![feature(platform_intrinsics, repr_simd)]
+
+extern "platform-intrinsic" {
+    pub(crate) fn simd_reduce_any<T>(x: T) -> bool;
+}
+
+#[repr(simd)]
+#[allow(non_camel_case_types)]
+struct i32x2(i32, i32);
+
+fn main() {
+    unsafe {
+        let x = i32x2(0, 1);
+        simd_reduce_any(x); //~ERROR: must be all-0-bits or all-1-bits
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/simd-reduce-invalid-bool.stderr b/src/tools/miri/tests/fail/intrinsics/simd-reduce-invalid-bool.stderr
new file mode 100644
index 0000000000000..1e5ac5277e6dc
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/simd-reduce-invalid-bool.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: each element of a SIMD mask must be all-0-bits or all-1-bits
+  --> $DIR/simd-reduce-invalid-bool.rs:LL:CC
+   |
+LL |         simd_reduce_any(x);
+   |         ^^^^^^^^^^^^^^^^^^ each element of a SIMD mask must be all-0-bits or all-1-bits
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/simd-reduce-invalid-bool.rs:LL:CC
+
+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/intrinsics/simd-rem-by-zero.rs b/src/tools/miri/tests/fail/intrinsics/simd-rem-by-zero.rs
new file mode 100644
index 0000000000000..625889bb67b57
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/simd-rem-by-zero.rs
@@ -0,0 +1,17 @@
+#![feature(platform_intrinsics, repr_simd)]
+
+extern "platform-intrinsic" {
+    pub(crate) fn simd_rem<T>(x: T, y: T) -> T;
+}
+
+#[repr(simd)]
+#[allow(non_camel_case_types)]
+struct i32x2(i32, i32);
+
+fn main() {
+    unsafe {
+        let x = i32x2(1, 1);
+        let y = i32x2(1, 0);
+        simd_rem(x, y); //~ERROR: Undefined Behavior: calculating the remainder with a divisor of zero
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/simd-rem-by-zero.stderr b/src/tools/miri/tests/fail/intrinsics/simd-rem-by-zero.stderr
new file mode 100644
index 0000000000000..96248e7e599cc
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/simd-rem-by-zero.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: calculating the remainder with a divisor of zero
+  --> $DIR/simd-rem-by-zero.rs:LL:CC
+   |
+LL |         simd_rem(x, y);
+   |         ^^^^^^^^^^^^^^ calculating the remainder with a divisor of zero
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/simd-rem-by-zero.rs:LL:CC
+
+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/intrinsics/simd-scatter.rs b/src/tools/miri/tests/fail/intrinsics/simd-scatter.rs
new file mode 100644
index 0000000000000..d2bc73399548b
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/simd-scatter.rs
@@ -0,0 +1,15 @@
+//@error-pattern: pointer to 1 byte starting at offset 9 is out-of-bounds
+#![feature(portable_simd)]
+use std::simd::*;
+
+fn main() {
+    unsafe {
+        let mut vec: Vec<i8> = vec![10, 11, 12, 13, 14, 15, 16, 17, 18];
+        let idxs = Simd::from_array([9, 3, 0, 17]);
+        Simd::from_array([-27, 82, -41, 124]).scatter_select_unchecked(
+            &mut vec,
+            Mask::splat(true),
+            idxs,
+        );
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/simd-scatter.stderr b/src/tools/miri/tests/fail/intrinsics/simd-scatter.stderr
new file mode 100644
index 0000000000000..fde85a63503b6
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/simd-scatter.stderr
@@ -0,0 +1,24 @@
+error: Undefined Behavior: dereferencing pointer failed: ALLOC has size 9, so pointer to 1 byte starting at offset 9 is out-of-bounds
+  --> RUSTLIB/core/src/../../portable-simd/crates/core_simd/src/vector.rs:LL:CC
+   |
+LL |             intrinsics::simd_scatter(self, ptrs, enable.to_int())
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ dereferencing pointer failed: ALLOC has size 9, so pointer to 1 byte starting at offset 9 is out-of-bounds
+   |
+   = 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 `std::simd::Simd::<i8, 4>::scatter_select_unchecked` at RUSTLIB/core/src/../../portable-simd/crates/core_simd/src/vector.rs:LL:CC
+note: inside `main` at $DIR/simd-scatter.rs:LL:CC
+  --> $DIR/simd-scatter.rs:LL:CC
+   |
+LL | /         Simd::from_array([-27, 82, -41, 124]).scatter_select_unchecked(
+LL | |             &mut vec,
+LL | |             Mask::splat(true),
+LL | |             idxs,
+LL | |         );
+   | |_________^
+
+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/intrinsics/simd-select-bitmask-invalid.rs b/src/tools/miri/tests/fail/intrinsics/simd-select-bitmask-invalid.rs
new file mode 100644
index 0000000000000..8a3895ac14cf8
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/simd-select-bitmask-invalid.rs
@@ -0,0 +1,17 @@
+#![feature(platform_intrinsics, repr_simd)]
+
+extern "platform-intrinsic" {
+    fn simd_select_bitmask<M, T>(m: M, yes: T, no: T) -> T;
+}
+
+#[repr(simd)]
+#[allow(non_camel_case_types)]
+#[derive(Copy, Clone)]
+struct i32x2(i32, i32);
+
+fn main() {
+    unsafe {
+        let x = i32x2(0, 1);
+        simd_select_bitmask(0b11111111u8, x, x); //~ERROR: bitmask less than 8 bits long must be filled with 0s for the remaining bits
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/simd-select-bitmask-invalid.stderr b/src/tools/miri/tests/fail/intrinsics/simd-select-bitmask-invalid.stderr
new file mode 100644
index 0000000000000..e72cce998d0ed
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/simd-select-bitmask-invalid.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: a SIMD bitmask less than 8 bits long must be filled with 0s for the remaining bits
+  --> $DIR/simd-select-bitmask-invalid.rs:LL:CC
+   |
+LL |         simd_select_bitmask(0b11111111u8, x, x);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ a SIMD bitmask less than 8 bits long must be filled with 0s for the remaining bits
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/simd-select-bitmask-invalid.rs:LL:CC
+
+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/intrinsics/simd-select-invalid-bool.rs b/src/tools/miri/tests/fail/intrinsics/simd-select-invalid-bool.rs
new file mode 100644
index 0000000000000..7f7ee3af49516
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/simd-select-invalid-bool.rs
@@ -0,0 +1,17 @@
+#![feature(platform_intrinsics, repr_simd)]
+
+extern "platform-intrinsic" {
+    fn simd_select<M, T>(m: M, yes: T, no: T) -> T;
+}
+
+#[repr(simd)]
+#[allow(non_camel_case_types)]
+#[derive(Copy, Clone)]
+struct i32x2(i32, i32);
+
+fn main() {
+    unsafe {
+        let x = i32x2(0, 1);
+        simd_select(x, x, x); //~ERROR: must be all-0-bits or all-1-bits
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/simd-select-invalid-bool.stderr b/src/tools/miri/tests/fail/intrinsics/simd-select-invalid-bool.stderr
new file mode 100644
index 0000000000000..277ceb54ec71e
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/simd-select-invalid-bool.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: each element of a SIMD mask must be all-0-bits or all-1-bits
+  --> $DIR/simd-select-invalid-bool.rs:LL:CC
+   |
+LL |         simd_select(x, x, x);
+   |         ^^^^^^^^^^^^^^^^^^^^ each element of a SIMD mask must be all-0-bits or all-1-bits
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/simd-select-invalid-bool.rs:LL:CC
+
+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/intrinsics/simd-shl-too-far.rs b/src/tools/miri/tests/fail/intrinsics/simd-shl-too-far.rs
new file mode 100644
index 0000000000000..5c517c17b3a4b
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/simd-shl-too-far.rs
@@ -0,0 +1,17 @@
+#![feature(platform_intrinsics, repr_simd)]
+
+extern "platform-intrinsic" {
+    pub(crate) fn simd_shl<T>(x: T, y: T) -> T;
+}
+
+#[repr(simd)]
+#[allow(non_camel_case_types)]
+struct i32x2(i32, i32);
+
+fn main() {
+    unsafe {
+        let x = i32x2(1, 1);
+        let y = i32x2(100, 0);
+        simd_shl(x, y); //~ERROR: overflowing shift by 100 in `simd_shl` in SIMD lane 0
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/simd-shl-too-far.stderr b/src/tools/miri/tests/fail/intrinsics/simd-shl-too-far.stderr
new file mode 100644
index 0000000000000..c8445bb3cdc75
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/simd-shl-too-far.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: overflowing shift by 100 in `simd_shl` in SIMD lane 0
+  --> $DIR/simd-shl-too-far.rs:LL:CC
+   |
+LL |         simd_shl(x, y);
+   |         ^^^^^^^^^^^^^^ overflowing shift by 100 in `simd_shl` in SIMD lane 0
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/simd-shl-too-far.rs:LL:CC
+
+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/intrinsics/simd-shr-too-far.rs b/src/tools/miri/tests/fail/intrinsics/simd-shr-too-far.rs
new file mode 100644
index 0000000000000..5f1475a677813
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/simd-shr-too-far.rs
@@ -0,0 +1,17 @@
+#![feature(platform_intrinsics, repr_simd)]
+
+extern "platform-intrinsic" {
+    pub(crate) fn simd_shr<T>(x: T, y: T) -> T;
+}
+
+#[repr(simd)]
+#[allow(non_camel_case_types)]
+struct i32x2(i32, i32);
+
+fn main() {
+    unsafe {
+        let x = i32x2(1, 1);
+        let y = i32x2(20, 40);
+        simd_shr(x, y); //~ERROR: overflowing shift by 40 in `simd_shr` in SIMD lane 1
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/simd-shr-too-far.stderr b/src/tools/miri/tests/fail/intrinsics/simd-shr-too-far.stderr
new file mode 100644
index 0000000000000..8eec30c5a52f2
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/simd-shr-too-far.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: overflowing shift by 40 in `simd_shr` in SIMD lane 1
+  --> $DIR/simd-shr-too-far.rs:LL:CC
+   |
+LL |         simd_shr(x, y);
+   |         ^^^^^^^^^^^^^^ overflowing shift by 40 in `simd_shr` in SIMD lane 1
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/simd-shr-too-far.rs:LL:CC
+
+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/intrinsics/unchecked_add1.rs b/src/tools/miri/tests/fail/intrinsics/unchecked_add1.rs
new file mode 100644
index 0000000000000..13265d0fb0ee4
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/unchecked_add1.rs
@@ -0,0 +1,6 @@
+#![feature(unchecked_math)]
+
+fn main() {
+    // MAX overflow
+    let _val = unsafe { 40000u16.unchecked_add(30000) }; //~ ERROR: overflow executing `unchecked_add`
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/unchecked_add1.stderr b/src/tools/miri/tests/fail/intrinsics/unchecked_add1.stderr
new file mode 100644
index 0000000000000..f5e96198ee4c9
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/unchecked_add1.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: overflow executing `unchecked_add`
+  --> $DIR/unchecked_add1.rs:LL:CC
+   |
+LL |     let _val = unsafe { 40000u16.unchecked_add(30000) };
+   |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflow executing `unchecked_add`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/unchecked_add1.rs:LL:CC
+
+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/intrinsics/unchecked_add2.rs b/src/tools/miri/tests/fail/intrinsics/unchecked_add2.rs
new file mode 100644
index 0000000000000..229f50321d7df
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/unchecked_add2.rs
@@ -0,0 +1,6 @@
+#![feature(unchecked_math)]
+
+fn main() {
+    // MIN overflow
+    let _val = unsafe { (-30000i16).unchecked_add(-8000) }; //~ ERROR: overflow executing `unchecked_add`
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/unchecked_add2.stderr b/src/tools/miri/tests/fail/intrinsics/unchecked_add2.stderr
new file mode 100644
index 0000000000000..5a5c7070ae0b4
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/unchecked_add2.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: overflow executing `unchecked_add`
+  --> $DIR/unchecked_add2.rs:LL:CC
+   |
+LL |     let _val = unsafe { (-30000i16).unchecked_add(-8000) };
+   |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflow executing `unchecked_add`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/unchecked_add2.rs:LL:CC
+
+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/intrinsics/unchecked_div1.rs b/src/tools/miri/tests/fail/intrinsics/unchecked_div1.rs
new file mode 100644
index 0000000000000..6706cee30ef54
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/unchecked_div1.rs
@@ -0,0 +1,7 @@
+#![feature(core_intrinsics)]
+fn main() {
+    // MIN/-1 cannot be represented
+    unsafe {
+        std::intrinsics::unchecked_div(i16::MIN, -1); //~ ERROR: overflow in signed division (dividing MIN by -1)
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/unchecked_div1.stderr b/src/tools/miri/tests/fail/intrinsics/unchecked_div1.stderr
new file mode 100644
index 0000000000000..9267e0c494731
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/unchecked_div1.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: overflow in signed division (dividing MIN by -1)
+  --> $DIR/unchecked_div1.rs:LL:CC
+   |
+LL |         std::intrinsics::unchecked_div(i16::MIN, -1);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflow in signed division (dividing MIN by -1)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/unchecked_div1.rs:LL:CC
+
+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/intrinsics/unchecked_mul1.rs b/src/tools/miri/tests/fail/intrinsics/unchecked_mul1.rs
new file mode 100644
index 0000000000000..810d3418dc8fe
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/unchecked_mul1.rs
@@ -0,0 +1,5 @@
+#![feature(unchecked_math)]
+fn main() {
+    // MAX overflow
+    let _val = unsafe { 300u16.unchecked_mul(250u16) }; //~ ERROR: overflow executing `unchecked_mul`
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/unchecked_mul1.stderr b/src/tools/miri/tests/fail/intrinsics/unchecked_mul1.stderr
new file mode 100644
index 0000000000000..9a5a585e1cce4
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/unchecked_mul1.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: overflow executing `unchecked_mul`
+  --> $DIR/unchecked_mul1.rs:LL:CC
+   |
+LL |     let _val = unsafe { 300u16.unchecked_mul(250u16) };
+   |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflow executing `unchecked_mul`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/unchecked_mul1.rs:LL:CC
+
+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/intrinsics/unchecked_mul2.rs b/src/tools/miri/tests/fail/intrinsics/unchecked_mul2.rs
new file mode 100644
index 0000000000000..421019542a95a
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/unchecked_mul2.rs
@@ -0,0 +1,5 @@
+#![feature(unchecked_math)]
+fn main() {
+    // MIN overflow
+    let _val = unsafe { 1_000_000_000i32.unchecked_mul(-4) }; //~ ERROR: overflow executing `unchecked_mul`
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/unchecked_mul2.stderr b/src/tools/miri/tests/fail/intrinsics/unchecked_mul2.stderr
new file mode 100644
index 0000000000000..46b9f61821728
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/unchecked_mul2.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: overflow executing `unchecked_mul`
+  --> $DIR/unchecked_mul2.rs:LL:CC
+   |
+LL |     let _val = unsafe { 1_000_000_000i32.unchecked_mul(-4) };
+   |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflow executing `unchecked_mul`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/unchecked_mul2.rs:LL:CC
+
+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/intrinsics/unchecked_sub1.rs b/src/tools/miri/tests/fail/intrinsics/unchecked_sub1.rs
new file mode 100644
index 0000000000000..c6e0066674413
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/unchecked_sub1.rs
@@ -0,0 +1,5 @@
+#![feature(unchecked_math)]
+fn main() {
+    // MIN overflow
+    let _val = unsafe { 14u32.unchecked_sub(22) }; //~ ERROR: overflow executing `unchecked_sub`
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/unchecked_sub1.stderr b/src/tools/miri/tests/fail/intrinsics/unchecked_sub1.stderr
new file mode 100644
index 0000000000000..01e569767bac0
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/unchecked_sub1.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: overflow executing `unchecked_sub`
+  --> $DIR/unchecked_sub1.rs:LL:CC
+   |
+LL |     let _val = unsafe { 14u32.unchecked_sub(22) };
+   |                         ^^^^^^^^^^^^^^^^^^^^^^^ overflow executing `unchecked_sub`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/unchecked_sub1.rs:LL:CC
+
+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/intrinsics/unchecked_sub2.rs b/src/tools/miri/tests/fail/intrinsics/unchecked_sub2.rs
new file mode 100644
index 0000000000000..65aa292e212da
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/unchecked_sub2.rs
@@ -0,0 +1,5 @@
+#![feature(unchecked_math)]
+fn main() {
+    // MAX overflow
+    let _val = unsafe { 30000i16.unchecked_sub(-7000) }; //~ ERROR: overflow executing `unchecked_sub`
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/unchecked_sub2.stderr b/src/tools/miri/tests/fail/intrinsics/unchecked_sub2.stderr
new file mode 100644
index 0000000000000..38c1647b4f49f
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/unchecked_sub2.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: overflow executing `unchecked_sub`
+  --> $DIR/unchecked_sub2.rs:LL:CC
+   |
+LL |     let _val = unsafe { 30000i16.unchecked_sub(-7000) };
+   |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflow executing `unchecked_sub`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/unchecked_sub2.rs:LL:CC
+
+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/intrinsics/uninit_uninhabited_type.rs b/src/tools/miri/tests/fail/intrinsics/uninit_uninhabited_type.rs
new file mode 100644
index 0000000000000..e606d8b283c8a
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/uninit_uninhabited_type.rs
@@ -0,0 +1,7 @@
+#![feature(never_type)]
+
+#[allow(deprecated, invalid_value)]
+fn main() {
+    unsafe { std::mem::uninitialized::<!>() };
+    //~^ ERROR: attempted to instantiate uninhabited type `!`
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/uninit_uninhabited_type.stderr b/src/tools/miri/tests/fail/intrinsics/uninit_uninhabited_type.stderr
new file mode 100644
index 0000000000000..150128ba2a41c
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/uninit_uninhabited_type.stderr
@@ -0,0 +1,12 @@
+error: abnormal termination: aborted execution: attempted to instantiate uninhabited type `!`
+  --> $DIR/uninit_uninhabited_type.rs:LL:CC
+   |
+LL |     unsafe { std::mem::uninitialized::<!>() };
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to instantiate uninhabited type `!`
+   |
+   = note: inside `main` at $DIR/uninit_uninhabited_type.rs:LL:CC
+
+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/intrinsics/write_bytes_null.rs b/src/tools/miri/tests/fail/intrinsics/write_bytes_null.rs
new file mode 100644
index 0000000000000..2f46c820fb73b
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/write_bytes_null.rs
@@ -0,0 +1,10 @@
+#![feature(intrinsics)]
+
+// Directly call intrinsic to avoid debug assertions in libstd
+extern "rust-intrinsic" {
+    fn write_bytes<T>(dst: *mut T, val: u8, count: usize);
+}
+
+fn main() {
+    unsafe { write_bytes::<u8>(std::ptr::null_mut(), 0, 0) }; //~ ERROR: memory access failed: null pointer is a dangling pointer
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/write_bytes_null.stderr b/src/tools/miri/tests/fail/intrinsics/write_bytes_null.stderr
new file mode 100644
index 0000000000000..b2969ca3b5929
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/write_bytes_null.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: memory access failed: null pointer is a dangling pointer (it has no provenance)
+  --> $DIR/write_bytes_null.rs:LL:CC
+   |
+LL |     unsafe { write_bytes::<u8>(std::ptr::null_mut(), 0, 0) };
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ memory access failed: null pointer is a dangling pointer (it has no provenance)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/write_bytes_null.rs:LL:CC
+
+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/intrinsics/write_bytes_overflow.rs b/src/tools/miri/tests/fail/intrinsics/write_bytes_overflow.rs
new file mode 100644
index 0000000000000..08bc096d6c366
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/write_bytes_overflow.rs
@@ -0,0 +1,9 @@
+use std::mem;
+
+fn main() {
+    let mut y = 0;
+    unsafe {
+        (&mut y as *mut i32).write_bytes(0u8, 1usize << (mem::size_of::<usize>() * 8 - 1));
+        //~^ ERROR: overflow computing total size of `write_bytes`
+    }
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/write_bytes_overflow.stderr b/src/tools/miri/tests/fail/intrinsics/write_bytes_overflow.stderr
new file mode 100644
index 0000000000000..f88afde879acf
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/write_bytes_overflow.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: overflow computing total size of `write_bytes`
+  --> $DIR/write_bytes_overflow.rs:LL:CC
+   |
+LL |         (&mut y as *mut i32).write_bytes(0u8, 1usize << (mem::size_of::<usize>() * 8 - 1));
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflow computing total size of `write_bytes`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/write_bytes_overflow.rs:LL:CC
+
+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/intrinsics/zero_fn_ptr.rs b/src/tools/miri/tests/fail/intrinsics/zero_fn_ptr.rs
new file mode 100644
index 0000000000000..6d9ae14c5c479
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/zero_fn_ptr.rs
@@ -0,0 +1,5 @@
+#[allow(deprecated, invalid_value)]
+fn main() {
+    unsafe { std::mem::zeroed::<fn()>() };
+    //~^ ERROR: attempted to zero-initialize type `fn()`, which is invalid
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/zero_fn_ptr.stderr b/src/tools/miri/tests/fail/intrinsics/zero_fn_ptr.stderr
new file mode 100644
index 0000000000000..9d44ba9f746ad
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/zero_fn_ptr.stderr
@@ -0,0 +1,12 @@
+error: abnormal termination: aborted execution: attempted to zero-initialize type `fn()`, which is invalid
+  --> $DIR/zero_fn_ptr.rs:LL:CC
+   |
+LL |     unsafe { std::mem::zeroed::<fn()>() };
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to zero-initialize type `fn()`, which is invalid
+   |
+   = note: inside `main` at $DIR/zero_fn_ptr.rs:LL:CC
+
+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/invalid_bool.rs b/src/tools/miri/tests/fail/invalid_bool.rs
new file mode 100644
index 0000000000000..525f8831c1c00
--- /dev/null
+++ b/src/tools/miri/tests/fail/invalid_bool.rs
@@ -0,0 +1,9 @@
+// Validation makes this fail in the wrong place
+// Make sure we find these even with many checks disabled.
+//@compile-flags: -Zmiri-disable-alignment-check -Zmiri-disable-stacked-borrows -Zmiri-disable-validation
+#![feature(bench_black_box)]
+
+fn main() {
+    let b = unsafe { std::mem::transmute::<u8, bool>(2) };
+    let _x = b == std::hint::black_box(true); //~ ERROR: interpreting an invalid 8-bit value as a bool
+}
diff --git a/src/tools/miri/tests/fail/invalid_bool.stderr b/src/tools/miri/tests/fail/invalid_bool.stderr
new file mode 100644
index 0000000000000..a522f6cd4fffe
--- /dev/null
+++ b/src/tools/miri/tests/fail/invalid_bool.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: interpreting an invalid 8-bit value as a bool: 0x02
+  --> $DIR/invalid_bool.rs:LL:CC
+   |
+LL |     let _x = b == std::hint::black_box(true);
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ interpreting an invalid 8-bit value as a bool: 0x02
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/invalid_bool.rs:LL:CC
+
+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/invalid_char.rs b/src/tools/miri/tests/fail/invalid_char.rs
new file mode 100644
index 0000000000000..699248229445f
--- /dev/null
+++ b/src/tools/miri/tests/fail/invalid_char.rs
@@ -0,0 +1,10 @@
+// Validation makes this fail in the wrong place
+// Make sure we find these even with many checks disabled.
+//@compile-flags: -Zmiri-disable-alignment-check -Zmiri-disable-stacked-borrows -Zmiri-disable-validation
+
+fn main() {
+    let c = 0xFFFFFFu32;
+    assert!(std::char::from_u32(c).is_none());
+    let c = unsafe { std::mem::transmute::<u32, char>(c) };
+    let _x = c == 'x'; //~ ERROR: interpreting an invalid 32-bit value as a char
+}
diff --git a/src/tools/miri/tests/fail/invalid_char.stderr b/src/tools/miri/tests/fail/invalid_char.stderr
new file mode 100644
index 0000000000000..d49d753d9e185
--- /dev/null
+++ b/src/tools/miri/tests/fail/invalid_char.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: interpreting an invalid 32-bit value as a char: $HEX
+  --> $DIR/invalid_char.rs:LL:CC
+   |
+LL |     let _x = c == 'x';
+   |              ^^^^^^^^ interpreting an invalid 32-bit value as a char: $HEX
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/invalid_char.rs:LL:CC
+
+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/invalid_enum_tag.rs b/src/tools/miri/tests/fail/invalid_enum_tag.rs
new file mode 100644
index 0000000000000..84fa2c2973901
--- /dev/null
+++ b/src/tools/miri/tests/fail/invalid_enum_tag.rs
@@ -0,0 +1,18 @@
+// Validation makes this fail in the wrong place
+// Make sure we find these even with many checks disabled.
+//@compile-flags: -Zmiri-disable-alignment-check -Zmiri-disable-stacked-borrows -Zmiri-disable-validation
+
+use std::mem;
+
+#[repr(C)]
+pub enum Foo {
+    A,
+    B,
+    C,
+    D,
+}
+
+fn main() {
+    let f = unsafe { std::mem::transmute::<i32, Foo>(42) };
+    let _val = mem::discriminant(&f); //~ERROR: enum value has invalid tag
+}
diff --git a/src/tools/miri/tests/fail/invalid_enum_tag.stderr b/src/tools/miri/tests/fail/invalid_enum_tag.stderr
new file mode 100644
index 0000000000000..01d931de919a4
--- /dev/null
+++ b/src/tools/miri/tests/fail/invalid_enum_tag.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: enum value has invalid tag: $HEX
+  --> $DIR/invalid_enum_tag.rs:LL:CC
+   |
+LL |     let _val = mem::discriminant(&f);
+   |                ^^^^^^^^^^^^^^^^^^^^^ enum value has invalid tag: $HEX
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/invalid_enum_tag.rs:LL:CC
+
+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/invalid_int.rs b/src/tools/miri/tests/fail/invalid_int.rs
new file mode 100644
index 0000000000000..2435a87a6f28c
--- /dev/null
+++ b/src/tools/miri/tests/fail/invalid_int.rs
@@ -0,0 +1,9 @@
+#![allow(invalid_value)]
+// Validation makes this fail in the wrong place
+// Make sure we find these even with many checks disabled.
+//@compile-flags: -Zmiri-disable-alignment-check -Zmiri-disable-stacked-borrows -Zmiri-disable-validation
+
+fn main() {
+    let i = unsafe { std::mem::MaybeUninit::<i32>::uninit().assume_init() }; //~ ERROR: uninitialized
+    let _x = i + 0;
+}
diff --git a/src/tools/miri/tests/fail/invalid_int.stderr b/src/tools/miri/tests/fail/invalid_int.stderr
new file mode 100644
index 0000000000000..eccdbff604574
--- /dev/null
+++ b/src/tools/miri/tests/fail/invalid_int.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
+  --> $DIR/invalid_int.rs:LL:CC
+   |
+LL |     let i = unsafe { std::mem::MaybeUninit::<i32>::uninit().assume_init() };
+   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ using uninitialized data, but this operation requires initialized memory
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/invalid_int.rs:LL:CC
+
+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/issue-miri-1112.rs b/src/tools/miri/tests/fail/issue-miri-1112.rs
new file mode 100644
index 0000000000000..387253a3f9872
--- /dev/null
+++ b/src/tools/miri/tests/fail/issue-miri-1112.rs
@@ -0,0 +1,42 @@
+trait Empty {}
+
+#[repr(transparent)]
+pub struct FunnyPointer(dyn Empty);
+
+#[repr(C)]
+pub struct Meta {
+    drop_fn: fn(&mut ()),
+    size: usize,
+    align: usize,
+}
+
+impl Meta {
+    pub fn new() -> Self {
+        Meta { drop_fn: |_| {}, size: 0, align: 1 }
+    }
+}
+
+#[repr(C)]
+pub struct FatPointer {
+    pub data: *const (),
+    pub vtable: *const (),
+}
+
+impl FunnyPointer {
+    pub unsafe fn from_data_ptr(data: &String, ptr: *const Meta) -> &Self {
+        let obj = FatPointer {
+            data: data as *const _ as *const (),
+            vtable: ptr as *const _ as *const (),
+        };
+        let obj = std::mem::transmute::<FatPointer, *mut FunnyPointer>(obj); //~ ERROR: expected a vtable pointer
+        &*obj
+    }
+}
+
+fn main() {
+    unsafe {
+        let meta = Meta::new();
+        let hello = "hello".to_string();
+        let _raw: &FunnyPointer = FunnyPointer::from_data_ptr(&hello, &meta as *const _);
+    }
+}
diff --git a/src/tools/miri/tests/fail/issue-miri-1112.stderr b/src/tools/miri/tests/fail/issue-miri-1112.stderr
new file mode 100644
index 0000000000000..e6644a72849ff
--- /dev/null
+++ b/src/tools/miri/tests/fail/issue-miri-1112.stderr
@@ -0,0 +1,20 @@
+error: Undefined Behavior: constructing invalid value: encountered $HEX[ALLOC]<TAG>, but expected a vtable pointer
+  --> $DIR/issue-miri-1112.rs:LL:CC
+   |
+LL |         let obj = std::mem::transmute::<FatPointer, *mut FunnyPointer>(obj);
+   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered $HEX[ALLOC]<TAG>, but expected a vtable pointer
+   |
+   = 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 `FunnyPointer::from_data_ptr` at $DIR/issue-miri-1112.rs:LL:CC
+note: inside `main` at $DIR/issue-miri-1112.rs:LL:CC
+  --> $DIR/issue-miri-1112.rs:LL:CC
+   |
+LL |         let _raw: &FunnyPointer = FunnyPointer::from_data_ptr(&hello, &meta as *const _);
+   |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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/issue-miri-2432.rs b/src/tools/miri/tests/fail/issue-miri-2432.rs
new file mode 100644
index 0000000000000..f822479c43685
--- /dev/null
+++ b/src/tools/miri/tests/fail/issue-miri-2432.rs
@@ -0,0 +1,19 @@
+#![allow(where_clauses_object_safety)]
+
+trait Trait {}
+
+trait X {
+    fn foo(&self)
+    where
+        Self: Trait;
+}
+
+impl X for () {
+    fn foo(&self) {}
+}
+
+impl Trait for dyn X {}
+
+pub fn main() {
+    <dyn X as X>::foo(&()); //~ERROR: trying to call something that is not a method
+}
diff --git a/src/tools/miri/tests/fail/issue-miri-2432.stderr b/src/tools/miri/tests/fail/issue-miri-2432.stderr
new file mode 100644
index 0000000000000..b8e13b61ceb60
--- /dev/null
+++ b/src/tools/miri/tests/fail/issue-miri-2432.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: `dyn` call trying to call something that is not a method
+  --> $DIR/issue-miri-2432.rs:LL:CC
+   |
+LL |     <dyn X as X>::foo(&());
+   |     ^^^^^^^^^^^^^^^^^^^^^^ `dyn` call trying to call something that is not a method
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/issue-miri-2432.rs:LL:CC
+
+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/memleak.rs b/src/tools/miri/tests/fail/memleak.rs
new file mode 100644
index 0000000000000..d384caf81a57e
--- /dev/null
+++ b/src/tools/miri/tests/fail/memleak.rs
@@ -0,0 +1,6 @@
+//@error-pattern: the evaluated program leaked memory
+//@normalize-stderr-test: ".*│.*" -> "$$stripped$$"
+
+fn main() {
+    std::mem::forget(Box::new(42));
+}
diff --git a/src/tools/miri/tests/fail/memleak.stderr b/src/tools/miri/tests/fail/memleak.stderr
new file mode 100644
index 0000000000000..f8b62af3eb857
--- /dev/null
+++ b/src/tools/miri/tests/fail/memleak.stderr
@@ -0,0 +1,10 @@
+The following memory was leaked: ALLOC (Rust heap, size: 4, align: 4) {
+$stripped$
+}
+
+error: the evaluated program leaked memory
+
+note: pass `-Zmiri-ignore-leaks` to disable this check
+
+error: aborting due to previous error
+
diff --git a/src/tools/miri/tests/fail/memleak_rc.32bit.stderr b/src/tools/miri/tests/fail/memleak_rc.32bit.stderr
new file mode 100644
index 0000000000000..da222609091ab
--- /dev/null
+++ b/src/tools/miri/tests/fail/memleak_rc.32bit.stderr
@@ -0,0 +1,10 @@
+The following memory was leaked: ALLOC (Rust heap, size: 16, align: 4) {
+$stripped$
+}
+
+error: the evaluated program leaked memory
+
+note: pass `-Zmiri-ignore-leaks` to disable this check
+
+error: aborting due to previous error
+
diff --git a/src/tools/miri/tests/fail/memleak_rc.64bit.stderr b/src/tools/miri/tests/fail/memleak_rc.64bit.stderr
new file mode 100644
index 0000000000000..8c24bbc779bd6
--- /dev/null
+++ b/src/tools/miri/tests/fail/memleak_rc.64bit.stderr
@@ -0,0 +1,11 @@
+The following memory was leaked: ALLOC (Rust heap, size: 32, align: 8) {
+$stripped$
+$stripped$
+}
+
+error: the evaluated program leaked memory
+
+note: pass `-Zmiri-ignore-leaks` to disable this check
+
+error: aborting due to previous error
+
diff --git a/src/tools/miri/tests/fail/memleak_rc.rs b/src/tools/miri/tests/fail/memleak_rc.rs
new file mode 100644
index 0000000000000..76ecd71b011aa
--- /dev/null
+++ b/src/tools/miri/tests/fail/memleak_rc.rs
@@ -0,0 +1,14 @@
+//@error-pattern: the evaluated program leaked memory
+//@stderr-per-bitwidth
+//@normalize-stderr-test: ".*│.*" -> "$$stripped$$"
+
+use std::cell::RefCell;
+use std::rc::Rc;
+
+struct Dummy(Rc<RefCell<Option<Dummy>>>);
+
+fn main() {
+    let x = Dummy(Rc::new(RefCell::new(None)));
+    let y = Dummy(x.0.clone());
+    *x.0.borrow_mut() = Some(y);
+}
diff --git a/src/tools/miri/tests/fail/memleak_rc.stderr b/src/tools/miri/tests/fail/memleak_rc.stderr
new file mode 100644
index 0000000000000..290de49c82c0b
--- /dev/null
+++ b/src/tools/miri/tests/fail/memleak_rc.stderr
@@ -0,0 +1,11 @@
+The following memory was leaked: ALLOC (Rust heap, size: 32, align: 8) {
+    0x00 │ 01 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 │ ................
+    0x10 │ 00 00 00 00 00 00 00 00 ╾$HEX[a1765]<TAG>─╼ │ ........╾──────╼
+}
+
+error: the evaluated program leaked memory
+
+note: pass `-Zmiri-ignore-leaks` to disable this check
+
+error: aborting due to previous error
+
diff --git a/src/tools/miri/tests/fail/modifying_constants.rs b/src/tools/miri/tests/fail/modifying_constants.rs
new file mode 100644
index 0000000000000..2783ebd155ff5
--- /dev/null
+++ b/src/tools/miri/tests/fail/modifying_constants.rs
@@ -0,0 +1,9 @@
+// This should fail even without validation/SB
+//@compile-flags: -Zmiri-disable-validation -Zmiri-disable-stacked-borrows
+
+fn main() {
+    let x = &1; // the `&1` is promoted to a constant, but it used to be that only the pointer is marked static, not the pointee
+    let y = unsafe { &mut *(x as *const i32 as *mut i32) };
+    *y = 42; //~ ERROR: read-only
+    assert_eq!(*x, 42);
+}
diff --git a/src/tools/miri/tests/fail/modifying_constants.stderr b/src/tools/miri/tests/fail/modifying_constants.stderr
new file mode 100644
index 0000000000000..6425a5d7a0ad4
--- /dev/null
+++ b/src/tools/miri/tests/fail/modifying_constants.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: writing to ALLOC which is read-only
+  --> $DIR/modifying_constants.rs:LL:CC
+   |
+LL |     *y = 42;
+   |     ^^^^^^^ writing to ALLOC which is read-only
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/modifying_constants.rs:LL:CC
+
+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/never_say_never.rs b/src/tools/miri/tests/fail/never_say_never.rs
new file mode 100644
index 0000000000000..f6d3dc790bf00
--- /dev/null
+++ b/src/tools/miri/tests/fail/never_say_never.rs
@@ -0,0 +1,17 @@
+// This should fail even without validation
+//@compile-flags: -Zmiri-disable-validation
+
+#![feature(never_type)]
+#![allow(unreachable_code)]
+
+fn main() {
+    let y = &5;
+    let x: ! = unsafe {
+        *(y as *const _ as *const !) //~ ERROR: entering unreachable code
+    };
+    f(x)
+}
+
+fn f(x: !) -> ! {
+    x
+}
diff --git a/src/tools/miri/tests/fail/never_say_never.stderr b/src/tools/miri/tests/fail/never_say_never.stderr
new file mode 100644
index 0000000000000..a2a63b8baf594
--- /dev/null
+++ b/src/tools/miri/tests/fail/never_say_never.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: entering unreachable code
+  --> $DIR/never_say_never.rs:LL:CC
+   |
+LL |         *(y as *const _ as *const !)
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ entering unreachable code
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/never_say_never.rs:LL:CC
+
+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/never_transmute_humans.rs b/src/tools/miri/tests/fail/never_transmute_humans.rs
new file mode 100644
index 0000000000000..de723433dc283
--- /dev/null
+++ b/src/tools/miri/tests/fail/never_transmute_humans.rs
@@ -0,0 +1,12 @@
+// This should fail even without validation
+//@compile-flags: -Zmiri-disable-validation
+
+#![feature(never_type)]
+
+struct Human;
+
+fn main() {
+    let _x: ! = unsafe {
+        std::mem::transmute::<Human, !>(Human) //~ ERROR: transmuting to uninhabited
+    };
+}
diff --git a/src/tools/miri/tests/fail/never_transmute_humans.stderr b/src/tools/miri/tests/fail/never_transmute_humans.stderr
new file mode 100644
index 0000000000000..e8df4739f9bcb
--- /dev/null
+++ b/src/tools/miri/tests/fail/never_transmute_humans.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: transmuting to uninhabited type
+  --> $DIR/never_transmute_humans.rs:LL:CC
+   |
+LL |         std::mem::transmute::<Human, !>(Human)
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ transmuting to uninhabited type
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/never_transmute_humans.rs:LL:CC
+
+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/never_transmute_void.rs b/src/tools/miri/tests/fail/never_transmute_void.rs
new file mode 100644
index 0000000000000..19473e9ac2141
--- /dev/null
+++ b/src/tools/miri/tests/fail/never_transmute_void.rs
@@ -0,0 +1,20 @@
+// This should fail even without validation
+//@compile-flags: -Zmiri-disable-validation
+//@require-annotations-for-level: ERROR
+
+#![feature(never_type)]
+#![allow(unused, invalid_value)]
+
+mod m {
+    enum VoidI {}
+    pub struct Void(VoidI);
+
+    pub fn f(v: Void) -> ! {
+        match v.0 {} //~ ERROR: entering unreachable code
+    }
+}
+
+fn main() {
+    let v = unsafe { std::mem::transmute::<(), m::Void>(()) };
+    m::f(v); //~ NOTE: inside `main`
+}
diff --git a/src/tools/miri/tests/fail/never_transmute_void.stderr b/src/tools/miri/tests/fail/never_transmute_void.stderr
new file mode 100644
index 0000000000000..4c3a3d075f028
--- /dev/null
+++ b/src/tools/miri/tests/fail/never_transmute_void.stderr
@@ -0,0 +1,20 @@
+error: Undefined Behavior: entering unreachable code
+  --> $DIR/never_transmute_void.rs:LL:CC
+   |
+LL |         match v.0 {}
+   |               ^^^ entering unreachable code
+   |
+   = 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 `m::f` at $DIR/never_transmute_void.rs:LL:CC
+note: inside `main` at $DIR/never_transmute_void.rs:LL:CC
+  --> $DIR/never_transmute_void.rs:LL:CC
+   |
+LL |     m::f(v);
+   |     ^^^^^^^
+
+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/no_main.rs b/src/tools/miri/tests/fail/no_main.rs
new file mode 100644
index 0000000000000..e28205040871a
--- /dev/null
+++ b/src/tools/miri/tests/fail/no_main.rs
@@ -0,0 +1,2 @@
+//@error-pattern: miri can only run programs that have a main function
+#![no_main]
diff --git a/src/tools/miri/tests/fail/no_main.stderr b/src/tools/miri/tests/fail/no_main.stderr
new file mode 100644
index 0000000000000..88bdfb4e387cf
--- /dev/null
+++ b/src/tools/miri/tests/fail/no_main.stderr
@@ -0,0 +1,4 @@
+error: miri can only run programs that have a main function
+
+error: aborting due to previous error
+
diff --git a/src/tools/miri/tests/fail/panic/bad_miri_start_panic.rs b/src/tools/miri/tests/fail/panic/bad_miri_start_panic.rs
new file mode 100644
index 0000000000000..4b0ae60b10101
--- /dev/null
+++ b/src/tools/miri/tests/fail/panic/bad_miri_start_panic.rs
@@ -0,0 +1,12 @@
+//@compile-flags: -Zmiri-disable-abi-check
+// This feature is required to trigger the error using the "C" ABI.
+#![feature(c_unwind)]
+
+extern "C" {
+    fn miri_start_panic(payload: *mut u8) -> !;
+}
+
+fn main() {
+    unsafe { miri_start_panic(&mut 0) }
+    //~^ ERROR: unwinding past a stack frame that does not allow unwinding
+}
diff --git a/src/tools/miri/tests/fail/panic/bad_miri_start_panic.stderr b/src/tools/miri/tests/fail/panic/bad_miri_start_panic.stderr
new file mode 100644
index 0000000000000..3bd2be03ea1ff
--- /dev/null
+++ b/src/tools/miri/tests/fail/panic/bad_miri_start_panic.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: unwinding past a stack frame that does not allow unwinding
+  --> $DIR/bad_miri_start_panic.rs:LL:CC
+   |
+LL |     unsafe { miri_start_panic(&mut 0) }
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^ unwinding past a stack frame that does not allow unwinding
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/bad_miri_start_panic.rs:LL:CC
+
+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/panic/bad_unwind.rs b/src/tools/miri/tests/fail/panic/bad_unwind.rs
new file mode 100644
index 0000000000000..370b372a7d373
--- /dev/null
+++ b/src/tools/miri/tests/fail/panic/bad_unwind.rs
@@ -0,0 +1,14 @@
+#![feature(c_unwind)]
+
+//! Unwinding when the caller ABI is "C" (without "-unwind") is UB.
+
+extern "C-unwind" fn unwind() {
+    panic!();
+}
+
+fn main() {
+    let unwind: extern "C-unwind" fn() = unwind;
+    let unwind: extern "C" fn() = unsafe { std::mem::transmute(unwind) };
+    std::panic::catch_unwind(|| unwind()).unwrap_err();
+    //~^ ERROR: unwinding past a stack frame that does not allow unwinding
+}
diff --git a/src/tools/miri/tests/fail/panic/bad_unwind.stderr b/src/tools/miri/tests/fail/panic/bad_unwind.stderr
new file mode 100644
index 0000000000000..23c33f5e7f3ff
--- /dev/null
+++ b/src/tools/miri/tests/fail/panic/bad_unwind.stderr
@@ -0,0 +1,25 @@
+thread 'main' panicked at 'explicit panic', $DIR/bad_unwind.rs:LL:CC
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
+error: Undefined Behavior: unwinding past a stack frame that does not allow unwinding
+  --> $DIR/bad_unwind.rs:LL:CC
+   |
+LL |     std::panic::catch_unwind(|| unwind()).unwrap_err();
+   |                                 ^^^^^^^^ unwinding past a stack frame that does not allow unwinding
+   |
+   = 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 closure at $DIR/bad_unwind.rs:LL:CC
+   = note: inside `std::panicking::r#try::do_call::<[closure@$DIR/bad_unwind.rs:LL:CC], ()>` at RUSTLIB/std/src/panicking.rs:LL:CC
+   = note: inside `std::panicking::r#try::<(), [closure@$DIR/bad_unwind.rs:LL:CC]>` at RUSTLIB/std/src/panicking.rs:LL:CC
+   = note: inside `std::panic::catch_unwind::<[closure@$DIR/bad_unwind.rs:LL:CC], ()>` at RUSTLIB/std/src/panic.rs:LL:CC
+note: inside `main` at $DIR/bad_unwind.rs:LL:CC
+  --> $DIR/bad_unwind.rs:LL:CC
+   |
+LL |     std::panic::catch_unwind(|| unwind()).unwrap_err();
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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/panic/double_panic.rs b/src/tools/miri/tests/fail/panic/double_panic.rs
new file mode 100644
index 0000000000000..8919d51bb2f74
--- /dev/null
+++ b/src/tools/miri/tests/fail/panic/double_panic.rs
@@ -0,0 +1,14 @@
+//@error-pattern: the program aborted
+//@normalize-stderr-test: "\| +\^+" -> "| ^"
+//@normalize-stderr-test: "unsafe \{ libc::abort\(\) \}|crate::intrinsics::abort\(\);" -> "ABORT();"
+
+struct Foo;
+impl Drop for Foo {
+    fn drop(&mut self) {
+        panic!("second");
+    }
+}
+fn main() {
+    let _foo = Foo;
+    panic!("first");
+}
diff --git a/src/tools/miri/tests/fail/panic/double_panic.stderr b/src/tools/miri/tests/fail/panic/double_panic.stderr
new file mode 100644
index 0000000000000..f1d2b4de97cc2
--- /dev/null
+++ b/src/tools/miri/tests/fail/panic/double_panic.stderr
@@ -0,0 +1,94 @@
+thread 'main' panicked at 'first', $DIR/double_panic.rs:LL:CC
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
+thread 'main' panicked at 'second', $DIR/double_panic.rs:LL:CC
+stack backtrace:
+   0: std::backtrace_rs::backtrace::miri::trace_unsynchronized
+ at RUSTLIB/std/src/../../backtrace/src/backtrace/miri.rs:LL:CC
+   1: std::backtrace_rs::backtrace::miri::trace
+ at RUSTLIB/std/src/../../backtrace/src/backtrace/miri.rs:LL:CC
+   2: std::backtrace_rs::backtrace::trace_unsynchronized
+ at RUSTLIB/std/src/../../backtrace/src/backtrace/mod.rs:LL:CC
+   3: std::sys_common::backtrace::_print_fmt
+ at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC
+   4: <std::sys_common::backtrace::_print::DisplayBacktrace as std::fmt::Display>::fmt
+ at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC
+   5: std::fmt::write
+ at RUSTLIB/core/src/fmt/mod.rs:LL:CC
+   6: <std::sys::PLATFORM::stdio::Stderr as std::io::Write>::write_fmt
+ at RUSTLIB/std/src/io/mod.rs:LL:CC
+   7: std::sys_common::backtrace::_print
+ at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC
+   8: std::sys_common::backtrace::print
+ at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC
+   9: std::panicking::default_hook::{closure#1}
+ at RUSTLIB/std/src/panicking.rs:LL:CC
+  10: std::panicking::default_hook
+ at RUSTLIB/std/src/panicking.rs:LL:CC
+  11: std::panicking::rust_panic_with_hook
+ at RUSTLIB/std/src/panicking.rs:LL:CC
+  12: std::rt::begin_panic::{closure#0}
+ at RUSTLIB/std/src/panicking.rs:LL:CC
+  13: std::sys_common::backtrace::__rust_end_short_backtrace
+ at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC
+  14: std::rt::begin_panic
+ at RUSTLIB/std/src/panicking.rs:LL:CC
+  15: <Foo as std::ops::Drop>::drop
+ at $DIR/double_panic.rs:LL:CC
+  16: std::ptr::drop_in_place - shim(Some(Foo))
+ at RUSTLIB/core/src/ptr/mod.rs:LL:CC
+  17: main
+ at $DIR/double_panic.rs:LL:CC
+  18: <fn() as std::ops::FnOnce<()>>::call_once - shim(fn())
+ at RUSTLIB/core/src/ops/function.rs:LL:CC
+  19: std::sys_common::backtrace::__rust_begin_short_backtrace
+ at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC
+  20: std::rt::lang_start::{closure#0}
+ at RUSTLIB/std/src/rt.rs:LL:CC
+  21: std::ops::function::impls::call_once
+ at RUSTLIB/core/src/ops/function.rs:LL:CC
+  22: std::panicking::r#try::do_call
+ at RUSTLIB/std/src/panicking.rs:LL:CC
+  23: std::panicking::r#try
+ at RUSTLIB/std/src/panicking.rs:LL:CC
+  24: std::panic::catch_unwind
+ at RUSTLIB/std/src/panic.rs:LL:CC
+  25: std::rt::lang_start_internal::{closure#2}
+ at RUSTLIB/std/src/rt.rs:LL:CC
+  26: std::panicking::r#try::do_call
+ at RUSTLIB/std/src/panicking.rs:LL:CC
+  27: std::panicking::r#try
+ at RUSTLIB/std/src/panicking.rs:LL:CC
+  28: std::panic::catch_unwind
+ at RUSTLIB/std/src/panic.rs:LL:CC
+  29: std::rt::lang_start_internal
+ at RUSTLIB/std/src/rt.rs:LL:CC
+  30: std::rt::lang_start
+ at RUSTLIB/std/src/rt.rs:LL:CC
+thread panicked while panicking. aborting.
+error: abnormal termination: the program aborted execution
+  --> RUSTLIB/std/src/sys/PLATFORM/mod.rs:LL:CC
+   |
+LL |     ABORT();
+   | ^ the program aborted execution
+   |
+   = note: inside `std::sys::PLATFORM::abort_internal` at RUSTLIB/std/src/sys/PLATFORM/mod.rs:LL:CC
+   = note: inside `std::panicking::rust_panic_with_hook` at RUSTLIB/std/src/panicking.rs:LL:CC
+   = note: inside closure at RUSTLIB/std/src/panicking.rs:LL:CC
+   = note: inside `std::sys_common::backtrace::__rust_end_short_backtrace::<[closure@std::rt::begin_panic<&str>::{closure#0}], !>` at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC
+note: inside `<Foo as std::ops::Drop>::drop` at RUSTLIB/std/src/panic.rs:LL:CC
+  --> $DIR/double_panic.rs:LL:CC
+   |
+LL |         panic!("second");
+   | ^
+   = note: inside `std::ptr::drop_in_place::<Foo> - shim(Some(Foo))` at RUSTLIB/core/src/ptr/mod.rs:LL:CC
+note: inside `main` at $DIR/double_panic.rs:LL:CC
+  --> $DIR/double_panic.rs:LL:CC
+   |
+LL | }
+   | ^
+   = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+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/panic/panic_abort1.rs b/src/tools/miri/tests/fail/panic/panic_abort1.rs
new file mode 100644
index 0000000000000..00a01ce6e8137
--- /dev/null
+++ b/src/tools/miri/tests/fail/panic/panic_abort1.rs
@@ -0,0 +1,8 @@
+//@error-pattern: the program aborted execution
+//@normalize-stderr-test: "\| +\^+" -> "| ^"
+//@normalize-stderr-test: "libc::abort\(\);|core::intrinsics::abort\(\);" -> "ABORT();"
+//@compile-flags: -C panic=abort
+
+fn main() {
+    std::panic!("panicking from libstd");
+}
diff --git a/src/tools/miri/tests/fail/panic/panic_abort1.stderr b/src/tools/miri/tests/fail/panic/panic_abort1.stderr
new file mode 100644
index 0000000000000..7547199454643
--- /dev/null
+++ b/src/tools/miri/tests/fail/panic/panic_abort1.stderr
@@ -0,0 +1,25 @@
+thread 'main' panicked at 'panicking from libstd', $DIR/panic_abort1.rs:LL:CC
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
+error: abnormal termination: the program aborted execution
+  --> RUSTLIB/panic_abort/src/lib.rs:LL:CC
+   |
+LL |                 ABORT();
+   | ^ the program aborted execution
+   |
+   = note: inside `panic_abort::__rust_start_panic::abort` at RUSTLIB/panic_abort/src/lib.rs:LL:CC
+   = note: inside `panic_abort::__rust_start_panic` at RUSTLIB/panic_abort/src/lib.rs:LL:CC
+   = note: inside `std::panicking::rust_panic` at RUSTLIB/std/src/panicking.rs:LL:CC
+   = note: inside `std::panicking::rust_panic_with_hook` at RUSTLIB/std/src/panicking.rs:LL:CC
+   = note: inside closure at RUSTLIB/std/src/panicking.rs:LL:CC
+   = note: inside `std::sys_common::backtrace::__rust_end_short_backtrace::<[closure@std::rt::begin_panic<&str>::{closure#0}], !>` at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC
+note: inside `main` at RUSTLIB/std/src/panic.rs:LL:CC
+  --> $DIR/panic_abort1.rs:LL:CC
+   |
+LL |     std::panic!("panicking from libstd");
+   | ^
+   = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `std::panic` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+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/panic/panic_abort2.rs b/src/tools/miri/tests/fail/panic/panic_abort2.rs
new file mode 100644
index 0000000000000..dee0de96703a1
--- /dev/null
+++ b/src/tools/miri/tests/fail/panic/panic_abort2.rs
@@ -0,0 +1,8 @@
+//@error-pattern: the program aborted execution
+//@normalize-stderr-test: "\| +\^+" -> "| ^"
+//@normalize-stderr-test: "libc::abort\(\);|core::intrinsics::abort\(\);" -> "ABORT();"
+//@compile-flags: -C panic=abort
+
+fn main() {
+    std::panic!("{}-panicking from libstd", 42);
+}
diff --git a/src/tools/miri/tests/fail/panic/panic_abort2.stderr b/src/tools/miri/tests/fail/panic/panic_abort2.stderr
new file mode 100644
index 0000000000000..2fdf889d798a2
--- /dev/null
+++ b/src/tools/miri/tests/fail/panic/panic_abort2.stderr
@@ -0,0 +1,26 @@
+thread 'main' panicked at '42-panicking from libstd', $DIR/panic_abort2.rs:LL:CC
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
+error: abnormal termination: the program aborted execution
+  --> RUSTLIB/panic_abort/src/lib.rs:LL:CC
+   |
+LL |                 ABORT();
+   | ^ the program aborted execution
+   |
+   = note: inside `panic_abort::__rust_start_panic::abort` at RUSTLIB/panic_abort/src/lib.rs:LL:CC
+   = note: inside `panic_abort::__rust_start_panic` at RUSTLIB/panic_abort/src/lib.rs:LL:CC
+   = note: inside `std::panicking::rust_panic` at RUSTLIB/std/src/panicking.rs:LL:CC
+   = note: inside `std::panicking::rust_panic_with_hook` at RUSTLIB/std/src/panicking.rs:LL:CC
+   = note: inside closure at RUSTLIB/std/src/panicking.rs:LL:CC
+   = note: inside `std::sys_common::backtrace::__rust_end_short_backtrace::<[closure@std::panicking::begin_panic_handler::{closure#0}], !>` at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC
+   = note: inside `std::panicking::begin_panic_handler` at RUSTLIB/std/src/panicking.rs:LL:CC
+note: inside `main` at RUSTLIB/std/src/panic.rs:LL:CC
+  --> $DIR/panic_abort2.rs:LL:CC
+   |
+LL |     std::panic!("{}-panicking from libstd", 42);
+   | ^
+   = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `std::panic` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+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/panic/panic_abort3.rs b/src/tools/miri/tests/fail/panic/panic_abort3.rs
new file mode 100644
index 0000000000000..a448aab3ea458
--- /dev/null
+++ b/src/tools/miri/tests/fail/panic/panic_abort3.rs
@@ -0,0 +1,8 @@
+//@error-pattern: the program aborted execution
+//@normalize-stderr-test: "\| +\^+" -> "| ^"
+//@normalize-stderr-test: "libc::abort\(\);|core::intrinsics::abort\(\);" -> "ABORT();"
+//@compile-flags: -C panic=abort
+
+fn main() {
+    core::panic!("panicking from libcore");
+}
diff --git a/src/tools/miri/tests/fail/panic/panic_abort3.stderr b/src/tools/miri/tests/fail/panic/panic_abort3.stderr
new file mode 100644
index 0000000000000..8704b0d940b7a
--- /dev/null
+++ b/src/tools/miri/tests/fail/panic/panic_abort3.stderr
@@ -0,0 +1,26 @@
+thread 'main' panicked at 'panicking from libcore', $DIR/panic_abort3.rs:LL:CC
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
+error: abnormal termination: the program aborted execution
+  --> RUSTLIB/panic_abort/src/lib.rs:LL:CC
+   |
+LL |                 ABORT();
+   | ^ the program aborted execution
+   |
+   = note: inside `panic_abort::__rust_start_panic::abort` at RUSTLIB/panic_abort/src/lib.rs:LL:CC
+   = note: inside `panic_abort::__rust_start_panic` at RUSTLIB/panic_abort/src/lib.rs:LL:CC
+   = note: inside `std::panicking::rust_panic` at RUSTLIB/std/src/panicking.rs:LL:CC
+   = note: inside `std::panicking::rust_panic_with_hook` at RUSTLIB/std/src/panicking.rs:LL:CC
+   = note: inside closure at RUSTLIB/std/src/panicking.rs:LL:CC
+   = note: inside `std::sys_common::backtrace::__rust_end_short_backtrace::<[closure@std::panicking::begin_panic_handler::{closure#0}], !>` at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC
+   = note: inside `std::panicking::begin_panic_handler` at RUSTLIB/std/src/panicking.rs:LL:CC
+note: inside `main` at RUSTLIB/core/src/panic.rs:LL:CC
+  --> $DIR/panic_abort3.rs:LL:CC
+   |
+LL |     core::panic!("panicking from libcore");
+   | ^
+   = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `core::panic` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+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/panic/panic_abort4.rs b/src/tools/miri/tests/fail/panic/panic_abort4.rs
new file mode 100644
index 0000000000000..4995dad9d71ab
--- /dev/null
+++ b/src/tools/miri/tests/fail/panic/panic_abort4.rs
@@ -0,0 +1,8 @@
+//@error-pattern: the program aborted execution
+//@normalize-stderr-test: "\| +\^+" -> "| ^"
+//@normalize-stderr-test: "libc::abort\(\);|core::intrinsics::abort\(\);" -> "ABORT();"
+//@compile-flags: -C panic=abort
+
+fn main() {
+    core::panic!("{}-panicking from libcore", 42);
+}
diff --git a/src/tools/miri/tests/fail/panic/panic_abort4.stderr b/src/tools/miri/tests/fail/panic/panic_abort4.stderr
new file mode 100644
index 0000000000000..1d75d72c0317c
--- /dev/null
+++ b/src/tools/miri/tests/fail/panic/panic_abort4.stderr
@@ -0,0 +1,26 @@
+thread 'main' panicked at '42-panicking from libcore', $DIR/panic_abort4.rs:LL:CC
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
+error: abnormal termination: the program aborted execution
+  --> RUSTLIB/panic_abort/src/lib.rs:LL:CC
+   |
+LL |                 ABORT();
+   | ^ the program aborted execution
+   |
+   = note: inside `panic_abort::__rust_start_panic::abort` at RUSTLIB/panic_abort/src/lib.rs:LL:CC
+   = note: inside `panic_abort::__rust_start_panic` at RUSTLIB/panic_abort/src/lib.rs:LL:CC
+   = note: inside `std::panicking::rust_panic` at RUSTLIB/std/src/panicking.rs:LL:CC
+   = note: inside `std::panicking::rust_panic_with_hook` at RUSTLIB/std/src/panicking.rs:LL:CC
+   = note: inside closure at RUSTLIB/std/src/panicking.rs:LL:CC
+   = note: inside `std::sys_common::backtrace::__rust_end_short_backtrace::<[closure@std::panicking::begin_panic_handler::{closure#0}], !>` at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC
+   = note: inside `std::panicking::begin_panic_handler` at RUSTLIB/std/src/panicking.rs:LL:CC
+note: inside `main` at RUSTLIB/core/src/panic.rs:LL:CC
+  --> $DIR/panic_abort4.rs:LL:CC
+   |
+LL |     core::panic!("{}-panicking from libcore", 42);
+   | ^
+   = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `core::panic` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+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/panic/unwind_panic_abort.rs b/src/tools/miri/tests/fail/panic/unwind_panic_abort.rs
new file mode 100644
index 0000000000000..c21fa85a90439
--- /dev/null
+++ b/src/tools/miri/tests/fail/panic/unwind_panic_abort.rs
@@ -0,0 +1,13 @@
+//@compile-flags: -Cpanic=abort
+
+//! Unwinding despite `-C panic=abort` is an error.
+
+extern "Rust" {
+    fn miri_start_panic(payload: *mut u8) -> !;
+}
+
+fn main() {
+    unsafe {
+        miri_start_panic(&mut 0); //~ ERROR: unwinding past a stack frame that does not allow unwinding
+    }
+}
diff --git a/src/tools/miri/tests/fail/panic/unwind_panic_abort.stderr b/src/tools/miri/tests/fail/panic/unwind_panic_abort.stderr
new file mode 100644
index 0000000000000..363e69ba41db9
--- /dev/null
+++ b/src/tools/miri/tests/fail/panic/unwind_panic_abort.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: unwinding past a stack frame that does not allow unwinding
+  --> $DIR/unwind_panic_abort.rs:LL:CC
+   |
+LL |         miri_start_panic(&mut 0);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^ unwinding past a stack frame that does not allow unwinding
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/unwind_panic_abort.rs:LL:CC
+
+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/pointer_partial_overwrite.rs b/src/tools/miri/tests/fail/pointer_partial_overwrite.rs
new file mode 100644
index 0000000000000..63f0649b8ed3e
--- /dev/null
+++ b/src/tools/miri/tests/fail/pointer_partial_overwrite.rs
@@ -0,0 +1,17 @@
+// Make sure we find these even with many checks disabled.
+//@compile-flags: -Zmiri-disable-alignment-check -Zmiri-disable-stacked-borrows -Zmiri-disable-validation
+
+// Test what happens when we overwrite parts of a pointer.
+// Also see <https://github.com/rust-lang/miri/issues/2181>.
+
+fn main() {
+    let mut p = &42;
+    unsafe {
+        let ptr: *mut _ = &mut p;
+        *(ptr as *mut u8) = 123; // if we ever support 8 bit pointers, this is gonna cause
+        // "attempted to interpret some raw bytes as a pointer address" instead of
+        // "attempted to read undefined bytes"
+    }
+    let x = *p; //~ ERROR: this operation requires initialized memory
+    panic!("this should never print: {}", x);
+}
diff --git a/src/tools/miri/tests/fail/pointer_partial_overwrite.stderr b/src/tools/miri/tests/fail/pointer_partial_overwrite.stderr
new file mode 100644
index 0000000000000..7d10b75e8805f
--- /dev/null
+++ b/src/tools/miri/tests/fail/pointer_partial_overwrite.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
+  --> $DIR/pointer_partial_overwrite.rs:LL:CC
+   |
+LL |     let x = *p;
+   |             ^^ using uninitialized data, but this operation requires initialized memory
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/pointer_partial_overwrite.rs:LL:CC
+
+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/provenance/provenance_transmute.rs b/src/tools/miri/tests/fail/provenance/provenance_transmute.rs
new file mode 100644
index 0000000000000..abcfc060e52bc
--- /dev/null
+++ b/src/tools/miri/tests/fail/provenance/provenance_transmute.rs
@@ -0,0 +1,27 @@
+//@compile-flags: -Zmiri-permissive-provenance
+#![feature(strict_provenance)]
+
+use std::mem;
+
+// This is the example from
+// <https://github.com/rust-lang/unsafe-code-guidelines/issues/286#issuecomment-1085144431>.
+
+unsafe fn deref(left: *const u8, right: *const u8) {
+    let left_int: usize = mem::transmute(left);
+    let right_int: usize = mem::transmute(right);
+    if left_int == right_int {
+        // The compiler is allowed to replace `left_int` by `right_int` here...
+        let left_ptr: *const u8 = mem::transmute(left_int);
+        // ...which however means here it could be dereferencing the wrong pointer.
+        let _val = *left_ptr; //~ERROR: dereferencing pointer failed
+    }
+}
+
+fn main() {
+    let ptr1 = &0u8 as *const u8;
+    let ptr2 = &1u8 as *const u8;
+    unsafe {
+        // Two pointers with the same address but different provenance.
+        deref(ptr1, ptr2.with_addr(ptr1.addr()));
+    }
+}
diff --git a/src/tools/miri/tests/fail/provenance/provenance_transmute.stderr b/src/tools/miri/tests/fail/provenance/provenance_transmute.stderr
new file mode 100644
index 0000000000000..f7c5f6046e198
--- /dev/null
+++ b/src/tools/miri/tests/fail/provenance/provenance_transmute.stderr
@@ -0,0 +1,20 @@
+error: Undefined Behavior: dereferencing pointer failed: $HEX[noalloc] is a dangling pointer (it has no provenance)
+  --> $DIR/provenance_transmute.rs:LL:CC
+   |
+LL |         let _val = *left_ptr;
+   |                    ^^^^^^^^^ dereferencing pointer failed: $HEX[noalloc] is a dangling pointer (it has no provenance)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `deref` at $DIR/provenance_transmute.rs:LL:CC
+note: inside `main` at $DIR/provenance_transmute.rs:LL:CC
+  --> $DIR/provenance_transmute.rs:LL:CC
+   |
+LL |         deref(ptr1, ptr2.with_addr(ptr1.addr()));
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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/provenance/ptr_int_unexposed.rs b/src/tools/miri/tests/fail/provenance/ptr_int_unexposed.rs
new file mode 100644
index 0000000000000..07de41d10a038
--- /dev/null
+++ b/src/tools/miri/tests/fail/provenance/ptr_int_unexposed.rs
@@ -0,0 +1,12 @@
+//@compile-flags: -Zmiri-permissive-provenance
+#![feature(strict_provenance)]
+
+fn main() {
+    let x: i32 = 3;
+    let x_ptr = &x as *const i32;
+
+    let x_usize: usize = x_ptr.addr();
+    // Cast back an address that did *not* get exposed.
+    let ptr = std::ptr::from_exposed_addr::<i32>(x_usize);
+    assert_eq!(unsafe { *ptr }, 3); //~ ERROR: is a dangling pointer
+}
diff --git a/src/tools/miri/tests/fail/provenance/ptr_int_unexposed.stderr b/src/tools/miri/tests/fail/provenance/ptr_int_unexposed.stderr
new file mode 100644
index 0000000000000..4ad885ddabdc0
--- /dev/null
+++ b/src/tools/miri/tests/fail/provenance/ptr_int_unexposed.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: dereferencing pointer failed: $HEX[noalloc] is a dangling pointer (it has no provenance)
+  --> $DIR/ptr_int_unexposed.rs:LL:CC
+   |
+LL |     assert_eq!(unsafe { *ptr }, 3);
+   |                         ^^^^ dereferencing pointer failed: $HEX[noalloc] is a dangling pointer (it has no provenance)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/ptr_int_unexposed.rs:LL:CC
+
+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/provenance/ptr_invalid.rs b/src/tools/miri/tests/fail/provenance/ptr_invalid.rs
new file mode 100644
index 0000000000000..d7d32d83e0771
--- /dev/null
+++ b/src/tools/miri/tests/fail/provenance/ptr_invalid.rs
@@ -0,0 +1,9 @@
+#![feature(strict_provenance)]
+
+// Ensure that a `ptr::invalid` ptr is truly invalid.
+fn main() {
+    let x = 42;
+    let xptr = &x as *const i32;
+    let xptr_invalid = std::ptr::invalid::<i32>(xptr.expose_addr());
+    let _val = unsafe { *xptr_invalid }; //~ ERROR: is a dangling pointer
+}
diff --git a/src/tools/miri/tests/fail/provenance/ptr_invalid.stderr b/src/tools/miri/tests/fail/provenance/ptr_invalid.stderr
new file mode 100644
index 0000000000000..ef9dcad97cbdc
--- /dev/null
+++ b/src/tools/miri/tests/fail/provenance/ptr_invalid.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: dereferencing pointer failed: $HEX[noalloc] is a dangling pointer (it has no provenance)
+  --> $DIR/ptr_invalid.rs:LL:CC
+   |
+LL |     let _val = unsafe { *xptr_invalid };
+   |                         ^^^^^^^^^^^^^ dereferencing pointer failed: $HEX[noalloc] is a dangling pointer (it has no provenance)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/ptr_invalid.rs:LL:CC
+
+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/provenance/ptr_invalid_offset.rs b/src/tools/miri/tests/fail/provenance/ptr_invalid_offset.rs
new file mode 100644
index 0000000000000..91ba18f768055
--- /dev/null
+++ b/src/tools/miri/tests/fail/provenance/ptr_invalid_offset.rs
@@ -0,0 +1,10 @@
+//@compile-flags: -Zmiri-strict-provenance
+#![feature(strict_provenance)]
+
+fn main() {
+    let x = 22;
+    let ptr = &x as *const _ as *const u8;
+    let roundtrip = std::ptr::invalid::<u8>(ptr as usize);
+    // Not even offsetting this is allowed.
+    let _ = unsafe { roundtrip.offset(1) }; //~ERROR: is a dangling pointer
+}
diff --git a/src/tools/miri/tests/fail/provenance/ptr_invalid_offset.stderr b/src/tools/miri/tests/fail/provenance/ptr_invalid_offset.stderr
new file mode 100644
index 0000000000000..3607635c8fbe5
--- /dev/null
+++ b/src/tools/miri/tests/fail/provenance/ptr_invalid_offset.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: out-of-bounds pointer arithmetic: $HEX[noalloc] is a dangling pointer (it has no provenance)
+  --> $DIR/ptr_invalid_offset.rs:LL:CC
+   |
+LL |     let _ = unsafe { roundtrip.offset(1) };
+   |                      ^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer arithmetic: $HEX[noalloc] is a dangling pointer (it has no provenance)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/ptr_invalid_offset.rs:LL:CC
+
+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/provenance/strict_provenance_cast.rs b/src/tools/miri/tests/fail/provenance/strict_provenance_cast.rs
new file mode 100644
index 0000000000000..04552d0c332fd
--- /dev/null
+++ b/src/tools/miri/tests/fail/provenance/strict_provenance_cast.rs
@@ -0,0 +1,7 @@
+//@compile-flags: -Zmiri-strict-provenance
+#![feature(strict_provenance)]
+
+fn main() {
+    let addr = &0 as *const i32 as usize;
+    let _ptr = std::ptr::from_exposed_addr::<i32>(addr); //~ ERROR: integer-to-pointer casts and `ptr::from_exposed_addr` are not supported
+}
diff --git a/src/tools/miri/tests/fail/provenance/strict_provenance_cast.stderr b/src/tools/miri/tests/fail/provenance/strict_provenance_cast.stderr
new file mode 100644
index 0000000000000..998ccc8bb49c6
--- /dev/null
+++ b/src/tools/miri/tests/fail/provenance/strict_provenance_cast.stderr
@@ -0,0 +1,14 @@
+error: unsupported operation: integer-to-pointer casts and `ptr::from_exposed_addr` are not supported with `-Zmiri-strict-provenance`
+  --> $DIR/strict_provenance_cast.rs:LL:CC
+   |
+LL |     let _ptr = std::ptr::from_exposed_addr::<i32>(addr);
+   |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ integer-to-pointer casts and `ptr::from_exposed_addr` are not supported with `-Zmiri-strict-provenance`
+   |
+   = help: use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/strict_provenance_cast.rs:LL:CC
+
+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/rc_as_ptr.rs b/src/tools/miri/tests/fail/rc_as_ptr.rs
new file mode 100644
index 0000000000000..6aea1870748cc
--- /dev/null
+++ b/src/tools/miri/tests/fail/rc_as_ptr.rs
@@ -0,0 +1,20 @@
+// This should fail even without validation
+//@compile-flags: -Zmiri-disable-validation
+
+use std::ptr;
+use std::rc::{Rc, Weak};
+
+/// Taken from the `Weak::as_ptr` doctest.
+fn main() {
+    let strong = Rc::new(Box::new(42));
+    let weak = Rc::downgrade(&strong);
+    // Both point to the same object
+    assert!(ptr::eq(&*strong, Weak::as_ptr(&weak)));
+    // The strong here keeps it alive, so we can still access the object.
+    assert_eq!(42, **unsafe { &*Weak::as_ptr(&weak) });
+
+    drop(strong);
+    // But not any more. We can do Weak::as_raw(&weak), but accessing the pointer would lead to
+    // undefined behaviour.
+    assert_eq!(42, **unsafe { &*Weak::as_ptr(&weak) }); //~ ERROR: dereferenced after this allocation got freed
+}
diff --git a/src/tools/miri/tests/fail/rc_as_ptr.stderr b/src/tools/miri/tests/fail/rc_as_ptr.stderr
new file mode 100644
index 0000000000000..70bdd157bdc34
--- /dev/null
+++ b/src/tools/miri/tests/fail/rc_as_ptr.stderr
@@ -0,0 +1,16 @@
+error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed
+  --> $DIR/rc_as_ptr.rs:LL:CC
+   |
+LL |     assert_eq!(42, **unsafe { &*Weak::as_ptr(&weak) });
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at RUSTLIB/core/src/macros/mod.rs:LL:CC
+   = note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+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/reading_half_a_pointer.rs b/src/tools/miri/tests/fail/reading_half_a_pointer.rs
new file mode 100644
index 0000000000000..2d66913262476
--- /dev/null
+++ b/src/tools/miri/tests/fail/reading_half_a_pointer.rs
@@ -0,0 +1,30 @@
+#![allow(dead_code)]
+
+// We use packed structs to get around alignment restrictions
+#[repr(packed)]
+struct Data {
+    pad: u8,
+    ptr: &'static i32,
+}
+
+// But we need to gurantee some alignment
+struct Wrapper {
+    align: u64,
+    data: Data,
+}
+
+static G: i32 = 0;
+
+fn main() {
+    let mut w = Wrapper { align: 0, data: Data { pad: 0, ptr: &G } };
+
+    // Get a pointer to the beginning of the Data struct (one u8 byte, then the pointer bytes).
+    // Thanks to the wrapper, we know this is aligned-enough to perform a load at ptr size.
+    // We load at pointer type, so having a relocation is ok -- but here, the relocation
+    // starts 1 byte to the right, so using it would actually be wrong!
+    let d_alias = &mut w.data as *mut _ as *mut *const u8;
+    unsafe {
+        let x = *d_alias;
+        let _val = *x; //~ERROR: is a dangling pointer (it has no provenance)
+    }
+}
diff --git a/src/tools/miri/tests/fail/reading_half_a_pointer.stderr b/src/tools/miri/tests/fail/reading_half_a_pointer.stderr
new file mode 100644
index 0000000000000..61a7161a98bb3
--- /dev/null
+++ b/src/tools/miri/tests/fail/reading_half_a_pointer.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: dereferencing pointer failed: $HEX[noalloc] is a dangling pointer (it has no provenance)
+  --> $DIR/reading_half_a_pointer.rs:LL:CC
+   |
+LL |         let _val = *x;
+   |                    ^^ dereferencing pointer failed: $HEX[noalloc] is a dangling pointer (it has no provenance)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/reading_half_a_pointer.rs:LL:CC
+
+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/rustc-error.rs b/src/tools/miri/tests/fail/rustc-error.rs
new file mode 100644
index 0000000000000..7fc73bf365d5e
--- /dev/null
+++ b/src/tools/miri/tests/fail/rustc-error.rs
@@ -0,0 +1,4 @@
+// Make sure we exit with non-0 status code when the program fails to build.
+fn main() {
+    println("Hello, world!"); //~ ERROR: expected function, found macro
+}
diff --git a/src/tools/miri/tests/fail/rustc-error.stderr b/src/tools/miri/tests/fail/rustc-error.stderr
new file mode 100644
index 0000000000000..09d0f7a6df955
--- /dev/null
+++ b/src/tools/miri/tests/fail/rustc-error.stderr
@@ -0,0 +1,14 @@
+error[E0423]: expected function, found macro `println`
+  --> $DIR/rustc-error.rs:LL:CC
+   |
+LL |     println("Hello, world!");
+   |     ^^^^^^^ not a function
+   |
+help: use `!` to invoke the macro
+   |
+LL |     println!("Hello, world!");
+   |            +
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0423`.
diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.rs b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.rs
new file mode 100644
index 0000000000000..97a70103e6461
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.rs
@@ -0,0 +1,13 @@
+extern "Rust" {
+    fn miri_get_backtrace(flags: u64) -> Box<[*mut ()]>;
+    fn miri_resolve_frame(ptr: *mut (), flags: u64);
+}
+
+fn main() {
+    let frames = unsafe { miri_get_backtrace(0) };
+    for frame in frames.into_iter() {
+        unsafe {
+            miri_resolve_frame(*frame, 0); //~ ERROR: Undefined Behavior: bad declaration of miri_resolve_frame - should return a struct with 5 fields
+        }
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.stderr b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.stderr
new file mode 100644
index 0000000000000..200f5f56213d6
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: bad declaration of miri_resolve_frame - should return a struct with 5 fields
+  --> $DIR/bad-backtrace-decl.rs:LL:CC
+   |
+LL | ...   miri_resolve_frame(*frame, 0);
+   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ bad declaration of miri_resolve_frame - should return a struct with 5 fields
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/bad-backtrace-decl.rs:LL:CC
+
+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/shims/backtrace/bad-backtrace-flags.rs b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-flags.rs
new file mode 100644
index 0000000000000..a4e186eaa98a4
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-flags.rs
@@ -0,0 +1,9 @@
+extern "Rust" {
+    fn miri_get_backtrace(flags: u64, buf: *mut *mut ());
+}
+
+fn main() {
+    unsafe {
+        miri_get_backtrace(2, std::ptr::null_mut()); //~ ERROR:  unsupported operation: unknown `miri_get_backtrace` flags 2
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-flags.stderr b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-flags.stderr
new file mode 100644
index 0000000000000..5d51790f8a5c1
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-flags.stderr
@@ -0,0 +1,14 @@
+error: unsupported operation: unknown `miri_get_backtrace` flags 2
+  --> $DIR/bad-backtrace-flags.rs:LL:CC
+   |
+LL |         miri_get_backtrace(2, std::ptr::null_mut());
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unknown `miri_get_backtrace` flags 2
+   |
+   = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/bad-backtrace-flags.rs:LL:CC
+
+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/shims/backtrace/bad-backtrace-ptr.rs b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-ptr.rs
new file mode 100644
index 0000000000000..843d0d11873d2
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-ptr.rs
@@ -0,0 +1,9 @@
+extern "Rust" {
+    fn miri_resolve_frame(ptr: *mut (), flags: u64);
+}
+
+fn main() {
+    unsafe {
+        miri_resolve_frame(std::ptr::null_mut(), 0); //~ ERROR: null pointer is a dangling pointer
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-ptr.stderr b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-ptr.stderr
new file mode 100644
index 0000000000000..f23f834000aa1
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-ptr.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: out-of-bounds pointer use: null pointer is a dangling pointer (it has no provenance)
+  --> $DIR/bad-backtrace-ptr.rs:LL:CC
+   |
+LL |         miri_resolve_frame(std::ptr::null_mut(), 0);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer use: null pointer is a dangling pointer (it has no provenance)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/bad-backtrace-ptr.rs:LL:CC
+
+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/shims/backtrace/bad-backtrace-resolve-flags.rs b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.rs
new file mode 100644
index 0000000000000..31e3915f3d64f
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.rs
@@ -0,0 +1,25 @@
+#[repr(C)]
+struct MiriFrame {
+    name_len: usize,
+    filename_len: usize,
+    lineno: u32,
+    colno: u32,
+    fn_ptr: *mut (),
+}
+
+extern "Rust" {
+    fn miri_backtrace_size(flags: u64) -> usize;
+    fn miri_get_backtrace(flags: u64, buf: *mut *mut ());
+    fn miri_resolve_frame(ptr: *mut (), flags: u64) -> MiriFrame;
+}
+
+fn main() {
+    unsafe {
+        let mut buf = vec![std::ptr::null_mut(); miri_backtrace_size(0)];
+
+        miri_get_backtrace(1, buf.as_mut_ptr());
+
+        // miri_resolve_frame will error from an invalid backtrace before it will from invalid flags
+        miri_resolve_frame(buf[0], 2); //~ ERROR:  unsupported operation: unknown `miri_resolve_frame` flags 2
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.stderr b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.stderr
new file mode 100644
index 0000000000000..fe123c2352f0a
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.stderr
@@ -0,0 +1,14 @@
+error: unsupported operation: unknown `miri_resolve_frame` flags 2
+  --> $DIR/bad-backtrace-resolve-flags.rs:LL:CC
+   |
+LL |         miri_resolve_frame(buf[0], 2);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unknown `miri_resolve_frame` flags 2
+   |
+   = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/bad-backtrace-resolve-flags.rs:LL:CC
+
+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/shims/backtrace/bad-backtrace-resolve-names-flags.rs b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-names-flags.rs
new file mode 100644
index 0000000000000..44c3c025043b6
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-names-flags.rs
@@ -0,0 +1,16 @@
+extern "Rust" {
+    fn miri_backtrace_size(flags: u64) -> usize;
+    fn miri_get_backtrace(flags: u64, buf: *mut *mut ());
+    fn miri_resolve_frame_names(ptr: *mut (), flags: u64, name_buf: *mut u8, filename_buf: *mut u8);
+}
+
+fn main() {
+    unsafe {
+        let mut buf = vec![std::ptr::null_mut(); miri_backtrace_size(0)];
+
+        miri_get_backtrace(1, buf.as_mut_ptr());
+
+        // miri_resolve_frame_names will error from an invalid backtrace before it will from invalid flags
+        miri_resolve_frame_names(buf[0], 2, std::ptr::null_mut(), std::ptr::null_mut()); //~ ERROR:  unsupported operation: unknown `miri_resolve_frame_names` flags 2
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-names-flags.stderr b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-names-flags.stderr
new file mode 100644
index 0000000000000..a3003c9093f72
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-names-flags.stderr
@@ -0,0 +1,14 @@
+error: unsupported operation: unknown `miri_resolve_frame_names` flags 2
+  --> $DIR/bad-backtrace-resolve-names-flags.rs:LL:CC
+   |
+LL | ...   miri_resolve_frame_names(buf[0], 2, std::ptr::null_mut(), std::ptr::null_mut());
+   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unknown `miri_resolve_frame_names` flags 2
+   |
+   = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/bad-backtrace-resolve-names-flags.rs:LL:CC
+
+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/shims/backtrace/bad-backtrace-size-flags.rs b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-size-flags.rs
new file mode 100644
index 0000000000000..bba74c71a5e8f
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-size-flags.rs
@@ -0,0 +1,9 @@
+extern "Rust" {
+    fn miri_backtrace_size(flags: u64) -> usize;
+}
+
+fn main() {
+    unsafe {
+        miri_backtrace_size(2); //~ ERROR:  unsupported operation: unknown `miri_backtrace_size` flags 2
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-size-flags.stderr b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-size-flags.stderr
new file mode 100644
index 0000000000000..b4a02c0e363ed
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-size-flags.stderr
@@ -0,0 +1,14 @@
+error: unsupported operation: unknown `miri_backtrace_size` flags 2
+  --> $DIR/bad-backtrace-size-flags.rs:LL:CC
+   |
+LL |         miri_backtrace_size(2);
+   |         ^^^^^^^^^^^^^^^^^^^^^^ unknown `miri_backtrace_size` flags 2
+   |
+   = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/bad-backtrace-size-flags.rs:LL:CC
+
+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/shims/fs/close_stdout.rs b/src/tools/miri/tests/fail/shims/fs/close_stdout.rs
new file mode 100644
index 0000000000000..09da8509af412
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/fs/close_stdout.rs
@@ -0,0 +1,10 @@
+//@ignore-target-windows: No libc on Windows
+//@compile-flags: -Zmiri-disable-isolation
+
+// FIXME: standard handles cannot be closed (https://github.com/rust-lang/rust/issues/40032)
+
+fn main() {
+    unsafe {
+        libc::close(1); //~ ERROR: cannot close stdout
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/fs/close_stdout.stderr b/src/tools/miri/tests/fail/shims/fs/close_stdout.stderr
new file mode 100644
index 0000000000000..02f1eee97fc04
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/fs/close_stdout.stderr
@@ -0,0 +1,14 @@
+error: unsupported operation: cannot close stdout
+  --> $DIR/close_stdout.rs:LL:CC
+   |
+LL |         libc::close(1);
+   |         ^^^^^^^^^^^^^^ cannot close stdout
+   |
+   = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/close_stdout.rs:LL:CC
+
+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/shims/fs/isolated_file.rs b/src/tools/miri/tests/fail/shims/fs/isolated_file.rs
new file mode 100644
index 0000000000000..9b664ffe52acd
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/fs/isolated_file.rs
@@ -0,0 +1,6 @@
+//@ignore-target-windows: File handling is not implemented yet
+//@error-pattern: `open` not available when isolation is enabled
+
+fn main() {
+    let _file = std::fs::File::open("file.txt").unwrap();
+}
diff --git a/src/tools/miri/tests/fail/shims/fs/isolated_file.stderr b/src/tools/miri/tests/fail/shims/fs/isolated_file.stderr
new file mode 100644
index 0000000000000..4e3fdc7a45801
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/fs/isolated_file.stderr
@@ -0,0 +1,26 @@
+error: unsupported operation: `open` not available when isolation is enabled
+  --> RUSTLIB/std/src/sys/PLATFORM/fs.rs:LL:CC
+   |
+LL |         let fd = cvt_r(|| unsafe { open64(path.as_ptr(), flags, opts.mode as c_int) })?;
+   |                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `open` not available when isolation is enabled
+   |
+   = help: pass the flag `-Zmiri-disable-isolation` to disable isolation;
+   = help: or pass `-Zmiri-isolation-error=warn` to configure Miri to return an error code from isolated operations (if supported for that operation) and continue with a warning
+   = note: BACKTRACE:
+   = note: inside closure at RUSTLIB/std/src/sys/PLATFORM/fs.rs:LL:CC
+   = note: inside `std::sys::PLATFORM::cvt_r::<i32, [closure@std::sys::PLATFORM::fs::File::open_c::{closure#0}]>` at RUSTLIB/std/src/sys/PLATFORM/mod.rs:LL:CC
+   = note: inside `std::sys::PLATFORM::fs::File::open_c` at RUSTLIB/std/src/sys/PLATFORM/fs.rs:LL:CC
+   = note: inside `std::sys::PLATFORM::fs::File::open` at RUSTLIB/std/src/sys/PLATFORM/fs.rs:LL:CC
+   = note: inside `std::fs::OpenOptions::_open` at RUSTLIB/std/src/fs.rs:LL:CC
+   = note: inside `std::fs::OpenOptions::open::<&std::path::Path>` at RUSTLIB/std/src/fs.rs:LL:CC
+   = note: inside `std::fs::File::open::<&str>` at RUSTLIB/std/src/fs.rs:LL:CC
+note: inside `main` at $DIR/isolated_file.rs:LL:CC
+  --> $DIR/isolated_file.rs:LL:CC
+   |
+LL |     let _file = std::fs::File::open("file.txt").unwrap();
+   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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/shims/fs/isolated_stdin.rs b/src/tools/miri/tests/fail/shims/fs/isolated_stdin.rs
new file mode 100644
index 0000000000000..a45f805696d49
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/fs/isolated_stdin.rs
@@ -0,0 +1,9 @@
+//@ignore-target-windows: No libc on Windows
+
+fn main() -> std::io::Result<()> {
+    let mut bytes = [0u8; 512];
+    unsafe {
+        libc::read(0, bytes.as_mut_ptr() as *mut libc::c_void, 512); //~ ERROR: `read` from stdin not available when isolation is enabled
+    }
+    Ok(())
+}
diff --git a/src/tools/miri/tests/fail/shims/fs/isolated_stdin.stderr b/src/tools/miri/tests/fail/shims/fs/isolated_stdin.stderr
new file mode 100644
index 0000000000000..ed826147e3bdb
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/fs/isolated_stdin.stderr
@@ -0,0 +1,15 @@
+error: unsupported operation: `read` from stdin not available when isolation is enabled
+  --> $DIR/isolated_stdin.rs:LL:CC
+   |
+LL |         libc::read(0, bytes.as_mut_ptr() as *mut libc::c_void, 512);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `read` from stdin not available when isolation is enabled
+   |
+   = help: pass the flag `-Zmiri-disable-isolation` to disable isolation;
+   = help: or pass `-Zmiri-isolation-error=warn` to configure Miri to return an error code from isolated operations (if supported for that operation) and continue with a warning
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/isolated_stdin.rs:LL:CC
+
+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/shims/fs/mkstemp_immutable_arg.rs b/src/tools/miri/tests/fail/shims/fs/mkstemp_immutable_arg.rs
new file mode 100644
index 0000000000000..ba9f404d7c9ac
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/fs/mkstemp_immutable_arg.rs
@@ -0,0 +1,11 @@
+//@ignore-target-windows: No libc on Windows
+//@compile-flags: -Zmiri-disable-isolation
+
+fn main() {
+    test_mkstemp_immutable_arg();
+}
+
+fn test_mkstemp_immutable_arg() {
+    let s: *mut libc::c_char = b"fooXXXXXX\0" as *const _ as *mut _;
+    let _fd = unsafe { libc::mkstemp(s) }; //~ ERROR: Undefined Behavior: writing to alloc1 which is read-only
+}
diff --git a/src/tools/miri/tests/fail/shims/fs/mkstemp_immutable_arg.stderr b/src/tools/miri/tests/fail/shims/fs/mkstemp_immutable_arg.stderr
new file mode 100644
index 0000000000000..414ac1cb1b702
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/fs/mkstemp_immutable_arg.stderr
@@ -0,0 +1,20 @@
+error: Undefined Behavior: writing to ALLOC which is read-only
+  --> $DIR/mkstemp_immutable_arg.rs:LL:CC
+   |
+LL |     let _fd = unsafe { libc::mkstemp(s) };
+   |                        ^^^^^^^^^^^^^^^^ writing to ALLOC which is read-only
+   |
+   = 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 `test_mkstemp_immutable_arg` at $DIR/mkstemp_immutable_arg.rs:LL:CC
+note: inside `main` at $DIR/mkstemp_immutable_arg.rs:LL:CC
+  --> $DIR/mkstemp_immutable_arg.rs:LL:CC
+   |
+LL |     test_mkstemp_immutable_arg();
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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/shims/fs/read_from_stdout.rs b/src/tools/miri/tests/fail/shims/fs/read_from_stdout.rs
new file mode 100644
index 0000000000000..073fca4712e9a
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/fs/read_from_stdout.rs
@@ -0,0 +1,10 @@
+//@compile-flags: -Zmiri-disable-isolation
+//@ignore-target-windows: No libc on Windows
+
+fn main() -> std::io::Result<()> {
+    let mut bytes = [0u8; 512];
+    unsafe {
+        libc::read(1, bytes.as_mut_ptr() as *mut libc::c_void, 512); //~ ERROR: cannot read from stdout
+    }
+    Ok(())
+}
diff --git a/src/tools/miri/tests/fail/shims/fs/read_from_stdout.stderr b/src/tools/miri/tests/fail/shims/fs/read_from_stdout.stderr
new file mode 100644
index 0000000000000..bcece7ad4e55d
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/fs/read_from_stdout.stderr
@@ -0,0 +1,14 @@
+error: unsupported operation: cannot read from stdout
+  --> $DIR/read_from_stdout.rs:LL:CC
+   |
+LL |         libc::read(1, bytes.as_mut_ptr() as *mut libc::c_void, 512);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot read from stdout
+   |
+   = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/read_from_stdout.rs:LL:CC
+
+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/shims/fs/unix_open_missing_required_mode.rs b/src/tools/miri/tests/fail/shims/fs/unix_open_missing_required_mode.rs
new file mode 100644
index 0000000000000..ae231d4be667e
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/fs/unix_open_missing_required_mode.rs
@@ -0,0 +1,12 @@
+//@ignore-target-windows: No libc on Windows
+//@compile-flags: -Zmiri-disable-isolation
+
+fn main() {
+    test_file_open_missing_needed_mode();
+}
+
+fn test_file_open_missing_needed_mode() {
+    let name = b"missing_arg.txt\0";
+    let name_ptr = name.as_ptr().cast::<libc::c_char>();
+    let _fd = unsafe { libc::open(name_ptr, libc::O_CREAT) }; //~ ERROR: Undefined Behavior: incorrect number of arguments for `open` with `O_CREAT`: got 2, expected at least 3
+}
diff --git a/src/tools/miri/tests/fail/shims/fs/unix_open_missing_required_mode.stderr b/src/tools/miri/tests/fail/shims/fs/unix_open_missing_required_mode.stderr
new file mode 100644
index 0000000000000..38d033b494554
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/fs/unix_open_missing_required_mode.stderr
@@ -0,0 +1,20 @@
+error: Undefined Behavior: incorrect number of arguments for `open` with `O_CREAT`: got 2, expected at least 3
+  --> $DIR/unix_open_missing_required_mode.rs:LL:CC
+   |
+LL | ...safe { libc::open(name_ptr, libc::O_CREAT) };
+   |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ incorrect number of arguments for `open` with `O_CREAT`: got 2, expected at least 3
+   |
+   = 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 `test_file_open_missing_needed_mode` at $DIR/unix_open_missing_required_mode.rs:LL:CC
+note: inside `main` at $DIR/unix_open_missing_required_mode.rs:LL:CC
+  --> $DIR/unix_open_missing_required_mode.rs:LL:CC
+   |
+LL |     test_file_open_missing_needed_mode();
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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/shims/fs/write_to_stdin.rs b/src/tools/miri/tests/fail/shims/fs/write_to_stdin.rs
new file mode 100644
index 0000000000000..d039ad718d339
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/fs/write_to_stdin.rs
@@ -0,0 +1,9 @@
+//@ignore-target-windows: No libc on Windows
+
+fn main() -> std::io::Result<()> {
+    let bytes = b"hello";
+    unsafe {
+        libc::write(0, bytes.as_ptr() as *const libc::c_void, 5); //~ ERROR: cannot write to stdin
+    }
+    Ok(())
+}
diff --git a/src/tools/miri/tests/fail/shims/fs/write_to_stdin.stderr b/src/tools/miri/tests/fail/shims/fs/write_to_stdin.stderr
new file mode 100644
index 0000000000000..d4a38e1ca9615
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/fs/write_to_stdin.stderr
@@ -0,0 +1,14 @@
+error: unsupported operation: cannot write to stdin
+  --> $DIR/write_to_stdin.rs:LL:CC
+   |
+LL |         libc::write(0, bytes.as_ptr() as *const libc::c_void, 5);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot write to stdin
+   |
+   = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/write_to_stdin.rs:LL:CC
+
+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/shims/shim_arg_size.rs b/src/tools/miri/tests/fail/shims/shim_arg_size.rs
new file mode 100644
index 0000000000000..3d7bc25bf5d31
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/shim_arg_size.rs
@@ -0,0 +1,10 @@
+fn main() {
+    extern "C" {
+        // Use the wrong type (ie. not `i32`) for the `c` argument.
+        fn memchr(s: *const std::ffi::c_void, c: u8, n: usize) -> *mut std::ffi::c_void;
+    }
+
+    unsafe {
+        memchr(std::ptr::null(), 0, 0); //~ ERROR: Undefined Behavior: scalar size mismatch
+    };
+}
diff --git a/src/tools/miri/tests/fail/shims/shim_arg_size.stderr b/src/tools/miri/tests/fail/shims/shim_arg_size.stderr
new file mode 100644
index 0000000000000..d951f81810ef6
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/shim_arg_size.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: scalar size mismatch: expected 4 bytes but got 1 bytes instead
+  --> $DIR/shim_arg_size.rs:LL:CC
+   |
+LL |         memchr(std::ptr::null(), 0, 0);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ scalar size mismatch: expected 4 bytes but got 1 bytes instead
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/shim_arg_size.rs:LL:CC
+
+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/shims/sync/libc_pthread_cond_double_destroy.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_cond_double_destroy.rs
new file mode 100644
index 0000000000000..94ca3496ed948
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_cond_double_destroy.rs
@@ -0,0 +1,20 @@
+//@ignore-target-windows: No libc on Windows
+
+/// Test that destroying a pthread_cond twice fails, even without a check for number validity
+
+fn main() {
+    unsafe {
+        use core::mem::MaybeUninit;
+        let mut attr = MaybeUninit::<libc::pthread_condattr_t>::uninit();
+        libc::pthread_condattr_init(attr.as_mut_ptr());
+
+        let mut cond = MaybeUninit::<libc::pthread_cond_t>::uninit();
+
+        libc::pthread_cond_init(cond.as_mut_ptr(), attr.as_ptr());
+
+        libc::pthread_cond_destroy(cond.as_mut_ptr());
+
+        libc::pthread_cond_destroy(cond.as_mut_ptr());
+        //~^ ERROR: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_cond_double_destroy.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_cond_double_destroy.stderr
new file mode 100644
index 0000000000000..ecfedf753703d
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_cond_double_destroy.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
+  --> $DIR/libc_pthread_cond_double_destroy.rs:LL:CC
+   |
+LL |         libc::pthread_cond_destroy(cond.as_mut_ptr());
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ using uninitialized data, but this operation requires initialized memory
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/libc_pthread_cond_double_destroy.rs:LL:CC
+
+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/shims/sync/libc_pthread_condattr_double_destroy.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_condattr_double_destroy.rs
new file mode 100644
index 0000000000000..13e639a867dcc
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_condattr_double_destroy.rs
@@ -0,0 +1,17 @@
+//@ignore-target-windows: No libc on Windows
+
+/// Test that destroying a pthread_condattr twice fails, even without a check for number validity
+
+fn main() {
+    unsafe {
+        use core::mem::MaybeUninit;
+        let mut attr = MaybeUninit::<libc::pthread_condattr_t>::uninit();
+
+        libc::pthread_condattr_init(attr.as_mut_ptr());
+
+        libc::pthread_condattr_destroy(attr.as_mut_ptr());
+
+        libc::pthread_condattr_destroy(attr.as_mut_ptr());
+        //~^ ERROR: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_condattr_double_destroy.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_condattr_double_destroy.stderr
new file mode 100644
index 0000000000000..f39d909adbd64
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_condattr_double_destroy.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
+  --> $DIR/libc_pthread_condattr_double_destroy.rs:LL:CC
+   |
+LL |         libc::pthread_condattr_destroy(attr.as_mut_ptr());
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ using uninitialized data, but this operation requires initialized memory
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/libc_pthread_condattr_double_destroy.rs:LL:CC
+
+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/shims/sync/libc_pthread_mutex_NULL_deadlock.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_NULL_deadlock.rs
new file mode 100644
index 0000000000000..8b2510733831f
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_NULL_deadlock.rs
@@ -0,0 +1,12 @@
+//@ignore-target-windows: No libc on Windows
+//
+// Check that if we pass NULL attribute, then we get the default mutex type.
+
+fn main() {
+    unsafe {
+        let mut mutex: libc::pthread_mutex_t = std::mem::zeroed();
+        assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, std::ptr::null() as *const _), 0);
+        assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0);
+        libc::pthread_mutex_lock(&mut mutex as *mut _); //~ ERROR: Undefined Behavior: trying to acquire already locked default mutex
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_NULL_deadlock.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_NULL_deadlock.stderr
new file mode 100644
index 0000000000000..4a138e6f8a25c
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_NULL_deadlock.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: trying to acquire already locked default mutex
+  --> $DIR/libc_pthread_mutex_NULL_deadlock.rs:LL:CC
+   |
+LL |         libc::pthread_mutex_lock(&mut mutex as *mut _);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to acquire already locked default mutex
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/libc_pthread_mutex_NULL_deadlock.rs:LL:CC
+
+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/shims/sync/libc_pthread_mutex_deadlock.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_deadlock.rs
new file mode 100644
index 0000000000000..6c3cb738e2997
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_deadlock.rs
@@ -0,0 +1,28 @@
+//@ignore-target-windows: No libc on Windows
+
+use std::cell::UnsafeCell;
+use std::sync::Arc;
+use std::thread;
+
+struct Mutex(UnsafeCell<libc::pthread_mutex_t>);
+
+unsafe impl Send for Mutex {}
+unsafe impl Sync for Mutex {}
+
+fn new_lock() -> Arc<Mutex> {
+    Arc::new(Mutex(UnsafeCell::new(libc::PTHREAD_MUTEX_INITIALIZER)))
+}
+
+fn main() {
+    unsafe {
+        let lock = new_lock();
+        assert_eq!(libc::pthread_mutex_lock(lock.0.get() as *mut _), 0);
+
+        let lock_copy = lock.clone();
+        thread::spawn(move || {
+            assert_eq!(libc::pthread_mutex_lock(lock_copy.0.get() as *mut _), 0); //~ ERROR: deadlock
+        })
+        .join()
+        .unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_deadlock.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_deadlock.stderr
new file mode 100644
index 0000000000000..599655a8692b1
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_deadlock.stderr
@@ -0,0 +1,12 @@
+error: deadlock: the evaluated program deadlocked
+  --> $DIR/libc_pthread_mutex_deadlock.rs:LL:CC
+   |
+LL |             assert_eq!(libc::pthread_mutex_lock(lock_copy.0.get() as *mut _), 0);
+   |                                                                            ^ the evaluated program deadlocked
+   |
+   = note: inside closure at $DIR/libc_pthread_mutex_deadlock.rs:LL:CC
+
+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/shims/sync/libc_pthread_mutex_default_deadlock.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_default_deadlock.rs
new file mode 100644
index 0000000000000..f443768819f96
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_default_deadlock.rs
@@ -0,0 +1,13 @@
+//@ignore-target-windows: No libc on Windows
+//
+// Check that if we do not set the mutex type, it is the default.
+
+fn main() {
+    unsafe {
+        let mutexattr: libc::pthread_mutexattr_t = std::mem::zeroed();
+        let mut mutex: libc::pthread_mutex_t = std::mem::zeroed();
+        assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, &mutexattr as *const _), 0);
+        assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0);
+        libc::pthread_mutex_lock(&mut mutex as *mut _); //~ ERROR: Undefined Behavior: trying to acquire already locked default mutex
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_default_deadlock.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_default_deadlock.stderr
new file mode 100644
index 0000000000000..8aea3f5c6932f
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_default_deadlock.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: trying to acquire already locked default mutex
+  --> $DIR/libc_pthread_mutex_default_deadlock.rs:LL:CC
+   |
+LL |         libc::pthread_mutex_lock(&mut mutex as *mut _);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to acquire already locked default mutex
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/libc_pthread_mutex_default_deadlock.rs:LL:CC
+
+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/shims/sync/libc_pthread_mutex_destroy_locked.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_destroy_locked.rs
new file mode 100644
index 0000000000000..ec3965c7574eb
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_destroy_locked.rs
@@ -0,0 +1,15 @@
+//@ignore-target-windows: No libc on Windows
+
+fn main() {
+    unsafe {
+        let mut mutexattr: libc::pthread_mutexattr_t = std::mem::zeroed();
+        assert_eq!(
+            libc::pthread_mutexattr_settype(&mut mutexattr as *mut _, libc::PTHREAD_MUTEX_NORMAL),
+            0,
+        );
+        let mut mutex: libc::pthread_mutex_t = std::mem::zeroed();
+        assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, &mutexattr as *const _), 0);
+        assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0);
+        libc::pthread_mutex_destroy(&mut mutex as *mut _); //~ ERROR: destroyed a locked mutex
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_destroy_locked.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_destroy_locked.stderr
new file mode 100644
index 0000000000000..a8ab948116e14
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_destroy_locked.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: destroyed a locked mutex
+  --> $DIR/libc_pthread_mutex_destroy_locked.rs:LL:CC
+   |
+LL |         libc::pthread_mutex_destroy(&mut mutex as *mut _);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ destroyed a locked mutex
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/libc_pthread_mutex_destroy_locked.rs:LL:CC
+
+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/shims/sync/libc_pthread_mutex_double_destroy.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_double_destroy.rs
new file mode 100644
index 0000000000000..622c3eaeae30d
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_double_destroy.rs
@@ -0,0 +1,21 @@
+//@ignore-target-windows: No libc on Windows
+
+/// Test that destroying a pthread_mutex twice fails, even without a check for number validity
+
+fn main() {
+    unsafe {
+        use core::mem::MaybeUninit;
+
+        let mut attr = MaybeUninit::<libc::pthread_mutexattr_t>::uninit();
+        libc::pthread_mutexattr_init(attr.as_mut_ptr());
+
+        let mut mutex = MaybeUninit::<libc::pthread_mutex_t>::uninit();
+
+        libc::pthread_mutex_init(mutex.as_mut_ptr(), attr.as_ptr());
+
+        libc::pthread_mutex_destroy(mutex.as_mut_ptr());
+
+        libc::pthread_mutex_destroy(mutex.as_mut_ptr());
+        //~^ ERROR: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_double_destroy.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_double_destroy.stderr
new file mode 100644
index 0000000000000..9620fdbd18b2f
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_double_destroy.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
+  --> $DIR/libc_pthread_mutex_double_destroy.rs:LL:CC
+   |
+LL |         libc::pthread_mutex_destroy(mutex.as_mut_ptr());
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ using uninitialized data, but this operation requires initialized memory
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/libc_pthread_mutex_double_destroy.rs:LL:CC
+
+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/shims/sync/libc_pthread_mutex_normal_deadlock.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_normal_deadlock.rs
new file mode 100644
index 0000000000000..5ea09fa5aac3d
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_normal_deadlock.rs
@@ -0,0 +1,15 @@
+//@ignore-target-windows: No libc on Windows
+
+fn main() {
+    unsafe {
+        let mut mutexattr: libc::pthread_mutexattr_t = std::mem::zeroed();
+        assert_eq!(
+            libc::pthread_mutexattr_settype(&mut mutexattr as *mut _, libc::PTHREAD_MUTEX_NORMAL),
+            0,
+        );
+        let mut mutex: libc::pthread_mutex_t = std::mem::zeroed();
+        assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, &mutexattr as *const _), 0);
+        assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0);
+        libc::pthread_mutex_lock(&mut mutex as *mut _); //~ ERROR: deadlock: the evaluated program deadlocked
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_normal_deadlock.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_normal_deadlock.stderr
new file mode 100644
index 0000000000000..b7877d3aa397d
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_normal_deadlock.stderr
@@ -0,0 +1,12 @@
+error: deadlock: the evaluated program deadlocked
+  --> $DIR/libc_pthread_mutex_normal_deadlock.rs:LL:CC
+   |
+LL |         libc::pthread_mutex_lock(&mut mutex as *mut _);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program deadlocked
+   |
+   = note: inside `main` at $DIR/libc_pthread_mutex_normal_deadlock.rs:LL:CC
+
+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/shims/sync/libc_pthread_mutex_normal_unlock_unlocked.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_normal_unlock_unlocked.rs
new file mode 100644
index 0000000000000..8ce7542edb87f
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_normal_unlock_unlocked.rs
@@ -0,0 +1,16 @@
+//@ignore-target-windows: No libc on Windows
+
+fn main() {
+    unsafe {
+        let mut mutexattr: libc::pthread_mutexattr_t = std::mem::zeroed();
+        assert_eq!(
+            libc::pthread_mutexattr_settype(&mut mutexattr as *mut _, libc::PTHREAD_MUTEX_NORMAL),
+            0,
+        );
+        let mut mutex: libc::pthread_mutex_t = std::mem::zeroed();
+        assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, &mutexattr as *const _), 0);
+        assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0);
+        assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0);
+        libc::pthread_mutex_unlock(&mut mutex as *mut _); //~ ERROR: was not locked
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_normal_unlock_unlocked.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_normal_unlock_unlocked.stderr
new file mode 100644
index 0000000000000..754137b85b9af
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_normal_unlock_unlocked.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: unlocked a PTHREAD_MUTEX_NORMAL mutex that was not locked by the current thread
+  --> $DIR/libc_pthread_mutex_normal_unlock_unlocked.rs:LL:CC
+   |
+LL |         libc::pthread_mutex_unlock(&mut mutex as *mut _);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unlocked a PTHREAD_MUTEX_NORMAL mutex that was not locked by the current thread
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/libc_pthread_mutex_normal_unlock_unlocked.rs:LL:CC
+
+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/shims/sync/libc_pthread_mutex_wrong_owner.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_wrong_owner.rs
new file mode 100644
index 0000000000000..b56775252e4b4
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_wrong_owner.rs
@@ -0,0 +1,28 @@
+//@ignore-target-windows: No libc on Windows
+
+use std::cell::UnsafeCell;
+use std::sync::Arc;
+use std::thread;
+
+struct Mutex(UnsafeCell<libc::pthread_mutex_t>);
+
+unsafe impl Send for Mutex {}
+unsafe impl Sync for Mutex {}
+
+fn new_lock() -> Arc<Mutex> {
+    Arc::new(Mutex(UnsafeCell::new(libc::PTHREAD_MUTEX_INITIALIZER)))
+}
+
+fn main() {
+    unsafe {
+        let lock = new_lock();
+        assert_eq!(libc::pthread_mutex_lock(lock.0.get() as *mut _), 0);
+
+        let lock_copy = lock.clone();
+        thread::spawn(move || {
+            assert_eq!(libc::pthread_mutex_unlock(lock_copy.0.get() as *mut _), 0); //~ ERROR: Undefined Behavior: unlocked a default mutex that was not locked by the current thread
+        })
+        .join()
+        .unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_wrong_owner.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_wrong_owner.stderr
new file mode 100644
index 0000000000000..aa81b06fc80af
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_wrong_owner.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: unlocked a default mutex that was not locked by the current thread
+  --> $DIR/libc_pthread_mutex_wrong_owner.rs:LL:CC
+   |
+LL | ...t_eq!(libc::pthread_mutex_unlock(lock_copy.0.get() as *mut _), 0);
+   |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unlocked a default mutex that was not locked by the current thread
+   |
+   = 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 closure at $DIR/libc_pthread_mutex_wrong_owner.rs:LL:CC
+
+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/shims/sync/libc_pthread_mutexattr_double_destroy.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutexattr_double_destroy.rs
new file mode 100644
index 0000000000000..474a277516d94
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutexattr_double_destroy.rs
@@ -0,0 +1,17 @@
+//@ignore-target-windows: No libc on Windows
+
+/// Test that destroying a pthread_mutexattr twice fails, even without a check for number validity
+
+fn main() {
+    unsafe {
+        use core::mem::MaybeUninit;
+        let mut attr = MaybeUninit::<libc::pthread_mutexattr_t>::uninit();
+
+        libc::pthread_mutexattr_init(attr.as_mut_ptr());
+
+        libc::pthread_mutexattr_destroy(attr.as_mut_ptr());
+
+        libc::pthread_mutexattr_destroy(attr.as_mut_ptr());
+        //~^ ERROR: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutexattr_double_destroy.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutexattr_double_destroy.stderr
new file mode 100644
index 0000000000000..82949047d2aab
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutexattr_double_destroy.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
+  --> $DIR/libc_pthread_mutexattr_double_destroy.rs:LL:CC
+   |
+LL |         libc::pthread_mutexattr_destroy(attr.as_mut_ptr());
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ using uninitialized data, but this operation requires initialized memory
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/libc_pthread_mutexattr_double_destroy.rs:LL:CC
+
+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/shims/sync/libc_pthread_rwlock_destroy_read_locked.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_destroy_read_locked.rs
new file mode 100644
index 0000000000000..603580ff58abd
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_destroy_read_locked.rs
@@ -0,0 +1,9 @@
+//@ignore-target-windows: No libc on Windows
+
+fn main() {
+    let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER);
+    unsafe {
+        assert_eq!(libc::pthread_rwlock_rdlock(rw.get()), 0);
+        libc::pthread_rwlock_destroy(rw.get()); //~ ERROR: destroyed a locked rwlock
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_destroy_read_locked.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_destroy_read_locked.stderr
new file mode 100644
index 0000000000000..be73e7f1e2ad4
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_destroy_read_locked.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: destroyed a locked rwlock
+  --> $DIR/libc_pthread_rwlock_destroy_read_locked.rs:LL:CC
+   |
+LL |         libc::pthread_rwlock_destroy(rw.get());
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ destroyed a locked rwlock
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/libc_pthread_rwlock_destroy_read_locked.rs:LL:CC
+
+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/shims/sync/libc_pthread_rwlock_destroy_write_locked.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_destroy_write_locked.rs
new file mode 100644
index 0000000000000..ae44f22d146ca
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_destroy_write_locked.rs
@@ -0,0 +1,9 @@
+//@ignore-target-windows: No libc on Windows
+
+fn main() {
+    let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER);
+    unsafe {
+        assert_eq!(libc::pthread_rwlock_wrlock(rw.get()), 0);
+        libc::pthread_rwlock_destroy(rw.get()); //~ ERROR: destroyed a locked rwlock
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_destroy_write_locked.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_destroy_write_locked.stderr
new file mode 100644
index 0000000000000..bc2713a5ffbfa
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_destroy_write_locked.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: destroyed a locked rwlock
+  --> $DIR/libc_pthread_rwlock_destroy_write_locked.rs:LL:CC
+   |
+LL |         libc::pthread_rwlock_destroy(rw.get());
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ destroyed a locked rwlock
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/libc_pthread_rwlock_destroy_write_locked.rs:LL:CC
+
+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/shims/sync/libc_pthread_rwlock_double_destroy.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_double_destroy.rs
new file mode 100644
index 0000000000000..800986f7506c0
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_double_destroy.rs
@@ -0,0 +1,14 @@
+//@ignore-target-windows: No libc on Windows
+
+/// Test that destroying a pthread_rwlock twice fails, even without a check for number validity
+
+fn main() {
+    unsafe {
+        let mut lock = libc::PTHREAD_RWLOCK_INITIALIZER;
+
+        libc::pthread_rwlock_destroy(&mut lock);
+
+        libc::pthread_rwlock_destroy(&mut lock);
+        //~^ ERROR: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_double_destroy.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_double_destroy.stderr
new file mode 100644
index 0000000000000..5004f84358da8
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_double_destroy.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
+  --> $DIR/libc_pthread_rwlock_double_destroy.rs:LL:CC
+   |
+LL |         libc::pthread_rwlock_destroy(&mut lock);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ using uninitialized data, but this operation requires initialized memory
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/libc_pthread_rwlock_double_destroy.rs:LL:CC
+
+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/shims/sync/libc_pthread_rwlock_read_write_deadlock_single_thread.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_read_write_deadlock_single_thread.rs
new file mode 100644
index 0000000000000..782c95b6d2e3c
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_read_write_deadlock_single_thread.rs
@@ -0,0 +1,9 @@
+//@ignore-target-windows: No libc on Windows
+
+fn main() {
+    let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER);
+    unsafe {
+        assert_eq!(libc::pthread_rwlock_rdlock(rw.get()), 0);
+        libc::pthread_rwlock_wrlock(rw.get()); //~ ERROR: deadlock
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_read_write_deadlock_single_thread.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_read_write_deadlock_single_thread.stderr
new file mode 100644
index 0000000000000..075c8f0ef529c
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_read_write_deadlock_single_thread.stderr
@@ -0,0 +1,12 @@
+error: deadlock: the evaluated program deadlocked
+  --> $DIR/libc_pthread_rwlock_read_write_deadlock_single_thread.rs:LL:CC
+   |
+LL |         libc::pthread_rwlock_wrlock(rw.get());
+   |                                             ^ the evaluated program deadlocked
+   |
+   = note: inside `main` at $DIR/libc_pthread_rwlock_read_write_deadlock_single_thread.rs:LL:CC
+
+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/shims/sync/libc_pthread_rwlock_read_wrong_owner.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_read_wrong_owner.rs
new file mode 100644
index 0000000000000..1b498ad8fcdb4
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_read_wrong_owner.rs
@@ -0,0 +1,28 @@
+//@ignore-target-windows: No libc on Windows
+
+use std::cell::UnsafeCell;
+use std::sync::Arc;
+use std::thread;
+
+struct RwLock(UnsafeCell<libc::pthread_rwlock_t>);
+
+unsafe impl Send for RwLock {}
+unsafe impl Sync for RwLock {}
+
+fn new_lock() -> Arc<RwLock> {
+    Arc::new(RwLock(UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER)))
+}
+
+fn main() {
+    unsafe {
+        let lock = new_lock();
+        assert_eq!(libc::pthread_rwlock_rdlock(lock.0.get() as *mut _), 0);
+
+        let lock_copy = lock.clone();
+        thread::spawn(move || {
+            assert_eq!(libc::pthread_rwlock_unlock(lock_copy.0.get() as *mut _), 0); //~ ERROR: Undefined Behavior: unlocked an rwlock that was not locked by the active thread
+        })
+        .join()
+        .unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_read_wrong_owner.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_read_wrong_owner.stderr
new file mode 100644
index 0000000000000..7dfa27b43d073
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_read_wrong_owner.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: unlocked an rwlock that was not locked by the active thread
+  --> $DIR/libc_pthread_rwlock_read_wrong_owner.rs:LL:CC
+   |
+LL | ...   assert_eq!(libc::pthread_rwlock_unlock(lock_copy.0.get() as *mut _), 0);
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unlocked an rwlock that was not locked by the active thread
+   |
+   = 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 closure at $DIR/libc_pthread_rwlock_read_wrong_owner.rs:LL:CC
+
+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/shims/sync/libc_pthread_rwlock_unlock_unlocked.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_unlock_unlocked.rs
new file mode 100644
index 0000000000000..05f7e7a06c57f
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_unlock_unlocked.rs
@@ -0,0 +1,8 @@
+//@ignore-target-windows: No libc on Windows
+
+fn main() {
+    let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER);
+    unsafe {
+        libc::pthread_rwlock_unlock(rw.get()); //~ ERROR: was not locked
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_unlock_unlocked.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_unlock_unlocked.stderr
new file mode 100644
index 0000000000000..1c25ac2c048fb
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_unlock_unlocked.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: unlocked an rwlock that was not locked by the active thread
+  --> $DIR/libc_pthread_rwlock_unlock_unlocked.rs:LL:CC
+   |
+LL |         libc::pthread_rwlock_unlock(rw.get());
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unlocked an rwlock that was not locked by the active thread
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/libc_pthread_rwlock_unlock_unlocked.rs:LL:CC
+
+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/shims/sync/libc_pthread_rwlock_write_read_deadlock.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_read_deadlock.rs
new file mode 100644
index 0000000000000..201844615e182
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_read_deadlock.rs
@@ -0,0 +1,28 @@
+//@ignore-target-windows: No libc on Windows
+
+use std::cell::UnsafeCell;
+use std::sync::Arc;
+use std::thread;
+
+struct RwLock(UnsafeCell<libc::pthread_rwlock_t>);
+
+unsafe impl Send for RwLock {}
+unsafe impl Sync for RwLock {}
+
+fn new_lock() -> Arc<RwLock> {
+    Arc::new(RwLock(UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER)))
+}
+
+fn main() {
+    unsafe {
+        let lock = new_lock();
+        assert_eq!(libc::pthread_rwlock_rdlock(lock.0.get() as *mut _), 0);
+
+        let lock_copy = lock.clone();
+        thread::spawn(move || {
+            assert_eq!(libc::pthread_rwlock_wrlock(lock_copy.0.get() as *mut _), 0); //~ ERROR: deadlock
+        })
+        .join()
+        .unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_read_deadlock.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_read_deadlock.stderr
new file mode 100644
index 0000000000000..333fb1afb91b7
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_read_deadlock.stderr
@@ -0,0 +1,12 @@
+error: deadlock: the evaluated program deadlocked
+  --> $DIR/libc_pthread_rwlock_write_read_deadlock.rs:LL:CC
+   |
+LL |             assert_eq!(libc::pthread_rwlock_wrlock(lock_copy.0.get() as *mut _), 0);
+   |                                                                               ^ the evaluated program deadlocked
+   |
+   = note: inside closure at $DIR/libc_pthread_rwlock_write_read_deadlock.rs:LL:CC
+
+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/shims/sync/libc_pthread_rwlock_write_read_deadlock_single_thread.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_read_deadlock_single_thread.rs
new file mode 100644
index 0000000000000..538f14ef89f20
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_read_deadlock_single_thread.rs
@@ -0,0 +1,9 @@
+//@ignore-target-windows: No libc on Windows
+
+fn main() {
+    let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER);
+    unsafe {
+        assert_eq!(libc::pthread_rwlock_wrlock(rw.get()), 0);
+        libc::pthread_rwlock_rdlock(rw.get()); //~ ERROR: deadlock
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_read_deadlock_single_thread.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_read_deadlock_single_thread.stderr
new file mode 100644
index 0000000000000..caab19a782f97
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_read_deadlock_single_thread.stderr
@@ -0,0 +1,12 @@
+error: deadlock: the evaluated program deadlocked
+  --> $DIR/libc_pthread_rwlock_write_read_deadlock_single_thread.rs:LL:CC
+   |
+LL |         libc::pthread_rwlock_rdlock(rw.get());
+   |                                             ^ the evaluated program deadlocked
+   |
+   = note: inside `main` at $DIR/libc_pthread_rwlock_write_read_deadlock_single_thread.rs:LL:CC
+
+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/shims/sync/libc_pthread_rwlock_write_write_deadlock.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_write_deadlock.rs
new file mode 100644
index 0000000000000..b1d7e0492e5a2
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_write_deadlock.rs
@@ -0,0 +1,28 @@
+//@ignore-target-windows: No libc on Windows
+
+use std::cell::UnsafeCell;
+use std::sync::Arc;
+use std::thread;
+
+struct RwLock(UnsafeCell<libc::pthread_rwlock_t>);
+
+unsafe impl Send for RwLock {}
+unsafe impl Sync for RwLock {}
+
+fn new_lock() -> Arc<RwLock> {
+    Arc::new(RwLock(UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER)))
+}
+
+fn main() {
+    unsafe {
+        let lock = new_lock();
+        assert_eq!(libc::pthread_rwlock_wrlock(lock.0.get() as *mut _), 0);
+
+        let lock_copy = lock.clone();
+        thread::spawn(move || {
+            assert_eq!(libc::pthread_rwlock_wrlock(lock_copy.0.get() as *mut _), 0); //~ ERROR: deadlock
+        })
+        .join()
+        .unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_write_deadlock.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_write_deadlock.stderr
new file mode 100644
index 0000000000000..93bede54fcf18
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_write_deadlock.stderr
@@ -0,0 +1,12 @@
+error: deadlock: the evaluated program deadlocked
+  --> $DIR/libc_pthread_rwlock_write_write_deadlock.rs:LL:CC
+   |
+LL |             assert_eq!(libc::pthread_rwlock_wrlock(lock_copy.0.get() as *mut _), 0);
+   |                                                                               ^ the evaluated program deadlocked
+   |
+   = note: inside closure at $DIR/libc_pthread_rwlock_write_write_deadlock.rs:LL:CC
+
+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/shims/sync/libc_pthread_rwlock_write_write_deadlock_single_thread.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_write_deadlock_single_thread.rs
new file mode 100644
index 0000000000000..2c963d36510e6
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_write_deadlock_single_thread.rs
@@ -0,0 +1,9 @@
+//@ignore-target-windows: No libc on Windows
+
+fn main() {
+    let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER);
+    unsafe {
+        assert_eq!(libc::pthread_rwlock_wrlock(rw.get()), 0);
+        libc::pthread_rwlock_wrlock(rw.get()); //~ ERROR: deadlock
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_write_deadlock_single_thread.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_write_deadlock_single_thread.stderr
new file mode 100644
index 0000000000000..30f5f447c717c
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_write_deadlock_single_thread.stderr
@@ -0,0 +1,12 @@
+error: deadlock: the evaluated program deadlocked
+  --> $DIR/libc_pthread_rwlock_write_write_deadlock_single_thread.rs:LL:CC
+   |
+LL |         libc::pthread_rwlock_wrlock(rw.get());
+   |                                             ^ the evaluated program deadlocked
+   |
+   = note: inside `main` at $DIR/libc_pthread_rwlock_write_write_deadlock_single_thread.rs:LL:CC
+
+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/shims/sync/libc_pthread_rwlock_write_wrong_owner.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_wrong_owner.rs
new file mode 100644
index 0000000000000..dd099474d8fed
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_wrong_owner.rs
@@ -0,0 +1,28 @@
+//@ignore-target-windows: No libc on Windows
+
+use std::cell::UnsafeCell;
+use std::sync::Arc;
+use std::thread;
+
+struct RwLock(UnsafeCell<libc::pthread_rwlock_t>);
+
+unsafe impl Send for RwLock {}
+unsafe impl Sync for RwLock {}
+
+fn new_lock() -> Arc<RwLock> {
+    Arc::new(RwLock(UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER)))
+}
+
+fn main() {
+    unsafe {
+        let lock = new_lock();
+        assert_eq!(libc::pthread_rwlock_wrlock(lock.0.get() as *mut _), 0);
+
+        let lock_copy = lock.clone();
+        thread::spawn(move || {
+            assert_eq!(libc::pthread_rwlock_unlock(lock_copy.0.get() as *mut _), 0); //~ ERROR: Undefined Behavior: unlocked an rwlock that was not locked by the active thread
+        })
+        .join()
+        .unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_wrong_owner.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_wrong_owner.stderr
new file mode 100644
index 0000000000000..5bf402c775ae5
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_wrong_owner.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: unlocked an rwlock that was not locked by the active thread
+  --> $DIR/libc_pthread_rwlock_write_wrong_owner.rs:LL:CC
+   |
+LL | ...   assert_eq!(libc::pthread_rwlock_unlock(lock_copy.0.get() as *mut _), 0);
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unlocked an rwlock that was not locked by the active thread
+   |
+   = 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 closure at $DIR/libc_pthread_rwlock_write_wrong_owner.rs:LL:CC
+
+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/should-pass/cpp20_rwc_syncs.rs b/src/tools/miri/tests/fail/should-pass/cpp20_rwc_syncs.rs
new file mode 100644
index 0000000000000..545875a582a46
--- /dev/null
+++ b/src/tools/miri/tests/fail/should-pass/cpp20_rwc_syncs.rs
@@ -0,0 +1,87 @@
+//@compile-flags: -Zmiri-ignore-leaks
+
+// https://plv.mpi-sws.org/scfix/paper.pdf
+// 2.2 Second Problem: SC Fences are Too Weak
+// This test should pass under the C++20 model Rust is using.
+// Unfortunately, Miri's weak memory emulation only follows the C++11 model
+// as we don't know how to correctly emulate C++20's revised SC semantics,
+// so we have to stick to C++11 emulation from existing research.
+
+use std::sync::atomic::Ordering::*;
+use std::sync::atomic::{fence, AtomicUsize};
+use std::thread::spawn;
+
+// Spins until it reads the given value
+fn reads_value(loc: &AtomicUsize, val: usize) -> usize {
+    while loc.load(Relaxed) != val {
+        std::hint::spin_loop();
+    }
+    val
+}
+
+// We can't create static items because we need to run each test
+// multiple tests
+fn static_atomic(val: usize) -> &'static AtomicUsize {
+    let ret = Box::leak(Box::new(AtomicUsize::new(val)));
+    // A workaround to put the initialization value in the store buffer.
+    // See https://github.com/rust-lang/miri/issues/2164
+    ret.load(Relaxed);
+    ret
+}
+
+fn test_cpp20_rwc_syncs() {
+    /*
+    int main() {
+        atomic_int x = 0;
+        atomic_int y = 0;
+
+        {{{ x.store(1,mo_relaxed);
+        ||| { r1=x.load(mo_relaxed).readsvalue(1);
+              fence(mo_seq_cst);
+              r2=y.load(mo_relaxed); }
+        ||| { y.store(1,mo_relaxed);
+              fence(mo_seq_cst);
+              r3=x.load(mo_relaxed); }
+        }}}
+        return 0;
+    }
+    */
+    let x = static_atomic(0);
+    let y = static_atomic(0);
+
+    let j1 = spawn(move || {
+        x.store(1, Relaxed);
+    });
+
+    let j2 = spawn(move || {
+        reads_value(&x, 1);
+        fence(SeqCst);
+        y.load(Relaxed)
+    });
+
+    let j3 = spawn(move || {
+        y.store(1, Relaxed);
+        fence(SeqCst);
+        x.load(Relaxed)
+    });
+
+    j1.join().unwrap();
+    let b = j2.join().unwrap();
+    let c = j3.join().unwrap();
+
+    // We cannot write assert_ne!() since ui_test's fail
+    // tests expect exit status 1, whereas panics produce 101.
+    // Our ui_test does not yet support overriding failure status codes.
+    if (b, c) == (0, 0) {
+        // This *should* be unreachable, but Miri will reach it.
+        unsafe {
+            std::hint::unreachable_unchecked(); //~ERROR: unreachable
+        }
+    }
+}
+
+pub fn main() {
+    for _ in 0..500 {
+        test_cpp20_rwc_syncs();
+    }
+}
diff --git a/src/tools/miri/tests/fail/should-pass/cpp20_rwc_syncs.stderr b/src/tools/miri/tests/fail/should-pass/cpp20_rwc_syncs.stderr
new file mode 100644
index 0000000000000..8a24b085a99f6
--- /dev/null
+++ b/src/tools/miri/tests/fail/should-pass/cpp20_rwc_syncs.stderr
@@ -0,0 +1,20 @@
+error: Undefined Behavior: entering unreachable code
+  --> $DIR/cpp20_rwc_syncs.rs:LL:CC
+   |
+LL |             std::hint::unreachable_unchecked();
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ entering unreachable code
+   |
+   = 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 `test_cpp20_rwc_syncs` at $DIR/cpp20_rwc_syncs.rs:LL:CC
+note: inside `main` at $DIR/cpp20_rwc_syncs.rs:LL:CC
+  --> $DIR/cpp20_rwc_syncs.rs:LL:CC
+   |
+LL |         test_cpp20_rwc_syncs();
+   |         ^^^^^^^^^^^^^^^^^^^^^^
+
+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/alias_through_mutation.rs b/src/tools/miri/tests/fail/stacked_borrows/alias_through_mutation.rs
new file mode 100644
index 0000000000000..73095bb2fc94b
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/alias_through_mutation.rs
@@ -0,0 +1,15 @@
+// This makes a ref that was passed to us via &mut alias with things it should not alias with
+fn retarget(x: &mut &u32, target: &mut u32) {
+    unsafe {
+        *x = &mut *(target as *mut _);
+    }
+}
+
+fn main() {
+    let target = &mut 42;
+    let mut target_alias = &42; // initial dummy value
+    retarget(&mut target_alias, target);
+    // now `target_alias` points to the same thing as `target`
+    *target = 13;
+    let _val = *target_alias; //~ ERROR: /read access .* tag does not exist in the borrow stack/
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/alias_through_mutation.stderr b/src/tools/miri/tests/fail/stacked_borrows/alias_through_mutation.stderr
new file mode 100644
index 0000000000000..461275c3fa346
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/alias_through_mutation.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/alias_through_mutation.rs:LL:CC
+   |
+LL |     let _val = *target_alias;
+   |                ^^^^^^^^^^^^^
+   |                |
+   |                attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |                this error occurs as part of an access at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadOnly retag at offsets [0x0..0x4]
+  --> $DIR/alias_through_mutation.rs:LL:CC
+   |
+LL |         *x = &mut *(target as *mut _);
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a write access
+  --> $DIR/alias_through_mutation.rs:LL:CC
+   |
+LL |     *target = 13;
+   |     ^^^^^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/alias_through_mutation.rs:LL:CC
+
+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/aliasing_mut1.rs b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut1.rs
new file mode 100644
index 0000000000000..14a27d8e9dd65
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut1.rs
@@ -0,0 +1,14 @@
+use std::mem;
+
+pub fn safe(_x: &mut i32, _y: &mut i32) {} //~ ERROR: protect
+
+fn main() {
+    let mut x = 0;
+    let xraw: *mut i32 = unsafe { mem::transmute(&mut x) };
+    // We need to apply some tricky to be able to call `safe` with two mutable references
+    // with the same tag: We transmute both the fn ptr (to take raw ptrs) and the argument
+    // (to be raw, but still have the unique tag).
+    let safe_raw: fn(x: *mut i32, y: *mut i32) =
+        unsafe { mem::transmute::<fn(&mut i32, &mut i32), _>(safe) };
+    safe_raw(xraw, xraw);
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut1.stderr b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut1.stderr
new file mode 100644
index 0000000000000..5d4679b13ad1f
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut1.stderr
@@ -0,0 +1,30 @@
+error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is protected because it is an argument of call ID
+  --> $DIR/aliasing_mut1.rs:LL:CC
+   |
+LL | pub fn safe(_x: &mut i32, _y: &mut i32) {}
+   |                           ^^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is protected because it is an argument of call ID
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a Unique retag at offsets [0x0..0x4]
+  --> $DIR/aliasing_mut1.rs:LL:CC
+   |
+LL |     let xraw: *mut i32 = unsafe { mem::transmute(&mut x) };
+   |                                                  ^^^^^^
+help: <TAG> is this argument
+  --> $DIR/aliasing_mut1.rs:LL:CC
+   |
+LL | pub fn safe(_x: &mut i32, _y: &mut i32) {}
+   |             ^^
+   = note: BACKTRACE:
+   = note: inside `safe` at $DIR/aliasing_mut1.rs:LL:CC
+note: inside `main` at $DIR/aliasing_mut1.rs:LL:CC
+  --> $DIR/aliasing_mut1.rs:LL:CC
+   |
+LL |     safe_raw(xraw, xraw);
+   |     ^^^^^^^^^^^^^^^^^^^^
+
+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/aliasing_mut2.rs b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut2.rs
new file mode 100644
index 0000000000000..84d901f83bbcc
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut2.rs
@@ -0,0 +1,14 @@
+use std::mem;
+
+pub fn safe(_x: &i32, _y: &mut i32) {} //~ ERROR: protect
+
+fn main() {
+    let mut x = 0;
+    let xref = &mut x;
+    let xraw: *mut i32 = unsafe { mem::transmute_copy(&xref) };
+    let xshr = &*xref;
+    // transmute fn ptr around so that we can avoid retagging
+    let safe_raw: fn(x: *const i32, y: *mut i32) =
+        unsafe { mem::transmute::<fn(&i32, &mut i32), _>(safe) };
+    safe_raw(xshr, xraw);
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut2.stderr b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut2.stderr
new file mode 100644
index 0000000000000..c8408c150e779
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut2.stderr
@@ -0,0 +1,30 @@
+error: Undefined Behavior: not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is protected because it is an argument of call ID
+  --> $DIR/aliasing_mut2.rs:LL:CC
+   |
+LL | pub fn safe(_x: &i32, _y: &mut i32) {}
+   |                       ^^ not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is protected because it is an argument of call ID
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a Unique retag at offsets [0x0..0x4]
+  --> $DIR/aliasing_mut2.rs:LL:CC
+   |
+LL |     let xref = &mut x;
+   |                ^^^^^^
+help: <TAG> is this argument
+  --> $DIR/aliasing_mut2.rs:LL:CC
+   |
+LL | pub fn safe(_x: &i32, _y: &mut i32) {}
+   |             ^^
+   = note: BACKTRACE:
+   = note: inside `safe` at $DIR/aliasing_mut2.rs:LL:CC
+note: inside `main` at $DIR/aliasing_mut2.rs:LL:CC
+  --> $DIR/aliasing_mut2.rs:LL:CC
+   |
+LL |     safe_raw(xshr, xraw);
+   |     ^^^^^^^^^^^^^^^^^^^^
+
+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/aliasing_mut3.rs b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut3.rs
new file mode 100644
index 0000000000000..f1ba06b6e4f7a
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut3.rs
@@ -0,0 +1,14 @@
+use std::mem;
+
+pub fn safe(_x: &mut i32, _y: &i32) {} //~ ERROR: borrow stack
+
+fn main() {
+    let mut x = 0;
+    let xref = &mut x;
+    let xraw: *mut i32 = unsafe { mem::transmute_copy(&xref) };
+    let xshr = &*xref;
+    // transmute fn ptr around so that we can avoid retagging
+    let safe_raw: fn(x: *mut i32, y: *const i32) =
+        unsafe { mem::transmute::<fn(&mut i32, &i32), _>(safe) };
+    safe_raw(xraw, xshr);
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut3.stderr b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut3.stderr
new file mode 100644
index 0000000000000..c2ea90f242a22
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut3.stderr
@@ -0,0 +1,33 @@
+error: Undefined Behavior: trying to retag from <TAG> for SharedReadOnly permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/aliasing_mut3.rs:LL:CC
+   |
+LL | pub fn safe(_x: &mut i32, _y: &i32) {}
+   |                           ^^
+   |                           |
+   |                           trying to retag from <TAG> for SharedReadOnly permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |                           this error occurs as part of FnEntry retag at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadOnly retag at offsets [0x0..0x4]
+  --> $DIR/aliasing_mut3.rs:LL:CC
+   |
+LL |     safe_raw(xraw, xshr);
+   |                    ^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a Unique FnEntry retag
+  --> $DIR/aliasing_mut3.rs:LL:CC
+   |
+LL |     safe_raw(xraw, xshr);
+   |     ^^^^^^^^^^^^^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `safe` at $DIR/aliasing_mut3.rs:LL:CC
+note: inside `main` at $DIR/aliasing_mut3.rs:LL:CC
+  --> $DIR/aliasing_mut3.rs:LL:CC
+   |
+LL |     safe_raw(xraw, xshr);
+   |     ^^^^^^^^^^^^^^^^^^^^
+
+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/aliasing_mut4.rs b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut4.rs
new file mode 100644
index 0000000000000..52081b56223da
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut4.rs
@@ -0,0 +1,16 @@
+use std::cell::Cell;
+use std::mem;
+
+// Make sure &mut UnsafeCell also is exclusive
+pub fn safe(_x: &i32, _y: &mut Cell<i32>) {} //~ ERROR: protect
+
+fn main() {
+    let mut x = 0;
+    let xref = &mut x;
+    let xraw: *mut i32 = unsafe { mem::transmute_copy(&xref) };
+    let xshr = &*xref;
+    // transmute fn ptr around so that we can avoid retagging
+    let safe_raw: fn(x: *const i32, y: *mut Cell<i32>) =
+        unsafe { mem::transmute::<fn(&i32, &mut Cell<i32>), _>(safe) };
+    safe_raw(xshr, xraw as *mut _);
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut4.stderr b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut4.stderr
new file mode 100644
index 0000000000000..c53fe70f6dd33
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut4.stderr
@@ -0,0 +1,30 @@
+error: Undefined Behavior: not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is protected because it is an argument of call ID
+  --> $DIR/aliasing_mut4.rs:LL:CC
+   |
+LL | pub fn safe(_x: &i32, _y: &mut Cell<i32>) {}
+   |                       ^^ not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is protected because it is an argument of call ID
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a Unique retag at offsets [0x0..0x4]
+  --> $DIR/aliasing_mut4.rs:LL:CC
+   |
+LL |     let xref = &mut x;
+   |                ^^^^^^
+help: <TAG> is this argument
+  --> $DIR/aliasing_mut4.rs:LL:CC
+   |
+LL | pub fn safe(_x: &i32, _y: &mut Cell<i32>) {}
+   |             ^^
+   = note: BACKTRACE:
+   = note: inside `safe` at $DIR/aliasing_mut4.rs:LL:CC
+note: inside `main` at $DIR/aliasing_mut4.rs:LL:CC
+  --> $DIR/aliasing_mut4.rs:LL:CC
+   |
+LL |     safe_raw(xshr, xraw as *mut _);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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/box_exclusive_violation1.rs b/src/tools/miri/tests/fail/stacked_borrows/box_exclusive_violation1.rs
new file mode 100644
index 0000000000000..87a6b7bbd67ec
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/box_exclusive_violation1.rs
@@ -0,0 +1,33 @@
+fn demo_box_advanced_unique(mut our: Box<i32>) -> i32 {
+    unknown_code_1(&*our);
+
+    // This "re-asserts" uniqueness of the reference: After writing, we know
+    // our tag is at the top of the stack.
+    *our = 5;
+
+    unknown_code_2();
+
+    // We know this will return 5
+    *our
+}
+
+// Now comes the evil context
+use std::ptr;
+
+static mut LEAK: *mut i32 = ptr::null_mut();
+
+fn unknown_code_1(x: &i32) {
+    unsafe {
+        LEAK = x as *const _ as *mut _;
+    }
+}
+
+fn unknown_code_2() {
+    unsafe {
+        *LEAK = 7; //~ ERROR: /write access .* tag does not exist in the borrow stack/
+    }
+}
+
+fn main() {
+    demo_box_advanced_unique(Box::new(0));
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/box_exclusive_violation1.stderr b/src/tools/miri/tests/fail/stacked_borrows/box_exclusive_violation1.stderr
new file mode 100644
index 0000000000000..d82b8342f1231
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/box_exclusive_violation1.stderr
@@ -0,0 +1,38 @@
+error: Undefined Behavior: attempting a write access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/box_exclusive_violation1.rs:LL:CC
+   |
+LL |         *LEAK = 7;
+   |         ^^^^^^^^^
+   |         |
+   |         attempting a write access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |         this error occurs as part of an access at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadOnly retag at offsets [0x0..0x4]
+  --> $DIR/box_exclusive_violation1.rs:LL:CC
+   |
+LL |         LEAK = x as *const _ as *mut _;
+   |                ^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a write access
+  --> $DIR/box_exclusive_violation1.rs:LL:CC
+   |
+LL |     *our = 5;
+   |     ^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `unknown_code_2` at $DIR/box_exclusive_violation1.rs:LL:CC
+note: inside `demo_box_advanced_unique` at $DIR/box_exclusive_violation1.rs:LL:CC
+  --> $DIR/box_exclusive_violation1.rs:LL:CC
+   |
+LL |     unknown_code_2();
+   |     ^^^^^^^^^^^^^^^^
+note: inside `main` at $DIR/box_exclusive_violation1.rs:LL:CC
+  --> $DIR/box_exclusive_violation1.rs:LL:CC
+   |
+LL |     demo_box_advanced_unique(Box::new(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/stacked_borrows/buggy_as_mut_slice.rs b/src/tools/miri/tests/fail/stacked_borrows/buggy_as_mut_slice.rs
new file mode 100644
index 0000000000000..8615a9a58ad96
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/buggy_as_mut_slice.rs
@@ -0,0 +1,15 @@
+mod safe {
+    use std::slice::from_raw_parts_mut;
+
+    pub fn as_mut_slice<T>(self_: &Vec<T>) -> &mut [T] {
+        unsafe { from_raw_parts_mut(self_.as_ptr() as *mut T, self_.len()) }
+    }
+}
+
+fn main() {
+    let v = vec![0, 1, 2];
+    let v1 = safe::as_mut_slice(&v);
+    let _v2 = safe::as_mut_slice(&v);
+    v1[1] = 5;
+    //~^ ERROR: /write access .* tag does not exist in the borrow stack/
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/buggy_as_mut_slice.stderr b/src/tools/miri/tests/fail/stacked_borrows/buggy_as_mut_slice.stderr
new file mode 100644
index 0000000000000..6aa14361287e3
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/buggy_as_mut_slice.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: attempting a write access using <TAG> at ALLOC[0x4], but that tag does not exist in the borrow stack for this location
+  --> $DIR/buggy_as_mut_slice.rs:LL:CC
+   |
+LL |     v1[1] = 5;
+   |     ^^^^^^^^^
+   |     |
+   |     attempting a write access using <TAG> at ALLOC[0x4], but that tag does not exist in the borrow stack for this location
+   |     this error occurs as part of an access at ALLOC[0x4..0x8]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a Unique retag at offsets [0x0..0xc]
+  --> $DIR/buggy_as_mut_slice.rs:LL:CC
+   |
+LL |     let v1 = safe::as_mut_slice(&v);
+   |              ^^^^^^^^^^^^^^^^^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0xc] by a Unique retag
+  --> $DIR/buggy_as_mut_slice.rs:LL:CC
+   |
+LL |         unsafe { from_raw_parts_mut(self_.as_ptr() as *mut T, self_.len()) }
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/buggy_as_mut_slice.rs:LL:CC
+
+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/buggy_split_at_mut.rs b/src/tools/miri/tests/fail/stacked_borrows/buggy_split_at_mut.rs
new file mode 100644
index 0000000000000..8a1ea86d63385
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/buggy_split_at_mut.rs
@@ -0,0 +1,25 @@
+mod safe {
+    use std::slice::from_raw_parts_mut;
+
+    pub fn split_at_mut<T>(self_: &mut [T], mid: usize) -> (&mut [T], &mut [T]) {
+        let len = self_.len();
+        let ptr = self_.as_mut_ptr();
+
+        unsafe {
+            assert!(mid <= len);
+
+            (
+                from_raw_parts_mut(ptr, len - mid), // BUG: should be "mid" instead of "len - mid"
+                from_raw_parts_mut(ptr.offset(mid as isize), len - mid),
+            )
+        }
+    }
+}
+
+fn main() {
+    let mut array = [1, 2, 3, 4];
+    let (a, b) = safe::split_at_mut(&mut array, 0);
+    //~^ ERROR: /retag .* tag does not exist in the borrow stack/
+    a[1] = 5;
+    b[1] = 6;
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/buggy_split_at_mut.stderr b/src/tools/miri/tests/fail/stacked_borrows/buggy_split_at_mut.stderr
new file mode 100644
index 0000000000000..cdeccc0855a95
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/buggy_split_at_mut.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: trying to retag from <TAG> for Unique permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/buggy_split_at_mut.rs:LL:CC
+   |
+LL |     let (a, b) = safe::split_at_mut(&mut array, 0);
+   |          ^
+   |          |
+   |          trying to retag from <TAG> for Unique permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |          this error occurs as part of retag at ALLOC[0x0..0x10]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a Unique retag at offsets [0x0..0x10]
+  --> $DIR/buggy_split_at_mut.rs:LL:CC
+   |
+LL |                 from_raw_parts_mut(ptr, len - mid), // BUG: should be "mid" instead of "len - mid"
+   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x10] by a Unique retag
+  --> $DIR/buggy_split_at_mut.rs:LL:CC
+   |
+LL |                 from_raw_parts_mut(ptr.offset(mid as isize), len - mid),
+   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/buggy_split_at_mut.rs:LL:CC
+
+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/deallocate_against_protector1.rs b/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.rs
new file mode 100644
index 0000000000000..9b710424c55c4
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.rs
@@ -0,0 +1,13 @@
+//@error-pattern: /deallocating while item \[Unique for .*\] is protected/
+
+fn inner(x: &mut i32, f: fn(&mut i32)) {
+    // `f` may mutate, but it may not deallocate!
+    f(x)
+}
+
+fn main() {
+    inner(Box::leak(Box::new(0)), |x| {
+        let raw = x as *mut _;
+        drop(unsafe { Box::from_raw(raw) });
+    });
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.stderr b/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.stderr
new file mode 100644
index 0000000000000..a5db4a00c69e7
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.stderr
@@ -0,0 +1,38 @@
+error: Undefined Behavior: deallocating while item [Unique for <TAG>] is protected by call ID
+  --> RUSTLIB/alloc/src/alloc.rs:LL:CC
+   |
+LL |     unsafe { __rust_dealloc(ptr, layout.size(), layout.align()) }
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ deallocating while item [Unique for <TAG>] is protected by call ID
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+   = note: BACKTRACE:
+   = 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
+   = note: inside `alloc::alloc::box_free::<i32, std::alloc::Global>` at RUSTLIB/alloc/src/alloc.rs:LL:CC
+   = note: inside `std::ptr::drop_in_place::<std::boxed::Box<i32>> - shim(Some(std::boxed::Box<i32>))` at RUSTLIB/core/src/ptr/mod.rs:LL:CC
+   = note: inside `std::mem::drop::<std::boxed::Box<i32>>` at RUSTLIB/core/src/mem/mod.rs:LL:CC
+note: inside closure at $DIR/deallocate_against_protector1.rs:LL:CC
+  --> $DIR/deallocate_against_protector1.rs:LL:CC
+   |
+LL |         drop(unsafe { Box::from_raw(raw) });
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   = note: inside `<[closure@$DIR/deallocate_against_protector1.rs:LL:CC] as std::ops::FnOnce<(&mut i32,)>>::call_once - shim` at RUSTLIB/core/src/ops/function.rs:LL:CC
+note: inside `inner` at $DIR/deallocate_against_protector1.rs:LL:CC
+  --> $DIR/deallocate_against_protector1.rs:LL:CC
+   |
+LL |     f(x)
+   |     ^^^^
+note: inside `main` at $DIR/deallocate_against_protector1.rs:LL:CC
+  --> $DIR/deallocate_against_protector1.rs:LL:CC
+   |
+LL | /     inner(Box::leak(Box::new(0)), |x| {
+LL | |         let raw = x as *mut _;
+LL | |         drop(unsafe { Box::from_raw(raw) });
+LL | |     });
+   | |______^
+
+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/deallocate_against_protector2.rs b/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector2.rs
new file mode 100644
index 0000000000000..36e133e383650
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector2.rs
@@ -0,0 +1,16 @@
+//@error-pattern: /deallocating while item \[SharedReadWrite for .*\] is protected/
+use std::marker::PhantomPinned;
+
+pub struct NotUnpin(i32, PhantomPinned);
+
+fn inner(x: &mut NotUnpin, f: fn(&mut NotUnpin)) {
+    // `f` may mutate, but it may not deallocate!
+    f(x)
+}
+
+fn main() {
+    inner(Box::leak(Box::new(NotUnpin(0, PhantomPinned))), |x| {
+        let raw = x as *mut _;
+        drop(unsafe { Box::from_raw(raw) });
+    });
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector2.stderr b/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector2.stderr
new file mode 100644
index 0000000000000..99c6ee6eb0743
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector2.stderr
@@ -0,0 +1,38 @@
+error: Undefined Behavior: deallocating while item [SharedReadWrite for <TAG>] is protected by call ID
+  --> RUSTLIB/alloc/src/alloc.rs:LL:CC
+   |
+LL |     unsafe { __rust_dealloc(ptr, layout.size(), layout.align()) }
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ deallocating while item [SharedReadWrite for <TAG>] is protected by call ID
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+   = note: BACKTRACE:
+   = 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
+   = note: inside `alloc::alloc::box_free::<NotUnpin, std::alloc::Global>` at RUSTLIB/alloc/src/alloc.rs:LL:CC
+   = note: inside `std::ptr::drop_in_place::<std::boxed::Box<NotUnpin>> - shim(Some(std::boxed::Box<NotUnpin>))` at RUSTLIB/core/src/ptr/mod.rs:LL:CC
+   = note: inside `std::mem::drop::<std::boxed::Box<NotUnpin>>` at RUSTLIB/core/src/mem/mod.rs:LL:CC
+note: inside closure at $DIR/deallocate_against_protector2.rs:LL:CC
+  --> $DIR/deallocate_against_protector2.rs:LL:CC
+   |
+LL |         drop(unsafe { Box::from_raw(raw) });
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   = note: inside `<[closure@$DIR/deallocate_against_protector2.rs:LL:CC] as std::ops::FnOnce<(&mut NotUnpin,)>>::call_once - shim` at RUSTLIB/core/src/ops/function.rs:LL:CC
+note: inside `inner` at $DIR/deallocate_against_protector2.rs:LL:CC
+  --> $DIR/deallocate_against_protector2.rs:LL:CC
+   |
+LL |     f(x)
+   |     ^^^^
+note: inside `main` at $DIR/deallocate_against_protector2.rs:LL:CC
+  --> $DIR/deallocate_against_protector2.rs:LL:CC
+   |
+LL | /     inner(Box::leak(Box::new(NotUnpin(0, PhantomPinned))), |x| {
+LL | |         let raw = x as *mut _;
+LL | |         drop(unsafe { Box::from_raw(raw) });
+LL | |     });
+   | |______^
+
+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/disable_mut_does_not_merge_srw.rs b/src/tools/miri/tests/fail/stacked_borrows/disable_mut_does_not_merge_srw.rs
new file mode 100644
index 0000000000000..fed5cd26f4d63
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/disable_mut_does_not_merge_srw.rs
@@ -0,0 +1,22 @@
+// This tests demonstrates the effect of 'Disabling' mutable references on reads, rather than
+// removing them from the stack -- the latter would 'merge' neighboring SRW groups which we would
+// like to avoid.
+fn main() {
+    unsafe {
+        let mut mem = 0;
+        let base = &mut mem as *mut i32; // the base pointer we build the rest of the stack on
+        let raw = {
+            let mutref = &mut *base;
+            mutref as *mut i32
+        };
+        // In the stack, we now have [base, mutref, raw].
+        // We do this in a weird way where `mutref` is out of scope here, just in case
+        // Miri decides to get smart and argue that items for tags that are no longer
+        // used by any pointer stored anywhere in the machine can be removed.
+        let _val = *base;
+        // now mutref is disabled
+        *base = 1;
+        // this should pop raw from the stack, since it is in a different SRW group
+        let _val = *raw; //~ERROR: that tag does not exist in the borrow stack
+    }
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/disable_mut_does_not_merge_srw.stderr b/src/tools/miri/tests/fail/stacked_borrows/disable_mut_does_not_merge_srw.stderr
new file mode 100644
index 0000000000000..e05f44fac9d2f
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/disable_mut_does_not_merge_srw.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/disable_mut_does_not_merge_srw.rs:LL:CC
+   |
+LL |         let _val = *raw;
+   |                    ^^^^
+   |                    |
+   |                    attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |                    this error occurs as part of an access at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadWrite retag at offsets [0x0..0x4]
+  --> $DIR/disable_mut_does_not_merge_srw.rs:LL:CC
+   |
+LL |             mutref as *mut i32
+   |             ^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a write access
+  --> $DIR/disable_mut_does_not_merge_srw.rs:LL:CC
+   |
+LL |         *base = 1;
+   |         ^^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/disable_mut_does_not_merge_srw.rs:LL:CC
+
+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/exposed_only_ro.rs b/src/tools/miri/tests/fail/stacked_borrows/exposed_only_ro.rs
new file mode 100644
index 0000000000000..0b4fb0ccd33bd
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/exposed_only_ro.rs
@@ -0,0 +1,12 @@
+//@compile-flags: -Zmiri-permissive-provenance
+#![feature(strict_provenance)]
+
+// If we have only exposed read-only pointers, doing a write through a wildcard ptr should fail.
+
+fn main() {
+    let mut x = 0;
+    let _fool = &mut x as *mut i32; // this would have fooled the old untagged pointer logic
+    let addr = (&x as *const i32).expose_addr();
+    let ptr = std::ptr::from_exposed_addr_mut::<i32>(addr);
+    unsafe { *ptr = 0 }; //~ ERROR: /write access using <wildcard> .* no exposed tags have suitable permission in the borrow stack/
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/exposed_only_ro.stderr b/src/tools/miri/tests/fail/stacked_borrows/exposed_only_ro.stderr
new file mode 100644
index 0000000000000..cb5e7bffde480
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/exposed_only_ro.stderr
@@ -0,0 +1,18 @@
+error: Undefined Behavior: attempting a write access using <wildcard> at ALLOC[0x0], but no exposed tags have suitable permission in the borrow stack for this location
+  --> $DIR/exposed_only_ro.rs:LL:CC
+   |
+LL |     unsafe { *ptr = 0 };
+   |              ^^^^^^^^
+   |              |
+   |              attempting a write access using <wildcard> at ALLOC[0x0], but no exposed tags have suitable permission in the borrow stack for this location
+   |              this error occurs as part of an access at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/exposed_only_ro.rs:LL:CC
+
+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/fnentry_invalidation.rs b/src/tools/miri/tests/fail/stacked_borrows/fnentry_invalidation.rs
new file mode 100644
index 0000000000000..37214bebb8285
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/fnentry_invalidation.rs
@@ -0,0 +1,20 @@
+// Test that spans displayed in diagnostics identify the function call, not the function
+// definition, as the location of invalidation due to FnEntry retag. Technically the FnEntry retag
+// occurs inside the function, but what the user wants to know is which call produced the
+// invalidation.
+fn main() {
+    let mut x = 0i32;
+    let z = &mut x as *mut i32;
+    x.do_bad();
+    unsafe {
+        let _oof = *z; //~ ERROR: /read access .* tag does not exist in the borrow stack/
+    }
+}
+
+trait Bad {
+    fn do_bad(&mut self) {
+        // who knows
+    }
+}
+
+impl Bad for i32 {}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/fnentry_invalidation.stderr b/src/tools/miri/tests/fail/stacked_borrows/fnentry_invalidation.stderr
new file mode 100644
index 0000000000000..653ceca858859
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/fnentry_invalidation.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/fnentry_invalidation.rs:LL:CC
+   |
+LL |         let _oof = *z;
+   |                    ^^
+   |                    |
+   |                    attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |                    this error occurs as part of an access at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadWrite retag at offsets [0x0..0x4]
+  --> $DIR/fnentry_invalidation.rs:LL:CC
+   |
+LL |     let z = &mut x as *mut i32;
+   |             ^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a Unique FnEntry retag
+  --> $DIR/fnentry_invalidation.rs:LL:CC
+   |
+LL |     x.do_bad();
+   |     ^^^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/fnentry_invalidation.rs:LL:CC
+
+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/illegal_read1.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_read1.rs
new file mode 100644
index 0000000000000..1dea282739ce1
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read1.rs
@@ -0,0 +1,16 @@
+// A callee may not read the destination of our `&mut` without
+// us noticing.
+
+#[rustfmt::skip] // rustfmt bug: https://github.com/rust-lang/rustfmt/issues/5391
+fn main() {
+    let mut x = 15;
+    let xraw = &mut x as *mut _;
+    let xref = unsafe { &mut *xraw }; // derived from raw, so using raw is still ok...
+    callee(xraw);
+    let _val = *xref; // ...but any use of raw will invalidate our ref.
+    //~^ ERROR: /read access .* tag does not exist in the borrow stack/
+}
+
+fn callee(xraw: *mut i32) {
+    let _val = unsafe { *xraw };
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read1.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_read1.stderr
new file mode 100644
index 0000000000000..95ff05d70c30e
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read1.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/illegal_read1.rs:LL:CC
+   |
+LL |     let _val = *xref; // ...but any use of raw will invalidate our ref.
+   |                ^^^^^
+   |                |
+   |                attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |                this error occurs as part of an access at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a Unique retag at offsets [0x0..0x4]
+  --> $DIR/illegal_read1.rs:LL:CC
+   |
+LL |     let xref = unsafe { &mut *xraw }; // derived from raw, so using raw is still ok...
+   |                         ^^^^^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a read access
+  --> $DIR/illegal_read1.rs:LL:CC
+   |
+LL |     let _val = unsafe { *xraw };
+   |                         ^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/illegal_read1.rs:LL:CC
+
+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/illegal_read2.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_read2.rs
new file mode 100644
index 0000000000000..b765b4d9ce99d
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read2.rs
@@ -0,0 +1,19 @@
+// A callee may not read the destination of our `&mut` without
+// us noticing.
+
+#[rustfmt::skip] // rustfmt bug: https://github.com/rust-lang/rustfmt/issues/5391
+fn main() {
+    let mut x = 15;
+    let xraw = &mut x as *mut _;
+    let xref = unsafe { &mut *xraw }; // derived from raw, so using raw is still ok...
+    callee(xraw);
+    let _val = *xref; // ...but any use of raw will invalidate our ref.
+    //~^ ERROR: /read access .* tag does not exist in the borrow stack/
+}
+
+fn callee(xraw: *mut i32) {
+    // We are a bit sneaky: We first create a shared ref, exploiting the reborrowing rules,
+    // and then we read through that.
+    let shr = unsafe { &*xraw };
+    let _val = *shr;
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read2.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_read2.stderr
new file mode 100644
index 0000000000000..5cfdf77dee402
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read2.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/illegal_read2.rs:LL:CC
+   |
+LL |     let _val = *xref; // ...but any use of raw will invalidate our ref.
+   |                ^^^^^
+   |                |
+   |                attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |                this error occurs as part of an access at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a Unique retag at offsets [0x0..0x4]
+  --> $DIR/illegal_read2.rs:LL:CC
+   |
+LL |     let xref = unsafe { &mut *xraw }; // derived from raw, so using raw is still ok...
+   |                         ^^^^^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a SharedReadOnly retag
+  --> $DIR/illegal_read2.rs:LL:CC
+   |
+LL |     let shr = unsafe { &*xraw };
+   |                        ^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/illegal_read2.rs:LL:CC
+
+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/illegal_read3.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_read3.rs
new file mode 100644
index 0000000000000..43ea0a0e84da5
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read3.rs
@@ -0,0 +1,28 @@
+// A callee may not read the destination of our `&mut` without us noticing.
+// Thise code got carefully checked to not introduce any reborrows
+// that are not explicit in the source. Let's hope the compiler does not break this later!
+
+use std::mem;
+
+union HiddenRef {
+    // We avoid retagging at this type, and we only read, so shared vs mutable does not matter.
+    r: &'static i32,
+}
+
+fn main() {
+    let mut x: i32 = 15;
+    let xref1 = &mut x;
+    let xref1_sneaky: HiddenRef = unsafe { mem::transmute_copy(&xref1) };
+    // Derived from `xref1`, so using raw value is still ok, ...
+    let xref2 = &mut *xref1;
+    callee(xref1_sneaky);
+    // ... though any use of it will invalidate our ref.
+    let _val = *xref2;
+    //~^ ERROR: /read access .* tag does not exist in the borrow stack/
+}
+
+fn callee(xref1: HiddenRef) {
+    // Doing the deref and the transmute (through the union) in the same place expression
+    // should avoid retagging.
+    let _val = unsafe { *xref1.r };
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read3.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_read3.stderr
new file mode 100644
index 0000000000000..dacf71fa3ee39
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read3.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/illegal_read3.rs:LL:CC
+   |
+LL |     let _val = *xref2;
+   |                ^^^^^^
+   |                |
+   |                attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |                this error occurs as part of an access at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a Unique retag at offsets [0x0..0x4]
+  --> $DIR/illegal_read3.rs:LL:CC
+   |
+LL |     let xref2 = &mut *xref1;
+   |                 ^^^^^^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a read access
+  --> $DIR/illegal_read3.rs:LL:CC
+   |
+LL |     let _val = unsafe { *xref1.r };
+   |                         ^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/illegal_read3.rs:LL:CC
+
+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/illegal_read4.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_read4.rs
new file mode 100644
index 0000000000000..a9ecb88d3520b
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read4.rs
@@ -0,0 +1,9 @@
+// Using a raw invalidates derived `&mut` even for reading.
+fn main() {
+    let mut x = 2;
+    let xref1 = &mut x;
+    let xraw = xref1 as *mut _;
+    let xref2 = unsafe { &mut *xraw };
+    let _val = unsafe { *xraw }; // use the raw again, this invalidates xref2 *even* with the special read except for uniq refs
+    let _illegal = *xref2; //~ ERROR: /read access .* tag does not exist in the borrow stack/
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read4.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_read4.stderr
new file mode 100644
index 0000000000000..5ce0cba617914
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read4.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/illegal_read4.rs:LL:CC
+   |
+LL |     let _illegal = *xref2;
+   |                    ^^^^^^
+   |                    |
+   |                    attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |                    this error occurs as part of an access at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a Unique retag at offsets [0x0..0x4]
+  --> $DIR/illegal_read4.rs:LL:CC
+   |
+LL |     let xref2 = unsafe { &mut *xraw };
+   |                          ^^^^^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a read access
+  --> $DIR/illegal_read4.rs:LL:CC
+   |
+LL |     let _val = unsafe { *xraw }; // use the raw again, this invalidates xref2 *even* with the special read except for uniq refs
+   |                         ^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/illegal_read4.rs:LL:CC
+
+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/illegal_read5.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_read5.rs
new file mode 100644
index 0000000000000..228c15f72e173
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read5.rs
@@ -0,0 +1,18 @@
+// We *can* have aliasing &RefCell<T> and &mut T, but we cannot read through the former.
+// Else we couldn't optimize based on the assumption that `xref` below is truly unique.
+//@normalize-stderr-test: "0x[0-9a-fA-F]+" -> "$$HEX"
+
+use std::cell::RefCell;
+use std::{mem, ptr};
+
+#[rustfmt::skip] // rustfmt bug: https://github.com/rust-lang/rustfmt/issues/5391
+fn main() {
+    let rc = RefCell::new(0);
+    let mut refmut = rc.borrow_mut();
+    let xref: &mut i32 = &mut *refmut;
+    let xshr = &rc; // creating this is ok
+    let _val = *xref; // we can even still use our mutable reference
+    mem::forget(unsafe { ptr::read(xshr) }); // but after reading through the shared ref
+    let _val = *xref; // the mutable one is dead and gone
+    //~^ ERROR: /read access .* tag does not exist in the borrow stack/
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read5.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_read5.stderr
new file mode 100644
index 0000000000000..63532f87944eb
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read5.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: attempting a read access using <TAG> at ALLOC[$HEX], but that tag does not exist in the borrow stack for this location
+  --> $DIR/illegal_read5.rs:LL:CC
+   |
+LL |     let _val = *xref; // the mutable one is dead and gone
+   |                ^^^^^
+   |                |
+   |                attempting a read access using <TAG> at ALLOC[$HEX], but that tag does not exist in the borrow stack for this location
+   |                this error occurs as part of an access at ALLOC[$HEX..$HEX]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a Unique retag at offsets [$HEX..$HEX]
+  --> $DIR/illegal_read5.rs:LL:CC
+   |
+LL |     let xref: &mut i32 = &mut *refmut;
+   |                          ^^^^^^^^^^^^
+help: <TAG> was later invalidated at offsets [$HEX..$HEX] by a read access
+  --> $DIR/illegal_read5.rs:LL:CC
+   |
+LL |     mem::forget(unsafe { ptr::read(xshr) }); // but after reading through the shared ref
+   |                          ^^^^^^^^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/illegal_read5.rs:LL:CC
+
+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/illegal_read6.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_read6.rs
new file mode 100644
index 0000000000000..4af22580ab64f
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read6.rs
@@ -0,0 +1,10 @@
+// Creating a shared reference does not leak the data to raw pointers.
+fn main() {
+    unsafe {
+        let x = &mut 0;
+        let raw = x as *mut _;
+        let x = &mut *x; // kill `raw`
+        let _y = &*x; // this should not activate `raw` again
+        let _val = *raw; //~ ERROR: /read access .* tag does not exist in the borrow stack/
+    }
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read6.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_read6.stderr
new file mode 100644
index 0000000000000..93a96ab601ea3
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read6.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/illegal_read6.rs:LL:CC
+   |
+LL |         let _val = *raw;
+   |                    ^^^^
+   |                    |
+   |                    attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |                    this error occurs as part of an access at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadWrite retag at offsets [0x0..0x4]
+  --> $DIR/illegal_read6.rs:LL:CC
+   |
+LL |         let raw = x as *mut _;
+   |                   ^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a Unique retag
+  --> $DIR/illegal_read6.rs:LL:CC
+   |
+LL |         let x = &mut *x; // kill `raw`
+   |                 ^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/illegal_read6.rs:LL:CC
+
+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/illegal_read7.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_read7.rs
new file mode 100644
index 0000000000000..1901e8e4e3480
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read7.rs
@@ -0,0 +1,22 @@
+// Creating a shared reference does not leak the data to raw pointers,
+// not even when interior mutability is involved.
+
+use std::cell::Cell;
+use std::ptr;
+
+fn main() {
+    unsafe {
+        let x = &mut Cell::new(0);
+        let raw = x as *mut Cell<i32>;
+        let x = &mut *raw;
+        let _shr = &*x;
+        // The state here is interesting because the top of the stack is [Unique, SharedReadWrite],
+        // just like if we had done `x as *mut _`.
+        // If we said that reading from a lower item is fine if the top item is `SharedReadWrite`
+        // (one way to maybe preserve a stack discipline), then we could now read from `raw`
+        // without invalidating `x`.  That would be bad!  It would mean that creating `shr`
+        // leaked `x` to `raw`.
+        let _val = ptr::read(raw);
+        let _val = *x.get_mut(); //~ ERROR: /retag .* tag does not exist in the borrow stack/
+    }
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read7.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_read7.stderr
new file mode 100644
index 0000000000000..2e8ac207beafb
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read7.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: trying to retag from <TAG> for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/illegal_read7.rs:LL:CC
+   |
+LL |         let _val = *x.get_mut();
+   |                     ^^^^^^^^^^^
+   |                     |
+   |                     trying to retag from <TAG> for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |                     this error occurs as part of two-phase retag at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a Unique retag at offsets [0x0..0x4]
+  --> $DIR/illegal_read7.rs:LL:CC
+   |
+LL |         let x = &mut *raw;
+   |                 ^^^^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a read access
+  --> $DIR/illegal_read7.rs:LL:CC
+   |
+LL |         let _val = ptr::read(raw);
+   |                    ^^^^^^^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/illegal_read7.rs:LL:CC
+
+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/illegal_read8.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_read8.rs
new file mode 100644
index 0000000000000..f0a1658a5a23a
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read8.rs
@@ -0,0 +1,15 @@
+// Make sure that creating a raw ptr next to a shared ref works
+// but the shared ref still gets invalidated when the raw ptr is used for writing.
+
+fn main() {
+    unsafe {
+        use std::mem;
+        let x = &mut 0;
+        let y1: &i32 = mem::transmute(&*x); // launder lifetimes
+        let y2 = x as *mut _;
+        let _val = *y2;
+        let _val = *y1;
+        *y2 += 1;
+        let _fail = *y1; //~ ERROR: /read access .* tag does not exist in the borrow stack/
+    }
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read8.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_read8.stderr
new file mode 100644
index 0000000000000..c34fa2d8955dd
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read8.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/illegal_read8.rs:LL:CC
+   |
+LL |         let _fail = *y1;
+   |                     ^^^
+   |                     |
+   |                     attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |                     this error occurs as part of an access at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadOnly retag at offsets [0x0..0x4]
+  --> $DIR/illegal_read8.rs:LL:CC
+   |
+LL |         let y1: &i32 = mem::transmute(&*x); // launder lifetimes
+   |                        ^^^^^^^^^^^^^^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a write access
+  --> $DIR/illegal_read8.rs:LL:CC
+   |
+LL |         *y2 += 1;
+   |         ^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/illegal_read8.rs:LL:CC
+
+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/illegal_read_despite_exposed1.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed1.rs
new file mode 100644
index 0000000000000..76516b7d924be
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed1.rs
@@ -0,0 +1,17 @@
+//@compile-flags: -Zmiri-permissive-provenance
+
+fn main() {
+    unsafe {
+        let root = &mut 42;
+        let addr = root as *mut i32 as usize;
+        let exposed_ptr = addr as *mut i32;
+        // From the exposed ptr, we get a new unique ptr.
+        let root2 = &mut *exposed_ptr;
+        let _fool = root2 as *mut _; // this would have fooled the old untagged pointer logic
+        // Stack: Unknown(<N), Unique(N), SRW(N+1)
+        // And we test that it has uniqueness by doing a conflicting write.
+        *exposed_ptr = 0;
+        // Stack: Unknown(<N)
+        let _val = *root2; //~ ERROR: /read access .* tag does not exist in the borrow stack/
+    }
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed1.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed1.stderr
new file mode 100644
index 0000000000000..43b4ec2ba652b
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed1.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/illegal_read_despite_exposed1.rs:LL:CC
+   |
+LL |         let _val = *root2;
+   |                    ^^^^^^
+   |                    |
+   |                    attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |                    this error occurs as part of an access at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a Unique retag at offsets [0x0..0x4]
+  --> $DIR/illegal_read_despite_exposed1.rs:LL:CC
+   |
+LL |         let root2 = &mut *exposed_ptr;
+   |                     ^^^^^^^^^^^^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a write access
+  --> $DIR/illegal_read_despite_exposed1.rs:LL:CC
+   |
+LL |         *exposed_ptr = 0;
+   |         ^^^^^^^^^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/illegal_read_despite_exposed1.rs:LL:CC
+
+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/illegal_read_despite_exposed2.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed2.rs
new file mode 100644
index 0000000000000..97e0bf40c0dd3
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed2.rs
@@ -0,0 +1,20 @@
+//@compile-flags: -Zmiri-permissive-provenance
+
+fn main() {
+    unsafe {
+        let root = &mut 42;
+        let addr = root as *mut i32 as usize;
+        let exposed_ptr = addr as *mut i32;
+        // From the exposed ptr, we get a new unique ptr.
+        let root2 = &mut *exposed_ptr;
+        // let _fool = root2 as *mut _; // this would fool us, since SRW(N+1) remains on the stack
+        // Stack: Unknown(<N), Unique(N)
+        // Stack if _fool existed: Unknown(<N), Unique(N), SRW(N+1)
+        // And we test that it has uniqueness by doing a conflicting read.
+        let _val = *exposed_ptr;
+        // Stack: Unknown(<N), Disabled(N)
+        // collapsed to Unknown(<N)
+        // Stack if _fool existed: Unknown(<N), Disabled(N), SRW(N+1); collapsed to Unknown(<N+2) which would not cause an ERROR
+        let _val = *root2; //~ ERROR: /read access .* tag does not exist in the borrow stack/
+    }
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed2.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed2.stderr
new file mode 100644
index 0000000000000..832320fc202e1
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed2.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/illegal_read_despite_exposed2.rs:LL:CC
+   |
+LL |         let _val = *root2;
+   |                    ^^^^^^
+   |                    |
+   |                    attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |                    this error occurs as part of an access at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a Unique retag at offsets [0x0..0x4]
+  --> $DIR/illegal_read_despite_exposed2.rs:LL:CC
+   |
+LL |         let root2 = &mut *exposed_ptr;
+   |                     ^^^^^^^^^^^^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a read access
+  --> $DIR/illegal_read_despite_exposed2.rs:LL:CC
+   |
+LL |         let _val = *exposed_ptr;
+   |                    ^^^^^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/illegal_read_despite_exposed2.rs:LL:CC
+
+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/illegal_write1.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_write1.rs
new file mode 100644
index 0000000000000..f28401938a925
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_write1.rs
@@ -0,0 +1,9 @@
+fn main() {
+    let target = Box::new(42); // has an implicit raw
+    let xref = &*target;
+    {
+        let x: *mut u32 = xref as *const _ as *mut _;
+        unsafe { *x = 42 }; //~ ERROR: /write access .* tag only grants SharedReadOnly permission/
+    }
+    let _x = *xref;
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_write1.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_write1.stderr
new file mode 100644
index 0000000000000..3bf27f4815e9a
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_write1.stderr
@@ -0,0 +1,23 @@
+error: Undefined Behavior: attempting a write access using <TAG> at ALLOC[0x0], but that tag only grants SharedReadOnly permission for this location
+  --> $DIR/illegal_write1.rs:LL:CC
+   |
+LL |         unsafe { *x = 42 };
+   |                  ^^^^^^^
+   |                  |
+   |                  attempting a write access using <TAG> at ALLOC[0x0], but that tag only grants SharedReadOnly permission for this location
+   |                  this error occurs as part of an access at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadOnly retag at offsets [0x0..0x4]
+  --> $DIR/illegal_write1.rs:LL:CC
+   |
+LL |         let x: *mut u32 = xref as *const _ as *mut _;
+   |                           ^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/illegal_write1.rs:LL:CC
+
+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/illegal_write2.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_write2.rs
new file mode 100644
index 0000000000000..70c51e671fe84
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_write2.rs
@@ -0,0 +1,8 @@
+fn main() {
+    let target = &mut 42;
+    let target2 = target as *mut _;
+    drop(&mut *target); // reborrow
+    // Now make sure our ref is still the only one.
+    unsafe { *target2 = 13 }; //~ ERROR: /write access .* tag does not exist in the borrow stack/
+    let _val = *target;
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_write2.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_write2.stderr
new file mode 100644
index 0000000000000..a9fe8cb6ccc02
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_write2.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: attempting a write access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/illegal_write2.rs:LL:CC
+   |
+LL |     unsafe { *target2 = 13 };
+   |              ^^^^^^^^^^^^^
+   |              |
+   |              attempting a write access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |              this error occurs as part of an access at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadWrite retag at offsets [0x0..0x4]
+  --> $DIR/illegal_write2.rs:LL:CC
+   |
+LL |     let target2 = target as *mut _;
+   |                   ^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a Unique retag
+  --> $DIR/illegal_write2.rs:LL:CC
+   |
+LL |     drop(&mut *target); // reborrow
+   |          ^^^^^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/illegal_write2.rs:LL:CC
+
+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/illegal_write3.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_write3.rs
new file mode 100644
index 0000000000000..6f55b63cb5c64
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_write3.rs
@@ -0,0 +1,8 @@
+fn main() {
+    let target = 42;
+    // Make sure raw ptr with raw tag cannot mutate frozen location without breaking the shared ref.
+    let r#ref = &target; // freeze
+    let ptr = r#ref as *const _ as *mut _; // raw ptr, with raw tag
+    unsafe { *ptr = 42 }; //~ ERROR: /write access .* only grants SharedReadOnly permission/
+    let _val = *r#ref;
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_write3.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_write3.stderr
new file mode 100644
index 0000000000000..d64f2ddd87670
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_write3.stderr
@@ -0,0 +1,23 @@
+error: Undefined Behavior: attempting a write access using <TAG> at ALLOC[0x0], but that tag only grants SharedReadOnly permission for this location
+  --> $DIR/illegal_write3.rs:LL:CC
+   |
+LL |     unsafe { *ptr = 42 };
+   |              ^^^^^^^^^
+   |              |
+   |              attempting a write access using <TAG> at ALLOC[0x0], but that tag only grants SharedReadOnly permission for this location
+   |              this error occurs as part of an access at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadOnly retag at offsets [0x0..0x4]
+  --> $DIR/illegal_write3.rs:LL:CC
+   |
+LL |     let ptr = r#ref as *const _ as *mut _; // raw ptr, with raw tag
+   |               ^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/illegal_write3.rs:LL:CC
+
+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/illegal_write4.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_write4.rs
new file mode 100644
index 0000000000000..4c5d5a4f66577
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_write4.rs
@@ -0,0 +1,13 @@
+use std::mem;
+
+fn main() {
+    let mut target = 42;
+    // Make sure we cannot use a raw-tagged `&mut` pointing to a frozen location.
+    // Even just creating it unfreezes.
+    let raw = &mut target as *mut _; // let this leak to raw
+    let reference = unsafe { &*raw }; // freeze
+    let _ptr = reference as *const _ as *mut i32; // raw ptr, with raw tag
+    let _mut_ref: &mut i32 = unsafe { mem::transmute(raw) }; // &mut, with raw tag
+    // Now we retag, making our ref top-of-stack -- and, in particular, unfreezing.
+    let _val = *reference; //~ ERROR: /read access .* tag does not exist in the borrow stack/
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_write4.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_write4.stderr
new file mode 100644
index 0000000000000..e3b8621eb74f2
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_write4.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/illegal_write4.rs:LL:CC
+   |
+LL |     let _val = *reference;
+   |                ^^^^^^^^^^
+   |                |
+   |                attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |                this error occurs as part of an access at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadOnly retag at offsets [0x0..0x4]
+  --> $DIR/illegal_write4.rs:LL:CC
+   |
+LL |     let reference = unsafe { &*raw }; // freeze
+   |                              ^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a Unique retag
+  --> $DIR/illegal_write4.rs:LL:CC
+   |
+LL |     let _mut_ref: &mut i32 = unsafe { mem::transmute(raw) }; // &mut, with raw tag
+   |                                       ^^^^^^^^^^^^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/illegal_write4.rs:LL:CC
+
+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/illegal_write5.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_write5.rs
new file mode 100644
index 0000000000000..1c655dc0a0fb4
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_write5.rs
@@ -0,0 +1,16 @@
+// A callee may not write to the destination of our `&mut` without us noticing.
+
+fn main() {
+    let mut x = 15;
+    let xraw = &mut x as *mut _;
+    // Derived from raw value, so using raw value is still ok ...
+    let xref = unsafe { &mut *xraw };
+    callee(xraw);
+    // ... though any use of raw value will invalidate our ref.
+    let _val = *xref;
+    //~^ ERROR: /read access .* tag does not exist in the borrow stack/
+}
+
+fn callee(xraw: *mut i32) {
+    unsafe { *xraw = 15 };
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_write5.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_write5.stderr
new file mode 100644
index 0000000000000..bbeb81258bde6
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_write5.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/illegal_write5.rs:LL:CC
+   |
+LL |     let _val = *xref;
+   |                ^^^^^
+   |                |
+   |                attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |                this error occurs as part of an access at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a Unique retag at offsets [0x0..0x4]
+  --> $DIR/illegal_write5.rs:LL:CC
+   |
+LL |     let xref = unsafe { &mut *xraw };
+   |                         ^^^^^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a write access
+  --> $DIR/illegal_write5.rs:LL:CC
+   |
+LL |     unsafe { *xraw = 15 };
+   |              ^^^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/illegal_write5.rs:LL:CC
+
+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/illegal_write6.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_write6.rs
new file mode 100644
index 0000000000000..448f1493367af
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_write6.rs
@@ -0,0 +1,12 @@
+fn main() {
+    let x = &mut 0u32;
+    let p = x as *mut u32;
+    foo(x, p);
+}
+
+fn foo(a: &mut u32, y: *mut u32) -> u32 {
+    *a = 1;
+    let _b = &*a;
+    unsafe { *y = 2 }; //~ ERROR: /not granting access .* because that would remove .* which is protected/
+    return *a;
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_write6.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_write6.stderr
new file mode 100644
index 0000000000000..331faa89f8604
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_write6.stderr
@@ -0,0 +1,30 @@
+error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is protected because it is an argument of call ID
+  --> $DIR/illegal_write6.rs:LL:CC
+   |
+LL |     unsafe { *y = 2 };
+   |              ^^^^^^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is protected because it is an argument of call ID
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadWrite retag at offsets [0x0..0x4]
+  --> $DIR/illegal_write6.rs:LL:CC
+   |
+LL |     let p = x as *mut u32;
+   |             ^
+help: <TAG> is this argument
+  --> $DIR/illegal_write6.rs:LL:CC
+   |
+LL | fn foo(a: &mut u32, y: *mut u32) -> u32 {
+   |        ^
+   = note: BACKTRACE:
+   = note: inside `foo` at $DIR/illegal_write6.rs:LL:CC
+note: inside `main` at $DIR/illegal_write6.rs:LL:CC
+  --> $DIR/illegal_write6.rs:LL:CC
+   |
+LL |     foo(x, p);
+   |     ^^^^^^^^^
+
+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/illegal_write_despite_exposed1.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_write_despite_exposed1.rs
new file mode 100644
index 0000000000000..0e34c5c98fc1f
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_write_despite_exposed1.rs
@@ -0,0 +1,17 @@
+//@compile-flags: -Zmiri-permissive-provenance
+
+fn main() {
+    unsafe {
+        let root = &mut 42;
+        let addr = root as *mut i32 as usize;
+        let exposed_ptr = addr as *mut i32;
+        // From the exposed ptr, we get a new SRO ptr.
+        let root2 = &*exposed_ptr;
+        // Stack: Unknown(<N), SRO(N), SRO(N+1)
+        // And we test that it is read-only by doing a conflicting write.
+        // (The write is still fine, using the `root as *mut i32` provenance which got exposed.)
+        *exposed_ptr = 0;
+        // Stack: Unknown(<N)
+        let _val = *root2; //~ ERROR: /read access .* tag does not exist in the borrow stack/
+    }
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_write_despite_exposed1.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_write_despite_exposed1.stderr
new file mode 100644
index 0000000000000..87ddf61d7586c
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_write_despite_exposed1.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/illegal_write_despite_exposed1.rs:LL:CC
+   |
+LL |         let _val = *root2;
+   |                    ^^^^^^
+   |                    |
+   |                    attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |                    this error occurs as part of an access at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadOnly retag at offsets [0x0..0x4]
+  --> $DIR/illegal_write_despite_exposed1.rs:LL:CC
+   |
+LL |         let root2 = &*exposed_ptr;
+   |                     ^^^^^^^^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a write access
+  --> $DIR/illegal_write_despite_exposed1.rs:LL:CC
+   |
+LL |         *exposed_ptr = 0;
+   |         ^^^^^^^^^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/illegal_write_despite_exposed1.rs:LL:CC
+
+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/interior_mut1.rs b/src/tools/miri/tests/fail/stacked_borrows/interior_mut1.rs
new file mode 100644
index 0000000000000..6911238fc53e9
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/interior_mut1.rs
@@ -0,0 +1,17 @@
+use std::cell::UnsafeCell;
+
+fn main() {
+    unsafe {
+        let c = &UnsafeCell::new(UnsafeCell::new(0));
+        let inner_uniq = &mut *c.get();
+        // stack: [c: SharedReadWrite, inner_uniq: Unique]
+
+        let inner_shr = &*inner_uniq; // adds a SharedReadWrite
+        // stack: [c: SharedReadWrite, inner_uniq: Unique, inner_shr: SharedReadWrite]
+
+        *c.get() = UnsafeCell::new(1); // invalidates inner_shr
+        // stack: [c: SharedReadWrite]
+
+        let _val = *inner_shr.get(); //~ ERROR: /retag .* tag does not exist in the borrow stack/
+    }
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/interior_mut1.stderr b/src/tools/miri/tests/fail/stacked_borrows/interior_mut1.stderr
new file mode 100644
index 0000000000000..1d68727c82af4
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/interior_mut1.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: trying to retag from <TAG> for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/interior_mut1.rs:LL:CC
+   |
+LL |         let _val = *inner_shr.get();
+   |                     ^^^^^^^^^^^^^^^
+   |                     |
+   |                     trying to retag from <TAG> for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |                     this error occurs as part of retag at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadWrite retag at offsets [0x0..0x4]
+  --> $DIR/interior_mut1.rs:LL:CC
+   |
+LL |         let inner_shr = &*inner_uniq; // adds a SharedReadWrite
+   |                         ^^^^^^^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a write access
+  --> $DIR/interior_mut1.rs:LL:CC
+   |
+LL |         *c.get() = UnsafeCell::new(1); // invalidates inner_shr
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/interior_mut1.rs:LL:CC
+
+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/interior_mut2.rs b/src/tools/miri/tests/fail/stacked_borrows/interior_mut2.rs
new file mode 100644
index 0000000000000..5e9d177cd038d
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/interior_mut2.rs
@@ -0,0 +1,30 @@
+use std::cell::UnsafeCell;
+use std::mem;
+
+// Like `&mut *x.get()`, but without intermediate raw pointers.
+#[allow(mutable_transmutes)]
+unsafe fn unsafe_cell_get<T>(x: &UnsafeCell<T>) -> &'static mut T {
+    mem::transmute(x)
+}
+
+fn main() {
+    unsafe {
+        let c = &UnsafeCell::new(UnsafeCell::new(0));
+        let inner_uniq = &mut *c.get();
+        let inner_shr = &*inner_uniq;
+        // stack: [c: SharedReadWrite, inner_uniq: Unique, inner_shr: SharedReadWrite]
+
+        let _val = c.get().read(); // invalidates inner_uniq
+        // stack: [c: SharedReadWrite, inner_uniq: Disabled, inner_shr: SharedReadWrite]
+
+        // We have to be careful not to add any raw pointers above inner_uniq in
+        // the stack, hence the use of unsafe_cell_get.
+        let _val = *unsafe_cell_get(inner_shr); // this still works
+
+        *c.get() = UnsafeCell::new(0); // now inner_shr gets invalidated
+        // stack: [c: SharedReadWrite]
+
+        // now this does not work any more
+        let _val = *inner_shr.get(); //~ ERROR: /retag .* tag does not exist in the borrow stack/
+    }
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/interior_mut2.stderr b/src/tools/miri/tests/fail/stacked_borrows/interior_mut2.stderr
new file mode 100644
index 0000000000000..8a3357142261b
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/interior_mut2.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: trying to retag from <TAG> for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/interior_mut2.rs:LL:CC
+   |
+LL |         let _val = *inner_shr.get();
+   |                     ^^^^^^^^^^^^^^^
+   |                     |
+   |                     trying to retag from <TAG> for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |                     this error occurs as part of retag at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadWrite retag at offsets [0x0..0x4]
+  --> $DIR/interior_mut2.rs:LL:CC
+   |
+LL |         let inner_shr = &*inner_uniq;
+   |                         ^^^^^^^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a write access
+  --> $DIR/interior_mut2.rs:LL:CC
+   |
+LL |         *c.get() = UnsafeCell::new(0); // now inner_shr gets invalidated
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/interior_mut2.rs:LL:CC
+
+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/invalidate_against_protector1.rs b/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector1.rs
new file mode 100644
index 0000000000000..d0f43510c28ff
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector1.rs
@@ -0,0 +1,13 @@
+fn inner(x: *mut i32, _y: &mut i32) {
+    // If `x` and `y` alias, retagging is fine with this... but we really
+    // shouldn't be allowed to use `x` at all because `y` was assumed to be
+    // unique for the duration of this call.
+    let _val = unsafe { *x }; //~ ERROR: protect
+}
+
+fn main() {
+    let mut x = 0;
+    let xraw = &mut x as *mut _;
+    let xref = unsafe { &mut *xraw };
+    inner(xraw, xref);
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector1.stderr b/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector1.stderr
new file mode 100644
index 0000000000000..f87bd2319abd7
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector1.stderr
@@ -0,0 +1,30 @@
+error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is protected because it is an argument of call ID
+  --> $DIR/invalidate_against_protector1.rs:LL:CC
+   |
+LL |     let _val = unsafe { *x };
+   |                         ^^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is protected because it is an argument of call ID
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadWrite retag at offsets [0x0..0x4]
+  --> $DIR/invalidate_against_protector1.rs:LL:CC
+   |
+LL |     let xraw = &mut x as *mut _;
+   |                ^^^^^^
+help: <TAG> is this argument
+  --> $DIR/invalidate_against_protector1.rs:LL:CC
+   |
+LL | fn inner(x: *mut i32, _y: &mut i32) {
+   |                       ^^
+   = note: BACKTRACE:
+   = note: inside `inner` at $DIR/invalidate_against_protector1.rs:LL:CC
+note: inside `main` at $DIR/invalidate_against_protector1.rs:LL:CC
+  --> $DIR/invalidate_against_protector1.rs:LL:CC
+   |
+LL |     inner(xraw, xref);
+   |     ^^^^^^^^^^^^^^^^^
+
+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/invalidate_against_protector2.rs b/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector2.rs
new file mode 100644
index 0000000000000..f4e767302fd00
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector2.rs
@@ -0,0 +1,13 @@
+fn inner(x: *mut i32, _y: &i32) {
+    // If `x` and `y` alias, retagging is fine with this... but we really
+    // shouldn't be allowed to write to `x` at all because `y` was assumed to be
+    // immutable for the duration of this call.
+    unsafe { *x = 0 }; //~ ERROR: protect
+}
+
+fn main() {
+    let mut x = 0;
+    let xraw = &mut x as *mut _;
+    let xref = unsafe { &*xraw };
+    inner(xraw, xref);
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector2.stderr b/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector2.stderr
new file mode 100644
index 0000000000000..07c51a39b825b
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector2.stderr
@@ -0,0 +1,30 @@
+error: Undefined Behavior: not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is protected because it is an argument of call ID
+  --> $DIR/invalidate_against_protector2.rs:LL:CC
+   |
+LL |     unsafe { *x = 0 };
+   |              ^^^^^^ not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is protected because it is an argument of call ID
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadWrite retag at offsets [0x0..0x4]
+  --> $DIR/invalidate_against_protector2.rs:LL:CC
+   |
+LL |     let xraw = &mut x as *mut _;
+   |                ^^^^^^
+help: <TAG> is this argument
+  --> $DIR/invalidate_against_protector2.rs:LL:CC
+   |
+LL | fn inner(x: *mut i32, _y: &i32) {
+   |                       ^^
+   = note: BACKTRACE:
+   = note: inside `inner` at $DIR/invalidate_against_protector2.rs:LL:CC
+note: inside `main` at $DIR/invalidate_against_protector2.rs:LL:CC
+  --> $DIR/invalidate_against_protector2.rs:LL:CC
+   |
+LL |     inner(xraw, xref);
+   |     ^^^^^^^^^^^^^^^^^
+
+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/invalidate_against_protector3.rs b/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector3.rs
new file mode 100644
index 0000000000000..634eb97217c6c
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector3.rs
@@ -0,0 +1,15 @@
+use std::alloc::{alloc, Layout};
+
+fn inner(x: *mut i32, _y: &i32) {
+    // If `x` and `y` alias, retagging is fine with this... but we really
+    // shouldn't be allowed to write to `x` at all because `y` was assumed to be
+    // immutable for the duration of this call.
+    unsafe { *x = 0 }; //~ ERROR: protect
+}
+
+fn main() {
+    unsafe {
+        let ptr = alloc(Layout::for_value(&0i32)) as *mut i32;
+        inner(ptr, &*ptr);
+    };
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector3.stderr b/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector3.stderr
new file mode 100644
index 0000000000000..afda15ea160e2
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector3.stderr
@@ -0,0 +1,30 @@
+error: Undefined Behavior: not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is protected because it is an argument of call ID
+  --> $DIR/invalidate_against_protector3.rs:LL:CC
+   |
+LL |     unsafe { *x = 0 };
+   |              ^^^^^^ not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is protected because it is an argument of call ID
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created here, as the base tag for ALLOC
+  --> $DIR/invalidate_against_protector3.rs:LL:CC
+   |
+LL |         let ptr = alloc(Layout::for_value(&0i32)) as *mut i32;
+   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: <TAG> is this argument
+  --> $DIR/invalidate_against_protector3.rs:LL:CC
+   |
+LL | fn inner(x: *mut i32, _y: &i32) {
+   |                       ^^
+   = note: BACKTRACE:
+   = note: inside `inner` at $DIR/invalidate_against_protector3.rs:LL:CC
+note: inside `main` at $DIR/invalidate_against_protector3.rs:LL:CC
+  --> $DIR/invalidate_against_protector3.rs:LL:CC
+   |
+LL |         inner(ptr, &*ptr);
+   |         ^^^^^^^^^^^^^^^^^
+
+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/issue-miri-1050-1.rs b/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-1.rs
new file mode 100644
index 0000000000000..1e44cc6c800ee
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-1.rs
@@ -0,0 +1,8 @@
+//@error-pattern: pointer to 4 bytes starting at offset 0 is out-of-bounds
+
+fn main() {
+    unsafe {
+        let ptr = Box::into_raw(Box::new(0u16));
+        drop(Box::from_raw(ptr as *mut u32));
+    }
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-1.stderr b/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-1.stderr
new file mode 100644
index 0000000000000..16c8810a8e6d9
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-1.stderr
@@ -0,0 +1,21 @@
+error: Undefined Behavior: out-of-bounds pointer use: ALLOC has size 2, so pointer to 4 bytes starting at offset 0 is out-of-bounds
+  --> RUSTLIB/alloc/src/boxed.rs:LL:CC
+   |
+LL |         Box(unsafe { Unique::new_unchecked(raw) }, alloc)
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer use: ALLOC has size 2, so pointer to 4 bytes starting at offset 0 is out-of-bounds
+   |
+   = 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 `std::boxed::Box::<u32>::from_raw_in` at RUSTLIB/alloc/src/boxed.rs:LL:CC
+   = note: inside `std::boxed::Box::<u32>::from_raw` at RUSTLIB/alloc/src/boxed.rs:LL:CC
+note: inside `main` at $DIR/issue-miri-1050-1.rs:LL:CC
+  --> $DIR/issue-miri-1050-1.rs:LL:CC
+   |
+LL |         drop(Box::from_raw(ptr as *mut u32));
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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/issue-miri-1050-2.rs b/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-2.rs
new file mode 100644
index 0000000000000..6e90559a9ef5b
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-2.rs
@@ -0,0 +1,9 @@
+//@error-pattern: is a dangling pointer
+use std::ptr::NonNull;
+
+fn main() {
+    unsafe {
+        let ptr = NonNull::<i32>::dangling();
+        drop(Box::from_raw(ptr.as_ptr()));
+    }
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-2.stderr b/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-2.stderr
new file mode 100644
index 0000000000000..d57e7662e504a
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-2.stderr
@@ -0,0 +1,21 @@
+error: Undefined Behavior: out-of-bounds pointer use: 0x4[noalloc] is a dangling pointer (it has no provenance)
+  --> RUSTLIB/alloc/src/boxed.rs:LL:CC
+   |
+LL |         Box(unsafe { Unique::new_unchecked(raw) }, alloc)
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer use: 0x4[noalloc] is a dangling pointer (it has no provenance)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `std::boxed::Box::<i32>::from_raw_in` at RUSTLIB/alloc/src/boxed.rs:LL:CC
+   = note: inside `std::boxed::Box::<i32>::from_raw` at RUSTLIB/alloc/src/boxed.rs:LL:CC
+note: inside `main` at $DIR/issue-miri-1050-2.rs:LL:CC
+  --> $DIR/issue-miri-1050-2.rs:LL:CC
+   |
+LL |         drop(Box::from_raw(ptr.as_ptr()));
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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/load_invalid_mut.rs b/src/tools/miri/tests/fail/stacked_borrows/load_invalid_mut.rs
new file mode 100644
index 0000000000000..5c99c82da6d6a
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/load_invalid_mut.rs
@@ -0,0 +1,12 @@
+// Make sure we catch this even without validation
+//@compile-flags: -Zmiri-disable-validation
+
+// Make sure that we cannot load from memory a `&mut` that got already invalidated.
+fn main() {
+    let x = &mut 42;
+    let xraw = x as *mut _;
+    let xref = unsafe { &mut *xraw };
+    let xref_in_mem = Box::new(xref);
+    let _val = unsafe { *xraw }; // invalidate xref
+    let _val = *xref_in_mem; //~ ERROR: /retag .* tag does not exist in the borrow stack/
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/load_invalid_mut.stderr b/src/tools/miri/tests/fail/stacked_borrows/load_invalid_mut.stderr
new file mode 100644
index 0000000000000..08dc171c9eef0
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/load_invalid_mut.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: trying to retag from <TAG> for Unique permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/load_invalid_mut.rs:LL:CC
+   |
+LL |     let _val = *xref_in_mem;
+   |                ^^^^^^^^^^^^
+   |                |
+   |                trying to retag from <TAG> for Unique permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |                this error occurs as part of retag at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a Unique retag at offsets [0x0..0x4]
+  --> $DIR/load_invalid_mut.rs:LL:CC
+   |
+LL |     let xref_in_mem = Box::new(xref);
+   |                       ^^^^^^^^^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a read access
+  --> $DIR/load_invalid_mut.rs:LL:CC
+   |
+LL |     let _val = unsafe { *xraw }; // invalidate xref
+   |                         ^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/load_invalid_mut.rs:LL:CC
+
+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/load_invalid_shr.rs b/src/tools/miri/tests/fail/stacked_borrows/load_invalid_shr.rs
new file mode 100644
index 0000000000000..8f94cc07a24f7
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/load_invalid_shr.rs
@@ -0,0 +1,12 @@
+// Make sure we catch this even without validation
+//@compile-flags: -Zmiri-disable-validation
+
+// Make sure that we cannot load from memory a `&` that got already invalidated.
+fn main() {
+    let x = &mut 42;
+    let xraw = x as *mut _;
+    let xref = unsafe { &*xraw };
+    let xref_in_mem = Box::new(xref);
+    unsafe { *xraw = 42 }; // unfreeze
+    let _val = *xref_in_mem; //~ ERROR: /retag .* tag does not exist in the borrow stack/
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/load_invalid_shr.stderr b/src/tools/miri/tests/fail/stacked_borrows/load_invalid_shr.stderr
new file mode 100644
index 0000000000000..50bbed2b295c9
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/load_invalid_shr.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: trying to retag from <TAG> for SharedReadOnly permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/load_invalid_shr.rs:LL:CC
+   |
+LL |     let _val = *xref_in_mem;
+   |                ^^^^^^^^^^^^
+   |                |
+   |                trying to retag from <TAG> for SharedReadOnly permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |                this error occurs as part of retag at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadOnly retag at offsets [0x0..0x4]
+  --> $DIR/load_invalid_shr.rs:LL:CC
+   |
+LL |     let xref_in_mem = Box::new(xref);
+   |                       ^^^^^^^^^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a write access
+  --> $DIR/load_invalid_shr.rs:LL:CC
+   |
+LL |     unsafe { *xraw = 42 }; // unfreeze
+   |              ^^^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/load_invalid_shr.rs:LL:CC
+
+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/mut_exclusive_violation1.rs b/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation1.rs
new file mode 100644
index 0000000000000..f6fcdf1acdd2a
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation1.rs
@@ -0,0 +1,33 @@
+fn demo_mut_advanced_unique(our: &mut i32) -> i32 {
+    unknown_code_1(&*our);
+
+    // This "re-asserts" uniqueness of the reference: After writing, we know
+    // our tag is at the top of the stack.
+    *our = 5;
+
+    unknown_code_2();
+
+    // We know this will return 5
+    *our
+}
+
+// Now comes the evil context
+use std::ptr;
+
+static mut LEAK: *mut i32 = ptr::null_mut();
+
+fn unknown_code_1(x: &i32) {
+    unsafe {
+        LEAK = x as *const _ as *mut _;
+    }
+}
+
+fn unknown_code_2() {
+    unsafe {
+        *LEAK = 7; //~ ERROR: /write access .* tag does not exist in the borrow stack/
+    }
+}
+
+fn main() {
+    demo_mut_advanced_unique(&mut 0);
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation1.stderr b/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation1.stderr
new file mode 100644
index 0000000000000..1c7f8e12d3d78
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation1.stderr
@@ -0,0 +1,38 @@
+error: Undefined Behavior: attempting a write access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/mut_exclusive_violation1.rs:LL:CC
+   |
+LL |         *LEAK = 7;
+   |         ^^^^^^^^^
+   |         |
+   |         attempting a write access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |         this error occurs as part of an access at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadOnly retag at offsets [0x0..0x4]
+  --> $DIR/mut_exclusive_violation1.rs:LL:CC
+   |
+LL |         LEAK = x as *const _ as *mut _;
+   |                ^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a write access
+  --> $DIR/mut_exclusive_violation1.rs:LL:CC
+   |
+LL |     *our = 5;
+   |     ^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `unknown_code_2` at $DIR/mut_exclusive_violation1.rs:LL:CC
+note: inside `demo_mut_advanced_unique` at $DIR/mut_exclusive_violation1.rs:LL:CC
+  --> $DIR/mut_exclusive_violation1.rs:LL:CC
+   |
+LL |     unknown_code_2();
+   |     ^^^^^^^^^^^^^^^^
+note: inside `main` at $DIR/mut_exclusive_violation1.rs:LL:CC
+  --> $DIR/mut_exclusive_violation1.rs:LL:CC
+   |
+LL |     demo_mut_advanced_unique(&mut 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/stacked_borrows/mut_exclusive_violation2.rs b/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation2.rs
new file mode 100644
index 0000000000000..2305ce746512a
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation2.rs
@@ -0,0 +1,12 @@
+use std::ptr::NonNull;
+
+fn main() {
+    unsafe {
+        let x = &mut 0;
+        let mut ptr1 = NonNull::from(x);
+        let mut ptr2 = ptr1.clone();
+        let raw1 = ptr1.as_mut();
+        let _raw2 = ptr2.as_mut();
+        let _val = *raw1; //~ ERROR: /read access .* tag does not exist in the borrow stack/
+    }
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation2.stderr b/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation2.stderr
new file mode 100644
index 0000000000000..43b5325fc541a
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation2.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/mut_exclusive_violation2.rs:LL:CC
+   |
+LL |         let _val = *raw1;
+   |                    ^^^^^
+   |                    |
+   |                    attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |                    this error occurs as part of an access at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a Unique retag at offsets [0x0..0x4]
+  --> $DIR/mut_exclusive_violation2.rs:LL:CC
+   |
+LL |         let raw1 = ptr1.as_mut();
+   |                    ^^^^^^^^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a Unique retag
+  --> $DIR/mut_exclusive_violation2.rs:LL:CC
+   |
+LL |         let _raw2 = ptr2.as_mut();
+   |                     ^^^^^^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/mut_exclusive_violation2.rs:LL:CC
+
+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/newtype_retagging.rs b/src/tools/miri/tests/fail/stacked_borrows/newtype_retagging.rs
new file mode 100644
index 0000000000000..6e7413cff5d4b
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/newtype_retagging.rs
@@ -0,0 +1,19 @@
+//@compile-flags: -Zmiri-retag-fields
+//@error-pattern: which is protected
+struct Newtype<'a>(&'a mut i32);
+
+fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) {
+    dealloc();
+}
+
+// Make sure that we protect references inside structs.
+fn main() {
+    let ptr = Box::into_raw(Box::new(0i32));
+    #[rustfmt::skip] // I like my newlines
+    unsafe {
+        dealloc_while_running(
+            Newtype(&mut *ptr),
+            || drop(Box::from_raw(ptr)),
+        )
+    };
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/newtype_retagging.stderr b/src/tools/miri/tests/fail/stacked_borrows/newtype_retagging.stderr
new file mode 100644
index 0000000000000..06a9b86c6f45a
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/newtype_retagging.stderr
@@ -0,0 +1,44 @@
+error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is protected because it is an argument of call ID
+  --> RUSTLIB/alloc/src/boxed.rs:LL:CC
+   |
+LL |         Box(unsafe { Unique::new_unchecked(raw) }, alloc)
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is protected because it is an argument of call ID
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadWrite retag at offsets [0x0..0x4]
+  --> $DIR/newtype_retagging.rs:LL:CC
+   |
+LL |     let ptr = Box::into_raw(Box::new(0i32));
+   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: <TAG> is this argument
+  --> $DIR/newtype_retagging.rs:LL:CC
+   |
+LL | fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) {
+   |                          ^^
+   = note: BACKTRACE:
+   = note: inside `std::boxed::Box::<i32>::from_raw_in` at RUSTLIB/alloc/src/boxed.rs:LL:CC
+   = note: inside `std::boxed::Box::<i32>::from_raw` at RUSTLIB/alloc/src/boxed.rs:LL:CC
+note: inside closure at $DIR/newtype_retagging.rs:LL:CC
+  --> $DIR/newtype_retagging.rs:LL:CC
+   |
+LL |             || drop(Box::from_raw(ptr)),
+   |                     ^^^^^^^^^^^^^^^^^^
+note: inside `dealloc_while_running::<[closure@$DIR/newtype_retagging.rs:LL:CC]>` at $DIR/newtype_retagging.rs:LL:CC
+  --> $DIR/newtype_retagging.rs:LL:CC
+   |
+LL |     dealloc();
+   |     ^^^^^^^^^
+note: inside `main` at $DIR/newtype_retagging.rs:LL:CC
+  --> $DIR/newtype_retagging.rs:LL:CC
+   |
+LL | /         dealloc_while_running(
+LL | |             Newtype(&mut *ptr),
+LL | |             || drop(Box::from_raw(ptr)),
+LL | |         )
+   | |_________^
+
+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/outdated_local.rs b/src/tools/miri/tests/fail/stacked_borrows/outdated_local.rs
new file mode 100644
index 0000000000000..4262157d1e3df
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/outdated_local.rs
@@ -0,0 +1,9 @@
+fn main() {
+    let mut x = 0;
+    let y: *const i32 = &x;
+    x = 1; // this invalidates y by reactivating the lowermost uniq borrow for this local
+
+    assert_eq!(unsafe { *y }, 1); //~ ERROR: /read access .* tag does not exist in the borrow stack/
+
+    assert_eq!(x, 1);
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/outdated_local.stderr b/src/tools/miri/tests/fail/stacked_borrows/outdated_local.stderr
new file mode 100644
index 0000000000000..8c2bba5391888
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/outdated_local.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/outdated_local.rs:LL:CC
+   |
+LL |     assert_eq!(unsafe { *y }, 1);
+   |                         ^^
+   |                         |
+   |                         attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |                         this error occurs as part of an access at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadOnly retag at offsets [0x0..0x4]
+  --> $DIR/outdated_local.rs:LL:CC
+   |
+LL |     let y: *const i32 = &x;
+   |                         ^^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a write access
+  --> $DIR/outdated_local.rs:LL:CC
+   |
+LL |     x = 1; // this invalidates y by reactivating the lowermost uniq borrow for this local
+   |     ^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/outdated_local.rs:LL:CC
+
+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/pass_invalid_mut.rs b/src/tools/miri/tests/fail/stacked_borrows/pass_invalid_mut.rs
new file mode 100644
index 0000000000000..8db2c149b17e0
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/pass_invalid_mut.rs
@@ -0,0 +1,10 @@
+// Make sure that we cannot pass by argument a `&mut` that got already invalidated.
+fn foo(_: &mut i32) {}
+
+fn main() {
+    let x = &mut 42;
+    let xraw = x as *mut _;
+    let xref = unsafe { &mut *xraw };
+    let _val = unsafe { *xraw }; // invalidate xref
+    foo(xref); //~ ERROR: /retag .* tag does not exist in the borrow stack/
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/pass_invalid_mut.stderr b/src/tools/miri/tests/fail/stacked_borrows/pass_invalid_mut.stderr
new file mode 100644
index 0000000000000..d7ab930aa3785
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/pass_invalid_mut.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: trying to retag from <TAG> for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/pass_invalid_mut.rs:LL:CC
+   |
+LL |     foo(xref);
+   |         ^^^^
+   |         |
+   |         trying to retag from <TAG> for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |         this error occurs as part of two-phase retag at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a Unique retag at offsets [0x0..0x4]
+  --> $DIR/pass_invalid_mut.rs:LL:CC
+   |
+LL |     let xref = unsafe { &mut *xraw };
+   |                         ^^^^^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a read access
+  --> $DIR/pass_invalid_mut.rs:LL:CC
+   |
+LL |     let _val = unsafe { *xraw }; // invalidate xref
+   |                         ^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/pass_invalid_mut.rs:LL:CC
+
+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/pass_invalid_shr.rs b/src/tools/miri/tests/fail/stacked_borrows/pass_invalid_shr.rs
new file mode 100644
index 0000000000000..903c2733107bb
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/pass_invalid_shr.rs
@@ -0,0 +1,10 @@
+// Make sure that we cannot pass by argument a `&` that got already invalidated.
+fn foo(_: &i32) {}
+
+fn main() {
+    let x = &mut 42;
+    let xraw = x as *mut _;
+    let xref = unsafe { &*xraw };
+    unsafe { *xraw = 42 }; // unfreeze
+    foo(xref); //~ ERROR: /retag .* tag does not exist in the borrow stack/
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/pass_invalid_shr.stderr b/src/tools/miri/tests/fail/stacked_borrows/pass_invalid_shr.stderr
new file mode 100644
index 0000000000000..c14b35c75c83d
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/pass_invalid_shr.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: trying to retag from <TAG> for SharedReadOnly permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/pass_invalid_shr.rs:LL:CC
+   |
+LL |     foo(xref);
+   |         ^^^^
+   |         |
+   |         trying to retag from <TAG> for SharedReadOnly permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |         this error occurs as part of retag at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadOnly retag at offsets [0x0..0x4]
+  --> $DIR/pass_invalid_shr.rs:LL:CC
+   |
+LL |     let xref = unsafe { &*xraw };
+   |                         ^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a write access
+  --> $DIR/pass_invalid_shr.rs:LL:CC
+   |
+LL |     unsafe { *xraw = 42 }; // unfreeze
+   |              ^^^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/pass_invalid_shr.rs:LL:CC
+
+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/pointer_smuggling.rs b/src/tools/miri/tests/fail/stacked_borrows/pointer_smuggling.rs
new file mode 100644
index 0000000000000..e1c3ff4928983
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/pointer_smuggling.rs
@@ -0,0 +1,20 @@
+static mut PTR: *mut u8 = 0 as *mut _;
+
+fn fun1(x: &mut u8) {
+    unsafe {
+        PTR = x;
+    }
+}
+
+fn fun2() {
+    // Now we use a pointer we are not allowed to use
+    let _x = unsafe { *PTR }; //~ ERROR: /read access .* tag does not exist in the borrow stack/
+}
+
+fn main() {
+    let mut val = 0;
+    let val = &mut val;
+    fun1(val);
+    *val = 2; // this invalidates any raw ptrs `fun1` might have created.
+    fun2(); // if they now use a raw ptr they break our reference
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/pointer_smuggling.stderr b/src/tools/miri/tests/fail/stacked_borrows/pointer_smuggling.stderr
new file mode 100644
index 0000000000000..6415af1e18bbf
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/pointer_smuggling.stderr
@@ -0,0 +1,33 @@
+error: Undefined Behavior: attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/pointer_smuggling.rs:LL:CC
+   |
+LL |     let _x = unsafe { *PTR };
+   |                       ^^^^
+   |                       |
+   |                       attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |                       this error occurs as part of an access at ALLOC[0x0..0x1]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadWrite retag at offsets [0x0..0x1]
+  --> $DIR/pointer_smuggling.rs:LL:CC
+   |
+LL |         PTR = x;
+   |               ^
+help: <TAG> was later invalidated at offsets [0x0..0x1] by a write access
+  --> $DIR/pointer_smuggling.rs:LL:CC
+   |
+LL |     *val = 2; // this invalidates any raw ptrs `fun1` might have created.
+   |     ^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `fun2` at $DIR/pointer_smuggling.rs:LL:CC
+note: inside `main` at $DIR/pointer_smuggling.rs:LL:CC
+  --> $DIR/pointer_smuggling.rs:LL:CC
+   |
+LL |     fun2(); // if they now use a raw ptr they break our reference
+   |     ^^^^^^
+
+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/raw_tracking.rs b/src/tools/miri/tests/fail/stacked_borrows/raw_tracking.rs
new file mode 100644
index 0000000000000..15306e0825b22
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/raw_tracking.rs
@@ -0,0 +1,11 @@
+//! This demonstrates a provenance problem that requires tracking of raw pointers to be detected.
+
+fn main() {
+    let mut l = 13;
+    let raw1 = &mut l as *mut _;
+    let raw2 = &mut l as *mut _; // invalidates raw1
+    // Without raw pointer tracking, Stacked Borrows cannot distinguish raw1 and raw2, and thus
+    // fails to realize that raw1 should not be used any more.
+    unsafe { *raw1 = 13 }; //~ ERROR: /write access .* tag does not exist in the borrow stack/
+    unsafe { *raw2 = 13 };
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/raw_tracking.stderr b/src/tools/miri/tests/fail/stacked_borrows/raw_tracking.stderr
new file mode 100644
index 0000000000000..d75934445e6d2
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/raw_tracking.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: attempting a write access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/raw_tracking.rs:LL:CC
+   |
+LL |     unsafe { *raw1 = 13 };
+   |              ^^^^^^^^^^
+   |              |
+   |              attempting a write access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |              this error occurs as part of an access at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadWrite retag at offsets [0x0..0x4]
+  --> $DIR/raw_tracking.rs:LL:CC
+   |
+LL |     let raw1 = &mut l as *mut _;
+   |                ^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a Unique retag
+  --> $DIR/raw_tracking.rs:LL:CC
+   |
+LL |     let raw2 = &mut l as *mut _; // invalidates raw1
+   |                ^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/raw_tracking.rs:LL:CC
+
+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/return_invalid_mut.rs b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut.rs
new file mode 100644
index 0000000000000..7d9a6f11aff97
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut.rs
@@ -0,0 +1,11 @@
+// Make sure that we cannot return a `&mut` that got already invalidated.
+fn foo(x: &mut (i32, i32)) -> &mut i32 {
+    let xraw = x as *mut (i32, i32);
+    let ret = unsafe { &mut (*xraw).1 };
+    let _val = unsafe { *xraw }; // invalidate xref
+    ret //~ ERROR: /retag .* tag does not exist in the borrow stack/
+}
+
+fn main() {
+    foo(&mut (1, 2));
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut.stderr b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut.stderr
new file mode 100644
index 0000000000000..9deb0c41742f3
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut.stderr
@@ -0,0 +1,33 @@
+error: Undefined Behavior: trying to retag from <TAG> for Unique permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location
+  --> $DIR/return_invalid_mut.rs:LL:CC
+   |
+LL |     ret
+   |     ^^^
+   |     |
+   |     trying to retag from <TAG> for Unique permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location
+   |     this error occurs as part of retag at ALLOC[0x4..0x8]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a Unique retag at offsets [0x4..0x8]
+  --> $DIR/return_invalid_mut.rs:LL:CC
+   |
+LL |     let ret = unsafe { &mut (*xraw).1 };
+   |                        ^^^^^^^^^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x8] by a read access
+  --> $DIR/return_invalid_mut.rs:LL:CC
+   |
+LL |     let _val = unsafe { *xraw }; // invalidate xref
+   |                         ^^^^^
+   = note: BACKTRACE:
+   = note: inside `foo` at $DIR/return_invalid_mut.rs:LL:CC
+note: inside `main` at $DIR/return_invalid_mut.rs:LL:CC
+  --> $DIR/return_invalid_mut.rs:LL:CC
+   |
+LL |     foo(&mut (1, 2));
+   |     ^^^^^^^^^^^^^^^^
+
+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/return_invalid_mut_option.rs b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_option.rs
new file mode 100644
index 0000000000000..7fa9cf77d44b2
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_option.rs
@@ -0,0 +1,16 @@
+// Make sure that we cannot return a `&mut` that got already invalidated, not even in an `Option`.
+// Due to shallow reborrowing, the error only surfaces when we look into the `Option`.
+fn foo(x: &mut (i32, i32)) -> Option<&mut i32> {
+    let xraw = x as *mut (i32, i32);
+    let ret = unsafe { &mut (*xraw).1 }; // let-bind to avoid 2phase
+    let ret = Some(ret);
+    let _val = unsafe { *xraw }; // invalidate xref
+    ret
+}
+
+fn main() {
+    match foo(&mut (1, 2)) {
+        Some(_x) => {} //~ ERROR: /retag .* tag does not exist in the borrow stack/
+        None => {}
+    }
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_option.stderr b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_option.stderr
new file mode 100644
index 0000000000000..1068c286c62fa
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_option.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: trying to retag from <TAG> for Unique permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location
+  --> $DIR/return_invalid_mut_option.rs:LL:CC
+   |
+LL |         Some(_x) => {}
+   |              ^^
+   |              |
+   |              trying to retag from <TAG> for Unique permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location
+   |              this error occurs as part of retag at ALLOC[0x4..0x8]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a Unique retag at offsets [0x4..0x8]
+  --> $DIR/return_invalid_mut_option.rs:LL:CC
+   |
+LL |     let ret = Some(ret);
+   |                    ^^^
+help: <TAG> was later invalidated at offsets [0x0..0x8] by a read access
+  --> $DIR/return_invalid_mut_option.rs:LL:CC
+   |
+LL |     let _val = unsafe { *xraw }; // invalidate xref
+   |                         ^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/return_invalid_mut_option.rs:LL:CC
+
+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/return_invalid_mut_tuple.rs b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_tuple.rs
new file mode 100644
index 0000000000000..c94fef90542fd
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_tuple.rs
@@ -0,0 +1,12 @@
+// Make sure that we cannot return a `&mut` that got already invalidated, not even in a tuple.
+// Due to shallow reborrowing, the error only surfaces when we look into the tuple.
+fn foo(x: &mut (i32, i32)) -> (&mut i32,) {
+    let xraw = x as *mut (i32, i32);
+    let ret = (unsafe { &mut (*xraw).1 },);
+    let _val = unsafe { *xraw }; // invalidate xref
+    ret
+}
+
+fn main() {
+    foo(&mut (1, 2)).0; //~ ERROR: /retag .* tag does not exist in the borrow stack/
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_tuple.stderr b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_tuple.stderr
new file mode 100644
index 0000000000000..79de9b668cf2b
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_tuple.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: trying to retag from <TAG> for Unique permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location
+  --> $DIR/return_invalid_mut_tuple.rs:LL:CC
+   |
+LL |     foo(&mut (1, 2)).0;
+   |     ^^^^^^^^^^^^^^^^^^
+   |     |
+   |     trying to retag from <TAG> for Unique permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location
+   |     this error occurs as part of retag at ALLOC[0x4..0x8]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a Unique retag at offsets [0x4..0x8]
+  --> $DIR/return_invalid_mut_tuple.rs:LL:CC
+   |
+LL |     let ret = (unsafe { &mut (*xraw).1 },);
+   |                         ^^^^^^^^^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x8] by a read access
+  --> $DIR/return_invalid_mut_tuple.rs:LL:CC
+   |
+LL |     let _val = unsafe { *xraw }; // invalidate xref
+   |                         ^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/return_invalid_mut_tuple.rs:LL:CC
+
+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/return_invalid_shr.rs b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr.rs
new file mode 100644
index 0000000000000..45526498dadf5
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr.rs
@@ -0,0 +1,11 @@
+// Make sure that we cannot return a `&` that got already invalidated.
+fn foo(x: &mut (i32, i32)) -> &i32 {
+    let xraw = x as *mut (i32, i32);
+    let ret = unsafe { &(*xraw).1 };
+    unsafe { *xraw = (42, 23) }; // unfreeze
+    ret //~ ERROR: /retag .* tag does not exist in the borrow stack/
+}
+
+fn main() {
+    foo(&mut (1, 2));
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr.stderr b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr.stderr
new file mode 100644
index 0000000000000..dd651517c2fb0
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr.stderr
@@ -0,0 +1,33 @@
+error: Undefined Behavior: trying to retag from <TAG> for SharedReadOnly permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location
+  --> $DIR/return_invalid_shr.rs:LL:CC
+   |
+LL |     ret
+   |     ^^^
+   |     |
+   |     trying to retag from <TAG> for SharedReadOnly permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location
+   |     this error occurs as part of retag at ALLOC[0x4..0x8]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadOnly retag at offsets [0x4..0x8]
+  --> $DIR/return_invalid_shr.rs:LL:CC
+   |
+LL |     let ret = unsafe { &(*xraw).1 };
+   |                        ^^^^^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x8] by a write access
+  --> $DIR/return_invalid_shr.rs:LL:CC
+   |
+LL |     unsafe { *xraw = (42, 23) }; // unfreeze
+   |              ^^^^^^^^^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `foo` at $DIR/return_invalid_shr.rs:LL:CC
+note: inside `main` at $DIR/return_invalid_shr.rs:LL:CC
+  --> $DIR/return_invalid_shr.rs:LL:CC
+   |
+LL |     foo(&mut (1, 2));
+   |     ^^^^^^^^^^^^^^^^
+
+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/return_invalid_shr_option.rs b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_option.rs
new file mode 100644
index 0000000000000..3a028ceed86ae
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_option.rs
@@ -0,0 +1,15 @@
+// Make sure that we cannot return a `&` that got already invalidated, not even in an `Option`.
+// Due to shallow reborrowing, the error only surfaces when we look into the `Option`.
+fn foo(x: &mut (i32, i32)) -> Option<&i32> {
+    let xraw = x as *mut (i32, i32);
+    let ret = Some(unsafe { &(*xraw).1 });
+    unsafe { *xraw = (42, 23) }; // unfreeze
+    ret
+}
+
+fn main() {
+    match foo(&mut (1, 2)) {
+        Some(_x) => {} //~ ERROR: /retag .* tag does not exist in the borrow stack/
+        None => {}
+    }
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_option.stderr b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_option.stderr
new file mode 100644
index 0000000000000..f45456305db29
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_option.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: trying to retag from <TAG> for SharedReadOnly permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location
+  --> $DIR/return_invalid_shr_option.rs:LL:CC
+   |
+LL |         Some(_x) => {}
+   |              ^^
+   |              |
+   |              trying to retag from <TAG> for SharedReadOnly permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location
+   |              this error occurs as part of retag at ALLOC[0x4..0x8]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadOnly retag at offsets [0x4..0x8]
+  --> $DIR/return_invalid_shr_option.rs:LL:CC
+   |
+LL |     let ret = Some(unsafe { &(*xraw).1 });
+   |                             ^^^^^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x8] by a write access
+  --> $DIR/return_invalid_shr_option.rs:LL:CC
+   |
+LL |     unsafe { *xraw = (42, 23) }; // unfreeze
+   |              ^^^^^^^^^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/return_invalid_shr_option.rs:LL:CC
+
+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/return_invalid_shr_tuple.rs b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_tuple.rs
new file mode 100644
index 0000000000000..e4536626dbf2c
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_tuple.rs
@@ -0,0 +1,12 @@
+// Make sure that we cannot return a `&` that got already invalidated, not even in a tuple.
+// Due to shallow reborrowing, the error only surfaces when we look into the tuple.
+fn foo(x: &mut (i32, i32)) -> (&i32,) {
+    let xraw = x as *mut (i32, i32);
+    let ret = (unsafe { &(*xraw).1 },);
+    unsafe { *xraw = (42, 23) }; // unfreeze
+    ret
+}
+
+fn main() {
+    foo(&mut (1, 2)).0; //~ ERROR: /retag .* tag does not exist in the borrow stack/
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_tuple.stderr b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_tuple.stderr
new file mode 100644
index 0000000000000..2e41f505bb9d2
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_tuple.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: trying to retag from <TAG> for SharedReadOnly permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location
+  --> $DIR/return_invalid_shr_tuple.rs:LL:CC
+   |
+LL |     foo(&mut (1, 2)).0;
+   |     ^^^^^^^^^^^^^^^^^^
+   |     |
+   |     trying to retag from <TAG> for SharedReadOnly permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location
+   |     this error occurs as part of retag at ALLOC[0x4..0x8]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadOnly retag at offsets [0x4..0x8]
+  --> $DIR/return_invalid_shr_tuple.rs:LL:CC
+   |
+LL |     let ret = (unsafe { &(*xraw).1 },);
+   |                         ^^^^^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x8] by a write access
+  --> $DIR/return_invalid_shr_tuple.rs:LL:CC
+   |
+LL |     unsafe { *xraw = (42, 23) }; // unfreeze
+   |              ^^^^^^^^^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/return_invalid_shr_tuple.rs:LL:CC
+
+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/shared_rw_borrows_are_weak1.rs b/src/tools/miri/tests/fail/stacked_borrows/shared_rw_borrows_are_weak1.rs
new file mode 100644
index 0000000000000..2450ec4b4a1d0
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/shared_rw_borrows_are_weak1.rs
@@ -0,0 +1,16 @@
+// We want to test that granting a SharedReadWrite will be added
+// *below* an already granted Unique -- so writing to
+// the SharedReadWrite will invalidate the Unique.
+
+use std::cell::Cell;
+use std::mem;
+
+fn main() {
+    unsafe {
+        let x = &mut Cell::new(0);
+        let y: &mut Cell<i32> = mem::transmute(&mut *x); // launder lifetime
+        let shr_rw = &*x; // thanks to interior mutability this will be a SharedReadWrite
+        shr_rw.set(1);
+        y.get_mut(); //~ ERROR: /retag .* tag does not exist in the borrow stack/
+    }
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/shared_rw_borrows_are_weak1.stderr b/src/tools/miri/tests/fail/stacked_borrows/shared_rw_borrows_are_weak1.stderr
new file mode 100644
index 0000000000000..3a139c3ab2120
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/shared_rw_borrows_are_weak1.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: trying to retag from <TAG> for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/shared_rw_borrows_are_weak1.rs:LL:CC
+   |
+LL |         y.get_mut();
+   |         ^^^^^^^^^^^
+   |         |
+   |         trying to retag from <TAG> for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |         this error occurs as part of two-phase retag at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a Unique retag at offsets [0x0..0x4]
+  --> $DIR/shared_rw_borrows_are_weak1.rs:LL:CC
+   |
+LL |         let y: &mut Cell<i32> = mem::transmute(&mut *x); // launder lifetime
+   |                                 ^^^^^^^^^^^^^^^^^^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a Unique retag
+  --> $DIR/shared_rw_borrows_are_weak1.rs:LL:CC
+   |
+LL |         shr_rw.set(1);
+   |         ^^^^^^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/shared_rw_borrows_are_weak1.rs:LL:CC
+
+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/shared_rw_borrows_are_weak2.rs b/src/tools/miri/tests/fail/stacked_borrows/shared_rw_borrows_are_weak2.rs
new file mode 100644
index 0000000000000..012e9561ca848
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/shared_rw_borrows_are_weak2.rs
@@ -0,0 +1,17 @@
+// We want to test that granting a SharedReadWrite will be added
+// *below* an already granted SharedReadWrite -- so writing to
+// the SharedReadWrite will invalidate the SharedReadWrite.
+//@normalize-stderr-test: "0x[0-9a-fA-F]+" -> "$$HEX"
+
+use std::cell::RefCell;
+use std::mem;
+
+fn main() {
+    unsafe {
+        let x = &mut RefCell::new(0);
+        let y: &i32 = mem::transmute(&*x.borrow()); // launder lifetime
+        let shr_rw = &*x; // thanks to interior mutability this will be a SharedReadWrite
+        shr_rw.replace(1);
+        let _val = *y; //~ ERROR: /read access .* tag does not exist in the borrow stack/
+    }
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/shared_rw_borrows_are_weak2.stderr b/src/tools/miri/tests/fail/stacked_borrows/shared_rw_borrows_are_weak2.stderr
new file mode 100644
index 0000000000000..0609a73e79315
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/shared_rw_borrows_are_weak2.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: attempting a read access using <TAG> at ALLOC[$HEX], but that tag does not exist in the borrow stack for this location
+  --> $DIR/shared_rw_borrows_are_weak2.rs:LL:CC
+   |
+LL |         let _val = *y;
+   |                    ^^
+   |                    |
+   |                    attempting a read access using <TAG> at ALLOC[$HEX], but that tag does not exist in the borrow stack for this location
+   |                    this error occurs as part of an access at ALLOC[$HEX..$HEX]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadOnly retag at offsets [$HEX..$HEX]
+  --> $DIR/shared_rw_borrows_are_weak2.rs:LL:CC
+   |
+LL |         let y: &i32 = mem::transmute(&*x.borrow()); // launder lifetime
+   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: <TAG> was later invalidated at offsets [$HEX..$HEX] by a Unique retag
+  --> $DIR/shared_rw_borrows_are_weak2.rs:LL:CC
+   |
+LL |         shr_rw.replace(1);
+   |         ^^^^^^^^^^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/shared_rw_borrows_are_weak2.rs:LL:CC
+
+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/shr_frozen_violation1.rs b/src/tools/miri/tests/fail/stacked_borrows/shr_frozen_violation1.rs
new file mode 100644
index 0000000000000..d1322dc6e57bb
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/shr_frozen_violation1.rs
@@ -0,0 +1,15 @@
+fn foo(x: &mut i32) -> i32 {
+    *x = 5;
+    unknown_code(&*x);
+    *x // must return 5
+}
+
+fn main() {
+    println!("{}", foo(&mut 0));
+}
+
+fn unknown_code(x: &i32) {
+    unsafe {
+        *(x as *const i32 as *mut i32) = 7; //~ ERROR: /write access .* only grants SharedReadOnly permission/
+    }
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/shr_frozen_violation1.stderr b/src/tools/miri/tests/fail/stacked_borrows/shr_frozen_violation1.stderr
new file mode 100644
index 0000000000000..0818d07da48e5
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/shr_frozen_violation1.stderr
@@ -0,0 +1,33 @@
+error: Undefined Behavior: attempting a write access using <TAG> at ALLOC[0x0], but that tag only grants SharedReadOnly permission for this location
+  --> $DIR/shr_frozen_violation1.rs:LL:CC
+   |
+LL |         *(x as *const i32 as *mut i32) = 7;
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |         |
+   |         attempting a write access using <TAG> at ALLOC[0x0], but that tag only grants SharedReadOnly permission for this location
+   |         this error occurs as part of an access at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadOnly retag at offsets [0x0..0x4]
+  --> $DIR/shr_frozen_violation1.rs:LL:CC
+   |
+LL |         *(x as *const i32 as *mut i32) = 7;
+   |           ^
+   = note: BACKTRACE:
+   = note: inside `unknown_code` at $DIR/shr_frozen_violation1.rs:LL:CC
+note: inside `foo` at $DIR/shr_frozen_violation1.rs:LL:CC
+  --> $DIR/shr_frozen_violation1.rs:LL:CC
+   |
+LL |     unknown_code(&*x);
+   |     ^^^^^^^^^^^^^^^^^
+note: inside `main` at $DIR/shr_frozen_violation1.rs:LL:CC
+  --> $DIR/shr_frozen_violation1.rs:LL:CC
+   |
+LL |     println!("{}", foo(&mut 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/stacked_borrows/static_memory_modification.rs b/src/tools/miri/tests/fail/stacked_borrows/static_memory_modification.rs
new file mode 100644
index 0000000000000..84d7878b264e5
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/static_memory_modification.rs
@@ -0,0 +1,8 @@
+static X: usize = 5;
+
+#[allow(mutable_transmutes)]
+fn main() {
+    let _x = unsafe {
+        std::mem::transmute::<&usize, &mut usize>(&X) //~ ERROR: writing to alloc1 which is read-only
+    };
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/static_memory_modification.stderr b/src/tools/miri/tests/fail/stacked_borrows/static_memory_modification.stderr
new file mode 100644
index 0000000000000..ca99a8262b8bd
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/static_memory_modification.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: writing to ALLOC which is read-only
+  --> $DIR/static_memory_modification.rs:LL:CC
+   |
+LL |         std::mem::transmute::<&usize, &mut usize>(&X)
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ writing to ALLOC which is read-only
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/static_memory_modification.rs:LL:CC
+
+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/track_caller.rs b/src/tools/miri/tests/fail/stacked_borrows/track_caller.rs
new file mode 100644
index 0000000000000..3455eb4684ea2
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/track_caller.rs
@@ -0,0 +1,17 @@
+// This is a copy of illegal_read1.rs, but with #[track_caller] on the test.
+// This test only checks that our diagnostics do not display the contents of callee.
+
+#[rustfmt::skip] // rustfmt bug: https://github.com/rust-lang/rustfmt/issues/5391
+fn main() {
+    let mut x = 15;
+    let xraw = &mut x as *mut _;
+    let xref = unsafe { &mut *xraw }; // derived from raw, so using raw is still ok...
+    callee(xraw);
+    let _val = *xref; // ...but any use of raw will invalidate our ref.
+    //~^ ERROR: /read access .* tag does not exist in the borrow stack/
+}
+
+#[track_caller]
+fn callee(xraw: *mut i32) {
+    let _val = unsafe { *xraw };
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/track_caller.stderr b/src/tools/miri/tests/fail/stacked_borrows/track_caller.stderr
new file mode 100644
index 0000000000000..6f1d0ccd348ec
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/track_caller.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/track_caller.rs:LL:CC
+   |
+LL |     let _val = *xref; // ...but any use of raw will invalidate our ref.
+   |                ^^^^^
+   |                |
+   |                attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |                this error occurs as part of an access at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a Unique retag at offsets [0x0..0x4]
+  --> $DIR/track_caller.rs:LL:CC
+   |
+LL |     let xref = unsafe { &mut *xraw }; // derived from raw, so using raw is still ok...
+   |                         ^^^^^^^^^^
+help: <TAG> was later invalidated at offsets [0x0..0x4] by a read access
+  --> $DIR/track_caller.rs:LL:CC
+   |
+LL |     callee(xraw);
+   |     ^^^^^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/track_caller.rs:LL:CC
+
+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/transmute-is-no-escape.rs b/src/tools/miri/tests/fail/stacked_borrows/transmute-is-no-escape.rs
new file mode 100644
index 0000000000000..233b927dfc7ee
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/transmute-is-no-escape.rs
@@ -0,0 +1,14 @@
+// Make sure we cannot use raw ptrs that got transmuted from mutable references
+// (i.e, no EscapeToRaw happened).
+// We could, in principle, do EscapeToRaw lazily to allow this code, but that
+// would no alleviate the need for EscapeToRaw (see `ref_raw_int_raw` in
+// `run-pass/stacked-borrows.rs`), and thus increase overall complexity.
+use std::mem;
+
+fn main() {
+    let mut x: [i32; 2] = [42, 43];
+    let _raw: *mut i32 = unsafe { mem::transmute(&mut x[0]) };
+    // `raw` still carries a tag, so we get another pointer to the same location that does not carry a tag
+    let raw = (&mut x[1] as *mut i32).wrapping_offset(-1);
+    unsafe { *raw = 13 }; //~ ERROR: /write access .* tag does not exist in the borrow stack/
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/transmute-is-no-escape.stderr b/src/tools/miri/tests/fail/stacked_borrows/transmute-is-no-escape.stderr
new file mode 100644
index 0000000000000..a2ecb07fd3117
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/transmute-is-no-escape.stderr
@@ -0,0 +1,23 @@
+error: Undefined Behavior: attempting a write access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+  --> $DIR/transmute-is-no-escape.rs:LL:CC
+   |
+LL |     unsafe { *raw = 13 };
+   |              ^^^^^^^^^
+   |              |
+   |              attempting a write access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
+   |              this error occurs as part of an access at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadWrite retag at offsets [0x4..0x8]
+  --> $DIR/transmute-is-no-escape.rs:LL:CC
+   |
+LL |     let raw = (&mut x[1] as *mut i32).wrapping_offset(-1);
+   |                ^^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/transmute-is-no-escape.rs:LL:CC
+
+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/unescaped_local.rs b/src/tools/miri/tests/fail/stacked_borrows/unescaped_local.rs
new file mode 100644
index 0000000000000..49c0e66d85701
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/unescaped_local.rs
@@ -0,0 +1,12 @@
+//@compile-flags: -Zmiri-permissive-provenance
+
+// Make sure we cannot use raw ptrs to access a local that
+// we took the direct address of.
+fn main() {
+    let mut x = 42;
+    let raw = &mut x as *mut i32 as usize as *mut i32;
+    let _ptr = &mut x;
+    unsafe {
+        *raw = 13; //~ ERROR: /write access .* no exposed tags/
+    }
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/unescaped_local.stderr b/src/tools/miri/tests/fail/stacked_borrows/unescaped_local.stderr
new file mode 100644
index 0000000000000..4deafa890005b
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/unescaped_local.stderr
@@ -0,0 +1,18 @@
+error: Undefined Behavior: attempting a write access using <wildcard> at ALLOC[0x0], but no exposed tags have suitable permission in the borrow stack for this location
+  --> $DIR/unescaped_local.rs:LL:CC
+   |
+LL |         *raw = 13;
+   |         ^^^^^^^^^
+   |         |
+   |         attempting a write access using <wildcard> at ALLOC[0x0], but no exposed tags have suitable permission in the borrow stack for this location
+   |         this error occurs as part of an access at ALLOC[0x0..0x4]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/unescaped_local.rs:LL:CC
+
+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/unescaped_static.rs b/src/tools/miri/tests/fail/stacked_borrows/unescaped_static.rs
new file mode 100644
index 0000000000000..a25d9b5162ec8
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/unescaped_static.rs
@@ -0,0 +1,7 @@
+static ARRAY: [u8; 2] = [0, 1];
+
+fn main() {
+    let ptr_to_first = &ARRAY[0] as *const u8;
+    // Illegally use this to access the 2nd element.
+    let _val = unsafe { *ptr_to_first.add(1) }; //~ ERROR: /read access .* tag does not exist in the borrow stack/
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/unescaped_static.stderr b/src/tools/miri/tests/fail/stacked_borrows/unescaped_static.stderr
new file mode 100644
index 0000000000000..01a4bf4340c78
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/unescaped_static.stderr
@@ -0,0 +1,23 @@
+error: Undefined Behavior: attempting a read access using <TAG> at ALLOC[0x1], but that tag does not exist in the borrow stack for this location
+  --> $DIR/unescaped_static.rs:LL:CC
+   |
+LL |     let _val = unsafe { *ptr_to_first.add(1) };
+   |                         ^^^^^^^^^^^^^^^^^^^^
+   |                         |
+   |                         attempting a read access using <TAG> at ALLOC[0x1], but that tag does not exist in the borrow stack for this location
+   |                         this error occurs as part of an access at ALLOC[0x1..0x2]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> was created by a SharedReadOnly retag at offsets [0x0..0x1]
+  --> $DIR/unescaped_static.rs:LL:CC
+   |
+LL |     let ptr_to_first = &ARRAY[0] as *const u8;
+   |                        ^^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/unescaped_static.rs:LL:CC
+
+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/zst_slice.rs b/src/tools/miri/tests/fail/stacked_borrows/zst_slice.rs
new file mode 100644
index 0000000000000..77daa9c9811ae
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/zst_slice.rs
@@ -0,0 +1,11 @@
+//@compile-flags: -Zmiri-strict-provenance
+//@error-pattern: /retag .* tag does not exist in the borrow stack/
+
+fn main() {
+    unsafe {
+        let a = [1, 2, 3];
+        let s = &a[0..0];
+        assert_eq!(s.len(), 0);
+        assert_eq!(*s.get_unchecked(1), 2);
+    }
+}
diff --git a/src/tools/miri/tests/fail/stacked_borrows/zst_slice.stderr b/src/tools/miri/tests/fail/stacked_borrows/zst_slice.stderr
new file mode 100644
index 0000000000000..86f1da1f70a33
--- /dev/null
+++ b/src/tools/miri/tests/fail/stacked_borrows/zst_slice.stderr
@@ -0,0 +1,28 @@
+error: Undefined Behavior: trying to retag from <TAG> for SharedReadOnly permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location
+  --> RUSTLIB/core/src/slice/mod.rs:LL:CC
+   |
+LL |         unsafe { &*index.get_unchecked(self) }
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                  |
+   |                  trying to retag from <TAG> for SharedReadOnly permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location
+   |                  this error occurs as part of retag at ALLOC[0x4..0x8]
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
+   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
+help: <TAG> would have been created here, but this is a zero-size retag ([0x0..0x0]) so the tag in question does not exist anywhere
+  --> $DIR/zst_slice.rs:LL:CC
+   |
+LL |         assert_eq!(*s.get_unchecked(1), 2);
+   |                     ^^^^^^^^^^^^^^^^^^
+   = note: BACKTRACE:
+   = note: inside `core::slice::<impl [i32]>::get_unchecked::<usize>` at RUSTLIB/core/src/slice/mod.rs:LL:CC
+note: inside `main` at $DIR/zst_slice.rs:LL:CC
+  --> $DIR/zst_slice.rs:LL:CC
+   |
+LL |         assert_eq!(*s.get_unchecked(1), 2);
+   |                     ^^^^^^^^^^^^^^^^^^
+
+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/static_memory_modification1.rs b/src/tools/miri/tests/fail/static_memory_modification1.rs
new file mode 100644
index 0000000000000..66794e7535a45
--- /dev/null
+++ b/src/tools/miri/tests/fail/static_memory_modification1.rs
@@ -0,0 +1,12 @@
+// Stacked Borrows detects that we are casting & to &mut and so it changes why we fail
+//@compile-flags: -Zmiri-disable-stacked-borrows
+
+static X: usize = 5;
+
+#[allow(mutable_transmutes)]
+fn main() {
+    unsafe {
+        *std::mem::transmute::<&usize, &mut usize>(&X) = 6; //~ ERROR: read-only
+        assert_eq!(X, 6);
+    }
+}
diff --git a/src/tools/miri/tests/fail/static_memory_modification1.stderr b/src/tools/miri/tests/fail/static_memory_modification1.stderr
new file mode 100644
index 0000000000000..5e7213ee6088e
--- /dev/null
+++ b/src/tools/miri/tests/fail/static_memory_modification1.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: writing to ALLOC which is read-only
+  --> $DIR/static_memory_modification1.rs:LL:CC
+   |
+LL |         *std::mem::transmute::<&usize, &mut usize>(&X) = 6;
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ writing to ALLOC which is read-only
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/static_memory_modification1.rs:LL:CC
+
+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/static_memory_modification2.rs b/src/tools/miri/tests/fail/static_memory_modification2.rs
new file mode 100644
index 0000000000000..d8ae3a57c51e3
--- /dev/null
+++ b/src/tools/miri/tests/fail/static_memory_modification2.rs
@@ -0,0 +1,12 @@
+// Stacked Borrows detects that we are casting & to &mut and so it changes why we fail
+//@compile-flags: -Zmiri-disable-stacked-borrows
+
+use std::mem::transmute;
+
+#[allow(mutable_transmutes)]
+fn main() {
+    unsafe {
+        let s = "this is a test";
+        transmute::<&[u8], &mut [u8]>(s.as_bytes())[4] = 42; //~ ERROR: read-only
+    }
+}
diff --git a/src/tools/miri/tests/fail/static_memory_modification2.stderr b/src/tools/miri/tests/fail/static_memory_modification2.stderr
new file mode 100644
index 0000000000000..4c160cd320688
--- /dev/null
+++ b/src/tools/miri/tests/fail/static_memory_modification2.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: writing to ALLOC which is read-only
+  --> $DIR/static_memory_modification2.rs:LL:CC
+   |
+LL |         transmute::<&[u8], &mut [u8]>(s.as_bytes())[4] = 42;
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ writing to ALLOC which is read-only
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/static_memory_modification2.rs:LL:CC
+
+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/static_memory_modification3.rs b/src/tools/miri/tests/fail/static_memory_modification3.rs
new file mode 100644
index 0000000000000..b8e2c6470ff20
--- /dev/null
+++ b/src/tools/miri/tests/fail/static_memory_modification3.rs
@@ -0,0 +1,12 @@
+// Stacked Borrows detects that we are casting & to &mut and so it changes why we fail
+//@compile-flags: -Zmiri-disable-stacked-borrows
+
+use std::mem::transmute;
+
+#[allow(mutable_transmutes)]
+fn main() {
+    unsafe {
+        let bs = b"this is a test";
+        transmute::<&[u8], &mut [u8]>(bs)[4] = 42; //~ ERROR: read-only
+    }
+}
diff --git a/src/tools/miri/tests/fail/static_memory_modification3.stderr b/src/tools/miri/tests/fail/static_memory_modification3.stderr
new file mode 100644
index 0000000000000..1986059c50a8e
--- /dev/null
+++ b/src/tools/miri/tests/fail/static_memory_modification3.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: writing to ALLOC which is read-only
+  --> $DIR/static_memory_modification3.rs:LL:CC
+   |
+LL |         transmute::<&[u8], &mut [u8]>(bs)[4] = 42;
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ writing to ALLOC which is read-only
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/static_memory_modification3.rs:LL:CC
+
+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/transmute-pair-uninit.rs b/src/tools/miri/tests/fail/transmute-pair-uninit.rs
new file mode 100644
index 0000000000000..bc95f3cb7ad3a
--- /dev/null
+++ b/src/tools/miri/tests/fail/transmute-pair-uninit.rs
@@ -0,0 +1,24 @@
+#![feature(core_intrinsics)]
+
+use std::mem;
+
+fn main() {
+    let x: Option<Box<[u8]>> = unsafe {
+        let z = std::intrinsics::add_with_overflow(0usize, 0usize);
+        std::mem::transmute::<(usize, bool), Option<Box<[u8]>>>(z)
+    };
+    let y = &x;
+    // Now read this bytewise. There should be (`ptr_size + 1`) def bytes followed by
+    // (`ptr_size - 1`) undef bytes (the padding after the bool) in there.
+    let z: *const u8 = y as *const _ as *const _;
+    let first_undef = mem::size_of::<usize>() as isize + 1;
+    for i in 0..first_undef {
+        let byte = unsafe { *z.offset(i) };
+        assert_eq!(byte, 0);
+    }
+    let v = unsafe { *z.offset(first_undef) };
+    //~^ ERROR: uninitialized
+    if v == 0 {
+        println!("it is zero");
+    }
+}
diff --git a/src/tools/miri/tests/fail/transmute-pair-uninit.stderr b/src/tools/miri/tests/fail/transmute-pair-uninit.stderr
new file mode 100644
index 0000000000000..642bf0a713436
--- /dev/null
+++ b/src/tools/miri/tests/fail/transmute-pair-uninit.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
+  --> $DIR/transmute-pair-uninit.rs:LL:CC
+   |
+LL |     let v = unsafe { *z.offset(first_undef) };
+   |                      ^^^^^^^^^^^^^^^^^^^^^^ using uninitialized data, but this operation requires initialized memory
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/transmute-pair-uninit.rs:LL:CC
+
+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/type-too-large.rs b/src/tools/miri/tests/fail/type-too-large.rs
new file mode 100644
index 0000000000000..21b272f8ec398
--- /dev/null
+++ b/src/tools/miri/tests/fail/type-too-large.rs
@@ -0,0 +1,6 @@
+//@ignore-32bit
+
+fn main() {
+    let _fat: [u8; (1 << 61) + (1 << 31)];
+    _fat = [0; (1u64 << 61) as usize + (1u64 << 31) as usize]; //~ ERROR: post-monomorphization error
+}
diff --git a/src/tools/miri/tests/fail/type-too-large.stderr b/src/tools/miri/tests/fail/type-too-large.stderr
new file mode 100644
index 0000000000000..cb1d725ec878c
--- /dev/null
+++ b/src/tools/miri/tests/fail/type-too-large.stderr
@@ -0,0 +1,12 @@
+error: post-monomorphization error: values of the type `[u8; 2305843011361177600]` are too big for the current architecture
+  --> $DIR/type-too-large.rs:LL:CC
+   |
+LL |     _fat = [0; (1u64 << 61) as usize + (1u64 << 31) as usize];
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ values of the type `[u8; 2305843011361177600]` are too big for the current architecture
+   |
+   = note: inside `main` at $DIR/type-too-large.rs:LL:CC
+
+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/unaligned_pointers/alignment.rs b/src/tools/miri/tests/fail/unaligned_pointers/alignment.rs
new file mode 100644
index 0000000000000..438e74e5b8d52
--- /dev/null
+++ b/src/tools/miri/tests/fail/unaligned_pointers/alignment.rs
@@ -0,0 +1,14 @@
+//@normalize-stderr-test: "\| +\^+" -> "| ^"
+
+fn main() {
+    // No retry needed, this fails reliably.
+
+    let mut x = [0u8; 20];
+    let x_ptr: *mut u8 = x.as_mut_ptr();
+    #[rustfmt::skip]
+    unsafe {
+        // At least one of these is definitely unaligned.
+        *(x_ptr as *mut u32) = 42; *(x_ptr.add(1) as *mut u32) = 42;
+        //~^ ERROR: but alignment 4 is required
+    };
+}
diff --git a/src/tools/miri/tests/fail/unaligned_pointers/alignment.stderr b/src/tools/miri/tests/fail/unaligned_pointers/alignment.stderr
new file mode 100644
index 0000000000000..bbebe3b89fd7e
--- /dev/null
+++ b/src/tools/miri/tests/fail/unaligned_pointers/alignment.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: accessing memory with alignment ALIGN, but alignment ALIGN is required
+  --> $DIR/alignment.rs:LL:CC
+   |
+LL |         *(x_ptr as *mut u32) = 42; *(x_ptr.add(1) as *mut u32) = 42;
+   | ^ accessing memory with alignment ALIGN, but alignment ALIGN is required
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/alignment.rs:LL:CC
+
+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/unaligned_pointers/atomic_unaligned.rs b/src/tools/miri/tests/fail/unaligned_pointers/atomic_unaligned.rs
new file mode 100644
index 0000000000000..9dd652fd8217a
--- /dev/null
+++ b/src/tools/miri/tests/fail/unaligned_pointers/atomic_unaligned.rs
@@ -0,0 +1,13 @@
+//@compile-flags: -Zmiri-symbolic-alignment-check
+#![feature(core_intrinsics)]
+
+fn main() {
+    // Do a 4-aligned u64 atomic access. That should be UB on all platforms,
+    // even if u64 only has alignment 4.
+    let z = [0u32; 2];
+    let zptr = &z as *const _ as *const u64;
+    unsafe {
+        ::std::intrinsics::atomic_load_seqcst(zptr);
+        //~^ERROR: accessing memory with alignment 4, but alignment 8 is required
+    }
+}
diff --git a/src/tools/miri/tests/fail/unaligned_pointers/atomic_unaligned.stderr b/src/tools/miri/tests/fail/unaligned_pointers/atomic_unaligned.stderr
new file mode 100644
index 0000000000000..8c3aa3429af5f
--- /dev/null
+++ b/src/tools/miri/tests/fail/unaligned_pointers/atomic_unaligned.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: accessing memory with alignment ALIGN, but alignment ALIGN is required
+  --> $DIR/atomic_unaligned.rs:LL:CC
+   |
+LL |         ::std::intrinsics::atomic_load_seqcst(zptr);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ accessing memory with alignment ALIGN, but alignment ALIGN is required
+   |
+   = help: this usually indicates that your program performed an invalid operation and caused Undefined Behavior
+   = help: but due to `-Zmiri-symbolic-alignment-check`, alignment errors can also be false positives
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/atomic_unaligned.rs:LL:CC
+
+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/unaligned_pointers/dyn_alignment.rs b/src/tools/miri/tests/fail/unaligned_pointers/dyn_alignment.rs
new file mode 100644
index 0000000000000..b943c7db7ccd6
--- /dev/null
+++ b/src/tools/miri/tests/fail/unaligned_pointers/dyn_alignment.rs
@@ -0,0 +1,24 @@
+// should find the bug even without validation and stacked borrows, but gets masked by optimizations
+//@compile-flags: -Zmiri-disable-validation -Zmiri-disable-stacked-borrows -Zmir-opt-level=0
+
+#[repr(align(256))]
+#[derive(Debug)]
+struct MuchAlign;
+
+fn main() {
+    // Try many times as this might work by chance.
+    for _ in 0..10 {
+        let buf = [0u32; 256];
+        // `buf` is sufficiently aligned for `layout.align` on a `dyn Debug`, but not
+        // for the actual alignment required by `MuchAlign`.
+        // We craft a wide reference `&dyn Debug` with the vtable for `MuchAlign`. That should be UB,
+        // as the reference is not aligned to its dynamic alignment requirements.
+        let mut ptr = &MuchAlign as &dyn std::fmt::Debug;
+        // Overwrite the data part of `ptr` so it points to `buf`.
+        unsafe {
+            (&mut ptr as *mut _ as *mut *const u8).write(&buf as *const _ as *const u8);
+        }
+        // Re-borrow that. This should be UB.
+        let _ptr = &*ptr; //~ERROR: alignment 256 is required
+    }
+}
diff --git a/src/tools/miri/tests/fail/unaligned_pointers/dyn_alignment.stderr b/src/tools/miri/tests/fail/unaligned_pointers/dyn_alignment.stderr
new file mode 100644
index 0000000000000..a900b46612b8a
--- /dev/null
+++ b/src/tools/miri/tests/fail/unaligned_pointers/dyn_alignment.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: accessing memory with alignment ALIGN, but alignment ALIGN is required
+  --> $DIR/dyn_alignment.rs:LL:CC
+   |
+LL |         let _ptr = &*ptr;
+   |                    ^^^^^ accessing memory with alignment ALIGN, but alignment ALIGN is required
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/dyn_alignment.rs:LL:CC
+
+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/unaligned_pointers/intptrcast_alignment_check.rs b/src/tools/miri/tests/fail/unaligned_pointers/intptrcast_alignment_check.rs
new file mode 100644
index 0000000000000..da4cadc1c8763
--- /dev/null
+++ b/src/tools/miri/tests/fail/unaligned_pointers/intptrcast_alignment_check.rs
@@ -0,0 +1,17 @@
+//@compile-flags: -Zmiri-symbolic-alignment-check -Zmiri-permissive-provenance
+// With the symbolic alignment check, even with intptrcast and without
+// validation, we want to be *sure* to catch bugs that arise from pointers being
+// insufficiently aligned. The only way to achieve that is not not let programs
+// exploit integer information for alignment, so here we test that this is
+// indeed the case.
+//
+// See https://github.com/rust-lang/miri/issues/1074.
+fn main() {
+    let x = &mut [0u8; 3];
+    let base_addr = x as *mut _ as usize;
+    // Manually make sure the pointer is properly aligned.
+    let base_addr_aligned = if base_addr % 2 == 0 { base_addr } else { base_addr + 1 };
+    let u16_ptr = base_addr_aligned as *mut u16;
+    unsafe { *u16_ptr = 2 }; //~ERROR: memory with alignment 1, but alignment 2 is required
+    println!("{:?}", x);
+}
diff --git a/src/tools/miri/tests/fail/unaligned_pointers/intptrcast_alignment_check.stderr b/src/tools/miri/tests/fail/unaligned_pointers/intptrcast_alignment_check.stderr
new file mode 100644
index 0000000000000..392495a386de7
--- /dev/null
+++ b/src/tools/miri/tests/fail/unaligned_pointers/intptrcast_alignment_check.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: accessing memory with alignment ALIGN, but alignment ALIGN is required
+  --> $DIR/intptrcast_alignment_check.rs:LL:CC
+   |
+LL |     unsafe { *u16_ptr = 2 };
+   |              ^^^^^^^^^^^^ accessing memory with alignment ALIGN, but alignment ALIGN is required
+   |
+   = help: this usually indicates that your program performed an invalid operation and caused Undefined Behavior
+   = help: but due to `-Zmiri-symbolic-alignment-check`, alignment errors can also be false positives
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/intptrcast_alignment_check.rs:LL:CC
+
+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/unaligned_pointers/reference_to_packed.rs b/src/tools/miri/tests/fail/unaligned_pointers/reference_to_packed.rs
new file mode 100644
index 0000000000000..752210dca46e8
--- /dev/null
+++ b/src/tools/miri/tests/fail/unaligned_pointers/reference_to_packed.rs
@@ -0,0 +1,19 @@
+// This should fail even without validation/SB
+//@compile-flags: -Zmiri-disable-validation -Zmiri-disable-stacked-borrows
+
+#![allow(dead_code, unused_variables, unaligned_references)]
+
+#[repr(packed)]
+struct Foo {
+    x: i32,
+    y: i32,
+}
+
+fn main() {
+    // Try many times as this might work by chance.
+    for _ in 0..10 {
+        let foo = Foo { x: 42, y: 99 };
+        let p = &foo.x;
+        let i = *p; //~ERROR: alignment 4 is required
+    }
+}
diff --git a/src/tools/miri/tests/fail/unaligned_pointers/reference_to_packed.stderr b/src/tools/miri/tests/fail/unaligned_pointers/reference_to_packed.stderr
new file mode 100644
index 0000000000000..6c2a3dca2deef
--- /dev/null
+++ b/src/tools/miri/tests/fail/unaligned_pointers/reference_to_packed.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: accessing memory with alignment ALIGN, but alignment ALIGN is required
+  --> $DIR/reference_to_packed.rs:LL:CC
+   |
+LL |         let i = *p;
+   |                 ^^ accessing memory with alignment ALIGN, but alignment ALIGN is required
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/reference_to_packed.rs:LL:CC
+
+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/unaligned_pointers/unaligned_ptr1.rs b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr1.rs
new file mode 100644
index 0000000000000..73adc4dc44916
--- /dev/null
+++ b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr1.rs
@@ -0,0 +1,12 @@
+// This should fail even without validation or Stacked Borrows.
+//@compile-flags: -Zmiri-disable-validation -Zmiri-disable-stacked-borrows
+
+fn main() {
+    // Try many times as this might work by chance.
+    for _ in 0..10 {
+        let x = [2u16, 3, 4]; // Make it big enough so we don't get an out-of-bounds error.
+        let x = &x[0] as *const _ as *const u32;
+        // This must fail because alignment is violated: the allocation's base is not sufficiently aligned.
+        let _x = unsafe { *x }; //~ERROR: memory with alignment 2, but alignment 4 is required
+    }
+}
diff --git a/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr1.stderr b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr1.stderr
new file mode 100644
index 0000000000000..49292be9cd158
--- /dev/null
+++ b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr1.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: accessing memory with alignment ALIGN, but alignment ALIGN is required
+  --> $DIR/unaligned_ptr1.rs:LL:CC
+   |
+LL |         let _x = unsafe { *x };
+   |                           ^^ accessing memory with alignment ALIGN, but alignment ALIGN is required
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/unaligned_ptr1.rs:LL:CC
+
+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/unaligned_pointers/unaligned_ptr2.rs b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr2.rs
new file mode 100644
index 0000000000000..c252944ffb7b7
--- /dev/null
+++ b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr2.rs
@@ -0,0 +1,12 @@
+// This should fail even without validation or Stacked Borrows.
+//@compile-flags: -Zmiri-disable-validation -Zmiri-disable-stacked-borrows
+
+fn main() {
+    // No retry needed, this fails reliably.
+
+    let x = [2u32, 3]; // Make it big enough so we don't get an out-of-bounds error.
+    let x = (x.as_ptr() as *const u8).wrapping_offset(3) as *const u32;
+    // This must fail because alignment is violated: the offset is not sufficiently aligned.
+    // Also make the offset not a power of 2, that used to ICE.
+    let _x = unsafe { *x }; //~ERROR: memory with alignment 1, but alignment 4 is required
+}
diff --git a/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr2.stderr b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr2.stderr
new file mode 100644
index 0000000000000..e75482f723b69
--- /dev/null
+++ b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr2.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: accessing memory with alignment ALIGN, but alignment ALIGN is required
+  --> $DIR/unaligned_ptr2.rs:LL:CC
+   |
+LL |     let _x = unsafe { *x };
+   |                       ^^ accessing memory with alignment ALIGN, but alignment ALIGN is required
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/unaligned_ptr2.rs:LL:CC
+
+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/unaligned_pointers/unaligned_ptr3.rs b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr3.rs
new file mode 100644
index 0000000000000..7605dd175a982
--- /dev/null
+++ b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr3.rs
@@ -0,0 +1,13 @@
+// This should fail even without validation or Stacked Borrows.
+//@compile-flags: -Zmiri-disable-validation -Zmiri-disable-stacked-borrows
+
+fn main() {
+    // Try many times as this might work by chance.
+    for _ in 0..10 {
+        let x = [2u16, 3, 4, 5]; // Make it big enough so we don't get an out-of-bounds error.
+        let x = &x[0] as *const _ as *const *const u8; // cast to ptr-to-ptr, so that we load a ptr
+        // This must fail because alignment is violated. Test specifically for loading pointers,
+        // which have special code in miri's memory.
+        let _x = unsafe { *x }; //~ERROR: but alignment
+    }
+}
diff --git a/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr3.stderr b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr3.stderr
new file mode 100644
index 0000000000000..50dd4fdfc89f5
--- /dev/null
+++ b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr3.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: accessing memory with alignment ALIGN, but alignment ALIGN is required
+  --> $DIR/unaligned_ptr3.rs:LL:CC
+   |
+LL |         let _x = unsafe { *x };
+   |                           ^^ accessing memory with alignment ALIGN, but alignment ALIGN is required
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/unaligned_ptr3.rs:LL:CC
+
+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/unaligned_pointers/unaligned_ptr4.rs b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr4.rs
new file mode 100644
index 0000000000000..852febe4c04cc
--- /dev/null
+++ b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr4.rs
@@ -0,0 +1,14 @@
+// This should fail even without validation or Stacked Borrows.
+//@compile-flags: -Zmiri-disable-validation -Zmiri-disable-stacked-borrows
+
+fn main() {
+    // Make sure we notice when a u16 is loaded at offset 1 into a u8 allocation.
+    // (This would be missed if u8 allocations are *always* at odd addresses.)
+    //
+    // Try many times as this might work by chance.
+    for _ in 0..10 {
+        let x = [0u8; 4];
+        let ptr = x.as_ptr().wrapping_offset(1).cast::<u16>();
+        let _val = unsafe { *ptr }; //~ERROR: but alignment
+    }
+}
diff --git a/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr4.stderr b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr4.stderr
new file mode 100644
index 0000000000000..182f3e0f876f5
--- /dev/null
+++ b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr4.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: accessing memory with alignment ALIGN, but alignment ALIGN is required
+  --> $DIR/unaligned_ptr4.rs:LL:CC
+   |
+LL |         let _val = unsafe { *ptr };
+   |                             ^^^^ accessing memory with alignment ALIGN, but alignment ALIGN is required
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/unaligned_ptr4.rs:LL:CC
+
+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/unaligned_pointers/unaligned_ptr_addr_of.rs b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr_addr_of.rs
new file mode 100644
index 0000000000000..e439cf2b03b96
--- /dev/null
+++ b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr_addr_of.rs
@@ -0,0 +1,14 @@
+// This should fail even without validation or Stacked Borrows.
+//@compile-flags: -Zmiri-disable-validation -Zmiri-disable-stacked-borrows
+use std::ptr;
+
+fn main() {
+    // Try many times as this might work by chance.
+    for _ in 0..10 {
+        let x = [2u16, 3, 4]; // Make it big enough so we don't get an out-of-bounds error.
+        let x = &x[0] as *const _ as *const u32;
+        // This must fail because alignment is violated: the allocation's base is not sufficiently aligned.
+        // The deref is UB even if we just put the result into a raw pointer.
+        let _x = unsafe { ptr::addr_of!(*x) }; //~ ERROR: memory with alignment 2, but alignment 4 is required
+    }
+}
diff --git a/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr_addr_of.stderr b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr_addr_of.stderr
new file mode 100644
index 0000000000000..2d8b1bf74508a
--- /dev/null
+++ b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr_addr_of.stderr
@@ -0,0 +1,16 @@
+error: Undefined Behavior: accessing memory with alignment ALIGN, but alignment ALIGN is required
+  --> $DIR/unaligned_ptr_addr_of.rs:LL:CC
+   |
+LL |         let _x = unsafe { ptr::addr_of!(*x) };
+   |                           ^^^^^^^^^^^^^^^^^ accessing memory with alignment ALIGN, but alignment ALIGN is required
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at RUSTLIB/core/src/ptr/mod.rs:LL:CC
+   = note: this error originates in the macro `ptr::addr_of` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+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/unaligned_pointers/unaligned_ptr_zst.rs b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr_zst.rs
new file mode 100644
index 0000000000000..9076581b55bbc
--- /dev/null
+++ b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr_zst.rs
@@ -0,0 +1,13 @@
+// This should fail even without validation
+// Some optimizations remove ZST accesses, thus masking this UB.
+//@compile-flags: -Zmir-opt-level=0 -Zmiri-disable-validation
+
+fn main() {
+    // Try many times as this might work by chance.
+    for i in 0..10 {
+        let x = i as u8;
+        let x = &x as *const _ as *const [u32; 0];
+        // This must fail because alignment is violated. Test specifically for loading ZST.
+        let _x = unsafe { *x }; //~ERROR: alignment 4 is required
+    }
+}
diff --git a/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr_zst.stderr b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr_zst.stderr
new file mode 100644
index 0000000000000..aa0cbe1623b6e
--- /dev/null
+++ b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr_zst.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: accessing memory with alignment ALIGN, but alignment ALIGN is required
+  --> $DIR/unaligned_ptr_zst.rs:LL:CC
+   |
+LL |         let _x = unsafe { *x };
+   |                           ^^ accessing memory with alignment ALIGN, but alignment ALIGN is required
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/unaligned_ptr_zst.rs:LL:CC
+
+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/uninit_buffer.rs b/src/tools/miri/tests/fail/uninit_buffer.rs
new file mode 100644
index 0000000000000..d21371225e571
--- /dev/null
+++ b/src/tools/miri/tests/fail/uninit_buffer.rs
@@ -0,0 +1,20 @@
+//@error-pattern: memory is uninitialized at [0x4..0x10]
+
+use std::alloc::{alloc, dealloc, Layout};
+use std::slice::from_raw_parts;
+
+fn main() {
+    let layout = Layout::from_size_align(32, 8).unwrap();
+    unsafe {
+        let ptr = alloc(layout);
+        *ptr = 0x41;
+        *ptr.add(1) = 0x42;
+        *ptr.add(2) = 0x43;
+        *ptr.add(3) = 0x44;
+        *ptr.add(16) = 0x00;
+        let slice1 = from_raw_parts(ptr, 16);
+        let slice2 = from_raw_parts(ptr.add(16), 16);
+        drop(slice1.cmp(slice2));
+        dealloc(ptr, layout);
+    }
+}
diff --git a/src/tools/miri/tests/fail/uninit_buffer.stderr b/src/tools/miri/tests/fail/uninit_buffer.stderr
new file mode 100644
index 0000000000000..a543d59addb14
--- /dev/null
+++ b/src/tools/miri/tests/fail/uninit_buffer.stderr
@@ -0,0 +1,27 @@
+error: Undefined Behavior: reading memory at ALLOC[0x0..0x10], but memory is uninitialized at [0x4..0x10], and this operation requires initialized memory
+  --> RUSTLIB/core/src/slice/cmp.rs:LL:CC
+   |
+LL |         let mut order = unsafe { memcmp(left.as_ptr(), right.as_ptr(), len) as isize };
+   |                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ reading memory at ALLOC[0x0..0x10], but memory is uninitialized at [0x4..0x10], and this operation requires initialized memory
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `<u8 as core::slice::cmp::SliceOrd>::compare` at RUSTLIB/core/src/slice/cmp.rs:LL:CC
+   = note: inside `core::slice::cmp::<impl std::cmp::Ord for [u8]>::cmp` at RUSTLIB/core/src/slice/cmp.rs:LL:CC
+note: inside `main` at $DIR/uninit_buffer.rs:LL:CC
+  --> $DIR/uninit_buffer.rs:LL:CC
+   |
+LL |         drop(slice1.cmp(slice2));
+   |              ^^^^^^^^^^^^^^^^^^
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+Uninitialized memory occurred at ALLOC[0x4..0x10], in this allocation:
+ALLOC (Rust heap, size: 32, align: 8) {
+    0x00 │ 41 42 43 44 __ __ __ __ __ __ __ __ __ __ __ __ │ ABCD░░░░░░░░░░░░
+    0x10 │ 00 __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ │ .░░░░░░░░░░░░░░░
+}
+
+error: aborting due to previous error
+
diff --git a/src/tools/miri/tests/fail/uninit_byte_read.rs b/src/tools/miri/tests/fail/uninit_byte_read.rs
new file mode 100644
index 0000000000000..f1dace0cff9b1
--- /dev/null
+++ b/src/tools/miri/tests/fail/uninit_byte_read.rs
@@ -0,0 +1,7 @@
+//@compile-flags: -Zmiri-disable-stacked-borrows
+fn main() {
+    let v: Vec<u8> = Vec::with_capacity(10);
+    let undef = unsafe { *v.get_unchecked(5) }; //~ ERROR: uninitialized
+    let x = undef + 1;
+    panic!("this should never print: {}", x);
+}
diff --git a/src/tools/miri/tests/fail/uninit_byte_read.stderr b/src/tools/miri/tests/fail/uninit_byte_read.stderr
new file mode 100644
index 0000000000000..9f7638888d643
--- /dev/null
+++ b/src/tools/miri/tests/fail/uninit_byte_read.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
+  --> $DIR/uninit_byte_read.rs:LL:CC
+   |
+LL |     let undef = unsafe { *v.get_unchecked(5) };
+   |                          ^^^^^^^^^^^^^^^^^^^ using uninitialized data, but this operation requires initialized memory
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/uninit_byte_read.rs:LL:CC
+
+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/unreachable.rs b/src/tools/miri/tests/fail/unreachable.rs
new file mode 100644
index 0000000000000..3389d5b9ddeaf
--- /dev/null
+++ b/src/tools/miri/tests/fail/unreachable.rs
@@ -0,0 +1,3 @@
+fn main() {
+    unsafe { std::hint::unreachable_unchecked() } //~ERROR: entering unreachable code
+}
diff --git a/src/tools/miri/tests/fail/unreachable.stderr b/src/tools/miri/tests/fail/unreachable.stderr
new file mode 100644
index 0000000000000..a57d731f1f713
--- /dev/null
+++ b/src/tools/miri/tests/fail/unreachable.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: entering unreachable code
+  --> $DIR/unreachable.rs:LL:CC
+   |
+LL |     unsafe { std::hint::unreachable_unchecked() }
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ entering unreachable code
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/unreachable.rs:LL:CC
+
+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/unsized-local.rs b/src/tools/miri/tests/fail/unsized-local.rs
new file mode 100644
index 0000000000000..ceccae4e3e76f
--- /dev/null
+++ b/src/tools/miri/tests/fail/unsized-local.rs
@@ -0,0 +1,23 @@
+#![feature(unsized_locals)]
+#![allow(incomplete_features)]
+
+fn main() {
+    pub trait Foo {
+        fn foo(self) -> String;
+    }
+
+    struct A;
+
+    impl Foo for A {
+        fn foo(self) -> String {
+            format!("hello")
+        }
+    }
+
+    let x = *(Box::new(A) as Box<dyn Foo>); //~ERROR: unsized locals are not supported
+    assert_eq!(x.foo(), format!("hello"));
+
+    // I'm not sure whether we want this to work
+    let x = Box::new(A) as Box<dyn Foo>;
+    assert_eq!(x.foo(), format!("hello"));
+}
diff --git a/src/tools/miri/tests/fail/unsized-local.stderr b/src/tools/miri/tests/fail/unsized-local.stderr
new file mode 100644
index 0000000000000..66d93c6f503cb
--- /dev/null
+++ b/src/tools/miri/tests/fail/unsized-local.stderr
@@ -0,0 +1,14 @@
+error: unsupported operation: unsized locals are not supported
+  --> $DIR/unsized-local.rs:LL:CC
+   |
+LL |     let x = *(Box::new(A) as Box<dyn Foo>);
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unsized locals are not supported
+   |
+   = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/unsized-local.rs:LL:CC
+
+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/unsupported_foreign_function.rs b/src/tools/miri/tests/fail/unsupported_foreign_function.rs
new file mode 100644
index 0000000000000..dfd099e734b99
--- /dev/null
+++ b/src/tools/miri/tests/fail/unsupported_foreign_function.rs
@@ -0,0 +1,9 @@
+fn main() {
+    extern "Rust" {
+        fn foo();
+    }
+
+    unsafe {
+        foo(); //~ ERROR: unsupported operation: can't call foreign function: foo
+    }
+}
diff --git a/src/tools/miri/tests/fail/unsupported_foreign_function.stderr b/src/tools/miri/tests/fail/unsupported_foreign_function.stderr
new file mode 100644
index 0000000000000..fde5fb78ac08a
--- /dev/null
+++ b/src/tools/miri/tests/fail/unsupported_foreign_function.stderr
@@ -0,0 +1,14 @@
+error: unsupported operation: can't call foreign function: foo
+  --> $DIR/unsupported_foreign_function.rs:LL:CC
+   |
+LL |         foo();
+   |         ^^^^^ can't call foreign function: foo
+   |
+   = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/unsupported_foreign_function.rs:LL:CC
+
+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/unsupported_signal.rs b/src/tools/miri/tests/fail/unsupported_signal.rs
new file mode 100644
index 0000000000000..d50041ffbd9de
--- /dev/null
+++ b/src/tools/miri/tests/fail/unsupported_signal.rs
@@ -0,0 +1,10 @@
+//! `signal()` is special on Linux and macOS that it's only supported within libstd.
+//! The implementation is not complete enough to permit user code to call it.
+//@ignore-target-windows: No libc on Windows
+
+fn main() {
+    unsafe {
+        libc::signal(libc::SIGPIPE, libc::SIG_IGN);
+        //~^ ERROR: unsupported operation: can't call foreign function: signal
+    }
+}
diff --git a/src/tools/miri/tests/fail/unsupported_signal.stderr b/src/tools/miri/tests/fail/unsupported_signal.stderr
new file mode 100644
index 0000000000000..d22ecbc11ff66
--- /dev/null
+++ b/src/tools/miri/tests/fail/unsupported_signal.stderr
@@ -0,0 +1,14 @@
+error: unsupported operation: can't call foreign function: signal
+  --> $DIR/unsupported_signal.rs:LL:CC
+   |
+LL |         libc::signal(libc::SIGPIPE, libc::SIG_IGN);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ can't call foreign function: signal
+   |
+   = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/unsupported_signal.rs:LL:CC
+
+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/validity/cast_fn_ptr1.rs b/src/tools/miri/tests/fail/validity/cast_fn_ptr1.rs
new file mode 100644
index 0000000000000..6ab73569c6347
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/cast_fn_ptr1.rs
@@ -0,0 +1,13 @@
+//@compile-flags: -Zmiri-permissive-provenance
+
+fn main() {
+    // Cast a function pointer such that on a call, the argument gets transmuted
+    // from raw ptr to reference. This is ABI-compatible, so it's not the call that
+    // should fail, but validation should.
+    fn f(_x: &i32) {}
+
+    let g: fn(*const i32) = unsafe { std::mem::transmute(f as fn(&i32)) };
+
+    g(0usize as *const i32)
+    //~^ ERROR: encountered a null reference
+}
diff --git a/src/tools/miri/tests/fail/validity/cast_fn_ptr1.stderr b/src/tools/miri/tests/fail/validity/cast_fn_ptr1.stderr
new file mode 100644
index 0000000000000..133e4b2c16a10
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/cast_fn_ptr1.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: constructing invalid value: encountered a null reference
+  --> $DIR/cast_fn_ptr1.rs:LL:CC
+   |
+LL |     g(0usize as *const i32)
+   |     ^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered a null reference
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/cast_fn_ptr1.rs:LL:CC
+
+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/validity/cast_fn_ptr2.rs b/src/tools/miri/tests/fail/validity/cast_fn_ptr2.rs
new file mode 100644
index 0000000000000..64ddb563be538
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/cast_fn_ptr2.rs
@@ -0,0 +1,15 @@
+//@compile-flags: -Zmiri-permissive-provenance
+
+fn main() {
+    // Cast a function pointer such that when returning, the return value gets transmuted
+    // from raw ptr to reference. This is ABI-compatible, so it's not the call that
+    // should fail, but validation should.
+    fn f() -> *const i32 {
+        0usize as *const i32
+    }
+
+    let g: fn() -> &'static i32 = unsafe { std::mem::transmute(f as fn() -> *const i32) };
+
+    let _x = g();
+    //~^ ERROR: encountered a null reference
+}
diff --git a/src/tools/miri/tests/fail/validity/cast_fn_ptr2.stderr b/src/tools/miri/tests/fail/validity/cast_fn_ptr2.stderr
new file mode 100644
index 0000000000000..21001f2b46096
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/cast_fn_ptr2.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: constructing invalid value: encountered a null reference
+  --> $DIR/cast_fn_ptr2.rs:LL:CC
+   |
+LL |     let _x = g();
+   |              ^^^ constructing invalid value: encountered a null reference
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/cast_fn_ptr2.rs:LL:CC
+
+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/validity/dangling_ref1.rs b/src/tools/miri/tests/fail/validity/dangling_ref1.rs
new file mode 100644
index 0000000000000..6bf2d9295a370
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/dangling_ref1.rs
@@ -0,0 +1,7 @@
+// Make sure we catch this even without Stacked Borrows
+//@compile-flags: -Zmiri-disable-stacked-borrows
+use std::mem;
+
+fn main() {
+    let _x: &i32 = unsafe { mem::transmute(16usize) }; //~ ERROR: encountered a dangling reference (address 0x10 is unallocated)
+}
diff --git a/src/tools/miri/tests/fail/validity/dangling_ref1.stderr b/src/tools/miri/tests/fail/validity/dangling_ref1.stderr
new file mode 100644
index 0000000000000..01ef071e86930
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/dangling_ref1.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: constructing invalid value: encountered a dangling reference (address 0x10 is unallocated)
+  --> $DIR/dangling_ref1.rs:LL:CC
+   |
+LL |     let _x: &i32 = unsafe { mem::transmute(16usize) };
+   |                             ^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered a dangling reference (address 0x10 is unallocated)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/dangling_ref1.rs:LL:CC
+
+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/validity/dangling_ref2.rs b/src/tools/miri/tests/fail/validity/dangling_ref2.rs
new file mode 100644
index 0000000000000..77d2358ae7772
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/dangling_ref2.rs
@@ -0,0 +1,9 @@
+// Make sure we catch this even without Stacked Borrows
+//@compile-flags: -Zmiri-disable-stacked-borrows
+use std::mem;
+
+fn main() {
+    let val = 14;
+    let ptr = (&val as *const i32).wrapping_offset(1);
+    let _x: &i32 = unsafe { mem::transmute(ptr) }; //~ ERROR: dangling reference (going beyond the bounds of its allocation)
+}
diff --git a/src/tools/miri/tests/fail/validity/dangling_ref2.stderr b/src/tools/miri/tests/fail/validity/dangling_ref2.stderr
new file mode 100644
index 0000000000000..4be4e8075a728
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/dangling_ref2.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: constructing invalid value: encountered a dangling reference (going beyond the bounds of its allocation)
+  --> $DIR/dangling_ref2.rs:LL:CC
+   |
+LL |     let _x: &i32 = unsafe { mem::transmute(ptr) };
+   |                             ^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered a dangling reference (going beyond the bounds of its allocation)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/dangling_ref2.rs:LL:CC
+
+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/validity/dangling_ref3.rs b/src/tools/miri/tests/fail/validity/dangling_ref3.rs
new file mode 100644
index 0000000000000..8e8a75bd7ec04
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/dangling_ref3.rs
@@ -0,0 +1,12 @@
+// Make sure we catch this even without Stacked Borrows
+//@compile-flags: -Zmiri-disable-stacked-borrows
+use std::mem;
+
+fn dangling() -> *const u8 {
+    let x = 0u8;
+    &x as *const _
+}
+
+fn main() {
+    let _x: &i32 = unsafe { mem::transmute(dangling()) }; //~ ERROR: dangling reference (use-after-free)
+}
diff --git a/src/tools/miri/tests/fail/validity/dangling_ref3.stderr b/src/tools/miri/tests/fail/validity/dangling_ref3.stderr
new file mode 100644
index 0000000000000..4b7bdf7823686
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/dangling_ref3.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: constructing invalid value: encountered a dangling reference (use-after-free)
+  --> $DIR/dangling_ref3.rs:LL:CC
+   |
+LL |     let _x: &i32 = unsafe { mem::transmute(dangling()) };
+   |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered a dangling reference (use-after-free)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/dangling_ref3.rs:LL:CC
+
+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/validity/invalid_bool.rs b/src/tools/miri/tests/fail/validity/invalid_bool.rs
new file mode 100644
index 0000000000000..4f11bb2629f5f
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/invalid_bool.rs
@@ -0,0 +1,3 @@
+fn main() {
+    let _b = unsafe { std::mem::transmute::<u8, bool>(2) }; //~ ERROR: expected a boolean
+}
diff --git a/src/tools/miri/tests/fail/validity/invalid_bool.stderr b/src/tools/miri/tests/fail/validity/invalid_bool.stderr
new file mode 100644
index 0000000000000..3972787a4d2fd
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/invalid_bool.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: constructing invalid value: encountered 0x02, but expected a boolean
+  --> $DIR/invalid_bool.rs:LL:CC
+   |
+LL |     let _b = unsafe { std::mem::transmute::<u8, bool>(2) };
+   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered 0x02, but expected a boolean
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/invalid_bool.rs:LL:CC
+
+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/validity/invalid_bool_uninit.rs b/src/tools/miri/tests/fail/validity/invalid_bool_uninit.rs
new file mode 100644
index 0000000000000..ce4fdcabd047a
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/invalid_bool_uninit.rs
@@ -0,0 +1,10 @@
+#![allow(invalid_value)]
+
+union MyUninit {
+    init: (),
+    uninit: [bool; 1],
+}
+
+fn main() {
+    let _b = unsafe { MyUninit { init: () }.uninit }; //~ ERROR: constructing invalid value
+}
diff --git a/src/tools/miri/tests/fail/validity/invalid_bool_uninit.stderr b/src/tools/miri/tests/fail/validity/invalid_bool_uninit.stderr
new file mode 100644
index 0000000000000..5a7bd80e40c15
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/invalid_bool_uninit.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: constructing invalid value at [0]: encountered uninitialized memory, but expected a boolean
+  --> $DIR/invalid_bool_uninit.rs:LL:CC
+   |
+LL |     let _b = unsafe { MyUninit { init: () }.uninit };
+   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at [0]: encountered uninitialized memory, but expected a boolean
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/invalid_bool_uninit.rs:LL:CC
+
+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/validity/invalid_char.rs b/src/tools/miri/tests/fail/validity/invalid_char.rs
new file mode 100644
index 0000000000000..568892e591096
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/invalid_char.rs
@@ -0,0 +1,9 @@
+fn main() {
+    assert!(std::char::from_u32(-1_i32 as u32).is_none());
+    let _val = match unsafe { std::mem::transmute::<i32, char>(-1) } {
+        //~^ ERROR: encountered 0xffffffff, but expected a valid unicode scalar value
+        'a' => true,
+        'b' => false,
+        _ => true,
+    };
+}
diff --git a/src/tools/miri/tests/fail/validity/invalid_char.stderr b/src/tools/miri/tests/fail/validity/invalid_char.stderr
new file mode 100644
index 0000000000000..eeff289dfa4e1
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/invalid_char.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: constructing invalid value: encountered $HEX, but expected a valid unicode scalar value (in `0..=$HEX` but not in `$HEX..=$HEX`)
+  --> $DIR/invalid_char.rs:LL:CC
+   |
+LL |     let _val = match unsafe { std::mem::transmute::<i32, char>(-1) } {
+   |                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered $HEX, but expected a valid unicode scalar value (in `0..=$HEX` but not in `$HEX..=$HEX`)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/invalid_char.rs:LL:CC
+
+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/validity/invalid_char_uninit.rs b/src/tools/miri/tests/fail/validity/invalid_char_uninit.rs
new file mode 100644
index 0000000000000..0e3c3ccac6f23
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/invalid_char_uninit.rs
@@ -0,0 +1,10 @@
+#![allow(invalid_value)]
+
+union MyUninit {
+    init: (),
+    uninit: [char; 1],
+}
+
+fn main() {
+    let _b = unsafe { MyUninit { init: () }.uninit }; //~ ERROR: constructing invalid value
+}
diff --git a/src/tools/miri/tests/fail/validity/invalid_char_uninit.stderr b/src/tools/miri/tests/fail/validity/invalid_char_uninit.stderr
new file mode 100644
index 0000000000000..fb5d3ee1f1f9c
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/invalid_char_uninit.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: constructing invalid value at [0]: encountered uninitialized memory, but expected a unicode scalar value
+  --> $DIR/invalid_char_uninit.rs:LL:CC
+   |
+LL |     let _b = unsafe { MyUninit { init: () }.uninit };
+   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at [0]: encountered uninitialized memory, but expected a unicode scalar value
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/invalid_char_uninit.rs:LL:CC
+
+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/validity/invalid_enum_tag.rs b/src/tools/miri/tests/fail/validity/invalid_enum_tag.rs
new file mode 100644
index 0000000000000..fa115e1e78e46
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/invalid_enum_tag.rs
@@ -0,0 +1,11 @@
+#[repr(C)]
+pub enum Foo {
+    A,
+    B,
+    C,
+    D,
+}
+
+fn main() {
+    let _f = unsafe { std::mem::transmute::<i32, Foo>(42) }; //~ ERROR: constructing invalid value at .<enum-tag>: encountered 0x0000002a, but expected a valid enum tag
+}
diff --git a/src/tools/miri/tests/fail/validity/invalid_enum_tag.stderr b/src/tools/miri/tests/fail/validity/invalid_enum_tag.stderr
new file mode 100644
index 0000000000000..9234b4d705a9d
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/invalid_enum_tag.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: constructing invalid value at .<enum-tag>: encountered $HEX, but expected a valid enum tag
+  --> $DIR/invalid_enum_tag.rs:LL:CC
+   |
+LL |     let _f = unsafe { std::mem::transmute::<i32, Foo>(42) };
+   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .<enum-tag>: encountered $HEX, but expected a valid enum tag
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/invalid_enum_tag.rs:LL:CC
+
+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/validity/invalid_fnptr_null.rs b/src/tools/miri/tests/fail/validity/invalid_fnptr_null.rs
new file mode 100644
index 0000000000000..8d2045ca4a659
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/invalid_fnptr_null.rs
@@ -0,0 +1,5 @@
+#![allow(invalid_value)]
+
+fn main() {
+    let _b: fn() = unsafe { std::mem::transmute(0usize) }; //~ ERROR: encountered a null function pointer
+}
diff --git a/src/tools/miri/tests/fail/validity/invalid_fnptr_null.stderr b/src/tools/miri/tests/fail/validity/invalid_fnptr_null.stderr
new file mode 100644
index 0000000000000..63fad1d56e38b
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/invalid_fnptr_null.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: constructing invalid value: encountered a null function pointer
+  --> $DIR/invalid_fnptr_null.rs:LL:CC
+   |
+LL |     let _b: fn() = unsafe { std::mem::transmute(0usize) };
+   |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered a null function pointer
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/invalid_fnptr_null.rs:LL:CC
+
+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/validity/invalid_fnptr_uninit.rs b/src/tools/miri/tests/fail/validity/invalid_fnptr_uninit.rs
new file mode 100644
index 0000000000000..014a8ae847a79
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/invalid_fnptr_uninit.rs
@@ -0,0 +1,10 @@
+#![allow(invalid_value)]
+
+union MyUninit {
+    init: (),
+    uninit: [fn(); 1],
+}
+
+fn main() {
+    let _b = unsafe { MyUninit { init: () }.uninit }; //~ ERROR: constructing invalid value
+}
diff --git a/src/tools/miri/tests/fail/validity/invalid_fnptr_uninit.stderr b/src/tools/miri/tests/fail/validity/invalid_fnptr_uninit.stderr
new file mode 100644
index 0000000000000..35309e90136cb
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/invalid_fnptr_uninit.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: constructing invalid value at [0]: encountered uninitialized memory, but expected a function pointer
+  --> $DIR/invalid_fnptr_uninit.rs:LL:CC
+   |
+LL |     let _b = unsafe { MyUninit { init: () }.uninit };
+   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at [0]: encountered uninitialized memory, but expected a function pointer
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/invalid_fnptr_uninit.rs:LL:CC
+
+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/validity/invalid_wide_raw.rs b/src/tools/miri/tests/fail/validity/invalid_wide_raw.rs
new file mode 100644
index 0000000000000..2ad972a9d4d7d
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/invalid_wide_raw.rs
@@ -0,0 +1,11 @@
+#![allow(invalid_value)]
+
+fn main() {
+    trait T {}
+    #[derive(Debug)]
+    struct S {
+        #[allow(dead_code)]
+        x: *mut dyn T,
+    }
+    dbg!(S { x: unsafe { std::mem::transmute((0usize, 0usize)) } }); //~ ERROR: encountered null pointer, but expected a vtable pointer
+}
diff --git a/src/tools/miri/tests/fail/validity/invalid_wide_raw.stderr b/src/tools/miri/tests/fail/validity/invalid_wide_raw.stderr
new file mode 100644
index 0000000000000..cf12ab8dbd55a
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/invalid_wide_raw.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: constructing invalid value: encountered null pointer, but expected a vtable pointer
+  --> $DIR/invalid_wide_raw.rs:LL:CC
+   |
+LL |     dbg!(S { x: unsafe { std::mem::transmute((0usize, 0usize)) } });
+   |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered null pointer, but expected a vtable pointer
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/invalid_wide_raw.rs:LL:CC
+
+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/validity/nonzero.rs b/src/tools/miri/tests/fail/validity/nonzero.rs
new file mode 100644
index 0000000000000..384c94a556998
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/nonzero.rs
@@ -0,0 +1,13 @@
+// gets masked by optimizations
+//@compile-flags: -Zmir-opt-level=0
+#![feature(rustc_attrs)]
+#![allow(unused_attributes)]
+
+#[rustc_layout_scalar_valid_range_start(1)]
+#[repr(transparent)]
+pub(crate) struct NonZero<T>(pub(crate) T);
+
+fn main() {
+    // Make sure that we detect this even when no function call is happening along the way
+    let _x = Some(unsafe { NonZero(0) }); //~ ERROR: encountered 0, but expected something greater or equal to 1
+}
diff --git a/src/tools/miri/tests/fail/validity/nonzero.stderr b/src/tools/miri/tests/fail/validity/nonzero.stderr
new file mode 100644
index 0000000000000..a9a68177ed973
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/nonzero.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: constructing invalid value: encountered 0, but expected something greater or equal to 1
+  --> $DIR/nonzero.rs:LL:CC
+   |
+LL |     let _x = Some(unsafe { NonZero(0) });
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered 0, but expected something greater or equal to 1
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/nonzero.rs:LL:CC
+
+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/validity/ref_to_uninhabited1.rs b/src/tools/miri/tests/fail/validity/ref_to_uninhabited1.rs
new file mode 100644
index 0000000000000..2e6be8b971c64
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/ref_to_uninhabited1.rs
@@ -0,0 +1,9 @@
+#![feature(never_type)]
+use std::mem::{forget, transmute};
+
+fn main() {
+    unsafe {
+        let x: Box<!> = transmute(&mut 42); //~ERROR: encountered a box pointing to uninhabited type !
+        forget(x);
+    }
+}
diff --git a/src/tools/miri/tests/fail/validity/ref_to_uninhabited1.stderr b/src/tools/miri/tests/fail/validity/ref_to_uninhabited1.stderr
new file mode 100644
index 0000000000000..4facd2159c8d0
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/ref_to_uninhabited1.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: constructing invalid value: encountered a box pointing to uninhabited type !
+  --> $DIR/ref_to_uninhabited1.rs:LL:CC
+   |
+LL |         let x: Box<!> = transmute(&mut 42);
+   |                         ^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered a box pointing to uninhabited type !
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/ref_to_uninhabited1.rs:LL:CC
+
+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/validity/ref_to_uninhabited2.rs b/src/tools/miri/tests/fail/validity/ref_to_uninhabited2.rs
new file mode 100644
index 0000000000000..8934a06b5d73a
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/ref_to_uninhabited2.rs
@@ -0,0 +1,9 @@
+use std::mem::transmute;
+
+enum Void {}
+
+fn main() {
+    unsafe {
+        let _x: &(i32, Void) = transmute(&42); //~ERROR: encountered a reference pointing to uninhabited type (i32, Void)
+    }
+}
diff --git a/src/tools/miri/tests/fail/validity/ref_to_uninhabited2.stderr b/src/tools/miri/tests/fail/validity/ref_to_uninhabited2.stderr
new file mode 100644
index 0000000000000..264465f939190
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/ref_to_uninhabited2.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: constructing invalid value: encountered a reference pointing to uninhabited type (i32, Void)
+  --> $DIR/ref_to_uninhabited2.rs:LL:CC
+   |
+LL |         let _x: &(i32, Void) = transmute(&42);
+   |                                ^^^^^^^^^^^^^^ constructing invalid value: encountered a reference pointing to uninhabited type (i32, Void)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/ref_to_uninhabited2.rs:LL:CC
+
+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/validity/too-big-slice.rs b/src/tools/miri/tests/fail/validity/too-big-slice.rs
new file mode 100644
index 0000000000000..61d9032207585
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/too-big-slice.rs
@@ -0,0 +1,8 @@
+use std::mem;
+
+fn main() {
+    unsafe {
+        let ptr = Box::into_raw(Box::new(0u8));
+        let _x: &[u8] = mem::transmute((ptr, usize::MAX)); //~ ERROR: invalid reference metadata: slice is bigger than largest supported object
+    }
+}
diff --git a/src/tools/miri/tests/fail/validity/too-big-slice.stderr b/src/tools/miri/tests/fail/validity/too-big-slice.stderr
new file mode 100644
index 0000000000000..6df00aefe7561
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/too-big-slice.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: constructing invalid value: encountered invalid reference metadata: slice is bigger than largest supported object
+  --> $DIR/too-big-slice.rs:LL:CC
+   |
+LL |         let _x: &[u8] = mem::transmute((ptr, usize::MAX));
+   |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered invalid reference metadata: slice is bigger than largest supported object
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/too-big-slice.rs:LL:CC
+
+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/validity/too-big-unsized.rs b/src/tools/miri/tests/fail/validity/too-big-unsized.rs
new file mode 100644
index 0000000000000..280205dccbf23
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/too-big-unsized.rs
@@ -0,0 +1,15 @@
+use std::mem;
+
+#[allow(unused)]
+struct MySlice {
+    prefix: u64,
+    tail: [u8],
+}
+
+fn main() {
+    unsafe {
+        let ptr = Box::into_raw(Box::new(0u8));
+        // The slice part is actually not "too big", but together with the `prefix` field it is.
+        let _x: &MySlice = mem::transmute((ptr, isize::MAX as usize)); //~ ERROR: invalid reference metadata: total size is bigger than largest supported object
+    }
+}
diff --git a/src/tools/miri/tests/fail/validity/too-big-unsized.stderr b/src/tools/miri/tests/fail/validity/too-big-unsized.stderr
new file mode 100644
index 0000000000000..cbcb31b29fc30
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/too-big-unsized.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: constructing invalid value: encountered invalid reference metadata: total size is bigger than largest supported object
+  --> $DIR/too-big-unsized.rs:LL:CC
+   |
+LL |         let _x: &MySlice = mem::transmute((ptr, isize::MAX as usize));
+   |                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered invalid reference metadata: total size is bigger than largest supported object
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/too-big-unsized.rs:LL:CC
+
+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/validity/transmute_through_ptr.rs b/src/tools/miri/tests/fail/validity/transmute_through_ptr.rs
new file mode 100644
index 0000000000000..60b3bdd6cd69a
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/transmute_through_ptr.rs
@@ -0,0 +1,19 @@
+#[repr(u32)]
+#[derive(Debug)]
+enum Bool {
+    True,
+}
+
+fn evil(x: &mut Bool) {
+    let x = x as *mut _ as *mut u32;
+    unsafe { *x = 44 }; // out-of-bounds enum tag
+}
+
+#[rustfmt::skip] // rustfmt bug: https://github.com/rust-lang/rustfmt/issues/5391
+fn main() {
+    let mut x = Bool::True;
+    evil(&mut x);
+    let y = x; // reading this ought to be enough to trigger validation
+    //~^ ERROR: constructing invalid value at .<enum-tag>: encountered 0x0000002c, but expected a valid enum tag
+    println!("{:?}", y); // make sure it is used (and not optimized away)
+}
diff --git a/src/tools/miri/tests/fail/validity/transmute_through_ptr.stderr b/src/tools/miri/tests/fail/validity/transmute_through_ptr.stderr
new file mode 100644
index 0000000000000..ea155405cd610
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/transmute_through_ptr.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: constructing invalid value at .<enum-tag>: encountered $HEX, but expected a valid enum tag
+  --> $DIR/transmute_through_ptr.rs:LL:CC
+   |
+LL |     let y = x; // reading this ought to be enough to trigger validation
+   |             ^ constructing invalid value at .<enum-tag>: encountered $HEX, but expected a valid enum tag
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/transmute_through_ptr.rs:LL:CC
+
+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/validity/uninit_float.rs b/src/tools/miri/tests/fail/validity/uninit_float.rs
new file mode 100644
index 0000000000000..045bb46464fae
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/uninit_float.rs
@@ -0,0 +1,9 @@
+#![allow(deprecated, invalid_value)]
+// This test is adapted from https://github.com/rust-lang/miri/issues/1340#issue-600900312.
+
+fn main() {
+    // Deliberately using `mem::uninitialized` to make sure that despite all the mitigations, we consider this UB.
+    // The array avoids a `Scalar` layout which detects uninit without even doing validation.
+    let _val: [f32; 1] = unsafe { std::mem::uninitialized() };
+    //~^ ERROR: uninitialized
+}
diff --git a/src/tools/miri/tests/fail/validity/uninit_float.stderr b/src/tools/miri/tests/fail/validity/uninit_float.stderr
new file mode 100644
index 0000000000000..677a0fc5570d7
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/uninit_float.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: constructing invalid value at .value[0]: encountered uninitialized bytes
+  --> $DIR/uninit_float.rs:LL:CC
+   |
+LL |     let _val: [f32; 1] = unsafe { std::mem::uninitialized() };
+   |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .value[0]: encountered uninitialized bytes
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/uninit_float.rs:LL:CC
+
+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/validity/uninit_integer.rs b/src/tools/miri/tests/fail/validity/uninit_integer.rs
new file mode 100644
index 0000000000000..a94302603a219
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/uninit_integer.rs
@@ -0,0 +1,8 @@
+#![allow(invalid_value)]
+// This test is from https://github.com/rust-lang/miri/issues/1340#issue-600900312.
+
+fn main() {
+    // The array avoids a `Scalar` layout which detects uninit without even doing validation.
+    let _val = unsafe { std::mem::MaybeUninit::<[usize; 1]>::uninit().assume_init() };
+    //~^ ERROR: uninitialized
+}
diff --git a/src/tools/miri/tests/fail/validity/uninit_integer.stderr b/src/tools/miri/tests/fail/validity/uninit_integer.stderr
new file mode 100644
index 0000000000000..a9ac2a6dc67e7
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/uninit_integer.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: constructing invalid value at .value[0]: encountered uninitialized bytes
+  --> $DIR/uninit_integer.rs:LL:CC
+   |
+LL |     let _val = unsafe { std::mem::MaybeUninit::<[usize; 1]>::uninit().assume_init() };
+   |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .value[0]: encountered uninitialized bytes
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/uninit_integer.rs:LL:CC
+
+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/validity/uninit_raw_ptr.rs b/src/tools/miri/tests/fail/validity/uninit_raw_ptr.rs
new file mode 100644
index 0000000000000..18703152ea108
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/uninit_raw_ptr.rs
@@ -0,0 +1,7 @@
+#![allow(invalid_value)]
+
+fn main() {
+    // The array avoids a `Scalar` layout which detects uninit without even doing validation.
+    let _val = unsafe { std::mem::MaybeUninit::<[*const u8; 1]>::uninit().assume_init() };
+    //~^ ERROR: uninitialized
+}
diff --git a/src/tools/miri/tests/fail/validity/uninit_raw_ptr.stderr b/src/tools/miri/tests/fail/validity/uninit_raw_ptr.stderr
new file mode 100644
index 0000000000000..bbae9cf69ffe1
--- /dev/null
+++ b/src/tools/miri/tests/fail/validity/uninit_raw_ptr.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: constructing invalid value at .value[0]: encountered uninitialized memory, but expected a raw pointer
+  --> $DIR/uninit_raw_ptr.rs:LL:CC
+   |
+LL |     let _val = unsafe { std::mem::MaybeUninit::<[*const u8; 1]>::uninit().assume_init() };
+   |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .value[0]: encountered uninitialized memory, but expected a raw pointer
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/uninit_raw_ptr.rs:LL:CC
+
+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/weak_memory/racing_mixed_size.rs b/src/tools/miri/tests/fail/weak_memory/racing_mixed_size.rs
new file mode 100644
index 0000000000000..7bbb7f9fe7c2a
--- /dev/null
+++ b/src/tools/miri/tests/fail/weak_memory/racing_mixed_size.rs
@@ -0,0 +1,40 @@
+// We want to control preemption here.
+//@compile-flags: -Zmiri-preemption-rate=0
+
+#![feature(core_intrinsics)]
+
+use std::ptr;
+use std::sync::atomic::AtomicU32;
+use std::sync::atomic::Ordering::*;
+use std::thread::spawn;
+
+fn static_atomic_u32(val: u32) -> &'static AtomicU32 {
+    let ret = Box::leak(Box::new(AtomicU32::new(val)));
+    ret
+}
+
+fn split_u32_ptr(dword: *const u32) -> *const [u16; 2] {
+    unsafe { std::mem::transmute::<*const u32, *const [u16; 2]>(dword) }
+}
+
+// Wine's SRWLock implementation does this, which is definitely undefined in C++ memory model
+// https://github.com/wine-mirror/wine/blob/303f8042f9db508adaca02ef21f8de4992cb9c03/dlls/ntdll/sync.c#L543-L566
+// Though it probably works just fine on x86
+pub fn main() {
+    let x = static_atomic_u32(0);
+    let j1 = spawn(move || {
+        x.store(1, Relaxed);
+    });
+
+    let j2 = spawn(move || {
+        let x_ptr = x as *const AtomicU32 as *const u32;
+        let x_split = split_u32_ptr(x_ptr);
+        unsafe {
+            let hi = ptr::addr_of!((*x_split)[0]);
+            std::intrinsics::atomic_load_relaxed(hi); //~ ERROR: imperfectly overlapping
+        }
+    });
+
+    j1.join().unwrap();
+    j2.join().unwrap();
+}
diff --git a/src/tools/miri/tests/fail/weak_memory/racing_mixed_size.stderr b/src/tools/miri/tests/fail/weak_memory/racing_mixed_size.stderr
new file mode 100644
index 0000000000000..dda22ac9ce24c
--- /dev/null
+++ b/src/tools/miri/tests/fail/weak_memory/racing_mixed_size.stderr
@@ -0,0 +1,14 @@
+error: unsupported operation: racy imperfectly overlapping atomic access is not possible in the C++20 memory model, and not supported by Miri's weak memory emulation
+  --> $DIR/racing_mixed_size.rs:LL:CC
+   |
+LL |             std::intrinsics::atomic_load_relaxed(hi);
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ racy imperfectly overlapping atomic access is not possible in the C++20 memory model, and not supported by Miri's weak memory emulation
+   |
+   = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
+   = note: BACKTRACE:
+   = note: inside closure at $DIR/racing_mixed_size.rs:LL:CC
+
+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/weak_memory/racing_mixed_size_read.rs b/src/tools/miri/tests/fail/weak_memory/racing_mixed_size_read.rs
new file mode 100644
index 0000000000000..73178980b7e5a
--- /dev/null
+++ b/src/tools/miri/tests/fail/weak_memory/racing_mixed_size_read.rs
@@ -0,0 +1,38 @@
+// We want to control preemption here.
+//@compile-flags: -Zmiri-preemption-rate=0
+
+use std::sync::atomic::Ordering::*;
+use std::sync::atomic::{AtomicU16, AtomicU32};
+use std::thread::spawn;
+
+fn static_atomic(val: u32) -> &'static AtomicU32 {
+    let ret = Box::leak(Box::new(AtomicU32::new(val)));
+    ret
+}
+
+fn split_u32_ptr(dword: *const u32) -> *const [u16; 2] {
+    unsafe { std::mem::transmute::<*const u32, *const [u16; 2]>(dword) }
+}
+
+// Racing mixed size reads may cause two loads to read-from
+// the same store but observe different values, which doesn't make
+// sense under the formal model so we forbade this.
+pub fn main() {
+    let x = static_atomic(0);
+
+    let j1 = spawn(move || {
+        x.load(Relaxed);
+    });
+
+    let j2 = spawn(move || {
+        let x_ptr = x as *const AtomicU32 as *const u32;
+        let x_split = split_u32_ptr(x_ptr);
+        unsafe {
+            let hi = x_split as *const u16 as *const AtomicU16;
+            (*hi).load(Relaxed); //~ ERROR: imperfectly overlapping
+        }
+    });
+
+    j1.join().unwrap();
+    j2.join().unwrap();
+}
diff --git a/src/tools/miri/tests/fail/weak_memory/racing_mixed_size_read.stderr b/src/tools/miri/tests/fail/weak_memory/racing_mixed_size_read.stderr
new file mode 100644
index 0000000000000..59fa5c7410237
--- /dev/null
+++ b/src/tools/miri/tests/fail/weak_memory/racing_mixed_size_read.stderr
@@ -0,0 +1,14 @@
+error: unsupported operation: racy imperfectly overlapping atomic access is not possible in the C++20 memory model, and not supported by Miri's weak memory emulation
+  --> $DIR/racing_mixed_size_read.rs:LL:CC
+   |
+LL |             (*hi).load(Relaxed);
+   |             ^^^^^^^^^^^^^^^^^^^ racy imperfectly overlapping atomic access is not possible in the C++20 memory model, and not supported by Miri's weak memory emulation
+   |
+   = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
+   = note: BACKTRACE:
+   = note: inside closure at $DIR/racing_mixed_size_read.rs:LL:CC
+
+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/zst1.rs b/src/tools/miri/tests/fail/zst1.rs
new file mode 100644
index 0000000000000..cc81481e4fa4c
--- /dev/null
+++ b/src/tools/miri/tests/fail/zst1.rs
@@ -0,0 +1,5 @@
+fn main() {
+    // make sure ZST locals cannot be accessed
+    let x = &() as *const () as *const i8;
+    let _val = unsafe { *x }; //~ ERROR: out-of-bounds
+}
diff --git a/src/tools/miri/tests/fail/zst1.stderr b/src/tools/miri/tests/fail/zst1.stderr
new file mode 100644
index 0000000000000..b89f06af95893
--- /dev/null
+++ b/src/tools/miri/tests/fail/zst1.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: dereferencing pointer failed: ALLOC has size 0, so pointer to 1 byte starting at offset 0 is out-of-bounds
+  --> $DIR/zst1.rs:LL:CC
+   |
+LL |     let _val = unsafe { *x };
+   |                         ^^ dereferencing pointer failed: ALLOC has size 0, so pointer to 1 byte starting at offset 0 is out-of-bounds
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/zst1.rs:LL:CC
+
+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/zst2.rs b/src/tools/miri/tests/fail/zst2.rs
new file mode 100644
index 0000000000000..82470866f179f
--- /dev/null
+++ b/src/tools/miri/tests/fail/zst2.rs
@@ -0,0 +1,15 @@
+// Some optimizations remove ZST accesses, thus masking this UB.
+//@compile-flags: -Zmir-opt-level=0
+
+fn main() {
+    // Not using the () type here, as writes of that type do not even have MIR generated.
+    // Also not assigning directly as that's array initialization, not assignment.
+    let zst_val = [1u8; 0];
+
+    // make sure ZST accesses are checked against being "truly" dangling pointers
+    // (into deallocated allocations).
+    let mut x_box = Box::new(1u8);
+    let x = &mut *x_box as *mut _ as *mut [u8; 0];
+    drop(x_box);
+    unsafe { *x = zst_val }; //~ ERROR: dereferenced after this allocation got freed
+}
diff --git a/src/tools/miri/tests/fail/zst2.stderr b/src/tools/miri/tests/fail/zst2.stderr
new file mode 100644
index 0000000000000..6c49656e4c67d
--- /dev/null
+++ b/src/tools/miri/tests/fail/zst2.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed
+  --> $DIR/zst2.rs:LL:CC
+   |
+LL |     unsafe { *x = zst_val };
+   |              ^^^^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/zst2.rs:LL:CC
+
+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/zst3.rs b/src/tools/miri/tests/fail/zst3.rs
new file mode 100644
index 0000000000000..a511f38998feb
--- /dev/null
+++ b/src/tools/miri/tests/fail/zst3.rs
@@ -0,0 +1,18 @@
+// Some optimizations remove ZST accesses, thus masking this UB.
+//@compile-flags: -Zmir-opt-level=0
+
+fn main() {
+    // Not using the () type here, as writes of that type do not even have MIR generated.
+    // Also not assigning directly as that's array initialization, not assignment.
+    let zst_val = [1u8; 0];
+
+    // make sure ZST accesses are checked against being "truly" dangling pointers
+    // (that are out-of-bounds).
+    let mut x_box = Box::new(1u8);
+    let x = (&mut *x_box as *mut u8).wrapping_offset(1);
+    // This one is just "at the edge", but still okay
+    unsafe { *(x as *mut [u8; 0]) = zst_val };
+    // One byte further is OOB.
+    let x = x.wrapping_offset(1);
+    unsafe { *(x as *mut [u8; 0]) = zst_val }; //~ ERROR: out-of-bounds
+}
diff --git a/src/tools/miri/tests/fail/zst3.stderr b/src/tools/miri/tests/fail/zst3.stderr
new file mode 100644
index 0000000000000..c9accf2c8fbeb
--- /dev/null
+++ b/src/tools/miri/tests/fail/zst3.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: dereferencing pointer failed: ALLOC has size 1, so pointer at offset 2 is out-of-bounds
+  --> $DIR/zst3.rs:LL:CC
+   |
+LL |     unsafe { *(x as *mut [u8; 0]) = zst_val };
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ dereferencing pointer failed: ALLOC has size 1, so pointer at offset 2 is out-of-bounds
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/zst3.rs:LL:CC
+
+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/panic/div-by-zero-2.rs b/src/tools/miri/tests/panic/div-by-zero-2.rs
new file mode 100644
index 0000000000000..fac5415696fc6
--- /dev/null
+++ b/src/tools/miri/tests/panic/div-by-zero-2.rs
@@ -0,0 +1,5 @@
+#![allow(unconditional_panic)]
+
+fn main() {
+    let _n = 1 / 0;
+}
diff --git a/src/tools/miri/tests/panic/div-by-zero-2.stderr b/src/tools/miri/tests/panic/div-by-zero-2.stderr
new file mode 100644
index 0000000000000..538d87113654d
--- /dev/null
+++ b/src/tools/miri/tests/panic/div-by-zero-2.stderr
@@ -0,0 +1,2 @@
+thread 'main' panicked at 'attempt to divide by zero', $DIR/div-by-zero-2.rs:LL:CC
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
diff --git a/src/tools/miri/tests/panic/function_calls/exported_symbol_good_unwind.rs b/src/tools/miri/tests/panic/function_calls/exported_symbol_good_unwind.rs
new file mode 100644
index 0000000000000..71b799a1f12ba
--- /dev/null
+++ b/src/tools/miri/tests/panic/function_calls/exported_symbol_good_unwind.rs
@@ -0,0 +1,38 @@
+// Make sure the workaround for "crate ... required to be available in rlib format, but was not
+// found in this form" errors works without `-C prefer-dynamic` (`panic!` calls foreign function
+// `__rust_start_panic`).
+// no-prefer-dynamic
+#![feature(c_unwind, unboxed_closures)]
+
+use std::panic;
+
+#[no_mangle]
+extern "C-unwind" fn good_unwind_c() {
+    panic!();
+}
+
+#[no_mangle]
+fn good_unwind_rust() {
+    panic!();
+}
+
+// Diverging function calls are on a different code path.
+#[no_mangle]
+extern "rust-call" fn good_unwind_rust_call(_: ()) -> ! {
+    panic!();
+}
+
+fn main() -> ! {
+    extern "C-unwind" {
+        fn good_unwind_c();
+    }
+    panic::catch_unwind(|| unsafe { good_unwind_c() }).unwrap_err();
+    extern "Rust" {
+        fn good_unwind_rust();
+    }
+    panic::catch_unwind(|| unsafe { good_unwind_rust() }).unwrap_err();
+    extern "rust-call" {
+        fn good_unwind_rust_call(_: ()) -> !;
+    }
+    unsafe { good_unwind_rust_call(()) }
+}
diff --git a/src/tools/miri/tests/panic/function_calls/exported_symbol_good_unwind.stderr b/src/tools/miri/tests/panic/function_calls/exported_symbol_good_unwind.stderr
new file mode 100644
index 0000000000000..bff897775e8f6
--- /dev/null
+++ b/src/tools/miri/tests/panic/function_calls/exported_symbol_good_unwind.stderr
@@ -0,0 +1,4 @@
+thread 'main' panicked at 'explicit panic', $DIR/exported_symbol_good_unwind.rs:LL:CC
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
+thread 'main' panicked at 'explicit panic', $DIR/exported_symbol_good_unwind.rs:LL:CC
+thread 'main' panicked at 'explicit panic', $DIR/exported_symbol_good_unwind.rs:LL:CC
diff --git a/src/tools/miri/tests/panic/overflowing-lsh-neg.rs b/src/tools/miri/tests/panic/overflowing-lsh-neg.rs
new file mode 100644
index 0000000000000..bf5eed1c550f1
--- /dev/null
+++ b/src/tools/miri/tests/panic/overflowing-lsh-neg.rs
@@ -0,0 +1,5 @@
+#![allow(arithmetic_overflow)]
+
+fn main() {
+    let _n = 2i64 << -1;
+}
diff --git a/src/tools/miri/tests/panic/overflowing-lsh-neg.stderr b/src/tools/miri/tests/panic/overflowing-lsh-neg.stderr
new file mode 100644
index 0000000000000..21e434d873f7b
--- /dev/null
+++ b/src/tools/miri/tests/panic/overflowing-lsh-neg.stderr
@@ -0,0 +1,2 @@
+thread 'main' panicked at 'attempt to shift left with overflow', $DIR/overflowing-lsh-neg.rs:LL:CC
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
diff --git a/src/tools/miri/tests/panic/overflowing-rsh-1.rs b/src/tools/miri/tests/panic/overflowing-rsh-1.rs
new file mode 100644
index 0000000000000..4c0106f0fb1fd
--- /dev/null
+++ b/src/tools/miri/tests/panic/overflowing-rsh-1.rs
@@ -0,0 +1,5 @@
+#![allow(arithmetic_overflow)]
+
+fn main() {
+    let _n = 1i64 >> 64;
+}
diff --git a/src/tools/miri/tests/panic/overflowing-rsh-1.stderr b/src/tools/miri/tests/panic/overflowing-rsh-1.stderr
new file mode 100644
index 0000000000000..fd04bf1bd4ec3
--- /dev/null
+++ b/src/tools/miri/tests/panic/overflowing-rsh-1.stderr
@@ -0,0 +1,2 @@
+thread 'main' panicked at 'attempt to shift right with overflow', $DIR/overflowing-rsh-1.rs:LL:CC
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
diff --git a/src/tools/miri/tests/panic/overflowing-rsh-2.rs b/src/tools/miri/tests/panic/overflowing-rsh-2.rs
new file mode 100644
index 0000000000000..19d16e7bc84a2
--- /dev/null
+++ b/src/tools/miri/tests/panic/overflowing-rsh-2.rs
@@ -0,0 +1,6 @@
+#![allow(arithmetic_overflow)]
+
+fn main() {
+    // Make sure we catch overflows that would be hidden by first casting the RHS to u32
+    let _n = 1i64 >> (u32::MAX as i64 + 1);
+}
diff --git a/src/tools/miri/tests/panic/overflowing-rsh-2.stderr b/src/tools/miri/tests/panic/overflowing-rsh-2.stderr
new file mode 100644
index 0000000000000..eb568e4d742bc
--- /dev/null
+++ b/src/tools/miri/tests/panic/overflowing-rsh-2.stderr
@@ -0,0 +1,2 @@
+thread 'main' panicked at 'attempt to shift right with overflow', $DIR/overflowing-rsh-2.rs:LL:CC
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
diff --git a/src/tools/miri/tests/panic/panic1.rs b/src/tools/miri/tests/panic/panic1.rs
new file mode 100644
index 0000000000000..dbddf41fdbf71
--- /dev/null
+++ b/src/tools/miri/tests/panic/panic1.rs
@@ -0,0 +1,6 @@
+//@rustc-env: RUST_BACKTRACE=1
+//@compile-flags: -Zmiri-disable-isolation
+
+fn main() {
+    std::panic!("panicking from libstd");
+}
diff --git a/src/tools/miri/tests/panic/panic1.stderr b/src/tools/miri/tests/panic/panic1.stderr
new file mode 100644
index 0000000000000..15834d58bc6e5
--- /dev/null
+++ b/src/tools/miri/tests/panic/panic1.stderr
@@ -0,0 +1,9 @@
+thread 'main' panicked at 'panicking from libstd', $DIR/panic1.rs:LL:CC
+stack backtrace:
+   0: std::rt::begin_panic
+ at RUSTLIB/std/src/panicking.rs:LL:CC
+   1: main
+ at $DIR/panic1.rs:LL:CC
+   2: <fn() as std::ops::FnOnce<()>>::call_once - shim(fn())
+ at RUSTLIB/core/src/ops/function.rs:LL:CC
+note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
diff --git a/src/tools/miri/tests/panic/panic2.rs b/src/tools/miri/tests/panic/panic2.rs
new file mode 100644
index 0000000000000..d90e3f2e0ac13
--- /dev/null
+++ b/src/tools/miri/tests/panic/panic2.rs
@@ -0,0 +1,3 @@
+fn main() {
+    std::panic!("{}-panicking from libstd", 42);
+}
diff --git a/src/tools/miri/tests/panic/panic2.stderr b/src/tools/miri/tests/panic/panic2.stderr
new file mode 100644
index 0000000000000..c192ca3f64c36
--- /dev/null
+++ b/src/tools/miri/tests/panic/panic2.stderr
@@ -0,0 +1,2 @@
+thread 'main' panicked at '42-panicking from libstd', $DIR/panic2.rs:LL:CC
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
diff --git a/src/tools/miri/tests/panic/panic3.rs b/src/tools/miri/tests/panic/panic3.rs
new file mode 100644
index 0000000000000..418ee4f8411eb
--- /dev/null
+++ b/src/tools/miri/tests/panic/panic3.rs
@@ -0,0 +1,3 @@
+fn main() {
+    core::panic!("panicking from libcore");
+}
diff --git a/src/tools/miri/tests/panic/panic3.stderr b/src/tools/miri/tests/panic/panic3.stderr
new file mode 100644
index 0000000000000..0ce4a37fd5197
--- /dev/null
+++ b/src/tools/miri/tests/panic/panic3.stderr
@@ -0,0 +1,2 @@
+thread 'main' panicked at 'panicking from libcore', $DIR/panic3.rs:LL:CC
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
diff --git a/src/tools/miri/tests/panic/panic4.rs b/src/tools/miri/tests/panic/panic4.rs
new file mode 100644
index 0000000000000..0fcc53813b5d7
--- /dev/null
+++ b/src/tools/miri/tests/panic/panic4.rs
@@ -0,0 +1,3 @@
+fn main() {
+    core::panic!("{}-panicking from libcore", 42);
+}
diff --git a/src/tools/miri/tests/panic/panic4.stderr b/src/tools/miri/tests/panic/panic4.stderr
new file mode 100644
index 0000000000000..82df953b61c03
--- /dev/null
+++ b/src/tools/miri/tests/panic/panic4.stderr
@@ -0,0 +1,2 @@
+thread 'main' panicked at '42-panicking from libcore', $DIR/panic4.rs:LL:CC
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
diff --git a/src/tools/miri/tests/panic/transmute_fat2.rs b/src/tools/miri/tests/panic/transmute_fat2.rs
new file mode 100644
index 0000000000000..0205433ad9fb9
--- /dev/null
+++ b/src/tools/miri/tests/panic/transmute_fat2.rs
@@ -0,0 +1,10 @@
+fn main() {
+    #[cfg(all(target_endian = "little", target_pointer_width = "64"))]
+    let bad = unsafe { std::mem::transmute::<u128, &[u8]>(42) };
+    #[cfg(all(target_endian = "big", target_pointer_width = "64"))]
+    let bad = unsafe { std::mem::transmute::<u128, &[u8]>(42 << 64) };
+    #[cfg(all(target_endian = "little", target_pointer_width = "32"))]
+    let bad = unsafe { std::mem::transmute::<u64, &[u8]>(42) };
+    // This created a slice with length 0, so the following will fail the bounds check.
+    bad[0];
+}
diff --git a/src/tools/miri/tests/panic/transmute_fat2.stderr b/src/tools/miri/tests/panic/transmute_fat2.stderr
new file mode 100644
index 0000000000000..f497ab672550f
--- /dev/null
+++ b/src/tools/miri/tests/panic/transmute_fat2.stderr
@@ -0,0 +1,2 @@
+thread 'main' panicked at 'index out of bounds: the len is 0 but the index is 0', $DIR/transmute_fat2.rs:LL:CC
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
diff --git a/src/tools/miri/tests/panic/unsupported_foreign_function.rs b/src/tools/miri/tests/panic/unsupported_foreign_function.rs
new file mode 100644
index 0000000000000..a78646528fb12
--- /dev/null
+++ b/src/tools/miri/tests/panic/unsupported_foreign_function.rs
@@ -0,0 +1,11 @@
+//@compile-flags: -Zmiri-panic-on-unsupported
+
+fn main() {
+    extern "Rust" {
+        fn foo();
+    }
+
+    unsafe {
+        foo();
+    }
+}
diff --git a/src/tools/miri/tests/panic/unsupported_foreign_function.stderr b/src/tools/miri/tests/panic/unsupported_foreign_function.stderr
new file mode 100644
index 0000000000000..9af3e48655f38
--- /dev/null
+++ b/src/tools/miri/tests/panic/unsupported_foreign_function.stderr
@@ -0,0 +1,2 @@
+thread 'main' panicked at 'unsupported Miri functionality: can't call foreign function: foo', $DIR/unsupported_foreign_function.rs:LL:CC
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
diff --git a/src/tools/miri/tests/panic/unsupported_syscall.rs b/src/tools/miri/tests/panic/unsupported_syscall.rs
new file mode 100644
index 0000000000000..31d666e1d9d80
--- /dev/null
+++ b/src/tools/miri/tests/panic/unsupported_syscall.rs
@@ -0,0 +1,9 @@
+//@ignore-target-windows: No libc on Windows
+//@ignore-target-apple: `syscall` is not supported on macOS
+//@compile-flags: -Zmiri-panic-on-unsupported
+
+fn main() {
+    unsafe {
+        libc::syscall(0);
+    }
+}
diff --git a/src/tools/miri/tests/panic/unsupported_syscall.stderr b/src/tools/miri/tests/panic/unsupported_syscall.stderr
new file mode 100644
index 0000000000000..90aa5a9073638
--- /dev/null
+++ b/src/tools/miri/tests/panic/unsupported_syscall.stderr
@@ -0,0 +1,2 @@
+thread 'main' panicked at 'unsupported Miri functionality: can't execute syscall with ID 0', $DIR/unsupported_syscall.rs:LL:CC
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
diff --git a/src/tools/miri/tests/pass-dep/calloc.rs b/src/tools/miri/tests/pass-dep/calloc.rs
new file mode 100644
index 0000000000000..62ab63c5fc788
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/calloc.rs
@@ -0,0 +1,22 @@
+//@ignore-target-windows: No libc on Windows
+
+use core::slice;
+
+fn main() {
+    unsafe {
+        let p1 = libc::calloc(0, 0);
+        assert!(p1.is_null());
+
+        let p2 = libc::calloc(20, 0);
+        assert!(p2.is_null());
+
+        let p3 = libc::calloc(0, 20);
+        assert!(p3.is_null());
+
+        let p4 = libc::calloc(4, 8);
+        assert!(!p4.is_null());
+        let slice = slice::from_raw_parts(p4 as *const u8, 4 * 8);
+        assert_eq!(&slice, &[0_u8; 4 * 8]);
+        libc::free(p4);
+    }
+}
diff --git a/src/tools/miri/tests/pass-dep/concurrency/libc_pthread_cond.rs b/src/tools/miri/tests/pass-dep/concurrency/libc_pthread_cond.rs
new file mode 100644
index 0000000000000..b0325f7d78e50
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/concurrency/libc_pthread_cond.rs
@@ -0,0 +1,84 @@
+//@ignore-target-windows: No libc on Windows
+//@ignore-target-apple: pthread_condattr_setclock is not supported on MacOS.
+//@compile-flags: -Zmiri-disable-isolation
+
+/// Test that conditional variable timeouts are working properly with both
+/// monotonic and system clocks.
+use std::mem::MaybeUninit;
+use std::time::Instant;
+
+fn test_timed_wait_timeout(clock_id: i32) {
+    unsafe {
+        let mut attr: MaybeUninit<libc::pthread_condattr_t> = MaybeUninit::uninit();
+        assert_eq!(libc::pthread_condattr_init(attr.as_mut_ptr()), 0);
+        assert_eq!(libc::pthread_condattr_setclock(attr.as_mut_ptr(), clock_id), 0);
+
+        let mut cond: MaybeUninit<libc::pthread_cond_t> = MaybeUninit::uninit();
+        assert_eq!(libc::pthread_cond_init(cond.as_mut_ptr(), attr.as_ptr()), 0);
+        assert_eq!(libc::pthread_condattr_destroy(attr.as_mut_ptr()), 0);
+
+        let mut mutex: libc::pthread_mutex_t = libc::PTHREAD_MUTEX_INITIALIZER;
+
+        let mut now_mu: MaybeUninit<libc::timespec> = MaybeUninit::uninit();
+        assert_eq!(libc::clock_gettime(clock_id, now_mu.as_mut_ptr()), 0);
+        let now = now_mu.assume_init();
+        // Waiting for a second... mostly because waiting less requires mich more tricky arithmetic.
+        // FIXME: wait less.
+        let timeout = libc::timespec { tv_sec: now.tv_sec + 1, tv_nsec: now.tv_nsec };
+
+        assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0);
+        let current_time = Instant::now();
+        assert_eq!(
+            libc::pthread_cond_timedwait(cond.as_mut_ptr(), &mut mutex as *mut _, &timeout),
+            libc::ETIMEDOUT
+        );
+        let elapsed_time = current_time.elapsed().as_millis();
+        assert!(900 <= elapsed_time && elapsed_time <= 1300);
+
+        // Test calling `pthread_cond_timedwait` again with an already elapsed timeout.
+        assert_eq!(
+            libc::pthread_cond_timedwait(cond.as_mut_ptr(), &mut mutex as *mut _, &timeout),
+            libc::ETIMEDOUT
+        );
+
+        // Test that invalid nanosecond values (above 10^9 or negative) are rejected with the
+        // correct error code.
+        let invalid_timeout_1 = libc::timespec { tv_sec: now.tv_sec + 1, tv_nsec: 1_000_000_000 };
+        assert_eq!(
+            libc::pthread_cond_timedwait(
+                cond.as_mut_ptr(),
+                &mut mutex as *mut _,
+                &invalid_timeout_1
+            ),
+            libc::EINVAL
+        );
+        let invalid_timeout_2 = libc::timespec { tv_sec: now.tv_sec + 1, tv_nsec: -1 };
+        assert_eq!(
+            libc::pthread_cond_timedwait(
+                cond.as_mut_ptr(),
+                &mut mutex as *mut _,
+                &invalid_timeout_2
+            ),
+            libc::EINVAL
+        );
+        // Test that invalid second values (negative) are rejected with the correct error code.
+        let invalid_timeout_3 = libc::timespec { tv_sec: -1, tv_nsec: 0 };
+        assert_eq!(
+            libc::pthread_cond_timedwait(
+                cond.as_mut_ptr(),
+                &mut mutex as *mut _,
+                &invalid_timeout_3
+            ),
+            libc::EINVAL
+        );
+
+        assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0);
+        assert_eq!(libc::pthread_mutex_destroy(&mut mutex as *mut _), 0);
+        assert_eq!(libc::pthread_cond_destroy(cond.as_mut_ptr()), 0);
+    }
+}
+
+fn main() {
+    test_timed_wait_timeout(libc::CLOCK_MONOTONIC);
+    test_timed_wait_timeout(libc::CLOCK_REALTIME);
+}
diff --git a/src/tools/miri/tests/pass-dep/concurrency/linux-futex.rs b/src/tools/miri/tests/pass-dep/concurrency/linux-futex.rs
new file mode 100644
index 0000000000000..a456528ec2074
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/concurrency/linux-futex.rs
@@ -0,0 +1,291 @@
+//@only-target-linux
+//@compile-flags: -Zmiri-disable-isolation
+
+use std::mem::MaybeUninit;
+use std::ptr;
+use std::sync::atomic::AtomicI32;
+use std::sync::atomic::Ordering;
+use std::thread;
+use std::time::{Duration, Instant};
+
+fn wake_nobody() {
+    let futex = 0;
+
+    // Wake 1 waiter. Expect zero waiters woken up, as nobody is waiting.
+    unsafe {
+        assert_eq!(libc::syscall(libc::SYS_futex, &futex as *const i32, libc::FUTEX_WAKE, 1), 0);
+    }
+
+    // Same, but without omitting the unused arguments.
+    unsafe {
+        assert_eq!(
+            libc::syscall(
+                libc::SYS_futex,
+                &futex as *const i32,
+                libc::FUTEX_WAKE,
+                1,
+                ptr::null::<libc::timespec>(),
+                0usize,
+                0,
+            ),
+            0,
+        );
+    }
+}
+
+fn wake_dangling() {
+    let futex = Box::new(0);
+    let ptr: *const i32 = &*futex;
+    drop(futex);
+
+    // Wake 1 waiter. Expect zero waiters woken up, as nobody is waiting.
+    unsafe {
+        assert_eq!(libc::syscall(libc::SYS_futex, ptr, libc::FUTEX_WAKE, 1), 0);
+    }
+}
+
+fn wait_wrong_val() {
+    let futex: i32 = 123;
+
+    // Only wait if the futex value is 456.
+    unsafe {
+        assert_eq!(
+            libc::syscall(
+                libc::SYS_futex,
+                &futex as *const i32,
+                libc::FUTEX_WAIT,
+                456,
+                ptr::null::<libc::timespec>(),
+            ),
+            -1,
+        );
+        assert_eq!(*libc::__errno_location(), libc::EAGAIN);
+    }
+}
+
+fn wait_timeout() {
+    let start = Instant::now();
+
+    let futex: i32 = 123;
+
+    // Wait for 200ms, with nobody waking us up early.
+    unsafe {
+        assert_eq!(
+            libc::syscall(
+                libc::SYS_futex,
+                &futex as *const i32,
+                libc::FUTEX_WAIT,
+                123,
+                &libc::timespec { tv_sec: 0, tv_nsec: 200_000_000 },
+            ),
+            -1,
+        );
+        assert_eq!(*libc::__errno_location(), libc::ETIMEDOUT);
+    }
+
+    assert!((200..1000).contains(&start.elapsed().as_millis()));
+}
+
+fn wait_absolute_timeout() {
+    let start = Instant::now();
+
+    // Get the current monotonic timestamp as timespec.
+    let mut timeout = unsafe {
+        let mut now: MaybeUninit<libc::timespec> = MaybeUninit::uninit();
+        assert_eq!(libc::clock_gettime(libc::CLOCK_MONOTONIC, now.as_mut_ptr()), 0);
+        now.assume_init()
+    };
+
+    // Add 200ms.
+    timeout.tv_nsec += 200_000_000;
+    if timeout.tv_nsec > 1_000_000_000 {
+        timeout.tv_nsec -= 1_000_000_000;
+        timeout.tv_sec += 1;
+    }
+
+    let futex: i32 = 123;
+
+    // Wait for 200ms from now, with nobody waking us up early.
+    unsafe {
+        assert_eq!(
+            libc::syscall(
+                libc::SYS_futex,
+                &futex as *const i32,
+                libc::FUTEX_WAIT_BITSET,
+                123,
+                &timeout,
+                0usize,
+                u32::MAX,
+            ),
+            -1,
+        );
+        assert_eq!(*libc::__errno_location(), libc::ETIMEDOUT);
+    }
+
+    assert!((200..1000).contains(&start.elapsed().as_millis()));
+}
+
+fn wait_wake() {
+    let start = Instant::now();
+
+    static mut FUTEX: i32 = 0;
+
+    let t = thread::spawn(move || {
+        thread::sleep(Duration::from_millis(200));
+        unsafe {
+            assert_eq!(
+                libc::syscall(
+                    libc::SYS_futex,
+                    &FUTEX as *const i32,
+                    libc::FUTEX_WAKE,
+                    10, // Wake up at most 10 threads.
+                ),
+                1, // Woken up one thread.
+            );
+        }
+    });
+
+    unsafe {
+        assert_eq!(
+            libc::syscall(
+                libc::SYS_futex,
+                &FUTEX as *const i32,
+                libc::FUTEX_WAIT,
+                0,
+                ptr::null::<libc::timespec>(),
+            ),
+            0,
+        );
+    }
+
+    assert!((200..1000).contains(&start.elapsed().as_millis()));
+    t.join().unwrap();
+}
+
+fn wait_wake_bitset() {
+    let start = Instant::now();
+
+    static mut FUTEX: i32 = 0;
+
+    let t = thread::spawn(move || {
+        thread::sleep(Duration::from_millis(200));
+        unsafe {
+            assert_eq!(
+                libc::syscall(
+                    libc::SYS_futex,
+                    &FUTEX as *const i32,
+                    libc::FUTEX_WAKE_BITSET,
+                    10, // Wake up at most 10 threads.
+                    ptr::null::<libc::timespec>(),
+                    0usize,
+                    0b1001, // bitset
+                ),
+                0, // Didn't match any thread.
+            );
+        }
+        thread::sleep(Duration::from_millis(200));
+        unsafe {
+            assert_eq!(
+                libc::syscall(
+                    libc::SYS_futex,
+                    &FUTEX as *const i32,
+                    libc::FUTEX_WAKE_BITSET,
+                    10, // Wake up at most 10 threads.
+                    ptr::null::<libc::timespec>(),
+                    0usize,
+                    0b0110, // bitset
+                ),
+                1, // Woken up one thread.
+            );
+        }
+    });
+
+    unsafe {
+        assert_eq!(
+            libc::syscall(
+                libc::SYS_futex,
+                &FUTEX as *const i32,
+                libc::FUTEX_WAIT_BITSET,
+                0,
+                ptr::null::<libc::timespec>(),
+                0usize,
+                0b0100, // bitset
+            ),
+            0,
+        );
+    }
+
+    assert!((400..1000).contains(&start.elapsed().as_millis()));
+    t.join().unwrap();
+}
+
+fn concurrent_wait_wake() {
+    const FREE: i32 = 0;
+    const HELD: i32 = 1;
+
+    static FUTEX: AtomicI32 = AtomicI32::new(0);
+    static mut DATA: i32 = 0;
+    static WOKEN: AtomicI32 = AtomicI32::new(0);
+
+    let rounds = 50;
+    for _ in 0..rounds {
+        unsafe { DATA = 0 }; // Reset
+        // Suppose the main thread is holding a lock implemented using futex...
+        FUTEX.store(HELD, Ordering::Relaxed);
+
+        let t = thread::spawn(move || {
+            // If this syscall runs first, then we'll be woken up by
+            // the main thread's FUTEX_WAKE, and all is fine.
+            //
+            // If this sycall runs after the main thread's store
+            // and FUTEX_WAKE, the syscall must observe that
+            // the FUTEX is FREE != HELD and return without waiting
+            // or we'll deadlock.
+            unsafe {
+                let ret = libc::syscall(
+                    libc::SYS_futex,
+                    &FUTEX as *const AtomicI32,
+                    libc::FUTEX_WAIT,
+                    HELD,
+                    ptr::null::<libc::timespec>(),
+                );
+                if ret == 0 {
+                    // We actually slept. And then woke up again. So we should be ordered-after
+                    // what happened-before the FUTEX_WAKE. So this is not a race.
+                    assert_eq!(DATA, 1);
+                    // Also remember that this happened at least once.
+                    WOKEN.fetch_add(1, Ordering::Relaxed);
+                }
+            }
+        });
+        // Increase the chance that the other thread actually goes to sleep.
+        // (5 yields in a loop seem to make that happen around 40% of the time.)
+        for _ in 0..5 {
+            thread::yield_now();
+        }
+
+        FUTEX.store(FREE, Ordering::Relaxed);
+        unsafe {
+            DATA = 1;
+            libc::syscall(libc::SYS_futex, &FUTEX as *const AtomicI32, libc::FUTEX_WAKE, 1);
+        }
+
+        t.join().unwrap();
+    }
+
+    // Make sure we got the interesting case (of having woken a thread) at least once, but not *each* time.
+    let woken = WOKEN.load(Ordering::Relaxed);
+    //eprintln!("waking happened {woken} times");
+    assert!(woken > 0 && woken < rounds);
+}
+
+fn main() {
+    wake_nobody();
+    wake_dangling();
+    wait_wrong_val();
+    wait_timeout();
+    wait_absolute_timeout();
+    wait_wake();
+    wait_wake_bitset();
+    concurrent_wait_wake();
+}
diff --git a/src/tools/miri/tests/pass-dep/concurrency/tls_pthread_drop_order.rs b/src/tools/miri/tests/pass-dep/concurrency/tls_pthread_drop_order.rs
new file mode 100644
index 0000000000000..6516396ac5404
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/concurrency/tls_pthread_drop_order.rs
@@ -0,0 +1,73 @@
+//@ignore-target-windows: No libc on Windows
+//! Test that pthread_key destructors are run in the right order.
+//! Note that these are *not* used by actual `thread_local!` on Linux! Those use
+//! `thread_local_dtor::register_dtor` from the stdlib instead. In Miri this hits the fallback path
+//! in `register_dtor_fallback`, which uses a *single* pthread_key to manage a thread-local list of
+//! dtors to call.
+
+use std::mem;
+use std::ptr;
+
+pub type Key = libc::pthread_key_t;
+
+static mut RECORD: usize = 0;
+static mut KEYS: [Key; 2] = [0; 2];
+static mut GLOBALS: [u64; 2] = [1, 0];
+
+static mut CANNARY: *mut u64 = ptr::null_mut(); // this serves as a cannary: if TLS dtors are not run properly, this will not get deallocated, making the test fail.
+
+pub unsafe fn create(dtor: Option<unsafe extern "C" fn(*mut u8)>) -> Key {
+    let mut key = 0;
+    assert_eq!(libc::pthread_key_create(&mut key, mem::transmute(dtor)), 0);
+    key
+}
+
+pub unsafe fn set(key: Key, value: *mut u8) {
+    let r = libc::pthread_setspecific(key, value as *mut _);
+    assert_eq!(r, 0);
+}
+
+pub fn record(r: usize) {
+    assert!(r < 10);
+    unsafe { RECORD = RECORD * 10 + r };
+}
+
+unsafe extern "C" fn dtor(ptr: *mut u64) {
+    assert!(CANNARY != ptr::null_mut()); // make sure we do not get run too often
+    let val = *ptr;
+
+    let which_key =
+        GLOBALS.iter().position(|global| global as *const _ == ptr).expect("Should find my global");
+    record(which_key);
+
+    if val > 0 {
+        *ptr = val - 1;
+        set(KEYS[which_key], ptr as *mut _);
+    }
+
+    // Check if the records matches what we expect. If yes, clear the cannary.
+    // If the record is wrong, the cannary will never get cleared, leading to a leak -> test fails.
+    // If the record is incomplete (i.e., more dtor calls happen), the check at the beginning of this function will fail -> test fails.
+    // The correct sequence is: First key 0, then key 1, then key 0.
+    // Note that this relies on dtor order, which is not specified by POSIX, but seems to be
+    // consistent between Miri and Linux currently (as of Aug 2022).
+    if RECORD == 0_1_0 {
+        drop(Box::from_raw(CANNARY));
+        CANNARY = ptr::null_mut();
+    }
+}
+
+fn main() {
+    unsafe {
+        create(None); // check that the no-dtor case works
+
+        // Initialize the keys we use to check destructor ordering
+        for (key, global) in KEYS.iter_mut().zip(GLOBALS.iter_mut()) {
+            *key = create(Some(mem::transmute(dtor as unsafe extern "C" fn(*mut u64))));
+            set(*key, global as *mut _ as *mut u8);
+        }
+
+        // Initialize cannary
+        CANNARY = Box::into_raw(Box::new(0u64));
+    }
+}
diff --git a/src/tools/miri/tests/pass-dep/foreign-fn-linkname.rs b/src/tools/miri/tests/pass-dep/foreign-fn-linkname.rs
new file mode 100644
index 0000000000000..9f090a4eff5d8
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/foreign-fn-linkname.rs
@@ -0,0 +1,20 @@
+use std::ffi::CString;
+
+mod mlibc {
+    use libc::{c_char, size_t};
+    extern "C" {
+        #[link_name = "strlen"]
+        pub fn my_strlen(str: *const c_char) -> size_t;
+    }
+}
+
+fn strlen(str: String) -> usize {
+    // C string is terminated with a zero
+    let s = CString::new(str).unwrap();
+    unsafe { mlibc::my_strlen(s.as_ptr()) as usize }
+}
+
+pub fn main() {
+    let len = strlen("Rust".to_string());
+    assert_eq!(len, 4);
+}
diff --git a/src/tools/miri/tests/pass-dep/malloc.rs b/src/tools/miri/tests/pass-dep/malloc.rs
new file mode 100644
index 0000000000000..f5e014c000d15
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/malloc.rs
@@ -0,0 +1,49 @@
+//@ignore-target-windows: No libc on Windows
+
+use core::{ptr, slice};
+
+fn main() {
+    // Test that small allocations sometimes *are* not very aligned.
+    let saw_unaligned = (0..64).any(|_| unsafe {
+        let p = libc::malloc(3);
+        libc::free(p);
+        (p as usize) % 4 != 0 // find any that this is *not* 4-aligned
+    });
+    assert!(saw_unaligned);
+
+    unsafe {
+        // Use calloc for initialized memory
+        let p1 = libc::calloc(20, 1);
+
+        // old size < new size
+        let p2 = libc::realloc(p1, 40);
+        let slice = slice::from_raw_parts(p2 as *const u8, 20);
+        assert_eq!(&slice, &[0_u8; 20]);
+
+        // old size == new size
+        let p3 = libc::realloc(p2, 40);
+        let slice = slice::from_raw_parts(p3 as *const u8, 20);
+        assert_eq!(&slice, &[0_u8; 20]);
+
+        // old size > new size
+        let p4 = libc::realloc(p3, 10);
+        let slice = slice::from_raw_parts(p4 as *const u8, 10);
+        assert_eq!(&slice, &[0_u8; 10]);
+
+        libc::free(p4);
+    }
+
+    unsafe {
+        let p1 = libc::malloc(20);
+
+        let p2 = libc::realloc(p1, 0);
+        assert!(p2.is_null());
+    }
+
+    unsafe {
+        let p1 = libc::realloc(ptr::null_mut(), 20);
+        assert!(!p1.is_null());
+
+        libc::free(p1);
+    }
+}
diff --git a/src/tools/miri/tests/pass-dep/num_cpus.rs b/src/tools/miri/tests/pass-dep/num_cpus.rs
new file mode 100644
index 0000000000000..84339feb11e17
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/num_cpus.rs
@@ -0,0 +1,5 @@
+//@compile-flags: -Zmiri-disable-isolation
+
+fn main() {
+    assert_eq!(num_cpus::get(), 1);
+}
diff --git a/src/tools/miri/tests/pass-dep/page_size.rs b/src/tools/miri/tests/pass-dep/page_size.rs
new file mode 100644
index 0000000000000..cdcabf3333814
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/page_size.rs
@@ -0,0 +1,6 @@
+fn main() {
+    let page_size = page_size::get();
+
+    // In particular, this checks that it is not 0.
+    assert!(page_size.is_power_of_two(), "page size not a power of two: {}", page_size);
+}
diff --git a/src/tools/miri/tests/pass-dep/random.rs b/src/tools/miri/tests/pass-dep/random.rs
new file mode 100644
index 0000000000000..5eccf3b0ea115
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/random.rs
@@ -0,0 +1,22 @@
+// mac-os `getrandom_1` does some pointer shenanigans
+//@compile-flags: -Zmiri-permissive-provenance
+use rand::{rngs::SmallRng, Rng, SeedableRng};
+
+fn main() {
+    // Test `getrandom` directly (in multiple different versions).
+    let mut data = vec![0; 16];
+    getrandom_1::getrandom(&mut data).unwrap();
+    getrandom_2::getrandom(&mut data).unwrap();
+
+    // Try seeding with "real" entropy.
+    let mut rng = SmallRng::from_entropy();
+    let _val = rng.gen::<i32>();
+    let _val = rng.gen::<isize>();
+    let _val = rng.gen::<i128>();
+
+    // Also try per-thread RNG.
+    let mut rng = rand::thread_rng();
+    let _val = rng.gen::<i32>();
+    let _val = rng.gen::<isize>();
+    let _val = rng.gen::<i128>();
+}
diff --git a/src/tools/miri/tests/pass-dep/regions-mock-trans.rs b/src/tools/miri/tests/pass-dep/regions-mock-trans.rs
new file mode 100644
index 0000000000000..57f1b75f4d52b
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/regions-mock-trans.rs
@@ -0,0 +1,45 @@
+use std::mem;
+
+struct Arena(());
+
+struct Bcx<'a> {
+    fcx: &'a Fcx<'a>,
+}
+
+#[allow(dead_code)]
+struct Fcx<'a> {
+    arena: &'a Arena,
+    ccx: &'a Ccx,
+}
+
+#[allow(dead_code)]
+struct Ccx {
+    x: isize,
+}
+
+fn alloc<'a>(_bcx: &'a Arena) -> &'a mut Bcx<'a> {
+    unsafe { mem::transmute(libc::malloc(mem::size_of::<Bcx<'a>>() as libc::size_t)) }
+}
+
+fn h<'a>(bcx: &'a Bcx<'a>) -> &'a mut Bcx<'a> {
+    return alloc(bcx.fcx.arena);
+}
+
+fn g(fcx: &Fcx) {
+    let bcx = Bcx { fcx: fcx };
+    let bcx2 = h(&bcx);
+    unsafe {
+        libc::free(mem::transmute(bcx2));
+    }
+}
+
+fn f(ccx: &Ccx) {
+    let a = Arena(());
+    let fcx = Fcx { arena: &a, ccx: ccx };
+    return g(&fcx);
+}
+
+pub fn main() {
+    let ccx = Ccx { x: 0 };
+    f(&ccx);
+}
diff --git a/src/tools/miri/tests/pass-dep/shims/env-cleanup-data-race.rs b/src/tools/miri/tests/pass-dep/shims/env-cleanup-data-race.rs
new file mode 100644
index 0000000000000..d36ffe70321b4
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/shims/env-cleanup-data-race.rs
@@ -0,0 +1,23 @@
+//@compile-flags: -Zmiri-disable-isolation -Zmiri-preemption-rate=0
+//@ignore-target-windows: No libc on Windows
+
+use std::ffi::CStr;
+use std::ffi::CString;
+use std::thread;
+
+fn main() {
+    unsafe {
+        thread::spawn(|| {
+            // Access the environment in another thread without taking the env lock
+            let k = CString::new("MIRI_ENV_VAR_TEST".as_bytes()).unwrap();
+            let s = libc::getenv(k.as_ptr()) as *const libc::c_char;
+            if s.is_null() {
+                panic!("null");
+            }
+            let _s = String::from_utf8_lossy(CStr::from_ptr(s).to_bytes());
+        });
+        thread::yield_now();
+        // After the main thread exits, env vars will be cleaned up -- but because we have not *joined*
+        // the other thread, those accesses technically race with those in the other thread.
+    }
+}
diff --git a/src/tools/miri/tests/pass-dep/shims/fs.rs b/src/tools/miri/tests/pass-dep/shims/fs.rs
new file mode 100644
index 0000000000000..9faced0291612
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/shims/fs.rs
@@ -0,0 +1,445 @@
+//@ignore-target-windows: File handling is not implemented yet
+//@compile-flags: -Zmiri-disable-isolation
+
+#![feature(io_error_more)]
+#![feature(io_error_uncategorized)]
+
+use std::ffi::CString;
+use std::fs::{
+    create_dir, read_dir, read_link, remove_dir, remove_dir_all, remove_file, rename, File,
+    OpenOptions,
+};
+use std::io::{Error, ErrorKind, Read, Result, Seek, SeekFrom, Write};
+use std::path::{Path, PathBuf};
+
+fn main() {
+    test_file();
+    test_file_clone();
+    test_file_create_new();
+    test_seek();
+    test_metadata();
+    test_file_set_len();
+    test_file_sync();
+    test_symlink();
+    test_errors();
+    test_rename();
+    test_directory();
+    test_canonicalize();
+    test_dup_stdout_stderr();
+    test_from_raw_os_error();
+
+    // These all require unix, if the test is changed to no longer `ignore-windows`, move these to a unix test
+    test_file_open_unix_allow_two_args();
+    test_file_open_unix_needs_three_args();
+    test_file_open_unix_extra_third_arg();
+}
+
+fn tmp() -> PathBuf {
+    std::env::var("MIRI_TEMP")
+        .map(|tmp| {
+            // MIRI_TEMP is set outside of our emulated
+            // program, so it may have path separators that don't
+            // correspond to our target platform. We normalize them here
+            // before constructing a `PathBuf`
+
+            #[cfg(windows)]
+            return PathBuf::from(tmp.replace("/", "\\"));
+
+            #[cfg(not(windows))]
+            return PathBuf::from(tmp.replace("\\", "/"));
+        })
+        .unwrap_or_else(|_| std::env::temp_dir())
+}
+
+/// Prepare: compute filename and make sure the file does not exist.
+fn prepare(filename: &str) -> PathBuf {
+    let path = tmp().join(filename);
+    // Clean the paths for robustness.
+    remove_file(&path).ok();
+    path
+}
+
+/// Prepare directory: compute directory name and make sure it does not exist.
+fn prepare_dir(dirname: &str) -> PathBuf {
+    let path = tmp().join(&dirname);
+    // Clean the directory for robustness.
+    remove_dir_all(&path).ok();
+    path
+}
+
+/// Prepare like above, and also write some initial content to the file.
+fn prepare_with_content(filename: &str, content: &[u8]) -> PathBuf {
+    let path = prepare(filename);
+    let mut file = File::create(&path).unwrap();
+    file.write(content).unwrap();
+    path
+}
+
+fn test_file() {
+    let bytes = b"Hello, World!\n";
+    let path = prepare("miri_test_fs_file.txt");
+
+    // Test creating, writing and closing a file (closing is tested when `file` is dropped).
+    let mut file = File::create(&path).unwrap();
+    // Writing 0 bytes should not change the file contents.
+    file.write(&mut []).unwrap();
+    assert_eq!(file.metadata().unwrap().len(), 0);
+
+    file.write(bytes).unwrap();
+    assert_eq!(file.metadata().unwrap().len(), bytes.len() as u64);
+    // Test opening, reading and closing a file.
+    let mut file = File::open(&path).unwrap();
+    let mut contents = Vec::new();
+    // Reading 0 bytes should not move the file pointer.
+    file.read(&mut []).unwrap();
+    // Reading until EOF should get the whole text.
+    file.read_to_end(&mut contents).unwrap();
+    assert_eq!(bytes, contents.as_slice());
+
+    // Removing file should succeed.
+    remove_file(&path).unwrap();
+}
+
+fn test_file_open_unix_allow_two_args() {
+    use std::os::unix::ffi::OsStrExt;
+
+    let path = prepare_with_content("test_file_open_unix_allow_two_args.txt", &[]);
+
+    let mut name = path.into_os_string();
+    name.push("\0");
+    let name_ptr = name.as_bytes().as_ptr().cast::<libc::c_char>();
+    let _fd = unsafe { libc::open(name_ptr, libc::O_RDONLY) };
+}
+
+fn test_file_open_unix_needs_three_args() {
+    use std::os::unix::ffi::OsStrExt;
+
+    let path = prepare_with_content("test_file_open_unix_needs_three_args.txt", &[]);
+
+    let mut name = path.into_os_string();
+    name.push("\0");
+    let name_ptr = name.as_bytes().as_ptr().cast::<libc::c_char>();
+    let _fd = unsafe { libc::open(name_ptr, libc::O_CREAT, 0o666) };
+}
+
+fn test_file_open_unix_extra_third_arg() {
+    use std::os::unix::ffi::OsStrExt;
+
+    let path = prepare_with_content("test_file_open_unix_extra_third_arg.txt", &[]);
+
+    let mut name = path.into_os_string();
+    name.push("\0");
+    let name_ptr = name.as_bytes().as_ptr().cast::<libc::c_char>();
+    let _fd = unsafe { libc::open(name_ptr, libc::O_RDONLY, 42) };
+}
+
+fn test_file_clone() {
+    let bytes = b"Hello, World!\n";
+    let path = prepare_with_content("miri_test_fs_file_clone.txt", bytes);
+
+    // Cloning a file should be successful.
+    let file = File::open(&path).unwrap();
+    let mut cloned = file.try_clone().unwrap();
+    // Reading from a cloned file should get the same text.
+    let mut contents = Vec::new();
+    cloned.read_to_end(&mut contents).unwrap();
+    assert_eq!(bytes, contents.as_slice());
+
+    // Removing file should succeed.
+    remove_file(&path).unwrap();
+}
+
+fn test_file_create_new() {
+    let path = prepare("miri_test_fs_file_create_new.txt");
+
+    // Creating a new file that doesn't yet exist should succeed.
+    OpenOptions::new().write(true).create_new(true).open(&path).unwrap();
+    // Creating a new file that already exists should fail.
+    assert_eq!(
+        ErrorKind::AlreadyExists,
+        OpenOptions::new().write(true).create_new(true).open(&path).unwrap_err().kind()
+    );
+    // Optionally creating a new file that already exists should succeed.
+    OpenOptions::new().write(true).create(true).open(&path).unwrap();
+
+    // Clean up
+    remove_file(&path).unwrap();
+}
+
+fn test_seek() {
+    let bytes = b"Hello, entire World!\n";
+    let path = prepare_with_content("miri_test_fs_seek.txt", bytes);
+
+    let mut file = File::open(&path).unwrap();
+    let mut contents = Vec::new();
+    file.read_to_end(&mut contents).unwrap();
+    assert_eq!(bytes, contents.as_slice());
+    // Test that seeking to the beginning and reading until EOF gets the text again.
+    file.seek(SeekFrom::Start(0)).unwrap();
+    let mut contents = Vec::new();
+    file.read_to_end(&mut contents).unwrap();
+    assert_eq!(bytes, contents.as_slice());
+    // Test seeking relative to the end of the file.
+    file.seek(SeekFrom::End(-1)).unwrap();
+    let mut contents = Vec::new();
+    file.read_to_end(&mut contents).unwrap();
+    assert_eq!(&bytes[bytes.len() - 1..], contents.as_slice());
+    // Test seeking relative to the current position.
+    file.seek(SeekFrom::Start(5)).unwrap();
+    file.seek(SeekFrom::Current(-3)).unwrap();
+    let mut contents = Vec::new();
+    file.read_to_end(&mut contents).unwrap();
+    assert_eq!(&bytes[2..], contents.as_slice());
+
+    // Removing file should succeed.
+    remove_file(&path).unwrap();
+}
+
+fn check_metadata(bytes: &[u8], path: &Path) -> Result<()> {
+    // Test that the file metadata is correct.
+    let metadata = path.metadata()?;
+    // `path` should point to a file.
+    assert!(metadata.is_file());
+    // The size of the file must be equal to the number of written bytes.
+    assert_eq!(bytes.len() as u64, metadata.len());
+    Ok(())
+}
+
+fn test_metadata() {
+    let bytes = b"Hello, meta-World!\n";
+    let path = prepare_with_content("miri_test_fs_metadata.txt", bytes);
+
+    // Test that metadata of an absolute path is correct.
+    check_metadata(bytes, &path).unwrap();
+    // Test that metadata of a relative path is correct.
+    std::env::set_current_dir(path.parent().unwrap()).unwrap();
+    check_metadata(bytes, Path::new(path.file_name().unwrap())).unwrap();
+
+    // Removing file should succeed.
+    remove_file(&path).unwrap();
+}
+
+fn test_file_set_len() {
+    let bytes = b"Hello, World!\n";
+    let path = prepare_with_content("miri_test_fs_set_len.txt", bytes);
+
+    // Test extending the file
+    let mut file = OpenOptions::new().read(true).write(true).open(&path).unwrap();
+    let bytes_extended = b"Hello, World!\n\x00\x00\x00\x00\x00\x00";
+    file.set_len(20).unwrap();
+    let mut contents = Vec::new();
+    file.read_to_end(&mut contents).unwrap();
+    assert_eq!(bytes_extended, contents.as_slice());
+
+    // Test truncating the file
+    file.seek(SeekFrom::Start(0)).unwrap();
+    file.set_len(10).unwrap();
+    let mut contents = Vec::new();
+    file.read_to_end(&mut contents).unwrap();
+    assert_eq!(&bytes[..10], contents.as_slice());
+
+    // Can't use set_len on a file not opened for writing
+    let file = OpenOptions::new().read(true).open(&path).unwrap();
+    assert_eq!(ErrorKind::InvalidInput, file.set_len(14).unwrap_err().kind());
+
+    remove_file(&path).unwrap();
+}
+
+fn test_file_sync() {
+    let bytes = b"Hello, World!\n";
+    let path = prepare_with_content("miri_test_fs_sync.txt", bytes);
+
+    // Test that we can call sync_data and sync_all (can't readily test effects of this operation)
+    let file = OpenOptions::new().write(true).open(&path).unwrap();
+    file.sync_data().unwrap();
+    file.sync_all().unwrap();
+
+    // Test that we can call sync_data and sync_all on a file opened for reading.
+    let file = File::open(&path).unwrap();
+    file.sync_data().unwrap();
+    file.sync_all().unwrap();
+
+    remove_file(&path).unwrap();
+}
+
+fn test_symlink() {
+    let bytes = b"Hello, World!\n";
+    let path = prepare_with_content("miri_test_fs_link_target.txt", bytes);
+    let symlink_path = prepare("miri_test_fs_symlink.txt");
+
+    // Creating a symbolic link should succeed.
+    #[cfg(unix)]
+    std::os::unix::fs::symlink(&path, &symlink_path).unwrap();
+    #[cfg(windows)]
+    std::os::windows::fs::symlink_file(&path, &symlink_path).unwrap();
+    // Test that the symbolic link has the same contents as the file.
+    let mut symlink_file = File::open(&symlink_path).unwrap();
+    let mut contents = Vec::new();
+    symlink_file.read_to_end(&mut contents).unwrap();
+    assert_eq!(bytes, contents.as_slice());
+
+    #[cfg(unix)]
+    {
+        use std::os::unix::ffi::OsStrExt;
+
+        let expected_path = path.as_os_str().as_bytes();
+
+        // Test that the expected string gets written to a buffer of proper
+        // length, and that a trailing null byte is not written.
+        let symlink_c_str = CString::new(symlink_path.as_os_str().as_bytes()).unwrap();
+        let symlink_c_ptr = symlink_c_str.as_ptr();
+
+        // Make the buf one byte larger than it needs to be,
+        // and check that the last byte is not overwritten.
+        let mut large_buf = vec![0xFF; expected_path.len() + 1];
+        let res = unsafe {
+            libc::readlink(symlink_c_ptr, large_buf.as_mut_ptr().cast(), large_buf.len())
+        };
+        // Check that the resovled path was properly written into the buf.
+        assert_eq!(&large_buf[..(large_buf.len() - 1)], expected_path);
+        assert_eq!(large_buf.last(), Some(&0xFF));
+        assert_eq!(res, large_buf.len() as isize - 1);
+
+        // Test that the resolved path is truncated if the provided buffer
+        // is too small.
+        let mut small_buf = [0u8; 2];
+        let res = unsafe {
+            libc::readlink(symlink_c_ptr, small_buf.as_mut_ptr().cast(), small_buf.len())
+        };
+        assert_eq!(small_buf, &expected_path[..small_buf.len()]);
+        assert_eq!(res, small_buf.len() as isize);
+
+        // Test that we report a proper error for a missing path.
+        let bad_path = CString::new("MIRI_MISSING_FILE_NAME").unwrap();
+        let res = unsafe {
+            libc::readlink(bad_path.as_ptr(), small_buf.as_mut_ptr().cast(), small_buf.len())
+        };
+        assert_eq!(res, -1);
+        assert_eq!(Error::last_os_error().kind(), ErrorKind::NotFound);
+    }
+
+    // Test that metadata of a symbolic link (i.e., the file it points to) is correct.
+    check_metadata(bytes, &symlink_path).unwrap();
+    // Test that the metadata of a symbolic link is correct when not following it.
+    assert!(symlink_path.symlink_metadata().unwrap().file_type().is_symlink());
+    // Check that we can follow the link.
+    assert_eq!(read_link(&symlink_path).unwrap(), path);
+    // Removing symbolic link should succeed.
+    remove_file(&symlink_path).unwrap();
+
+    // Removing file should succeed.
+    remove_file(&path).unwrap();
+}
+
+fn test_errors() {
+    let bytes = b"Hello, World!\n";
+    let path = prepare("miri_test_fs_errors.txt");
+
+    // The following tests also check that the `__errno_location()` shim is working properly.
+    // Opening a non-existing file should fail with a "not found" error.
+    assert_eq!(ErrorKind::NotFound, File::open(&path).unwrap_err().kind());
+    // Make sure we can also format this.
+    format!("{0:?}: {0}", File::open(&path).unwrap_err());
+    // Removing a non-existing file should fail with a "not found" error.
+    assert_eq!(ErrorKind::NotFound, remove_file(&path).unwrap_err().kind());
+    // Reading the metadata of a non-existing file should fail with a "not found" error.
+    assert_eq!(ErrorKind::NotFound, check_metadata(bytes, &path).unwrap_err().kind());
+}
+
+fn test_rename() {
+    // Renaming a file should succeed.
+    let path1 = prepare("miri_test_fs_rename_source.txt");
+    let path2 = prepare("miri_test_fs_rename_destination.txt");
+
+    let file = File::create(&path1).unwrap();
+    drop(file);
+
+    // Renaming should succeed
+    rename(&path1, &path2).unwrap();
+    // Check that the old file path isn't present
+    assert_eq!(ErrorKind::NotFound, path1.metadata().unwrap_err().kind());
+    // Check that the file has moved successfully
+    assert!(path2.metadata().unwrap().is_file());
+
+    // Renaming a nonexistent file should fail
+    assert_eq!(ErrorKind::NotFound, rename(&path1, &path2).unwrap_err().kind());
+
+    remove_file(&path2).unwrap();
+}
+
+fn test_canonicalize() {
+    use std::fs::canonicalize;
+    let dir_path = prepare_dir("miri_test_fs_dir");
+    create_dir(&dir_path).unwrap();
+    let path = dir_path.join("test_file");
+    drop(File::create(&path).unwrap());
+
+    let p = canonicalize(format!("{}/./test_file", dir_path.to_string_lossy())).unwrap();
+    assert_eq!(p.to_string_lossy().find('.'), None);
+
+    remove_dir_all(&dir_path).unwrap();
+
+    // Make sure we get an error for long paths.
+    use std::convert::TryInto;
+    let too_long = "x/".repeat(libc::PATH_MAX.try_into().unwrap());
+    assert!(canonicalize(too_long).is_err());
+}
+
+fn test_directory() {
+    let dir_path = prepare_dir("miri_test_fs_dir");
+    // Creating a directory should succeed.
+    create_dir(&dir_path).unwrap();
+    // Test that the metadata of a directory is correct.
+    assert!(dir_path.metadata().unwrap().is_dir());
+    // Creating a directory when it already exists should fail.
+    assert_eq!(ErrorKind::AlreadyExists, create_dir(&dir_path).unwrap_err().kind());
+
+    // Create some files inside the directory
+    let path_1 = dir_path.join("test_file_1");
+    drop(File::create(&path_1).unwrap());
+    let path_2 = dir_path.join("test_file_2");
+    drop(File::create(&path_2).unwrap());
+    // Test that the files are present inside the directory
+    let dir_iter = read_dir(&dir_path).unwrap();
+    let mut file_names = dir_iter.map(|e| e.unwrap().file_name()).collect::<Vec<_>>();
+    file_names.sort_unstable();
+    assert_eq!(file_names, vec!["test_file_1", "test_file_2"]);
+    // Deleting the directory should fail, since it is not empty.
+    assert_eq!(ErrorKind::DirectoryNotEmpty, remove_dir(&dir_path).unwrap_err().kind());
+    // Clean up the files in the directory
+    remove_file(&path_1).unwrap();
+    remove_file(&path_2).unwrap();
+    // Now there should be nothing left in the directory.
+    let dir_iter = read_dir(&dir_path).unwrap();
+    let file_names = dir_iter.map(|e| e.unwrap().file_name()).collect::<Vec<_>>();
+    assert!(file_names.is_empty());
+
+    // Deleting the directory should succeed.
+    remove_dir(&dir_path).unwrap();
+    // Reading the metadata of a non-existent directory should fail with a "not found" error.
+    assert_eq!(ErrorKind::NotFound, check_metadata(&[], &dir_path).unwrap_err().kind());
+
+    // To test remove_dir_all, re-create the directory with a file and a directory in it.
+    create_dir(&dir_path).unwrap();
+    drop(File::create(&path_1).unwrap());
+    create_dir(&path_2).unwrap();
+    remove_dir_all(&dir_path).unwrap();
+}
+
+fn test_dup_stdout_stderr() {
+    let bytes = b"hello dup fd\n";
+    unsafe {
+        let new_stdout = libc::fcntl(1, libc::F_DUPFD, 0);
+        let new_stderr = libc::fcntl(2, libc::F_DUPFD, 0);
+        libc::write(new_stdout, bytes.as_ptr() as *const libc::c_void, bytes.len());
+        libc::write(new_stderr, bytes.as_ptr() as *const libc::c_void, bytes.len());
+    }
+}
+
+fn test_from_raw_os_error() {
+    let code = 6; // not a code that std or Miri know
+    let error = Error::from_raw_os_error(code);
+    assert!(matches!(error.kind(), ErrorKind::Uncategorized));
+    // Make sure we can also format this.
+    format!("{error:?}");
+}
diff --git a/src/tools/miri/tests/pass-dep/shims/fs.stderr b/src/tools/miri/tests/pass-dep/shims/fs.stderr
new file mode 100644
index 0000000000000..b6fa69e3d5d2e
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/shims/fs.stderr
@@ -0,0 +1 @@
+hello dup fd
diff --git a/src/tools/miri/tests/pass-dep/shims/fs.stdout b/src/tools/miri/tests/pass-dep/shims/fs.stdout
new file mode 100644
index 0000000000000..b6fa69e3d5d2e
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/shims/fs.stdout
@@ -0,0 +1 @@
+hello dup fd
diff --git a/src/tools/miri/tests/pass-dep/shims/fs_with_isolation.rs b/src/tools/miri/tests/pass-dep/shims/fs_with_isolation.rs
new file mode 100644
index 0000000000000..f5420dbc5538e
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/shims/fs_with_isolation.rs
@@ -0,0 +1,53 @@
+//@ignore-target-windows: File handling is not implemented yet
+//@compile-flags: -Zmiri-isolation-error=warn-nobacktrace
+//@normalize-stderr-test: "(stat(x)?)" -> "$$STAT"
+
+use std::ffi::CString;
+use std::fs::{self, File};
+use std::io::{Error, ErrorKind};
+use std::os::unix;
+
+fn main() {
+    // test `open`
+    assert_eq!(File::create("foo.txt").unwrap_err().kind(), ErrorKind::PermissionDenied);
+
+    // test `fcntl`
+    unsafe {
+        assert_eq!(libc::fcntl(1, libc::F_DUPFD, 0), -1);
+        assert_eq!(Error::last_os_error().raw_os_error(), Some(libc::EPERM));
+    }
+
+    // test `unlink`
+    assert_eq!(fs::remove_file("foo.txt").unwrap_err().kind(), ErrorKind::PermissionDenied);
+
+    // test `symlink`
+    assert_eq!(
+        unix::fs::symlink("foo.txt", "foo_link.txt").unwrap_err().kind(),
+        ErrorKind::PermissionDenied
+    );
+
+    // test `readlink`
+    let symlink_c_str = CString::new("foo.txt").unwrap();
+    let mut buf = vec![0; "foo_link.txt".len() + 1];
+    unsafe {
+        assert_eq!(libc::readlink(symlink_c_str.as_ptr(), buf.as_mut_ptr(), buf.len()), -1);
+        assert_eq!(Error::last_os_error().raw_os_error(), Some(libc::EACCES));
+    }
+
+    // test `stat`
+    assert_eq!(fs::metadata("foo.txt").unwrap_err().kind(), ErrorKind::PermissionDenied);
+    assert_eq!(Error::last_os_error().raw_os_error(), Some(libc::EACCES));
+
+    // test `rename`
+    assert_eq!(fs::rename("a.txt", "b.txt").unwrap_err().kind(), ErrorKind::PermissionDenied);
+
+    // test `mkdir`
+    assert_eq!(fs::create_dir("foo/bar").unwrap_err().kind(), ErrorKind::PermissionDenied);
+
+    // test `rmdir`
+    assert_eq!(fs::remove_dir("foo/bar").unwrap_err().kind(), ErrorKind::PermissionDenied);
+
+    // test `opendir`
+    assert_eq!(fs::read_dir("foo/bar").unwrap_err().kind(), ErrorKind::PermissionDenied);
+    assert_eq!(Error::last_os_error().raw_os_error(), Some(libc::EACCES));
+}
diff --git a/src/tools/miri/tests/pass-dep/shims/fs_with_isolation.stderr b/src/tools/miri/tests/pass-dep/shims/fs_with_isolation.stderr
new file mode 100644
index 0000000000000..ad75e42831b0a
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/shims/fs_with_isolation.stderr
@@ -0,0 +1,20 @@
+warning: `open` was made to return an error due to isolation
+
+warning: `fcntl` was made to return an error due to isolation
+
+warning: `unlink` was made to return an error due to isolation
+
+warning: `symlink` was made to return an error due to isolation
+
+warning: `readlink` was made to return an error due to isolation
+
+warning: `$STAT` was made to return an error due to isolation
+
+warning: `rename` was made to return an error due to isolation
+
+warning: `mkdir` was made to return an error due to isolation
+
+warning: `rmdir` was made to return an error due to isolation
+
+warning: `opendir` was made to return an error due to isolation
+
diff --git a/src/tools/miri/tests/pass-dep/shims/libc-misc.rs b/src/tools/miri/tests/pass-dep/shims/libc-misc.rs
new file mode 100644
index 0000000000000..a883a3d967a3c
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/shims/libc-misc.rs
@@ -0,0 +1,305 @@
+//@ignore-target-windows: No libc on Windows
+//@compile-flags: -Zmiri-disable-isolation
+#![feature(io_error_more)]
+
+use std::fs::{remove_file, File};
+use std::os::unix::io::AsRawFd;
+use std::path::PathBuf;
+
+fn tmp() -> PathBuf {
+    std::env::var("MIRI_TEMP")
+        .map(|tmp| {
+            // MIRI_TEMP is set outside of our emulated
+            // program, so it may have path separators that don't
+            // correspond to our target platform. We normalize them here
+            // before constructing a `PathBuf`
+            return PathBuf::from(tmp.replace("\\", "/"));
+        })
+        .unwrap_or_else(|_| std::env::temp_dir())
+}
+
+/// Test allocating variant of `realpath`.
+fn test_posix_realpath_alloc() {
+    use std::ffi::OsString;
+    use std::ffi::{CStr, CString};
+    use std::os::unix::ffi::OsStrExt;
+    use std::os::unix::ffi::OsStringExt;
+
+    let buf;
+    let path = tmp().join("miri_test_libc_posix_realpath_alloc");
+    let c_path = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed");
+
+    // Cleanup before test.
+    remove_file(&path).ok();
+    // Create file.
+    drop(File::create(&path).unwrap());
+    unsafe {
+        let r = libc::realpath(c_path.as_ptr(), std::ptr::null_mut());
+        assert!(!r.is_null());
+        buf = CStr::from_ptr(r).to_bytes().to_vec();
+        libc::free(r as *mut _);
+    }
+    let canonical = PathBuf::from(OsString::from_vec(buf));
+    assert_eq!(path.file_name(), canonical.file_name());
+
+    // Cleanup after test.
+    remove_file(&path).unwrap();
+}
+
+/// Test non-allocating variant of `realpath`.
+fn test_posix_realpath_noalloc() {
+    use std::ffi::{CStr, CString};
+    use std::os::unix::ffi::OsStrExt;
+
+    let path = tmp().join("miri_test_libc_posix_realpath_noalloc");
+    let c_path = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed");
+
+    let mut v = vec![0; libc::PATH_MAX as usize];
+
+    // Cleanup before test.
+    remove_file(&path).ok();
+    // Create file.
+    drop(File::create(&path).unwrap());
+    unsafe {
+        let r = libc::realpath(c_path.as_ptr(), v.as_mut_ptr());
+        assert!(!r.is_null());
+    }
+    let c = unsafe { CStr::from_ptr(v.as_ptr()) };
+    let canonical = PathBuf::from(c.to_str().expect("CStr to str"));
+
+    assert_eq!(path.file_name(), canonical.file_name());
+
+    // Cleanup after test.
+    remove_file(&path).unwrap();
+}
+
+/// Test failure cases for `realpath`.
+fn test_posix_realpath_errors() {
+    use std::ffi::CString;
+    use std::io::ErrorKind;
+
+    // Test non-existent path returns an error.
+    let c_path = CString::new("./nothing_to_see_here").expect("CString::new failed");
+    let r = unsafe { libc::realpath(c_path.as_ptr(), std::ptr::null_mut()) };
+    assert!(r.is_null());
+    let e = std::io::Error::last_os_error();
+    assert_eq!(e.raw_os_error(), Some(libc::ENOENT));
+    assert_eq!(e.kind(), ErrorKind::NotFound);
+}
+
+#[cfg(any(target_os = "linux"))]
+fn test_posix_fadvise() {
+    use std::convert::TryInto;
+    use std::io::Write;
+
+    let path = tmp().join("miri_test_libc_posix_fadvise.txt");
+    // Cleanup before test
+    remove_file(&path).ok();
+
+    // Set up an open file
+    let mut file = File::create(&path).unwrap();
+    let bytes = b"Hello, World!\n";
+    file.write(bytes).unwrap();
+
+    // Test calling posix_fadvise on a file.
+    let result = unsafe {
+        libc::posix_fadvise(
+            file.as_raw_fd(),
+            0,
+            bytes.len().try_into().unwrap(),
+            libc::POSIX_FADV_DONTNEED,
+        )
+    };
+    drop(file);
+    remove_file(&path).unwrap();
+    assert_eq!(result, 0);
+}
+
+#[cfg(any(target_os = "linux"))]
+fn test_sync_file_range() {
+    use std::io::Write;
+
+    let path = tmp().join("miri_test_libc_sync_file_range.txt");
+    // Cleanup before test.
+    remove_file(&path).ok();
+
+    // Write to a file.
+    let mut file = File::create(&path).unwrap();
+    let bytes = b"Hello, World!\n";
+    file.write(bytes).unwrap();
+
+    // Test calling sync_file_range on the file.
+    let result_1 = unsafe {
+        libc::sync_file_range(
+            file.as_raw_fd(),
+            0,
+            0,
+            libc::SYNC_FILE_RANGE_WAIT_BEFORE
+                | libc::SYNC_FILE_RANGE_WRITE
+                | libc::SYNC_FILE_RANGE_WAIT_AFTER,
+        )
+    };
+    drop(file);
+
+    // Test calling sync_file_range on a file opened for reading.
+    let file = File::open(&path).unwrap();
+    let result_2 = unsafe {
+        libc::sync_file_range(
+            file.as_raw_fd(),
+            0,
+            0,
+            libc::SYNC_FILE_RANGE_WAIT_BEFORE
+                | libc::SYNC_FILE_RANGE_WRITE
+                | libc::SYNC_FILE_RANGE_WAIT_AFTER,
+        )
+    };
+    drop(file);
+
+    remove_file(&path).unwrap();
+    assert_eq!(result_1, 0);
+    assert_eq!(result_2, 0);
+}
+
+/// Tests whether each thread has its own `__errno_location`.
+fn test_thread_local_errno() {
+    #[cfg(target_os = "linux")]
+    use libc::__errno_location;
+    #[cfg(any(target_os = "macos", target_os = "freebsd"))]
+    use libc::__error as __errno_location;
+
+    unsafe {
+        *__errno_location() = 0xBEEF;
+        std::thread::spawn(|| {
+            assert_eq!(*__errno_location(), 0);
+            *__errno_location() = 0xBAD1DEA;
+            assert_eq!(*__errno_location(), 0xBAD1DEA);
+        })
+        .join()
+        .unwrap();
+        assert_eq!(*__errno_location(), 0xBEEF);
+    }
+}
+
+/// Tests whether clock support exists at all
+#[cfg(any(target_os = "linux"))]
+fn test_clocks() {
+    let mut tp = std::mem::MaybeUninit::<libc::timespec>::uninit();
+    let is_error = unsafe { libc::clock_gettime(libc::CLOCK_REALTIME, tp.as_mut_ptr()) };
+    assert_eq!(is_error, 0);
+    let is_error = unsafe { libc::clock_gettime(libc::CLOCK_REALTIME_COARSE, tp.as_mut_ptr()) };
+    assert_eq!(is_error, 0);
+    let is_error = unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, tp.as_mut_ptr()) };
+    assert_eq!(is_error, 0);
+    let is_error = unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC_COARSE, tp.as_mut_ptr()) };
+    assert_eq!(is_error, 0);
+}
+
+fn test_posix_gettimeofday() {
+    let mut tp = std::mem::MaybeUninit::<libc::timeval>::uninit();
+    let tz = std::ptr::null_mut::<libc::timezone>();
+    #[cfg(target_os = "macos")] // `tz` has a different type on macOS
+    let tz = tz as *mut libc::c_void;
+    let is_error = unsafe { libc::gettimeofday(tp.as_mut_ptr(), tz) };
+    assert_eq!(is_error, 0);
+    let tv = unsafe { tp.assume_init() };
+    assert!(tv.tv_sec > 0);
+    assert!(tv.tv_usec >= 0); // Theoretically this could be 0.
+
+    // Test that non-null tz returns an error.
+    let mut tz = std::mem::MaybeUninit::<libc::timezone>::uninit();
+    let tz_ptr = tz.as_mut_ptr();
+    #[cfg(target_os = "macos")] // `tz` has a different type on macOS
+    let tz_ptr = tz_ptr as *mut libc::c_void;
+    let is_error = unsafe { libc::gettimeofday(tp.as_mut_ptr(), tz_ptr) };
+    assert_eq!(is_error, -1);
+}
+
+fn test_isatty() {
+    // Testing whether our isatty shim returns the right value would require controlling whether
+    // these streams are actually TTYs, which is hard.
+    // For now, we just check that these calls are supported at all.
+    unsafe {
+        libc::isatty(libc::STDIN_FILENO);
+        libc::isatty(libc::STDOUT_FILENO);
+        libc::isatty(libc::STDERR_FILENO);
+
+        // But when we open a file, it is definitely not a TTY.
+        let path = tmp().join("notatty.txt");
+        // Cleanup before test.
+        remove_file(&path).ok();
+        let file = File::create(&path).unwrap();
+
+        assert_eq!(libc::isatty(file.as_raw_fd()), 0);
+        assert_eq!(std::io::Error::last_os_error().raw_os_error().unwrap(), libc::ENOTTY);
+
+        // Cleanup after test.
+        drop(file);
+        remove_file(&path).unwrap();
+    }
+}
+
+fn test_posix_mkstemp() {
+    use std::ffi::CString;
+    use std::ffi::OsStr;
+    use std::os::unix::ffi::OsStrExt;
+    use std::os::unix::io::FromRawFd;
+    use std::path::Path;
+
+    let valid_template = "fooXXXXXX";
+    // C needs to own this as `mkstemp(3)` says:
+    // "Since it will be modified, `template` must not be a string constant, but
+    // should be declared as a character array."
+    // There seems to be no `as_mut_ptr` on `CString` so we need to use `into_raw`.
+    let ptr = CString::new(valid_template).unwrap().into_raw();
+    let fd = unsafe { libc::mkstemp(ptr) };
+    // Take ownership back in Rust to not leak memory.
+    let slice = unsafe { CString::from_raw(ptr) };
+    assert!(fd > 0);
+    let osstr = OsStr::from_bytes(slice.to_bytes());
+    let path: &Path = osstr.as_ref();
+    let name = path.file_name().unwrap().to_string_lossy();
+    assert!(name.ne("fooXXXXXX"));
+    assert!(name.starts_with("foo"));
+    assert_eq!(name.len(), 9);
+    assert_eq!(
+        name.chars().skip(3).filter(char::is_ascii_alphanumeric).collect::<Vec<char>>().len(),
+        6
+    );
+    let file = unsafe { File::from_raw_fd(fd) };
+    assert!(file.set_len(0).is_ok());
+
+    let invalid_templates = vec!["foo", "barXX", "XXXXXXbaz", "whatXXXXXXever", "X"];
+    for t in invalid_templates {
+        let ptr = CString::new(t).unwrap().into_raw();
+        let fd = unsafe { libc::mkstemp(ptr) };
+        let _ = unsafe { CString::from_raw(ptr) };
+        // "On error, -1 is returned, and errno is set to
+        // indicate the error"
+        assert_eq!(fd, -1);
+        let e = std::io::Error::last_os_error();
+        assert_eq!(e.raw_os_error(), Some(libc::EINVAL));
+        assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput);
+    }
+}
+
+fn main() {
+    #[cfg(any(target_os = "linux"))]
+    test_posix_fadvise();
+
+    test_posix_gettimeofday();
+    test_posix_mkstemp();
+
+    test_posix_realpath_alloc();
+    test_posix_realpath_noalloc();
+    test_posix_realpath_errors();
+
+    #[cfg(any(target_os = "linux"))]
+    test_sync_file_range();
+
+    test_thread_local_errno();
+
+    #[cfg(any(target_os = "linux"))]
+    test_clocks();
+
+    test_isatty();
+}
diff --git a/src/tools/miri/tests/pass-dep/shims/linux-getrandom-without-isolation.rs b/src/tools/miri/tests/pass-dep/shims/linux-getrandom-without-isolation.rs
new file mode 100644
index 0000000000000..349b447569a4b
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/shims/linux-getrandom-without-isolation.rs
@@ -0,0 +1,41 @@
+//@only-target-linux
+//@compile-flags: -Zmiri-disable-isolation
+
+use std::ptr;
+
+fn main() {
+    let mut buf = [0u8; 5];
+    unsafe {
+        assert_eq!(
+            libc::syscall(
+                libc::SYS_getrandom,
+                ptr::null_mut::<libc::c_void>(),
+                0 as libc::size_t,
+                0 as libc::c_uint,
+            ),
+            0,
+        );
+        assert_eq!(
+            libc::syscall(
+                libc::SYS_getrandom,
+                buf.as_mut_ptr() as *mut libc::c_void,
+                5 as libc::size_t,
+                0 as libc::c_uint,
+            ),
+            5,
+        );
+
+        assert_eq!(
+            libc::getrandom(ptr::null_mut::<libc::c_void>(), 0 as libc::size_t, 0 as libc::c_uint),
+            0,
+        );
+        assert_eq!(
+            libc::getrandom(
+                buf.as_mut_ptr() as *mut libc::c_void,
+                5 as libc::size_t,
+                0 as libc::c_uint,
+            ),
+            5,
+        );
+    }
+}
diff --git a/src/tools/miri/tests/pass-dep/shims/linux-getrandom.rs b/src/tools/miri/tests/pass-dep/shims/linux-getrandom.rs
new file mode 100644
index 0000000000000..a1436c7319d33
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/shims/linux-getrandom.rs
@@ -0,0 +1,40 @@
+//@only-target-linux
+
+use std::ptr;
+
+fn main() {
+    let mut buf = [0u8; 5];
+    unsafe {
+        assert_eq!(
+            libc::syscall(
+                libc::SYS_getrandom,
+                ptr::null_mut::<libc::c_void>(),
+                0 as libc::size_t,
+                0 as libc::c_uint,
+            ),
+            0,
+        );
+        assert_eq!(
+            libc::syscall(
+                libc::SYS_getrandom,
+                buf.as_mut_ptr() as *mut libc::c_void,
+                5 as libc::size_t,
+                0 as libc::c_uint,
+            ),
+            5,
+        );
+
+        assert_eq!(
+            libc::getrandom(ptr::null_mut::<libc::c_void>(), 0 as libc::size_t, 0 as libc::c_uint),
+            0,
+        );
+        assert_eq!(
+            libc::getrandom(
+                buf.as_mut_ptr() as *mut libc::c_void,
+                5 as libc::size_t,
+                0 as libc::c_uint,
+            ),
+            5,
+        );
+    }
+}
diff --git a/src/tools/miri/tests/pass-dep/shims/posix_memalign.rs b/src/tools/miri/tests/pass-dep/shims/posix_memalign.rs
new file mode 100644
index 0000000000000..9bd8a00d68dcd
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/shims/posix_memalign.rs
@@ -0,0 +1,82 @@
+//@ignore-target-windows: No libc on Windows
+
+#![feature(pointer_is_aligned)]
+#![feature(strict_provenance)]
+
+use core::ptr;
+
+fn main() {
+    // A normal allocation.
+    unsafe {
+        let mut ptr: *mut libc::c_void = ptr::null_mut();
+        let align = 8;
+        let size = 64;
+        assert_eq!(libc::posix_memalign(&mut ptr, align, size), 0);
+        assert!(!ptr.is_null());
+        assert!(ptr.is_aligned_to(align));
+        ptr.cast::<u8>().write_bytes(1, size);
+        libc::free(ptr);
+    }
+
+    // Align > size.
+    unsafe {
+        let mut ptr: *mut libc::c_void = ptr::null_mut();
+        let align = 64;
+        let size = 8;
+        assert_eq!(libc::posix_memalign(&mut ptr, align, size), 0);
+        assert!(!ptr.is_null());
+        assert!(ptr.is_aligned_to(align));
+        ptr.cast::<u8>().write_bytes(1, size);
+        libc::free(ptr);
+    }
+
+    // Size not multiple of align
+    unsafe {
+        let mut ptr: *mut libc::c_void = ptr::null_mut();
+        let align = 16;
+        let size = 31;
+        assert_eq!(libc::posix_memalign(&mut ptr, align, size), 0);
+        assert!(!ptr.is_null());
+        assert!(ptr.is_aligned_to(align));
+        ptr.cast::<u8>().write_bytes(1, size);
+        libc::free(ptr);
+    }
+
+    // Size == 0
+    unsafe {
+        let mut ptr: *mut libc::c_void = ptr::null_mut();
+        let align = 64;
+        let size = 0;
+        assert_eq!(libc::posix_memalign(&mut ptr, align, size), 0);
+        // We are not required to return null if size == 0, but we currently do.
+        // It's fine to remove this assert if we start returning non-null pointers.
+        assert!(ptr.is_null());
+        assert!(ptr.is_aligned_to(align));
+        // Regardless of what we return, it must be `free`able.
+        libc::free(ptr);
+    }
+
+    // Non-power of 2 align
+    unsafe {
+        let mut ptr: *mut libc::c_void = ptr::invalid_mut(0x1234567);
+        let align = 15;
+        let size = 8;
+        assert_eq!(libc::posix_memalign(&mut ptr, align, size), libc::EINVAL);
+        // The pointer is not modified on failure, posix_memalign(3) says:
+        // > On Linux (and other systems), posix_memalign() does  not  modify  memptr  on failure.
+        // > A requirement standardizing this behavior was added in POSIX.1-2008 TC2.
+        assert_eq!(ptr.addr(), 0x1234567);
+    }
+
+    // Too small align (smaller than ptr)
+    unsafe {
+        let mut ptr: *mut libc::c_void = ptr::invalid_mut(0x1234567);
+        let align = std::mem::size_of::<usize>() / 2;
+        let size = 8;
+        assert_eq!(libc::posix_memalign(&mut ptr, align, size), libc::EINVAL);
+        // The pointer is not modified on failure, posix_memalign(3) says:
+        // > On Linux (and other systems), posix_memalign() does  not  modify  memptr  on failure.
+        // > A requirement standardizing this behavior was added in POSIX.1-2008 TC2.
+        assert_eq!(ptr.addr(), 0x1234567);
+    }
+}
diff --git a/src/tools/miri/tests/pass-dep/shims/pthreads.rs b/src/tools/miri/tests/pass-dep/shims/pthreads.rs
new file mode 100644
index 0000000000000..d062eda7e90c8
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/shims/pthreads.rs
@@ -0,0 +1,127 @@
+//@ignore-target-windows: No libc on Windows
+
+fn main() {
+    test_mutex_libc_init_recursive();
+    test_mutex_libc_init_normal();
+    test_mutex_libc_init_errorcheck();
+    test_rwlock_libc_static_initializer();
+
+    #[cfg(any(target_os = "linux"))]
+    test_mutex_libc_static_initializer_recursive();
+}
+
+fn test_mutex_libc_init_recursive() {
+    unsafe {
+        let mut attr: libc::pthread_mutexattr_t = std::mem::zeroed();
+        assert_eq!(libc::pthread_mutexattr_init(&mut attr as *mut _), 0);
+        assert_eq!(
+            libc::pthread_mutexattr_settype(&mut attr as *mut _, libc::PTHREAD_MUTEX_RECURSIVE),
+            0,
+        );
+        let mut mutex: libc::pthread_mutex_t = std::mem::zeroed();
+        assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, &mut attr as *mut _), 0);
+        assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0);
+        assert_eq!(libc::pthread_mutex_trylock(&mut mutex as *mut _), 0);
+        assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0);
+        assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0);
+        assert_eq!(libc::pthread_mutex_trylock(&mut mutex as *mut _), 0);
+        assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0);
+        assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0);
+        assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0);
+        assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), libc::EPERM);
+        assert_eq!(libc::pthread_mutex_destroy(&mut mutex as *mut _), 0);
+        assert_eq!(libc::pthread_mutexattr_destroy(&mut attr as *mut _), 0);
+    }
+}
+
+fn test_mutex_libc_init_normal() {
+    unsafe {
+        let mut mutexattr: libc::pthread_mutexattr_t = std::mem::zeroed();
+        assert_eq!(
+            libc::pthread_mutexattr_settype(&mut mutexattr as *mut _, 0x12345678),
+            libc::EINVAL,
+        );
+        assert_eq!(
+            libc::pthread_mutexattr_settype(&mut mutexattr as *mut _, libc::PTHREAD_MUTEX_NORMAL),
+            0,
+        );
+        let mut mutex: libc::pthread_mutex_t = std::mem::zeroed();
+        assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, &mutexattr as *const _), 0);
+        assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0);
+        assert_eq!(libc::pthread_mutex_trylock(&mut mutex as *mut _), libc::EBUSY);
+        assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0);
+        assert_eq!(libc::pthread_mutex_trylock(&mut mutex as *mut _), 0);
+        assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0);
+        assert_eq!(libc::pthread_mutex_destroy(&mut mutex as *mut _), 0);
+    }
+}
+
+fn test_mutex_libc_init_errorcheck() {
+    unsafe {
+        let mut mutexattr: libc::pthread_mutexattr_t = std::mem::zeroed();
+        assert_eq!(
+            libc::pthread_mutexattr_settype(
+                &mut mutexattr as *mut _,
+                libc::PTHREAD_MUTEX_ERRORCHECK,
+            ),
+            0,
+        );
+        let mut mutex: libc::pthread_mutex_t = std::mem::zeroed();
+        assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, &mutexattr as *const _), 0);
+        assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0);
+        assert_eq!(libc::pthread_mutex_trylock(&mut mutex as *mut _), libc::EBUSY);
+        assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), libc::EDEADLK);
+        assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0);
+        assert_eq!(libc::pthread_mutex_trylock(&mut mutex as *mut _), 0);
+        assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0);
+        assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), libc::EPERM);
+        assert_eq!(libc::pthread_mutex_destroy(&mut mutex as *mut _), 0);
+    }
+}
+
+// Only linux provides PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP,
+// libc for macOS just has the default PTHREAD_MUTEX_INITIALIZER.
+#[cfg(target_os = "linux")]
+fn test_mutex_libc_static_initializer_recursive() {
+    let mutex = std::cell::UnsafeCell::new(libc::PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP);
+    unsafe {
+        assert_eq!(libc::pthread_mutex_lock(mutex.get()), 0);
+        assert_eq!(libc::pthread_mutex_trylock(mutex.get()), 0);
+        assert_eq!(libc::pthread_mutex_unlock(mutex.get()), 0);
+        assert_eq!(libc::pthread_mutex_unlock(mutex.get()), 0);
+        assert_eq!(libc::pthread_mutex_trylock(mutex.get()), 0);
+        assert_eq!(libc::pthread_mutex_lock(mutex.get()), 0);
+        assert_eq!(libc::pthread_mutex_unlock(mutex.get()), 0);
+        assert_eq!(libc::pthread_mutex_unlock(mutex.get()), 0);
+        assert_eq!(libc::pthread_mutex_unlock(mutex.get()), libc::EPERM);
+        assert_eq!(libc::pthread_mutex_destroy(mutex.get()), 0);
+    }
+}
+
+// Testing the behavior of std::sync::RwLock does not fully exercise the pthread rwlock shims, we
+// need to go a layer deeper and test the behavior of the libc functions, because
+// std::sys::unix::rwlock::RWLock itself keeps track of write_locked and num_readers.
+fn test_rwlock_libc_static_initializer() {
+    let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER);
+    unsafe {
+        assert_eq!(libc::pthread_rwlock_rdlock(rw.get()), 0);
+        assert_eq!(libc::pthread_rwlock_rdlock(rw.get()), 0);
+        assert_eq!(libc::pthread_rwlock_unlock(rw.get()), 0);
+        assert_eq!(libc::pthread_rwlock_tryrdlock(rw.get()), 0);
+        assert_eq!(libc::pthread_rwlock_unlock(rw.get()), 0);
+        assert_eq!(libc::pthread_rwlock_trywrlock(rw.get()), libc::EBUSY);
+        assert_eq!(libc::pthread_rwlock_unlock(rw.get()), 0);
+
+        assert_eq!(libc::pthread_rwlock_wrlock(rw.get()), 0);
+        assert_eq!(libc::pthread_rwlock_tryrdlock(rw.get()), libc::EBUSY);
+        assert_eq!(libc::pthread_rwlock_trywrlock(rw.get()), libc::EBUSY);
+        assert_eq!(libc::pthread_rwlock_unlock(rw.get()), 0);
+
+        assert_eq!(libc::pthread_rwlock_trywrlock(rw.get()), 0);
+        assert_eq!(libc::pthread_rwlock_tryrdlock(rw.get()), libc::EBUSY);
+        assert_eq!(libc::pthread_rwlock_trywrlock(rw.get()), libc::EBUSY);
+        assert_eq!(libc::pthread_rwlock_unlock(rw.get()), 0);
+
+        assert_eq!(libc::pthread_rwlock_destroy(rw.get()), 0);
+    }
+}
diff --git a/src/tools/miri/tests/pass/0weak_memory_consistency.rs b/src/tools/miri/tests/pass/0weak_memory_consistency.rs
new file mode 100644
index 0000000000000..f3820bd660d28
--- /dev/null
+++ b/src/tools/miri/tests/pass/0weak_memory_consistency.rs
@@ -0,0 +1,301 @@
+//@compile-flags: -Zmiri-ignore-leaks -Zmiri-disable-stacked-borrows
+
+// The following tests check whether our weak memory emulation produces
+// any inconsistent execution outcomes
+//
+// Due to the random nature of choosing valid stores, it is always
+// possible that our tests spuriously succeeds: even though our weak
+// memory emulation code has incorrectly identified a store in
+// modification order as being valid, it may be never chosen by
+// the RNG and never observed in our tests.
+//
+// To mitigate this, each test is ran enough times such that the chance
+// of spurious success is very low. These tests never supriously fail.
+
+// Test cases and their consistent outcomes are from
+// http://svr-pes20-cppmem.cl.cam.ac.uk/cppmem/
+// Based on
+// M. Batty, S. Owens, S. Sarkar, P. Sewell and T. Weber,
+// "Mathematizing C++ concurrency", ACM SIGPLAN Notices, vol. 46, no. 1, pp. 55-66, 2011.
+// Available: https://ss265.host.cs.st-andrews.ac.uk/papers/n3132.pdf.
+
+use std::sync::atomic::Ordering::*;
+use std::sync::atomic::{fence, AtomicBool, AtomicI32};
+use std::thread::spawn;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+// We can't create static items because we need to run each test
+// multiple times
+fn static_atomic(val: i32) -> &'static AtomicI32 {
+    let ret = Box::leak(Box::new(AtomicI32::new(val)));
+    ret.store(val, Relaxed); // work around https://github.com/rust-lang/miri/issues/2164
+    ret
+}
+fn static_atomic_bool(val: bool) -> &'static AtomicBool {
+    let ret = Box::leak(Box::new(AtomicBool::new(val)));
+    ret.store(val, Relaxed); // work around https://github.com/rust-lang/miri/issues/2164
+    ret
+}
+
+// Spins until it acquires a pre-determined value.
+fn acquires_value(loc: &AtomicI32, val: i32) -> i32 {
+    while loc.load(Acquire) != val {
+        std::hint::spin_loop();
+    }
+    val
+}
+
+fn test_corr() {
+    let x = static_atomic(0);
+    let y = static_atomic(0);
+
+    let j1 = spawn(move || {
+        x.store(1, Relaxed);
+        x.store(2, Relaxed);
+    });
+
+    #[rustfmt::skip]
+    let j2 = spawn(move || {
+        let r2 = x.load(Relaxed); // -------------------------------------+
+        y.store(1, Release); // ---------------------+                    |
+        r2 //                                        |                    |
+    }); //                                           |                    |
+    #[rustfmt::skip] //                              |synchronizes-with   |happens-before
+    let j3 = spawn(move || { //                      |                    |
+        acquires_value(&y, 1); // <------------------+                    |
+        x.load(Relaxed) // <----------------------------------------------+
+        // The two reads on x are ordered by hb, so they cannot observe values
+        // differently from the modification order. If the first read observed
+        // 2, then the second read must observe 2 as well.
+    });
+
+    j1.join().unwrap();
+    let r2 = j2.join().unwrap();
+    let r3 = j3.join().unwrap();
+    if r2 == 2 {
+        assert_eq!(r3, 2);
+    }
+}
+
+fn test_wrc() {
+    let x = static_atomic(0);
+    let y = static_atomic(0);
+
+    #[rustfmt::skip]
+    let j1 = spawn(move || {
+        x.store(1, Release); // ---------------------+---------------------+
+    }); //                                           |                     |
+    #[rustfmt::skip] //                              |synchronizes-with    |
+    let j2 = spawn(move || { //                      |                     |
+        acquires_value(&x, 1); // <------------------+                     |
+        y.store(1, Release); // ---------------------+                     |happens-before
+    }); //                                           |                     |
+    #[rustfmt::skip] //                              |synchronizes-with    |
+    let j3 = spawn(move || { //                      |                     |
+        acquires_value(&y, 1); // <------------------+                     |
+        x.load(Relaxed) // <-----------------------------------------------+
+    });
+
+    j1.join().unwrap();
+    j2.join().unwrap();
+    let r3 = j3.join().unwrap();
+
+    assert_eq!(r3, 1);
+}
+
+fn test_message_passing() {
+    let mut var = 0u32;
+    let ptr = &mut var as *mut u32;
+    let x = EvilSend(ptr);
+    let y = static_atomic(0);
+
+    #[rustfmt::skip]
+    let j1 = spawn(move || {
+        unsafe { *x.0 = 1 }; // -----------------------------------------+
+        y.store(1, Release); // ---------------------+                   |
+    }); //                                           |                   |
+    #[rustfmt::skip] //                              |synchronizes-with  | happens-before
+    let j2 = spawn(move || { //                      |                   |
+        acquires_value(&y, 1); // <------------------+                   |
+        unsafe { *x.0 } // <---------------------------------------------+
+    });
+
+    j1.join().unwrap();
+    let r2 = j2.join().unwrap();
+
+    assert_eq!(r2, 1);
+}
+
+// LB+acq_rel+acq_rel
+fn test_load_buffering_acq_rel() {
+    let x = static_atomic(0);
+    let y = static_atomic(0);
+    let j1 = spawn(move || {
+        let r1 = x.load(Acquire);
+        y.store(1, Release);
+        r1
+    });
+
+    let j2 = spawn(move || {
+        let r2 = y.load(Acquire);
+        x.store(1, Release);
+        r2
+    });
+
+    let r1 = j1.join().unwrap();
+    let r2 = j2.join().unwrap();
+
+    // 3 consistent outcomes: (0,0), (0,1), (1,0)
+    assert_ne!((r1, r2), (1, 1));
+}
+
+fn test_mixed_access() {
+    /*
+    int main() {
+      atomic_int x = 0;
+      {{{
+        x.store(1, mo_relaxed);
+      }}}
+
+      x.store(2, mo_relaxed);
+
+      {{{
+        r1 = x.load(mo_relaxed);
+      }}}
+
+      return 0;
+    }
+        */
+    let x = static_atomic(0);
+
+    spawn(move || {
+        x.store(1, Relaxed);
+    })
+    .join()
+    .unwrap();
+
+    x.store(2, Relaxed);
+
+    let r2 = spawn(move || x.load(Relaxed)).join().unwrap();
+
+    assert_eq!(r2, 2);
+}
+
+// The following two tests are taken from Repairing Sequential Consistency in C/C++11
+// by Lahav et al.
+// https://plv.mpi-sws.org/scfix/paper.pdf
+
+// Test case SB
+fn test_sc_store_buffering() {
+    let x = static_atomic(0);
+    let y = static_atomic(0);
+
+    let j1 = spawn(move || {
+        x.store(1, SeqCst);
+        y.load(SeqCst)
+    });
+
+    let j2 = spawn(move || {
+        y.store(1, SeqCst);
+        x.load(SeqCst)
+    });
+
+    let a = j1.join().unwrap();
+    let b = j2.join().unwrap();
+
+    assert_ne!((a, b), (0, 0));
+}
+
+fn test_single_thread() {
+    let x = AtomicI32::new(42);
+
+    assert_eq!(x.load(Relaxed), 42);
+
+    x.store(43, Relaxed);
+
+    assert_eq!(x.load(Relaxed), 43);
+}
+
+fn test_sync_through_rmw_and_fences() {
+    // Example from https://github.com/llvm/llvm-project/issues/56450#issuecomment-1183695905
+    #[no_mangle]
+    pub fn rdmw(storing: &AtomicI32, sync: &AtomicI32, loading: &AtomicI32) -> i32 {
+        storing.store(1, Relaxed);
+        fence(Release);
+        sync.fetch_add(0, Relaxed);
+        fence(Acquire);
+        loading.load(Relaxed)
+    }
+
+    let x = static_atomic(0);
+    let y = static_atomic(0);
+    let z = static_atomic(0);
+
+    // Since each thread is so short, we need to make sure that they truely run at the same time
+    // Otherwise t1 will finish before t2 even starts
+    let go = static_atomic_bool(false);
+
+    let t1 = spawn(move || {
+        while !go.load(Relaxed) {}
+        rdmw(y, x, z)
+    });
+
+    let t2 = spawn(move || {
+        while !go.load(Relaxed) {}
+        rdmw(z, x, y)
+    });
+
+    go.store(true, Relaxed);
+
+    let a = t1.join().unwrap();
+    let b = t2.join().unwrap();
+    assert_ne!((a, b), (0, 0));
+}
+
+// Test case by @SabrinaJewson
+// https://github.com/rust-lang/miri/issues/2301#issuecomment-1221502757
+// Demonstrating C++20 SC access changes
+fn test_iriw_sc_rlx() {
+    let x = static_atomic_bool(false);
+    let y = static_atomic_bool(false);
+
+    x.store(false, Relaxed);
+    y.store(false, Relaxed);
+
+    let a = spawn(move || x.store(true, Relaxed));
+    let b = spawn(move || y.store(true, Relaxed));
+    let c = spawn(move || {
+        while !x.load(SeqCst) {}
+        y.load(SeqCst)
+    });
+    let d = spawn(move || {
+        while !y.load(SeqCst) {}
+        x.load(SeqCst)
+    });
+
+    a.join().unwrap();
+    b.join().unwrap();
+    let c = c.join().unwrap();
+    let d = d.join().unwrap();
+
+    assert!(c || d);
+}
+
+pub fn main() {
+    for _ in 0..50 {
+        test_single_thread();
+        test_mixed_access();
+        test_load_buffering_acq_rel();
+        test_message_passing();
+        test_wrc();
+        test_corr();
+        test_sc_store_buffering();
+        test_sync_through_rmw_and_fences();
+        test_iriw_sc_rlx();
+    }
+}
diff --git a/src/tools/miri/tests/pass/adjacent-allocs.rs b/src/tools/miri/tests/pass/adjacent-allocs.rs
new file mode 100644
index 0000000000000..0051c62121cd5
--- /dev/null
+++ b/src/tools/miri/tests/pass/adjacent-allocs.rs
@@ -0,0 +1,63 @@
+//@compile-flags: -Zmiri-permissive-provenance
+
+fn ensure_allocs_can_be_adjacent() {
+    for _ in 0..512 {
+        let n = 0u64;
+        let ptr: *const u64 = &n;
+        let ptr2 = {
+            let m = 0u64;
+            &m as *const u64
+        };
+        if ptr.wrapping_add(1) == ptr2 {
+            return;
+        }
+    }
+    panic!("never saw adjacent stack variables?");
+}
+
+fn test1() {
+    // The slack between allocations is random.
+    // Loop a few times to hit the zero-slack case.
+    for _ in 0..512 {
+        let n = 0u64;
+        let ptr: *const u64 = &n;
+
+        // Allocate a new stack variable whose lifetime quickly ends.
+        // If there's a chance that &m == ptr.add(1), then an int-to-ptr cast of
+        // that value will have ambiguous provenance between n and m.
+        // See https://github.com/rust-lang/miri/issues/1866#issuecomment-985770125
+        {
+            let m = 0u64;
+            let _ = &m as *const u64;
+        }
+
+        let iptr = ptr as usize;
+        let zst = (iptr + 8) as *const ();
+        // This is a ZST ptr just at the end of `n`, so it should be valid to deref.
+        unsafe { *zst }
+    }
+}
+
+fn test2() {
+    fn foo() -> u64 {
+        0
+    }
+
+    for _ in 0..512 {
+        let n = 0u64;
+        let ptr: *const u64 = &n;
+        foo();
+        let iptr = ptr as usize;
+        unsafe {
+            let start = &*std::ptr::slice_from_raw_parts(iptr as *const (), 1);
+            let end = &*std::ptr::slice_from_raw_parts((iptr + 8) as *const (), 1);
+            assert_eq!(start.len(), end.len());
+        }
+    }
+}
+
+fn main() {
+    ensure_allocs_can_be_adjacent();
+    test1();
+    test2();
+}
diff --git a/src/tools/miri/tests/pass/align.rs b/src/tools/miri/tests/pass/align.rs
new file mode 100644
index 0000000000000..997abd7339226
--- /dev/null
+++ b/src/tools/miri/tests/pass/align.rs
@@ -0,0 +1,29 @@
+//@compile-flags: -Zmiri-permissive-provenance
+
+/// This manually makes sure that we have a pointer with the proper alignment.
+fn manual_alignment() {
+    let x = &mut [0u8; 3];
+    let base_addr = x as *mut _ as usize;
+    let base_addr_aligned = if base_addr % 2 == 0 { base_addr } else { base_addr + 1 };
+    let u16_ptr = base_addr_aligned as *mut u16;
+    unsafe {
+        *u16_ptr = 2;
+    }
+}
+
+/// Test standard library `align_to`.
+fn align_to() {
+    const LEN: usize = 128;
+    let buf = &[0u8; LEN];
+    let (l, m, r) = unsafe { buf.align_to::<i32>() };
+    assert!(m.len() * 4 >= LEN - 4);
+    assert!(l.len() + r.len() <= 4);
+}
+
+fn main() {
+    // Do this a couple times in a loop because it may work "by chance".
+    for _ in 0..10 {
+        manual_alignment();
+        align_to();
+    }
+}
diff --git a/src/tools/miri/tests/pass/align_offset_symbolic.rs b/src/tools/miri/tests/pass/align_offset_symbolic.rs
new file mode 100644
index 0000000000000..b3e5733836390
--- /dev/null
+++ b/src/tools/miri/tests/pass/align_offset_symbolic.rs
@@ -0,0 +1,105 @@
+//@compile-flags: -Zmiri-symbolic-alignment-check
+
+fn test_align_offset() {
+    let d = Box::new([0u32; 4]);
+    // Get u8 pointer to base
+    let raw = d.as_ptr() as *const u8;
+
+    assert_eq!(raw.align_offset(2), 0);
+    assert_eq!(raw.align_offset(4), 0);
+    assert_eq!(raw.align_offset(8), usize::MAX); // requested alignment higher than allocation alignment
+
+    assert_eq!(raw.wrapping_offset(1).align_offset(2), 1);
+    assert_eq!(raw.wrapping_offset(1).align_offset(4), 3);
+    assert_eq!(raw.wrapping_offset(1).align_offset(8), usize::MAX); // requested alignment higher than allocation alignment
+
+    assert_eq!(raw.wrapping_offset(2).align_offset(2), 0);
+    assert_eq!(raw.wrapping_offset(2).align_offset(4), 2);
+    assert_eq!(raw.wrapping_offset(2).align_offset(8), usize::MAX); // requested alignment higher than allocation alignment
+}
+
+fn test_align_to() {
+    const N: usize = 4;
+    let d = Box::new([0u32; N]);
+    // Get u8 slice covering the entire thing
+    let s = unsafe { std::slice::from_raw_parts(d.as_ptr() as *const u8, 4 * N) };
+    let raw = s.as_ptr();
+
+    {
+        let (l, m, r) = unsafe { s.align_to::<u32>() };
+        assert_eq!(l.len(), 0);
+        assert_eq!(r.len(), 0);
+        assert_eq!(m.len(), N);
+        assert_eq!(raw, m.as_ptr() as *const u8);
+    }
+
+    {
+        let (l, m, r) = unsafe { s[1..].align_to::<u32>() };
+        assert_eq!(l.len(), 3);
+        assert_eq!(m.len(), N - 1);
+        assert_eq!(r.len(), 0);
+        assert_eq!(raw.wrapping_offset(4), m.as_ptr() as *const u8);
+    }
+
+    {
+        let (l, m, r) = unsafe { s[..4 * N - 1].align_to::<u32>() };
+        assert_eq!(l.len(), 0);
+        assert_eq!(m.len(), N - 1);
+        assert_eq!(r.len(), 3);
+        assert_eq!(raw, m.as_ptr() as *const u8);
+    }
+
+    {
+        let (l, m, r) = unsafe { s[1..4 * N - 1].align_to::<u32>() };
+        assert_eq!(l.len(), 3);
+        assert_eq!(m.len(), N - 2);
+        assert_eq!(r.len(), 3);
+        assert_eq!(raw.wrapping_offset(4), m.as_ptr() as *const u8);
+    }
+
+    {
+        #[repr(align(8))]
+        struct Align8(u64);
+
+        let (l, m, r) = unsafe { s.align_to::<Align8>() }; // requested alignment higher than allocation alignment
+        assert_eq!(l.len(), 4 * N);
+        assert_eq!(r.len(), 0);
+        assert_eq!(m.len(), 0);
+    }
+}
+
+fn test_from_utf8() {
+    const N: usize = 10;
+    let vec = vec![0x4141414141414141u64; N];
+    let content = unsafe { std::slice::from_raw_parts(vec.as_ptr() as *const u8, 8 * N) };
+    println!("{:?}", std::str::from_utf8(content).unwrap());
+}
+
+fn test_u64_array() {
+    #[derive(Default)]
+    #[repr(align(8))]
+    struct AlignToU64<T>(T);
+
+    const BYTE_LEN: usize = std::mem::size_of::<[u64; 4]>();
+    type Data = AlignToU64<[u8; BYTE_LEN]>;
+
+    fn example(data: &Data) {
+        let (head, u64_arrays, tail) = unsafe { data.0.align_to::<[u64; 4]>() };
+
+        assert!(head.is_empty(), "buffer was not aligned for 64-bit numbers");
+        assert_eq!(u64_arrays.len(), 1, "buffer was not long enough");
+        assert!(tail.is_empty(), "buffer was too long");
+
+        let u64_array = &u64_arrays[0];
+        let _val = u64_array[0]; // make sure we can actually load this
+    }
+
+    example(&Data::default());
+}
+
+fn main() {
+    test_align_offset();
+    test_align_to();
+    test_from_utf8();
+    test_u64_array();
+}
diff --git a/src/tools/miri/tests/pass/align_offset_symbolic.stdout b/src/tools/miri/tests/pass/align_offset_symbolic.stdout
new file mode 100644
index 0000000000000..66d4399481596
--- /dev/null
+++ b/src/tools/miri/tests/pass/align_offset_symbolic.stdout
@@ -0,0 +1 @@
+"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
diff --git a/src/tools/miri/tests/pass/arrays.rs b/src/tools/miri/tests/pass/arrays.rs
new file mode 100644
index 0000000000000..9589ffa1724c2
--- /dev/null
+++ b/src/tools/miri/tests/pass/arrays.rs
@@ -0,0 +1,78 @@
+use std::convert::TryFrom;
+
+fn empty_array() -> [u16; 0] {
+    []
+}
+
+fn mini_array() -> [u16; 1] {
+    [42]
+}
+
+fn big_array() -> [u16; 5] {
+    [5, 4, 3, 2, 1]
+}
+
+fn array_array() -> [[u8; 2]; 3] {
+    [[5, 4], [3, 2], [1, 0]]
+}
+
+fn index_unsafe() -> i32 {
+    let a = [0, 10, 20, 30];
+    unsafe { *a.get_unchecked(2) }
+}
+
+fn index() -> i32 {
+    let a = [0, 10, 20, 30];
+    a[2]
+}
+
+fn array_repeat() -> [u8; 8] {
+    [42; 8]
+}
+
+fn slice_index() -> u8 {
+    let arr: &[_] = &[101, 102, 103, 104, 105, 106];
+    arr[5]
+}
+
+fn from() {
+    const N: usize = 16;
+    type Array = [u8; N];
+    let array: Array = [0; N];
+    let slice: &[u8] = &array[..];
+
+    let result = <&Array>::try_from(slice);
+    assert_eq!(&array, result.unwrap());
+
+    let vec = Vec::from(array);
+    assert_eq!(vec.len(), N);
+}
+
+fn eq() {
+    const N: usize = 16;
+    type Array = [u8; N];
+    let array1: Array = [0; N];
+    let array2: Array = [0; N];
+    let array3: Array = [1; N];
+    assert_eq!(array1, array2);
+    assert_ne!(array1, array3);
+}
+
+fn debug() {
+    let array = [0u8, 42, 13, 71];
+    println!("{:?}", array);
+}
+
+fn main() {
+    assert_eq!(empty_array(), []);
+    assert_eq!(index_unsafe(), 20);
+    assert_eq!(index(), 20);
+    assert_eq!(slice_index(), 106);
+    assert_eq!(big_array(), [5, 4, 3, 2, 1]);
+    assert_eq!(array_array(), [[5, 4], [3, 2], [1, 0]]);
+    assert_eq!(array_repeat(), [42; 8]);
+    assert_eq!(mini_array(), [42]);
+    from();
+    eq();
+    debug();
+}
diff --git a/src/tools/miri/tests/pass/arrays.stdout b/src/tools/miri/tests/pass/arrays.stdout
new file mode 100644
index 0000000000000..6c3cefadf8035
--- /dev/null
+++ b/src/tools/miri/tests/pass/arrays.stdout
@@ -0,0 +1 @@
+[0, 42, 13, 71]
diff --git a/src/tools/miri/tests/pass/associated-const.rs b/src/tools/miri/tests/pass/associated-const.rs
new file mode 100644
index 0000000000000..2ff08ffc4bf6a
--- /dev/null
+++ b/src/tools/miri/tests/pass/associated-const.rs
@@ -0,0 +1,11 @@
+trait Foo {
+    const ID: i32;
+}
+
+impl Foo for i32 {
+    const ID: i32 = 1;
+}
+
+fn main() {
+    assert_eq!(1, <i32 as Foo>::ID);
+}
diff --git a/src/tools/miri/tests/pass/assume_bug.rs b/src/tools/miri/tests/pass/assume_bug.rs
new file mode 100644
index 0000000000000..e14f875c022e3
--- /dev/null
+++ b/src/tools/miri/tests/pass/assume_bug.rs
@@ -0,0 +1,3 @@
+fn main() {
+    vec![()].into_iter();
+}
diff --git a/src/tools/miri/tests/pass/async-fn.rs b/src/tools/miri/tests/pass/async-fn.rs
new file mode 100644
index 0000000000000..1d5d9a27cc8a5
--- /dev/null
+++ b/src/tools/miri/tests/pass/async-fn.rs
@@ -0,0 +1,90 @@
+#![feature(never_type)]
+
+use std::future::Future;
+
+// See if we can run a basic `async fn`
+pub async fn foo(x: &u32, y: u32) -> u32 {
+    let y = &y;
+    let z = 9;
+    let z = &z;
+    let y = async { *y + *z }.await;
+    let a = 10;
+    let a = &a;
+    *x + y + *a
+}
+
+async fn add(x: u32, y: u32) -> u32 {
+    let a = async { x + y };
+    a.await
+}
+
+async fn build_aggregate(a: u32, b: u32, c: u32, d: u32) -> u32 {
+    let x = (add(a, b).await, add(c, d).await);
+    x.0 + x.1
+}
+
+enum Never {}
+fn never() -> Never {
+    panic!()
+}
+
+async fn includes_never(crash: bool, x: u32) -> u32 {
+    let result = async { x * x }.await;
+    if !crash {
+        return result;
+    }
+    #[allow(unused)]
+    let bad = never();
+    result *= async { x + x }.await;
+    drop(bad);
+    result
+}
+
+async fn partial_init(x: u32) -> u32 {
+    #[allow(unreachable_code)]
+    let _x: (String, !) = (String::new(), return async { x + x }.await);
+}
+
+async fn read_exact(_from: &mut &[u8], _to: &mut [u8]) -> Option<()> {
+    Some(())
+}
+
+async fn hello_world() {
+    let data = [0u8; 1];
+    let mut reader = &data[..];
+
+    let mut marker = [0u8; 1];
+    read_exact(&mut reader, &mut marker).await.unwrap();
+}
+
+fn run_fut<T>(fut: impl Future<Output = T>) -> T {
+    use std::sync::Arc;
+    use std::task::{Context, Poll, Wake, Waker};
+
+    struct MyWaker;
+    impl Wake for MyWaker {
+        fn wake(self: Arc<Self>) {
+            unimplemented!()
+        }
+    }
+
+    let waker = Waker::from(Arc::new(MyWaker));
+    let mut context = Context::from_waker(&waker);
+
+    let mut pinned = Box::pin(fut);
+    loop {
+        match pinned.as_mut().poll(&mut context) {
+            Poll::Pending => continue,
+            Poll::Ready(v) => return v,
+        }
+    }
+}
+
+fn main() {
+    let x = 5;
+    assert_eq!(run_fut(foo(&x, 7)), 31);
+    assert_eq!(run_fut(build_aggregate(1, 2, 3, 4)), 10);
+    assert_eq!(run_fut(includes_never(false, 4)), 16);
+    assert_eq!(run_fut(partial_init(4)), 8);
+    run_fut(hello_world());
+}
diff --git a/src/tools/miri/tests/pass/atomic-compare-exchange-weak-never-fail.rs b/src/tools/miri/tests/pass/atomic-compare-exchange-weak-never-fail.rs
new file mode 100644
index 0000000000000..8d3d71869f42e
--- /dev/null
+++ b/src/tools/miri/tests/pass/atomic-compare-exchange-weak-never-fail.rs
@@ -0,0 +1,17 @@
+//@compile-flags: -Zmiri-compare-exchange-weak-failure-rate=0.0
+use std::sync::atomic::{AtomicBool, Ordering::*};
+
+// Ensure that compare_exchange_weak never fails.
+fn main() {
+    let atomic = AtomicBool::new(false);
+    let tries = 100;
+    for _ in 0..tries {
+        let cur = atomic.load(Relaxed);
+        // Try (weakly) to flip the flag.
+        if atomic.compare_exchange_weak(cur, !cur, Relaxed, Relaxed).is_err() {
+            // We failed. Avoid panic machinery as that uses atomics/locks.
+            eprintln!("compare_exchange_weak failed");
+            std::process::abort();
+        }
+    }
+}
diff --git a/src/tools/miri/tests/pass/atomic.rs b/src/tools/miri/tests/pass/atomic.rs
new file mode 100644
index 0000000000000..e3d80a78916f6
--- /dev/null
+++ b/src/tools/miri/tests/pass/atomic.rs
@@ -0,0 +1,191 @@
+//@compile-flags: -Zmiri-strict-provenance
+#![feature(strict_provenance, strict_provenance_atomic_ptr)]
+use std::sync::atomic::{
+    compiler_fence, fence, AtomicBool, AtomicIsize, AtomicPtr, AtomicU64, Ordering::*,
+};
+
+fn main() {
+    atomic_bool();
+    atomic_all_ops();
+    atomic_u64();
+    atomic_fences();
+    atomic_ptr();
+    weak_sometimes_fails();
+}
+
+fn atomic_bool() {
+    static mut ATOMIC: AtomicBool = AtomicBool::new(false);
+
+    unsafe {
+        assert_eq!(*ATOMIC.get_mut(), false);
+        ATOMIC.store(true, SeqCst);
+        assert_eq!(*ATOMIC.get_mut(), true);
+        ATOMIC.fetch_or(false, SeqCst);
+        assert_eq!(*ATOMIC.get_mut(), true);
+        ATOMIC.fetch_and(false, SeqCst);
+        assert_eq!(*ATOMIC.get_mut(), false);
+        ATOMIC.fetch_nand(true, SeqCst);
+        assert_eq!(*ATOMIC.get_mut(), true);
+        ATOMIC.fetch_xor(true, SeqCst);
+        assert_eq!(*ATOMIC.get_mut(), false);
+    }
+}
+
+// There isn't a trait to use to make this generic, so just use a macro
+macro_rules! compare_exchange_weak_loop {
+    ($atom:expr, $from:expr, $to:expr, $succ_order:expr, $fail_order:expr) => {
+        loop {
+            match $atom.compare_exchange_weak($from, $to, $succ_order, $fail_order) {
+                Ok(n) => {
+                    assert_eq!(n, $from);
+                    break;
+                }
+                Err(n) => assert_eq!(n, $from),
+            }
+        }
+    };
+}
+
+/// Make sure we can handle all the intrinsics
+fn atomic_all_ops() {
+    static ATOMIC: AtomicIsize = AtomicIsize::new(0);
+    static ATOMIC_UNSIGNED: AtomicU64 = AtomicU64::new(0);
+
+    let load_orders = [Relaxed, Acquire, SeqCst];
+    let stored_orders = [Relaxed, Release, SeqCst];
+    let rmw_orders = [Relaxed, Release, Acquire, AcqRel, SeqCst];
+
+    // loads
+    for o in load_orders {
+        ATOMIC.load(o);
+    }
+
+    // stores
+    for o in stored_orders {
+        ATOMIC.store(1, o);
+    }
+
+    // most RMWs
+    for o in rmw_orders {
+        ATOMIC.swap(0, o);
+        ATOMIC.fetch_or(0, o);
+        ATOMIC.fetch_xor(0, o);
+        ATOMIC.fetch_and(0, o);
+        ATOMIC.fetch_nand(0, o);
+        ATOMIC.fetch_add(0, o);
+        ATOMIC.fetch_sub(0, o);
+        ATOMIC.fetch_min(0, o);
+        ATOMIC.fetch_max(0, o);
+        ATOMIC_UNSIGNED.fetch_min(0, o);
+        ATOMIC_UNSIGNED.fetch_max(0, o);
+    }
+
+    // RMWs with separate failure ordering
+    for o1 in rmw_orders {
+        for o2 in load_orders {
+            let _res = ATOMIC.compare_exchange(0, 0, o1, o2);
+            let _res = ATOMIC.compare_exchange_weak(0, 0, o1, o2);
+        }
+    }
+}
+
+fn atomic_u64() {
+    static ATOMIC: AtomicU64 = AtomicU64::new(0);
+
+    ATOMIC.store(1, SeqCst);
+    assert_eq!(ATOMIC.compare_exchange(0, 0x100, AcqRel, Acquire), Err(1));
+    assert_eq!(ATOMIC.compare_exchange(0, 1, Release, Relaxed), Err(1));
+    assert_eq!(ATOMIC.compare_exchange(1, 0, AcqRel, Relaxed), Ok(1));
+    assert_eq!(ATOMIC.compare_exchange(0, 1, Relaxed, Relaxed), Ok(0));
+    compare_exchange_weak_loop!(ATOMIC, 1, 0x100, AcqRel, Acquire);
+    assert_eq!(ATOMIC.compare_exchange_weak(0, 2, Acquire, Relaxed), Err(0x100));
+    assert_eq!(ATOMIC.compare_exchange_weak(0, 1, Release, Relaxed), Err(0x100));
+    assert_eq!(ATOMIC.load(Relaxed), 0x100);
+
+    assert_eq!(ATOMIC.fetch_max(0x10, SeqCst), 0x100);
+    assert_eq!(ATOMIC.fetch_max(0x100, SeqCst), 0x100);
+    assert_eq!(ATOMIC.fetch_max(0x1000, SeqCst), 0x100);
+    assert_eq!(ATOMIC.fetch_max(0x1000, SeqCst), 0x1000);
+    assert_eq!(ATOMIC.fetch_max(0x2000, SeqCst), 0x1000);
+    assert_eq!(ATOMIC.fetch_max(0x2000, SeqCst), 0x2000);
+
+    assert_eq!(ATOMIC.fetch_min(0x2000, SeqCst), 0x2000);
+    assert_eq!(ATOMIC.fetch_min(0x2000, SeqCst), 0x2000);
+    assert_eq!(ATOMIC.fetch_min(0x1000, SeqCst), 0x2000);
+    assert_eq!(ATOMIC.fetch_min(0x1000, SeqCst), 0x1000);
+    assert_eq!(ATOMIC.fetch_min(0x100, SeqCst), 0x1000);
+    assert_eq!(ATOMIC.fetch_min(0x10, SeqCst), 0x100);
+}
+
+fn atomic_fences() {
+    fence(SeqCst);
+    fence(Release);
+    fence(Acquire);
+    fence(AcqRel);
+    compiler_fence(SeqCst);
+    compiler_fence(Release);
+    compiler_fence(Acquire);
+    compiler_fence(AcqRel);
+}
+
+fn atomic_ptr() {
+    use std::ptr;
+    let array: Vec<i32> = (0..100).into_iter().collect(); // a target to point to, to test provenance things
+    let x = array.as_ptr() as *mut i32;
+
+    let ptr = AtomicPtr::<i32>::new(ptr::null_mut());
+    assert!(ptr.load(Relaxed).addr() == 0);
+    ptr.store(ptr::invalid_mut(13), SeqCst);
+    assert!(ptr.swap(x, Relaxed).addr() == 13);
+    unsafe { assert!(*ptr.load(Acquire) == 0) };
+
+    // comparison ignores provenance
+    assert_eq!(
+        ptr.compare_exchange(
+            (&mut 0 as *mut i32).with_addr(x.addr()),
+            ptr::invalid_mut(0),
+            SeqCst,
+            SeqCst
+        )
+        .unwrap()
+        .addr(),
+        x.addr(),
+    );
+    assert_eq!(
+        ptr.compare_exchange(
+            (&mut 0 as *mut i32).with_addr(x.addr()),
+            ptr::invalid_mut(0),
+            SeqCst,
+            SeqCst
+        )
+        .unwrap_err()
+        .addr(),
+        0,
+    );
+    ptr.store(x, Relaxed);
+
+    assert_eq!(ptr.fetch_ptr_add(13, AcqRel).addr(), x.addr());
+    unsafe { assert_eq!(*ptr.load(SeqCst), 13) }; // points to index 13 now
+    assert_eq!(ptr.fetch_ptr_sub(4, AcqRel).addr(), x.addr() + 13 * 4);
+    unsafe { assert_eq!(*ptr.load(SeqCst), 9) };
+    assert_eq!(ptr.fetch_or(3, AcqRel).addr(), x.addr() + 9 * 4); // ptr is 4-aligned, so set the last 2 bits
+    assert_eq!(ptr.fetch_and(!3, AcqRel).addr(), (x.addr() + 9 * 4) | 3); // and unset them again
+    unsafe { assert_eq!(*ptr.load(SeqCst), 9) };
+    assert_eq!(ptr.fetch_xor(0xdeadbeef, AcqRel).addr(), x.addr() + 9 * 4);
+    assert_eq!(ptr.fetch_xor(0xdeadbeef, AcqRel).addr(), (x.addr() + 9 * 4) ^ 0xdeadbeef);
+    unsafe { assert_eq!(*ptr.load(SeqCst), 9) }; // after XORing twice with the same thing, we get our ptr back
+}
+
+fn weak_sometimes_fails() {
+    let atomic = AtomicBool::new(false);
+    let tries = 100;
+    for _ in 0..tries {
+        let cur = atomic.load(Relaxed);
+        // Try (weakly) to flip the flag.
+        if atomic.compare_exchange_weak(cur, !cur, Relaxed, Relaxed).is_err() {
+            // We failed, so return and skip the panic.
+            return;
+        }
+    }
+    panic!("compare_exchange_weak succeeded {} tries in a row", tries);
+}
diff --git a/src/tools/miri/tests/pass/available-parallelism.rs b/src/tools/miri/tests/pass/available-parallelism.rs
new file mode 100644
index 0000000000000..eb4d09b1f5495
--- /dev/null
+++ b/src/tools/miri/tests/pass/available-parallelism.rs
@@ -0,0 +1,3 @@
+fn main() {
+    assert_eq!(std::thread::available_parallelism().unwrap().get(), 1);
+}
diff --git a/src/tools/miri/tests/pass/backtrace/backtrace-api-v0.rs b/src/tools/miri/tests/pass/backtrace/backtrace-api-v0.rs
new file mode 100644
index 0000000000000..5cd12959ca40e
--- /dev/null
+++ b/src/tools/miri/tests/pass/backtrace/backtrace-api-v0.rs
@@ -0,0 +1,67 @@
+//@normalize-stderr-test: "::<.*>" -> ""
+
+#[inline(never)]
+fn func_a() -> Box<[*mut ()]> {
+    func_b::<u8>()
+}
+#[inline(never)]
+fn func_b<T>() -> Box<[*mut ()]> {
+    func_c()
+}
+
+macro_rules! invoke_func_d {
+    () => {
+        func_d()
+    };
+}
+
+#[inline(never)]
+fn func_c() -> Box<[*mut ()]> {
+    invoke_func_d!()
+}
+#[inline(never)]
+fn func_d() -> Box<[*mut ()]> {
+    unsafe { miri_get_backtrace(0) }
+}
+
+fn main() {
+    let mut seen_main = false;
+    let frames = func_a();
+    for frame in frames.into_iter() {
+        let miri_frame = unsafe { miri_resolve_frame(*frame, 0) };
+        let name = String::from_utf8(miri_frame.name.into()).unwrap();
+        let filename = String::from_utf8(miri_frame.filename.into()).unwrap();
+
+        if name == "func_a" {
+            assert_eq!(func_a as *mut (), miri_frame.fn_ptr);
+        }
+
+        // Print every frame to stderr.
+        let out = format!("{}:{}:{} ({})", filename, miri_frame.lineno, miri_frame.colno, name);
+        eprintln!("{}", out);
+        // Print the 'main' frame (and everything before it) to stdout, skipping
+        // the printing of internal (and possibly fragile) libstd frames.
+        if !seen_main {
+            println!("{}", out);
+            seen_main = name == "main";
+        }
+    }
+}
+
+// This goes at the bottom of the file so that we can change it
+// without disturbing line numbers of the functions in the backtrace.
+
+extern "Rust" {
+    fn miri_get_backtrace(flags: u64) -> Box<[*mut ()]>;
+    fn miri_resolve_frame(ptr: *mut (), flags: u64) -> MiriFrame;
+}
+
+#[derive(Debug)]
+#[repr(C)]
+struct MiriFrame {
+    name: Box<[u8]>,
+    filename: Box<[u8]>,
+    lineno: u32,
+    colno: u32,
+    fn_ptr: *mut (),
+}
diff --git a/src/tools/miri/tests/pass/backtrace/backtrace-api-v0.stderr b/src/tools/miri/tests/pass/backtrace/backtrace-api-v0.stderr
new file mode 100644
index 0000000000000..ee556b3e4a05a
--- /dev/null
+++ b/src/tools/miri/tests/pass/backtrace/backtrace-api-v0.stderr
@@ -0,0 +1,5 @@
+$DIR/backtrace-api-v0.rs:LL:CC (func_d)
+$DIR/backtrace-api-v0.rs:LL:CC (func_c)
+$DIR/backtrace-api-v0.rs:LL:CC (func_b)
+$DIR/backtrace-api-v0.rs:LL:CC (func_a)
+$DIR/backtrace-api-v0.rs:LL:CC RUSTLIB/core/src/ops/function.rs:LL:CC (<fn() as std::ops::FnOnce<()>>::call_once - RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC (std::sys_common::backtrace::__rust_begin_short_backtrace)
diff --git a/src/tools/miri/tests/pass/backtrace/backtrace-api-v0.stdout b/src/tools/miri/tests/pass/backtrace/backtrace-api-v0.stdout
new file mode 100644
index 0000000000000..2fe31dd0e6bae
--- /dev/null
+++ b/src/tools/miri/tests/pass/backtrace/backtrace-api-v0.stdout
@@ -0,0 +1,5 @@
+$DIR/backtrace-api-v0.rs:24:14 (func_d)
+$DIR/backtrace-api-v0.rs:20:5 (func_c)
+$DIR/backtrace-api-v0.rs:9:5 (func_b)
+$DIR/backtrace-api-v0.rs:5:5 (func_a)
+$DIR/backtrace-api-v0.rs:29:18 (main)
diff --git a/src/tools/miri/tests/pass/backtrace/backtrace-api-v1.rs b/src/tools/miri/tests/pass/backtrace/backtrace-api-v1.rs
new file mode 100644
index 0000000000000..1e35574b39b6c
--- /dev/null
+++ b/src/tools/miri/tests/pass/backtrace/backtrace-api-v1.rs
@@ -0,0 +1,82 @@
+//@normalize-stderr-test: "::<.*>" -> ""
+
+#[inline(never)]
+fn func_a() -> Box<[*mut ()]> {
+    func_b::<u8>()
+}
+#[inline(never)]
+fn func_b<T>() -> Box<[*mut ()]> {
+    func_c()
+}
+
+macro_rules! invoke_func_d {
+    () => {
+        func_d()
+    };
+}
+
+#[inline(never)]
+fn func_c() -> Box<[*mut ()]> {
+    invoke_func_d!()
+}
+#[inline(never)]
+fn func_d() -> Box<[*mut ()]> {
+    unsafe {
+        let count = miri_backtrace_size(0);
+        let mut buf = vec![std::ptr::null_mut(); count];
+        miri_get_backtrace(1, buf.as_mut_ptr());
+        buf.into()
+    }
+}
+
+fn main() {
+    let mut seen_main = false;
+    let frames = func_a();
+    for frame in frames.into_iter() {
+        let miri_frame = unsafe { miri_resolve_frame(*frame, 1) };
+
+        let mut name = vec![0; miri_frame.name_len];
+        let mut filename = vec![0; miri_frame.filename_len];
+
+        unsafe {
+            miri_resolve_frame_names(*frame, 0, name.as_mut_ptr(), filename.as_mut_ptr());
+        }
+
+        let name = String::from_utf8(name).unwrap();
+        let filename = String::from_utf8(filename).unwrap();
+
+        if name == "func_a" {
+            assert_eq!(func_a as *mut (), miri_frame.fn_ptr);
+        }
+
+        // Print every frame to stderr.
+        let out = format!("{}:{}:{} ({})", filename, miri_frame.lineno, miri_frame.colno, name);
+        eprintln!("{}", out);
+        // Print the 'main' frame (and everything before it) to stdout, skipping
+        // the printing of internal (and possibly fragile) libstd frames.
+        if !seen_main {
+            println!("{}", out);
+            seen_main = name == "main";
+        }
+    }
+}
+
+// This goes at the bottom of the file so that we can change it
+// without disturbing line numbers of the functions in the backtrace.
+
+extern "Rust" {
+    fn miri_backtrace_size(flags: u64) -> usize;
+    fn miri_get_backtrace(flags: u64, buf: *mut *mut ());
+    fn miri_resolve_frame(ptr: *mut (), flags: u64) -> MiriFrame;
+    fn miri_resolve_frame_names(ptr: *mut (), flags: u64, name_buf: *mut u8, filename_buf: *mut u8);
+}
+
+#[derive(Debug)]
+#[repr(C)]
+struct MiriFrame {
+    name_len: usize,
+    filename_len: usize,
+    lineno: u32,
+    colno: u32,
+    fn_ptr: *mut (),
+}
diff --git a/src/tools/miri/tests/pass/backtrace/backtrace-api-v1.stderr b/src/tools/miri/tests/pass/backtrace/backtrace-api-v1.stderr
new file mode 100644
index 0000000000000..7dc281af31ddc
--- /dev/null
+++ b/src/tools/miri/tests/pass/backtrace/backtrace-api-v1.stderr
@@ -0,0 +1,5 @@
+$DIR/backtrace-api-v1.rs:LL:CC (func_d)
+$DIR/backtrace-api-v1.rs:LL:CC (func_c)
+$DIR/backtrace-api-v1.rs:LL:CC (func_b)
+$DIR/backtrace-api-v1.rs:LL:CC (func_a)
+$DIR/backtrace-api-v1.rs:LL:CC RUSTLIB/core/src/ops/function.rs:LL:CC (<fn() as std::ops::FnOnce<()>>::call_once - RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC (std::sys_common::backtrace::__rust_begin_short_backtrace)
diff --git a/src/tools/miri/tests/pass/backtrace/backtrace-api-v1.stdout b/src/tools/miri/tests/pass/backtrace/backtrace-api-v1.stdout
new file mode 100644
index 0000000000000..0d2ae3b516a87
--- /dev/null
+++ b/src/tools/miri/tests/pass/backtrace/backtrace-api-v1.stdout
@@ -0,0 +1,5 @@
+$DIR/backtrace-api-v1.rs:27:9 (func_d)
+$DIR/backtrace-api-v1.rs:20:5 (func_c)
+$DIR/backtrace-api-v1.rs:9:5 (func_b)
+$DIR/backtrace-api-v1.rs:5:5 (func_a)
+$DIR/backtrace-api-v1.rs:34:18 (main)
diff --git a/src/tools/miri/tests/pass/backtrace/backtrace-global-alloc.rs b/src/tools/miri/tests/pass/backtrace/backtrace-global-alloc.rs
new file mode 100644
index 0000000000000..6660e5bb57c09
--- /dev/null
+++ b/src/tools/miri/tests/pass/backtrace/backtrace-global-alloc.rs
@@ -0,0 +1,12 @@
+//@compile-flags: -Zmiri-disable-isolation
+//@rustc-env: RUST_BACKTRACE=1
+
+use std::alloc::System;
+use std::backtrace::Backtrace;
+
+#[global_allocator]
+static GLOBAL_ALLOCATOR: System = System;
+
+fn main() {
+    eprint!("{}", Backtrace::capture());
+}
diff --git a/src/tools/miri/tests/pass/backtrace/backtrace-global-alloc.stderr b/src/tools/miri/tests/pass/backtrace/backtrace-global-alloc.stderr
new file mode 100644
index 0000000000000..c48061d64d088
--- /dev/null
+++ b/src/tools/miri/tests/pass/backtrace/backtrace-global-alloc.stderr
@@ -0,0 +1,28 @@
+   0: main
+ at $DIR/backtrace-global-alloc.rs:LL:CC
+   1: <fn() as std::ops::FnOnce<()>>::call_once - shim(fn())
+ at RUSTLIB/core/src/ops/function.rs:LL:CC
+   2: std::sys_common::backtrace::__rust_begin_short_backtrace
+ at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC
+   3: std::rt::lang_start::{closure#0}
+ at RUSTLIB/std/src/rt.rs:LL:CC
+   4: std::ops::function::impls::call_once
+ at RUSTLIB/core/src/ops/function.rs:LL:CC
+   5: std::panicking::r#try::do_call
+ at RUSTLIB/std/src/panicking.rs:LL:CC
+   6: std::panicking::r#try
+ at RUSTLIB/std/src/panicking.rs:LL:CC
+   7: std::panic::catch_unwind
+ at RUSTLIB/std/src/panic.rs:LL:CC
+   8: std::rt::lang_start_internal::{closure#2}
+ at RUSTLIB/std/src/rt.rs:LL:CC
+   9: std::panicking::r#try::do_call
+ at RUSTLIB/std/src/panicking.rs:LL:CC
+  10: std::panicking::r#try
+ at RUSTLIB/std/src/panicking.rs:LL:CC
+  11: std::panic::catch_unwind
+ at RUSTLIB/std/src/panic.rs:LL:CC
+  12: std::rt::lang_start_internal
+ at RUSTLIB/std/src/rt.rs:LL:CC
+  13: std::rt::lang_start
+ at RUSTLIB/std/src/rt.rs:LL:CC
diff --git a/src/tools/miri/tests/pass/backtrace/backtrace-std.rs b/src/tools/miri/tests/pass/backtrace/backtrace-std.rs
new file mode 100644
index 0000000000000..b2a5ac180298c
--- /dev/null
+++ b/src/tools/miri/tests/pass/backtrace/backtrace-std.rs
@@ -0,0 +1,32 @@
+//@compile-flags: -Zmiri-disable-isolation
+//@rustc-env: RUST_BACKTRACE=1
+
+use std::backtrace::Backtrace;
+
+#[inline(never)]
+fn func_a() -> Backtrace {
+    func_b::<u8>()
+}
+#[inline(never)]
+fn func_b<T>() -> Backtrace {
+    func_c()
+}
+
+macro_rules! invoke_func_d {
+    () => {
+        func_d()
+    };
+}
+
+#[inline(never)]
+fn func_c() -> Backtrace {
+    invoke_func_d!()
+}
+#[inline(never)]
+fn func_d() -> Backtrace {
+    Backtrace::capture()
+}
+
+fn main() {
+    eprint!("{}", func_a());
+}
diff --git a/src/tools/miri/tests/pass/backtrace/backtrace-std.stderr b/src/tools/miri/tests/pass/backtrace/backtrace-std.stderr
new file mode 100644
index 0000000000000..4596cadb958d8
--- /dev/null
+++ b/src/tools/miri/tests/pass/backtrace/backtrace-std.stderr
@@ -0,0 +1,36 @@
+   0: func_d
+ at $DIR/backtrace-std.rs:LL:CC
+   1: func_c
+ at $DIR/backtrace-std.rs:LL:CC
+   2: func_b
+ at $DIR/backtrace-std.rs:LL:CC
+   3: func_a
+ at $DIR/backtrace-std.rs:LL:CC
+   4: main
+ at $DIR/backtrace-std.rs:LL:CC
+   5: <fn() as std::ops::FnOnce<()>>::call_once - shim(fn())
+ at RUSTLIB/core/src/ops/function.rs:LL:CC
+   6: std::sys_common::backtrace::__rust_begin_short_backtrace
+ at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC
+   7: std::rt::lang_start::{closure#0}
+ at RUSTLIB/std/src/rt.rs:LL:CC
+   8: std::ops::function::impls::call_once
+ at RUSTLIB/core/src/ops/function.rs:LL:CC
+   9: std::panicking::r#try::do_call
+ at RUSTLIB/std/src/panicking.rs:LL:CC
+  10: std::panicking::r#try
+ at RUSTLIB/std/src/panicking.rs:LL:CC
+  11: std::panic::catch_unwind
+ at RUSTLIB/std/src/panic.rs:LL:CC
+  12: std::rt::lang_start_internal::{closure#2}
+ at RUSTLIB/std/src/rt.rs:LL:CC
+  13: std::panicking::r#try::do_call
+ at RUSTLIB/std/src/panicking.rs:LL:CC
+  14: std::panicking::r#try
+ at RUSTLIB/std/src/panicking.rs:LL:CC
+  15: std::panic::catch_unwind
+ at RUSTLIB/std/src/panic.rs:LL:CC
+  16: std::rt::lang_start_internal
+ at RUSTLIB/std/src/rt.rs:LL:CC
+  17: std::rt::lang_start
+ at RUSTLIB/std/src/rt.rs:LL:CC
diff --git a/src/tools/miri/tests/pass/bad_substs.rs b/src/tools/miri/tests/pass/bad_substs.rs
new file mode 100644
index 0000000000000..d8da2de5d6df9
--- /dev/null
+++ b/src/tools/miri/tests/pass/bad_substs.rs
@@ -0,0 +1,4 @@
+fn main() {
+    let f: fn(i32) -> Option<i32> = Some::<i32>;
+    f(42);
+}
diff --git a/src/tools/miri/tests/pass/binary-heap.rs b/src/tools/miri/tests/pass/binary-heap.rs
new file mode 100644
index 0000000000000..278038d8ad321
--- /dev/null
+++ b/src/tools/miri/tests/pass/binary-heap.rs
@@ -0,0 +1,37 @@
+use std::collections::BinaryHeap;
+use std::iter::Iterator;
+
+fn zero_sized_push() {
+    const N: usize = 8;
+
+    for len in 0..N {
+        let mut tester = BinaryHeap::with_capacity(len);
+        assert_eq!(tester.len(), 0);
+        assert!(tester.capacity() >= len);
+        for _ in 0..len {
+            tester.push(());
+        }
+        assert_eq!(tester.len(), len);
+        assert_eq!(tester.iter().count(), len);
+        tester.clear();
+    }
+}
+
+fn drain() {
+    let mut heap = (0..128i32).collect::<BinaryHeap<_>>();
+
+    assert!(!heap.is_empty());
+
+    let mut sum = 0;
+    for x in heap.drain() {
+        sum += x;
+    }
+    assert_eq!(sum, 127 * 128 / 2);
+
+    assert!(heap.is_empty());
+}
+
+fn main() {
+    zero_sized_push();
+    drain();
+}
diff --git a/src/tools/miri/tests/pass/binops.rs b/src/tools/miri/tests/pass/binops.rs
new file mode 100644
index 0000000000000..0988d7ccc4c6e
--- /dev/null
+++ b/src/tools/miri/tests/pass/binops.rs
@@ -0,0 +1,78 @@
+// Binop corner cases
+
+fn test_nil() {
+    assert_eq!((), ());
+    assert!((!(() != ())));
+    assert!((!(() < ())));
+    assert!((() <= ()));
+    assert!((!(() > ())));
+    assert!((() >= ()));
+}
+
+fn test_bool() {
+    assert!((!(true < false)));
+    assert!((!(true <= false)));
+    assert!((true > false));
+    assert!((true >= false));
+
+    assert!((false < true));
+    assert!((false <= true));
+    assert!((!(false > true)));
+    assert!((!(false >= true)));
+
+    // Bools support bitwise binops
+    assert_eq!(false & false, false);
+    assert_eq!(true & false, false);
+    assert_eq!(true & true, true);
+    assert_eq!(false | false, false);
+    assert_eq!(true | false, true);
+    assert_eq!(true | true, true);
+    assert_eq!(false ^ false, false);
+    assert_eq!(true ^ false, true);
+    assert_eq!(true ^ true, false);
+}
+
+fn test_ptr() {
+    unsafe {
+        let p1: *const u8 = ::std::mem::transmute(0_usize);
+        let p2: *const u8 = ::std::mem::transmute(0_usize);
+        let p3: *const u8 = ::std::mem::transmute(1_usize);
+
+        assert_eq!(p1, p2);
+        assert!(p1 != p3);
+        assert!(p1 < p3);
+        assert!(p1 <= p3);
+        assert!(p3 > p1);
+        assert!(p3 >= p3);
+        assert!(p1 <= p2);
+        assert!(p1 >= p2);
+    }
+}
+
+#[derive(PartialEq, Debug)]
+struct P {
+    x: isize,
+    y: isize,
+}
+
+fn p(x: isize, y: isize) -> P {
+    P { x: x, y: y }
+}
+
+fn test_class() {
+    let q = p(1, 2);
+    let mut r = p(1, 2);
+
+    assert_eq!(q, r);
+    r.y = 17;
+    assert!((r.y != q.y));
+    assert_eq!(r.y, 17);
+    assert!((q != r));
+}
+
+pub fn main() {
+    test_nil();
+    test_bool();
+    test_ptr();
+    test_class();
+}
diff --git a/src/tools/miri/tests/pass/bools.rs b/src/tools/miri/tests/pass/bools.rs
new file mode 100644
index 0000000000000..30fc14704d9d0
--- /dev/null
+++ b/src/tools/miri/tests/pass/bools.rs
@@ -0,0 +1,29 @@
+fn boolean() -> bool {
+    true
+}
+
+fn if_false() -> i64 {
+    let c = false;
+    if c { 1 } else { 0 }
+}
+
+fn if_true() -> i64 {
+    let c = true;
+    if c { 1 } else { 0 }
+}
+
+fn match_bool() -> i16 {
+    let b = true;
+    match b {
+        true => 1,
+        _ => 0,
+    }
+}
+
+fn main() {
+    assert!(boolean());
+    assert_eq!(if_false(), 0);
+    assert_eq!(if_true(), 1);
+    assert_eq!(match_bool(), 1);
+    assert_eq!(true == true, true);
+}
diff --git a/src/tools/miri/tests/pass/box-custom-alloc.rs b/src/tools/miri/tests/pass/box-custom-alloc.rs
new file mode 100644
index 0000000000000..ef432a86d460a
--- /dev/null
+++ b/src/tools/miri/tests/pass/box-custom-alloc.rs
@@ -0,0 +1,87 @@
+#![allow(incomplete_features)] // for trait upcasting
+#![feature(allocator_api, trait_upcasting)]
+
+use std::alloc::Layout;
+use std::alloc::{AllocError, Allocator};
+use std::cell::Cell;
+use std::mem::MaybeUninit;
+use std::ptr::{self, NonNull};
+
+struct OnceAlloc<'a> {
+    space: Cell<&'a mut [MaybeUninit<u8>]>,
+}
+
+unsafe impl<'shared, 'a: 'shared> Allocator for &'shared OnceAlloc<'a> {
+    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
+        let space = self.space.replace(&mut []);
+
+        let (ptr, len) = (space.as_mut_ptr(), space.len());
+
+        if ptr.align_offset(layout.align()) != 0 || len < layout.size() {
+            return Err(AllocError);
+        }
+
+        let slice_ptr = ptr::slice_from_raw_parts_mut(ptr as *mut u8, len);
+        unsafe { Ok(NonNull::new_unchecked(slice_ptr)) }
+    }
+
+    unsafe fn deallocate(&self, _ptr: NonNull<u8>, _layout: Layout) {}
+}
+
+trait MyTrait {
+    fn hello(&self) -> u8;
+}
+
+impl MyTrait for [u8; 1] {
+    fn hello(&self) -> u8 {
+        self[0]
+    }
+}
+
+trait TheTrait: MyTrait {}
+
+impl TheTrait for [u8; 1] {}
+
+/// `Box<T, G>` is a `ScalarPair` where the 2nd component is the allocator.
+fn test1() {
+    let mut space = vec![MaybeUninit::new(0); 1];
+    let once_alloc = OnceAlloc { space: Cell::new(&mut space[..]) };
+
+    let boxed = Box::new_in([42u8; 1], &once_alloc);
+    let _val = *boxed;
+    let with_dyn: Box<dyn TheTrait, &OnceAlloc> = boxed;
+    assert_eq!(42, with_dyn.hello());
+    let with_dyn: Box<dyn MyTrait, &OnceAlloc> = with_dyn; // upcast
+    assert_eq!(42, with_dyn.hello());
+}
+
+// Make the allocator itself so big that the Box is not even a ScalarPair any more.
+struct OnceAllocRef<'s, 'a>(&'s OnceAlloc<'a>, u64);
+
+unsafe impl<'shared, 'a: 'shared> Allocator for OnceAllocRef<'shared, 'a> {
+    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
+        self.0.allocate(layout)
+    }
+
+    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
+        self.0.deallocate(ptr, layout)
+    }
+}
+
+/// `Box<T, G>` is an `Aggregate`.
+fn test2() {
+    let mut space = vec![MaybeUninit::new(0); 1];
+    let once_alloc = OnceAlloc { space: Cell::new(&mut space[..]) };
+
+    let boxed = Box::new_in([42u8; 1], OnceAllocRef(&once_alloc, 0));
+    let _val = *boxed;
+    let with_dyn: Box<dyn TheTrait, OnceAllocRef> = boxed;
+    assert_eq!(42, with_dyn.hello());
+    let with_dyn: Box<dyn MyTrait, OnceAllocRef> = with_dyn; // upcast
+    assert_eq!(42, with_dyn.hello());
+}
+
+fn main() {
+    test1();
+    test2();
+}
diff --git a/src/tools/miri/tests/pass/box.rs b/src/tools/miri/tests/pass/box.rs
new file mode 100644
index 0000000000000..7bbe7be516b9a
--- /dev/null
+++ b/src/tools/miri/tests/pass/box.rs
@@ -0,0 +1,59 @@
+#![feature(ptr_internals)]
+
+fn main() {
+    into_raw();
+    into_unique();
+    boxed_pair_to_vec();
+}
+
+fn into_raw() {
+    unsafe {
+        let b = Box::new(4i32);
+        let r = Box::into_raw(b);
+
+        // "lose the tag"
+        let r2 = ((r as usize) + 0) as *mut i32;
+        *(&mut *r2) = 7;
+
+        // Use original ptr again
+        *(&mut *r) = 17;
+        drop(Box::from_raw(r));
+    }
+}
+
+fn into_unique() {
+    unsafe {
+        let b = Box::new(4i32);
+        let u = Box::into_unique(b).0;
+
+        // "lose the tag"
+        let r = ((u.as_ptr() as usize) + 0) as *mut i32;
+        *(&mut *r) = 7;
+
+        // Use original ptr again.
+        drop(Box::from_raw(u.as_ptr()));
+    }
+}
+
+fn boxed_pair_to_vec() {
+    #[repr(C)]
+    #[derive(Debug)]
+    struct PairFoo {
+        fst: Foo,
+        snd: Foo,
+    }
+
+    #[derive(Debug)]
+    struct Foo(u64);
+    fn reinterstruct(box_pair: Box<PairFoo>) -> Vec<Foo> {
+        let ref_pair = Box::leak(box_pair) as *mut PairFoo;
+        let ptr_foo = unsafe { std::ptr::addr_of_mut!((*ref_pair).fst) };
+        unsafe { Vec::from_raw_parts(ptr_foo, 2, 2) }
+    }
+
+    let pair_foo = Box::new(PairFoo { fst: Foo(42), snd: Foo(1337) });
+    println!("pair_foo = {:?}", pair_foo);
+    for (n, foo) in reinterstruct(pair_foo).into_iter().enumerate() {
+        println!("foo #{} = {:?}", n, foo);
+    }
+}
diff --git a/src/tools/miri/tests/pass/box.stderr b/src/tools/miri/tests/pass/box.stderr
new file mode 100644
index 0000000000000..0001a8dd6eb33
--- /dev/null
+++ b/src/tools/miri/tests/pass/box.stderr
@@ -0,0 +1,33 @@
+warning: integer-to-pointer cast
+  --> $DIR/box.rs:LL:CC
+   |
+LL |         let r2 = ((r as usize) + 0) as *mut i32;
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ integer-to-pointer cast
+   |
+   = help: This program is using integer-to-pointer casts or (equivalently) `ptr::from_exposed_addr`,
+   = help: which means that Miri might miss pointer bugs in this program.
+   = help: See https://doc.rust-lang.org/nightly/std/ptr/fn.from_exposed_addr.html for more details on that operation.
+   = help: To ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead.
+   = help: You can then pass the `-Zmiri-strict-provenance` flag to Miri, to ensure you are not relying on `from_exposed_addr` semantics.
+   = help: Alternatively, the `-Zmiri-permissive-provenance` flag disables this warning.
+   = note: BACKTRACE:
+   = note: inside `into_raw` at $DIR/box.rs:LL:CC
+note: inside `main` at $DIR/box.rs:LL:CC
+  --> $DIR/box.rs:LL:CC
+   |
+LL |     into_raw();
+   |     ^^^^^^^^^^
+
+warning: integer-to-pointer cast
+  --> $DIR/box.rs:LL:CC
+   |
+LL |         let r = ((u.as_ptr() as usize) + 0) as *mut i32;
+   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ integer-to-pointer cast
+   |
+   = note: inside `into_unique` at $DIR/box.rs:LL:CC
+note: inside `main` at $DIR/box.rs:LL:CC
+  --> $DIR/box.rs:LL:CC
+   |
+LL |     into_unique();
+   |     ^^^^^^^^^^^^^
+
diff --git a/src/tools/miri/tests/pass/box.stdout b/src/tools/miri/tests/pass/box.stdout
new file mode 100644
index 0000000000000..230ef368da644
--- /dev/null
+++ b/src/tools/miri/tests/pass/box.stdout
@@ -0,0 +1,3 @@
+pair_foo = PairFoo { fst: Foo(42), snd: Foo(1337) }
+foo #0 = Foo(42)
+foo #1 = Foo(1337)
diff --git a/src/tools/miri/tests/pass/btreemap.rs b/src/tools/miri/tests/pass/btreemap.rs
new file mode 100644
index 0000000000000..040c648d4747b
--- /dev/null
+++ b/src/tools/miri/tests/pass/btreemap.rs
@@ -0,0 +1,54 @@
+//@compile-flags: -Zmiri-strict-provenance
+#![feature(btree_drain_filter)]
+use std::collections::{BTreeMap, BTreeSet};
+use std::mem;
+
+#[derive(PartialEq, Eq, PartialOrd, Ord)]
+pub enum Foo {
+    A(&'static str),
+    _B,
+    _C,
+}
+
+// Gather all references from a mutable iterator and make sure Miri notices if
+// using them is dangerous.
+fn test_all_refs<'a, T: 'a>(dummy: &mut T, iter: impl Iterator<Item = &'a mut T>) {
+    // Gather all those references.
+    let mut refs: Vec<&mut T> = iter.collect();
+    // Use them all. Twice, to be sure we got all interleavings.
+    for r in refs.iter_mut() {
+        std::mem::swap(dummy, r);
+    }
+    for r in refs {
+        std::mem::swap(dummy, r);
+    }
+}
+
+pub fn main() {
+    let mut b = BTreeSet::new();
+    b.insert(Foo::A("\'"));
+    b.insert(Foo::A("/="));
+    b.insert(Foo::A("#"));
+    b.insert(Foo::A("0o"));
+    assert!(b.remove(&Foo::A("/=")));
+    assert!(!b.remove(&Foo::A("/=")));
+
+    // Also test a lower-alignment type, where the NodeHeader overlaps with
+    // the keys.
+    let mut b = BTreeSet::new();
+    b.insert(1024u16);
+    b.insert(7u16);
+
+    let mut b = BTreeMap::new();
+    b.insert(format!("bar"), 1024);
+    b.insert(format!("baz"), 7);
+    for i in 0..60 {
+        b.insert(format!("key{}", i), i);
+    }
+    test_all_refs(&mut 13, b.values_mut());
+
+    // Test forgetting the drain.
+    let mut d = b.drain_filter(|_, i| *i < 30);
+    d.next().unwrap();
+    mem::forget(d);
+}
diff --git a/src/tools/miri/tests/pass/c_enums.rs b/src/tools/miri/tests/pass/c_enums.rs
new file mode 100644
index 0000000000000..16b795342eab8
--- /dev/null
+++ b/src/tools/miri/tests/pass/c_enums.rs
@@ -0,0 +1,34 @@
+enum Foo {
+    Bar = 42,
+    Baz,
+    Quux = 100,
+}
+
+enum Signed {
+    Bar = -42,
+    Baz,
+    Quux = 100,
+}
+
+fn foo() -> [u8; 3] {
+    let baz = Foo::Baz; // let-expansion changes the MIR significantly
+    [Foo::Bar as u8, baz as u8, Foo::Quux as u8]
+}
+
+fn signed() -> [i8; 3] {
+    let baz = Signed::Baz; // let-expansion changes the MIR significantly
+    [Signed::Bar as i8, baz as i8, Signed::Quux as i8]
+}
+
+fn unsafe_match() -> bool {
+    match unsafe { std::mem::transmute::<u8, Foo>(43) } {
+        Foo::Baz => true,
+        _ => false,
+    }
+}
+
+fn main() {
+    assert_eq!(foo(), [42, 43, 100]);
+    assert_eq!(signed(), [-42, -41, 100]);
+    assert!(unsafe_match());
+}
diff --git a/src/tools/miri/tests/pass/calls.rs b/src/tools/miri/tests/pass/calls.rs
new file mode 100644
index 0000000000000..014d1d3acab7f
--- /dev/null
+++ b/src/tools/miri/tests/pass/calls.rs
@@ -0,0 +1,43 @@
+fn call() -> i32 {
+    fn increment(x: i32) -> i32 {
+        x + 1
+    }
+    increment(1)
+}
+
+fn factorial_recursive() -> i64 {
+    fn fact(n: i64) -> i64 {
+        if n == 0 { 1 } else { n * fact(n - 1) }
+    }
+    fact(10)
+}
+
+fn call_generic() -> (i16, bool) {
+    fn id<T>(t: T) -> T {
+        t
+    }
+    (id(42), id(true))
+}
+
+// Test calling a very simple function from the standard library.
+fn cross_crate_fn_call() -> i64 {
+    if 1i32.is_positive() { 1 } else { 0 }
+}
+
+const fn foo(i: i64) -> i64 {
+    *&i + 1
+}
+
+fn const_fn_call() -> i64 {
+    let x = 5 + foo(5);
+    assert_eq!(x, 11);
+    x
+}
+
+fn main() {
+    assert_eq!(call(), 2);
+    assert_eq!(factorial_recursive(), 3628800);
+    assert_eq!(call_generic(), (42, true));
+    assert_eq!(cross_crate_fn_call(), 1);
+    assert_eq!(const_fn_call(), 11);
+}
diff --git a/src/tools/miri/tests/pass/cast-rfc0401-vtable-kinds.rs b/src/tools/miri/tests/pass/cast-rfc0401-vtable-kinds.rs
new file mode 100644
index 0000000000000..d76c23633da1b
--- /dev/null
+++ b/src/tools/miri/tests/pass/cast-rfc0401-vtable-kinds.rs
@@ -0,0 +1,53 @@
+// Check that you can cast between different pointers to trait objects
+// whose vtable have the same kind (both lengths, or both trait pointers).
+
+trait Foo<T> {
+    fn foo(&self, _: T) -> u32 {
+        42
+    }
+}
+
+trait Bar {
+    fn bar(&self) {
+        println!("Bar!");
+    }
+}
+
+impl<T> Foo<T> for () {}
+impl Foo<u32> for u32 {
+    fn foo(&self, _: u32) -> u32 {
+        self + 43
+    }
+}
+impl Bar for () {}
+
+unsafe fn round_trip_and_call<'a>(t: *const (dyn Foo<u32> + 'a)) -> u32 {
+    let foo_e: *const dyn Foo<u16> = t as *const _;
+    let r_1 = foo_e as *mut dyn Foo<u32>;
+
+    (&*r_1).foo(0)
+}
+
+#[repr(C)]
+struct FooS<T: ?Sized>(T);
+#[repr(C)]
+struct BarS<T: ?Sized>(T);
+
+fn foo_to_bar<T: ?Sized>(u: *const FooS<T>) -> *const BarS<T> {
+    u as *const BarS<T>
+}
+
+fn main() {
+    let x = 4u32;
+    let y: &dyn Foo<u32> = &x;
+    let fl = unsafe { round_trip_and_call(y as *const dyn Foo<u32>) };
+    assert_eq!(fl, (43 + 4));
+
+    let s = FooS([0, 1, 2]);
+    let u: &FooS<[u32]> = &s;
+    let u: *const FooS<[u32]> = u;
+    let bar_ref: *const BarS<[u32]> = foo_to_bar(u);
+    let z: &BarS<[u32]> = unsafe { &*bar_ref };
+    assert_eq!(&z.0, &[0, 1, 2]);
+    // If validation fails here, that's likely because an immutable suspension is recovered mutably.
+}
diff --git a/src/tools/miri/tests/pass/cast_fn_ptr.rs b/src/tools/miri/tests/pass/cast_fn_ptr.rs
new file mode 100644
index 0000000000000..8954048f4262c
--- /dev/null
+++ b/src/tools/miri/tests/pass/cast_fn_ptr.rs
@@ -0,0 +1,7 @@
+fn main() {
+    fn f(_: *const u8) {}
+
+    let g = unsafe { std::mem::transmute::<fn(*const u8), fn(*const i32)>(f) };
+
+    g(&42 as *const _);
+}
diff --git a/src/tools/miri/tests/pass/cast_fn_ptr_unsafe.rs b/src/tools/miri/tests/pass/cast_fn_ptr_unsafe.rs
new file mode 100644
index 0000000000000..0cabb369bfdd9
--- /dev/null
+++ b/src/tools/miri/tests/pass/cast_fn_ptr_unsafe.rs
@@ -0,0 +1,8 @@
+fn main() {
+    fn f() {}
+
+    let g = f as fn() as unsafe fn();
+    unsafe {
+        g();
+    }
+}
diff --git a/src/tools/miri/tests/pass/catch.rs b/src/tools/miri/tests/pass/catch.rs
new file mode 100644
index 0000000000000..4ede23e68ce25
--- /dev/null
+++ b/src/tools/miri/tests/pass/catch.rs
@@ -0,0 +1,7 @@
+use std::panic::{catch_unwind, AssertUnwindSafe};
+
+fn main() {
+    let mut i = 3;
+    let _val = catch_unwind(AssertUnwindSafe(|| i -= 2));
+    println!("{}", i);
+}
diff --git a/src/tools/miri/tests/pass/catch.stdout b/src/tools/miri/tests/pass/catch.stdout
new file mode 100644
index 0000000000000..d00491fd7e5bb
--- /dev/null
+++ b/src/tools/miri/tests/pass/catch.stdout
@@ -0,0 +1 @@
+1
diff --git a/src/tools/miri/tests/pass/cfg_miri.rs b/src/tools/miri/tests/pass/cfg_miri.rs
new file mode 100644
index 0000000000000..558b9a4f50db9
--- /dev/null
+++ b/src/tools/miri/tests/pass/cfg_miri.rs
@@ -0,0 +1,3 @@
+fn main() {
+    assert!(cfg!(miri));
+}
diff --git a/src/tools/miri/tests/pass/char.rs b/src/tools/miri/tests/pass/char.rs
new file mode 100644
index 0000000000000..5524f0ae7abea
--- /dev/null
+++ b/src/tools/miri/tests/pass/char.rs
@@ -0,0 +1,7 @@
+fn main() {
+    let c = 'x';
+    assert_eq!(c, 'x');
+    assert!('a' < 'z');
+    assert!('1' < '9');
+    assert_eq!(std::char::from_u32('x' as u32), Some('x'));
+}
diff --git a/src/tools/miri/tests/pass/closure-drop.rs b/src/tools/miri/tests/pass/closure-drop.rs
new file mode 100644
index 0000000000000..9f9454b4c71c5
--- /dev/null
+++ b/src/tools/miri/tests/pass/closure-drop.rs
@@ -0,0 +1,26 @@
+struct Foo<'a>(&'a mut bool);
+
+impl<'a> Drop for Foo<'a> {
+    fn drop(&mut self) {
+        *self.0 = true;
+    }
+}
+
+fn f<T: FnOnce()>(t: T) {
+    t()
+}
+
+fn main() {
+    let mut ran_drop = false;
+    {
+        let x = Foo(&mut ran_drop);
+        // this closure never by val uses its captures
+        // so it's basically a fn(&self)
+        // the shim used to not drop the `x`
+        let x = move || {
+            let _val = x;
+        };
+        f(x);
+    }
+    assert!(ran_drop);
+}
diff --git a/src/tools/miri/tests/pass/closure-field-ty.rs b/src/tools/miri/tests/pass/closure-field-ty.rs
new file mode 100644
index 0000000000000..1c90a15f8c5ca
--- /dev/null
+++ b/src/tools/miri/tests/pass/closure-field-ty.rs
@@ -0,0 +1,10 @@
+// miri issue #304
+fn main() {
+    let mut y = 0;
+    {
+        let mut box_maybe_closure = Box::new(None);
+        *box_maybe_closure = Some(|| y += 1);
+        (box_maybe_closure.unwrap())();
+    }
+    assert_eq!(y, 1);
+}
diff --git a/src/tools/miri/tests/pass/closures.rs b/src/tools/miri/tests/pass/closures.rs
new file mode 100644
index 0000000000000..40aedbcaf4545
--- /dev/null
+++ b/src/tools/miri/tests/pass/closures.rs
@@ -0,0 +1,129 @@
+fn simple() -> i32 {
+    let y = 10;
+    let f = |x| x + y;
+    f(2)
+}
+
+fn crazy_closure() -> (i32, i32, i32) {
+    fn inner<T: Copy>(t: T) -> (i32, T, T) {
+        struct NonCopy;
+        let x = NonCopy;
+
+        let a = 2;
+        let b = 40;
+        let f = move |y, z, asdf| {
+            drop(x);
+            (a + b + y + z, asdf, t)
+        };
+        f(a, b, t)
+    }
+
+    inner(10)
+}
+
+fn closure_arg_adjustment_problem() -> i64 {
+    fn once<F: FnOnce(i64)>(f: F) {
+        f(2);
+    }
+    let mut y = 1;
+    {
+        let f = |x| y += x;
+        once(f);
+    }
+    y
+}
+
+fn fn_once_closure_with_multiple_args() -> i64 {
+    fn once<F: FnOnce(i64, i64) -> i64>(f: F) -> i64 {
+        f(2, 3)
+    }
+    let y = 1;
+    {
+        let f = |x, z| x + y + z;
+        once(f)
+    }
+}
+
+fn boxed_fn_once(f: Box<dyn FnOnce() -> i32>) -> i32 {
+    f()
+}
+
+fn box_dyn() {
+    let x: Box<dyn Fn(i32) -> i32> = Box::new(|x| x * 2);
+    assert_eq!(x(21), 42);
+    let mut i = 5;
+    {
+        let mut x: Box<dyn FnMut()> = Box::new(|| i *= 2);
+        x();
+        x();
+    }
+    assert_eq!(i, 20);
+}
+
+fn fn_item_as_closure_trait_object() {
+    fn foo() {}
+    let f: &dyn Fn() = &foo;
+    f();
+}
+
+fn fn_item_with_args_as_closure_trait_object() {
+    fn foo(i: i32) {
+        assert_eq!(i, 42);
+    }
+    let f: &dyn Fn(i32) = &foo;
+    f(42);
+}
+
+fn fn_item_with_multiple_args_as_closure_trait_object() {
+    fn foo(i: i32, j: i32) {
+        assert_eq!(i, 42);
+        assert_eq!(j, 55);
+    }
+
+    fn bar(i: i32, j: i32, k: f32) {
+        assert_eq!(i, 42);
+        assert_eq!(j, 55);
+        assert_eq!(k, 3.14159)
+    }
+    let f: &dyn Fn(i32, i32) = &foo;
+    f(42, 55);
+    let f: &dyn Fn(i32, i32, f32) = &bar;
+    f(42, 55, 3.14159);
+}
+
+fn fn_ptr_as_closure_trait_object() {
+    fn foo() {}
+    fn bar(u: u32) {
+        assert_eq!(u, 42);
+    }
+    fn baa(u: u32, f: f32) {
+        assert_eq!(u, 42);
+        assert_eq!(f, 3.141);
+    }
+    let f: &dyn Fn() = &(foo as fn());
+    f();
+    let f: &dyn Fn(u32) = &(bar as fn(u32));
+    f(42);
+    let f: &dyn Fn(u32, f32) = &(baa as fn(u32, f32));
+    f(42, 3.141);
+}
+
+fn main() {
+    assert_eq!(simple(), 12);
+    assert_eq!(crazy_closure(), (84, 10, 10));
+    assert_eq!(closure_arg_adjustment_problem(), 3);
+    assert_eq!(fn_once_closure_with_multiple_args(), 6);
+    assert_eq!(
+        boxed_fn_once(Box::new({
+            let x = 13;
+            move || x
+        })),
+        13,
+    );
+
+    box_dyn();
+    fn_item_as_closure_trait_object();
+    fn_item_with_args_as_closure_trait_object();
+    fn_item_with_multiple_args_as_closure_trait_object();
+    fn_ptr_as_closure_trait_object();
+}
diff --git a/src/tools/miri/tests/pass/coerce_non_capture_closure_to_fn_ptr.rs b/src/tools/miri/tests/pass/coerce_non_capture_closure_to_fn_ptr.rs
new file mode 100644
index 0000000000000..fde120db027b1
--- /dev/null
+++ b/src/tools/miri/tests/pass/coerce_non_capture_closure_to_fn_ptr.rs
@@ -0,0 +1,39 @@
+static FOO: fn() = || assert_ne!(42, 43);
+static BAR: fn(i32, i32) = |a, b| assert_ne!(a, b);
+
+// use to first make the closure FnOnce() before making it fn()
+fn force_once0<R, F: FnOnce() -> R>(f: F) -> F {
+    f
+}
+fn force_once1<T, R, F: FnOnce(T) -> R>(f: F) -> F {
+    f
+}
+fn force_mut0<R, F: FnMut() -> R>(f: F) -> F {
+    f
+}
+fn force_mut1<T, R, F: FnMut(T) -> R>(f: F) -> F {
+    f
+}
+
+fn main() {
+    FOO();
+    BAR(44, 45);
+    let bar: unsafe fn(i32, i32) = BAR;
+    unsafe { bar(46, 47) };
+    let boo: &dyn Fn(i32, i32) = &BAR;
+    boo(48, 49);
+
+    let f: fn() = || {};
+    f();
+    let f = force_once0(|| {}) as fn();
+    f();
+    let f = force_mut0(|| {}) as fn();
+    f();
+
+    let g: fn(i32) = |i| assert_eq!(i, 2);
+    g(2);
+    let g = force_once1(|i| assert_eq!(i, 2)) as fn(i32);
+    g(2);
+    let g = force_mut1(|i| assert_eq!(i, 2)) as fn(i32);
+    g(2);
+}
diff --git a/src/tools/miri/tests/pass/coercions.rs b/src/tools/miri/tests/pass/coercions.rs
new file mode 100644
index 0000000000000..db0d2ffd44bcf
--- /dev/null
+++ b/src/tools/miri/tests/pass/coercions.rs
@@ -0,0 +1,76 @@
+#![feature(coerce_unsized, unsize)]
+
+use std::marker::Unsize;
+use std::ops::CoerceUnsized;
+
+fn identity_coercion(x: &(dyn Fn(u32) -> u32 + Send)) -> &dyn Fn(u32) -> u32 {
+    x
+}
+fn fn_coercions(f: &fn(u32) -> u32) -> (unsafe fn(u32) -> u32, &(dyn Fn(u32) -> u32 + Send)) {
+    (*f, f)
+}
+
+fn simple_array_coercion(x: &[u8; 3]) -> &[u8] {
+    x
+}
+
+fn square(a: u32) -> u32 {
+    a * a
+}
+
+#[derive(PartialEq, Eq)]
+struct PtrWrapper<'a, T: 'a + ?Sized>(u32, u32, (), &'a T);
+impl<'a, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<PtrWrapper<'a, U>> for PtrWrapper<'a, T> {}
+
+struct TrivPtrWrapper<'a, T: 'a + ?Sized>(&'a T);
+impl<'a, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<TrivPtrWrapper<'a, U>>
+    for TrivPtrWrapper<'a, T>
+{
+}
+
+fn coerce_ptr_wrapper(p: PtrWrapper<[u8; 3]>) -> PtrWrapper<[u8]> {
+    p
+}
+
+fn coerce_triv_ptr_wrapper(p: TrivPtrWrapper<[u8; 3]>) -> TrivPtrWrapper<[u8]> {
+    p
+}
+
+fn coerce_fat_ptr_wrapper(
+    p: PtrWrapper<dyn Fn(u32) -> u32 + Send>,
+) -> PtrWrapper<dyn Fn(u32) -> u32> {
+    p
+}
+
+fn coerce_ptr_wrapper_poly<'a, T, Trait: ?Sized>(p: PtrWrapper<'a, T>) -> PtrWrapper<'a, Trait>
+where
+    PtrWrapper<'a, T>: CoerceUnsized<PtrWrapper<'a, Trait>>,
+{
+    p
+}
+
+fn main() {
+    let a = [0, 1, 2];
+    let square_local: fn(u32) -> u32 = square;
+    let (f, g) = fn_coercions(&square_local);
+    // cannot use `square as *const ()` because we can't know whether the compiler duplicates
+    // functions, so two function pointers are only equal if they result from the same function
+    // to function pointer cast
+    assert_eq!(f as *const (), square_local as *const ());
+    assert_eq!(g(4), 16);
+    assert_eq!(identity_coercion(g)(5), 25);
+
+    assert_eq!(simple_array_coercion(&a), &a);
+    let w = coerce_ptr_wrapper(PtrWrapper(2, 3, (), &a));
+    assert!(w == PtrWrapper(2, 3, (), &a) as PtrWrapper<[u8]>);
+
+    let w = coerce_triv_ptr_wrapper(TrivPtrWrapper(&a));
+    assert_eq!(&w.0, &a);
+
+    let z = coerce_fat_ptr_wrapper(PtrWrapper(2, 3, (), &square_local));
+    assert_eq!((z.3)(6), 36);
+
+    let z: PtrWrapper<dyn Fn(u32) -> u32> =
+        coerce_ptr_wrapper_poly(PtrWrapper(2, 3, (), &square_local));
+    assert_eq!((z.3)(6), 36);
+}
diff --git a/src/tools/miri/tests/pass/concurrency/channels.rs b/src/tools/miri/tests/pass/concurrency/channels.rs
new file mode 100644
index 0000000000000..c75c5199bf11d
--- /dev/null
+++ b/src/tools/miri/tests/pass/concurrency/channels.rs
@@ -0,0 +1,66 @@
+//@ignore-target-windows: Channels on Windows are not supported yet.
+//@compile-flags: -Zmiri-strict-provenance
+
+use std::sync::mpsc::{channel, sync_channel};
+use std::thread;
+
+// Check if channels are working.
+
+/// The test taken from the Rust documentation.
+fn simple_send() {
+    let (tx, rx) = channel();
+    let t = thread::spawn(move || {
+        tx.send(10).unwrap();
+    });
+    assert_eq!(rx.recv().unwrap(), 10);
+    t.join().unwrap();
+}
+
+/// The test taken from the Rust documentation.
+fn multiple_send() {
+    let (tx, rx) = channel();
+    let mut threads = vec![];
+    for i in 0..10 {
+        let tx = tx.clone();
+        let t = thread::spawn(move || {
+            tx.send(i).unwrap();
+        });
+        threads.push(t);
+    }
+
+    let mut sum = 0;
+    for _ in 0..10 {
+        let j = rx.recv().unwrap();
+        assert!(0 <= j && j < 10);
+        sum += j;
+    }
+    assert_eq!(sum, 45);
+
+    for t in threads {
+        t.join().unwrap();
+    }
+}
+
+/// The test taken from the Rust documentation.
+fn send_on_sync() {
+    let (sender, receiver) = sync_channel(1);
+
+    // this returns immediately
+    sender.send(1).unwrap();
+
+    let t = thread::spawn(move || {
+        // this will block until the previous message has been received
+        sender.send(2).unwrap();
+    });
+
+    assert_eq!(receiver.recv().unwrap(), 1);
+    assert_eq!(receiver.recv().unwrap(), 2);
+
+    t.join().unwrap();
+}
+
+fn main() {
+    simple_send();
+    multiple_send();
+    send_on_sync();
+}
diff --git a/src/tools/miri/tests/pass/concurrency/concurrent_caller_location.rs b/src/tools/miri/tests/pass/concurrency/concurrent_caller_location.rs
new file mode 100644
index 0000000000000..0490330a15d8b
--- /dev/null
+++ b/src/tools/miri/tests/pass/concurrency/concurrent_caller_location.rs
@@ -0,0 +1,17 @@
+use std::panic::Location;
+use std::thread::spawn;
+
+fn initialize() {
+    let _ignore = initialize_inner();
+}
+
+fn initialize_inner() -> &'static Location<'static> {
+    Location::caller()
+}
+
+fn main() {
+    let j1 = spawn(initialize);
+    let j2 = spawn(initialize);
+    j1.join().unwrap();
+    j2.join().unwrap();
+}
diff --git a/src/tools/miri/tests/pass/concurrency/data_race.rs b/src/tools/miri/tests/pass/concurrency/data_race.rs
new file mode 100644
index 0000000000000..4e3c99058a0df
--- /dev/null
+++ b/src/tools/miri/tests/pass/concurrency/data_race.rs
@@ -0,0 +1,110 @@
+//@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
+
+use std::sync::atomic::{fence, AtomicUsize, Ordering};
+use std::thread::spawn;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+fn test_fence_sync() {
+    static SYNC: AtomicUsize = AtomicUsize::new(0);
+
+    let mut var = 0u32;
+    let ptr = &mut var as *mut u32;
+    let evil_ptr = EvilSend(ptr);
+
+    let j1 = spawn(move || {
+        unsafe { *evil_ptr.0 = 1 };
+        fence(Ordering::Release);
+        SYNC.store(1, Ordering::Relaxed)
+    });
+
+    let j2 = spawn(move || {
+        if SYNC.load(Ordering::Relaxed) == 1 {
+            fence(Ordering::Acquire);
+            unsafe { *evil_ptr.0 }
+        } else {
+            panic!(); // relies on thread 2 going last
+        }
+    });
+
+    j1.join().unwrap();
+    j2.join().unwrap();
+}
+
+fn test_multiple_reads() {
+    let mut var = 42u32;
+    let ptr = &mut var as *mut u32;
+    let evil_ptr = EvilSend(ptr);
+
+    let j1 = spawn(move || unsafe { *evil_ptr.0 });
+    let j2 = spawn(move || unsafe { *evil_ptr.0 });
+    let j3 = spawn(move || unsafe { *evil_ptr.0 });
+    let j4 = spawn(move || unsafe { *evil_ptr.0 });
+
+    assert_eq!(j1.join().unwrap(), 42);
+    assert_eq!(j2.join().unwrap(), 42);
+    assert_eq!(j3.join().unwrap(), 42);
+    assert_eq!(j4.join().unwrap(), 42);
+
+    var = 10;
+    assert_eq!(var, 10);
+}
+
+pub fn test_rmw_no_block() {
+    static SYNC: AtomicUsize = AtomicUsize::new(0);
+
+    let mut a = 0u32;
+    let b = &mut a as *mut u32;
+    let c = EvilSend(b);
+
+    unsafe {
+        let j1 = spawn(move || {
+            *c.0 = 1;
+            SYNC.store(1, Ordering::Release);
+        });
+
+        let j2 = spawn(move || {
+            if SYNC.swap(2, Ordering::Relaxed) == 1 {
+                //No op, blocking store removed
+            }
+        });
+
+        let j3 = spawn(move || if SYNC.load(Ordering::Acquire) == 2 { *c.0 } else { 0 });
+
+        j1.join().unwrap();
+        j2.join().unwrap();
+        let v = j3.join().unwrap();
+        assert!(v == 1 || v == 2); // relies on thread 3 going last
+    }
+}
+
+pub fn test_simple_release() {
+    static SYNC: AtomicUsize = AtomicUsize::new(0);
+
+    let mut a = 0u32;
+    let b = &mut a as *mut u32;
+    let c = EvilSend(b);
+
+    unsafe {
+        let j1 = spawn(move || {
+            *c.0 = 1;
+            SYNC.store(1, Ordering::Release);
+        });
+
+        let j2 = spawn(move || if SYNC.load(Ordering::Acquire) == 1 { *c.0 } else { 0 });
+
+        j1.join().unwrap();
+        assert_eq!(j2.join().unwrap(), 1); // relies on thread 2 going last
+    }
+}
+
+pub fn main() {
+    test_fence_sync();
+    test_multiple_reads();
+    test_rmw_no_block();
+    test_simple_release();
+}
diff --git a/src/tools/miri/tests/pass/concurrency/disable_data_race_detector.rs b/src/tools/miri/tests/pass/concurrency/disable_data_race_detector.rs
new file mode 100644
index 0000000000000..d71e51b038429
--- /dev/null
+++ b/src/tools/miri/tests/pass/concurrency/disable_data_race_detector.rs
@@ -0,0 +1,27 @@
+//@compile-flags: -Zmiri-disable-data-race-detector
+
+use std::thread::spawn;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+pub fn main() {
+    let mut a = 0u32;
+    let b = &mut a as *mut u32;
+    let c = EvilSend(b);
+    unsafe {
+        let j1 = spawn(move || {
+            *c.0 = 32;
+        });
+
+        let j2 = spawn(move || {
+            *c.0 = 64; // Data race (but not detected as the detector is disabled)
+        });
+
+        j1.join().unwrap();
+        j2.join().unwrap();
+    }
+}
diff --git a/src/tools/miri/tests/pass/concurrency/issue1643.rs b/src/tools/miri/tests/pass/concurrency/issue1643.rs
new file mode 100644
index 0000000000000..c0956569ad8f9
--- /dev/null
+++ b/src/tools/miri/tests/pass/concurrency/issue1643.rs
@@ -0,0 +1,14 @@
+use std::thread::spawn;
+
+fn initialize() {
+    initialize_inner(&mut || false)
+}
+
+fn initialize_inner(_init: &mut dyn FnMut() -> bool) {}
+
+fn main() {
+    let j1 = spawn(initialize);
+    let j2 = spawn(initialize);
+    j1.join().unwrap();
+    j2.join().unwrap();
+}
diff --git a/src/tools/miri/tests/pass/concurrency/mutex_leak.rs b/src/tools/miri/tests/pass/concurrency/mutex_leak.rs
new file mode 100644
index 0000000000000..3ac0c9336b7b1
--- /dev/null
+++ b/src/tools/miri/tests/pass/concurrency/mutex_leak.rs
@@ -0,0 +1,9 @@
+//@compile-flags: -Zmiri-ignore-leaks
+use std::mem;
+use std::sync::Mutex;
+
+fn main() {
+    // Test for https://github.com/rust-lang/rust/issues/85434
+    let m = Mutex::new(5i32);
+    mem::forget(m.lock());
+}
diff --git a/src/tools/miri/tests/pass/concurrency/simple.rs b/src/tools/miri/tests/pass/concurrency/simple.rs
new file mode 100644
index 0000000000000..556e0a24769d7
--- /dev/null
+++ b/src/tools/miri/tests/pass/concurrency/simple.rs
@@ -0,0 +1,76 @@
+//@compile-flags: -Zmiri-strict-provenance
+
+use std::thread;
+
+fn create_and_detach() {
+    thread::spawn(|| ());
+}
+
+fn create_and_join() {
+    thread::spawn(|| ()).join().unwrap();
+}
+
+fn create_and_get_result() {
+    let nine = thread::spawn(|| 5 + 4).join().unwrap();
+    assert_eq!(nine, 9);
+}
+
+fn create_and_leak_result() {
+    thread::spawn(|| 7);
+}
+
+fn create_nested_and_detach() {
+    thread::spawn(|| {
+        thread::spawn(|| ());
+    });
+}
+
+fn create_nested_and_join() {
+    let handle = thread::spawn(|| thread::spawn(|| ()));
+    let handle_nested = handle.join().unwrap();
+    handle_nested.join().unwrap();
+}
+
+fn create_move_in() {
+    let x = String::from("Hello!");
+    thread::spawn(move || {
+        assert_eq!(x.len(), 6);
+    })
+    .join()
+    .unwrap();
+}
+
+fn create_move_out() {
+    let result = thread::spawn(|| String::from("Hello!")).join().unwrap();
+    assert_eq!(result.len(), 6);
+}
+
+fn panic() {
+    let result = thread::spawn(|| panic!("Hello!")).join().unwrap_err();
+    let msg = result.downcast_ref::<&'static str>().unwrap();
+    assert_eq!(*msg, "Hello!");
+}
+
+fn panic_named() {
+    thread::Builder::new()
+        .name("childthread".to_string())
+        .spawn(move || {
+            panic!("Hello, world!");
+        })
+        .unwrap()
+        .join()
+        .unwrap_err();
+}
+
+fn main() {
+    create_and_detach();
+    create_and_join();
+    create_and_get_result();
+    create_and_leak_result();
+    create_nested_and_detach();
+    create_nested_and_join();
+    create_move_in();
+    create_move_out();
+    panic();
+    panic_named();
+}
diff --git a/src/tools/miri/tests/pass/concurrency/simple.stderr b/src/tools/miri/tests/pass/concurrency/simple.stderr
new file mode 100644
index 0000000000000..028cc0fb736ff
--- /dev/null
+++ b/src/tools/miri/tests/pass/concurrency/simple.stderr
@@ -0,0 +1,3 @@
+thread '<unnamed>' panicked at 'Hello!', $DIR/simple.rs:LL:CC
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
+thread 'childthread' panicked at 'Hello, world!', $DIR/simple.rs:LL:CC
diff --git a/src/tools/miri/tests/pass/concurrency/spin_loop.rs b/src/tools/miri/tests/pass/concurrency/spin_loop.rs
new file mode 100644
index 0000000000000..019bd44f16488
--- /dev/null
+++ b/src/tools/miri/tests/pass/concurrency/spin_loop.rs
@@ -0,0 +1,42 @@
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::thread;
+
+static FLAG: AtomicUsize = AtomicUsize::new(0);
+
+fn spin() {
+    let j = thread::spawn(|| {
+        while FLAG.load(Ordering::Acquire) == 0 {
+            // We do *not* yield, and yet this should terminate eventually.
+        }
+    });
+    thread::yield_now(); // schedule the other thread
+    FLAG.store(1, Ordering::Release);
+    j.join().unwrap();
+}
+
+fn two_player_ping_pong() {
+    static FLAG: AtomicUsize = AtomicUsize::new(0);
+
+    let waiter1 = thread::spawn(|| {
+        while FLAG.load(Ordering::Acquire) == 0 {
+            // We do *not* yield, and yet this should terminate eventually.
+        }
+    });
+    let waiter2 = thread::spawn(|| {
+        while FLAG.load(Ordering::Acquire) == 0 {
+            // We do *not* yield, and yet this should terminate eventually.
+        }
+    });
+    let progress = thread::spawn(|| {
+        FLAG.store(1, Ordering::Release);
+    });
+    // The first `join` blocks the main thread and thus takes it out of the equation.
+    waiter1.join().unwrap();
+    waiter2.join().unwrap();
+    progress.join().unwrap();
+}
+
+fn main() {
+    spin();
+    two_player_ping_pong();
+}
diff --git a/src/tools/miri/tests/pass/concurrency/spin_loops.stderr b/src/tools/miri/tests/pass/concurrency/spin_loops.stderr
new file mode 100644
index 0000000000000..9fe6daa778c1f
--- /dev/null
+++ b/src/tools/miri/tests/pass/concurrency/spin_loops.stderr
@@ -0,0 +1,3 @@
+warning: thread support is experimental: the scheduler is not preemptive, and can get stuck in spin loops.
+         (see https://github.com/rust-lang/miri/issues/1388)
+
diff --git a/src/tools/miri/tests/pass/concurrency/spin_loops_nopreempt.rs b/src/tools/miri/tests/pass/concurrency/spin_loops_nopreempt.rs
new file mode 100644
index 0000000000000..5d8e2ef5f0282
--- /dev/null
+++ b/src/tools/miri/tests/pass/concurrency/spin_loops_nopreempt.rs
@@ -0,0 +1,84 @@
+//@ignore-target-windows: Channels on Windows are not supported yet.
+// This specifically tests behavior *without* preemption.
+//@compile-flags: -Zmiri-preemption-rate=0
+
+use std::cell::Cell;
+use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
+use std::sync::mpsc;
+use std::thread;
+
+/// When a thread yields, Miri's scheduler used to pick the thread with the lowest ID
+/// that can run. IDs are assigned in thread creation order.
+/// This means we could make 2 threads infinitely ping-pong with each other while
+/// really there is a 3rd thread that we should schedule to make progress.
+fn two_player_ping_pong() {
+    static FLAG: AtomicUsize = AtomicUsize::new(0);
+
+    let waiter1 = thread::spawn(|| {
+        while FLAG.load(Ordering::Acquire) == 0 {
+            // spin and wait
+            thread::yield_now();
+        }
+    });
+    let waiter2 = thread::spawn(|| {
+        while FLAG.load(Ordering::Acquire) == 0 {
+            // spin and wait
+            thread::yield_now();
+        }
+    });
+    let progress = thread::spawn(|| {
+        FLAG.store(1, Ordering::Release);
+    });
+    // The first `join` blocks the main thread and thus takes it out of the equation.
+    waiter1.join().unwrap();
+    waiter2.join().unwrap();
+    progress.join().unwrap();
+}
+
+/// Based on a test by @jethrogb.
+fn launcher() {
+    static THREAD2_LAUNCHED: AtomicBool = AtomicBool::new(false);
+
+    for _ in 0..10 {
+        let (tx, rx) = mpsc::sync_channel(0);
+        THREAD2_LAUNCHED.store(false, Ordering::SeqCst);
+
+        let jh = thread::spawn(move || {
+            struct RecvOnDrop(Cell<Option<mpsc::Receiver<()>>>);
+
+            impl Drop for RecvOnDrop {
+                fn drop(&mut self) {
+                    let rx = self.0.take().unwrap();
+                    while !THREAD2_LAUNCHED.load(Ordering::SeqCst) {
+                        thread::yield_now();
+                    }
+                    rx.recv().unwrap();
+                }
+            }
+
+            let tl_rx: RecvOnDrop = RecvOnDrop(Cell::new(None));
+            tl_rx.0.set(Some(rx));
+        });
+
+        let tx_clone = tx.clone();
+        let jh2 = thread::spawn(move || {
+            THREAD2_LAUNCHED.store(true, Ordering::SeqCst);
+            jh.join().unwrap();
+            tx_clone.send(()).expect_err(
+                "Expecting channel to be closed because thread 1 TLS destructors must've run",
+            );
+        });
+
+        while !THREAD2_LAUNCHED.load(Ordering::SeqCst) {
+            thread::yield_now();
+        }
+        thread::yield_now();
+        tx.send(()).expect("Expecting channel to be live because thread 2 must block on join");
+        jh2.join().unwrap();
+    }
+}
+
+fn main() {
+    two_player_ping_pong();
+    launcher();
+}
diff --git a/src/tools/miri/tests/pass/concurrency/sync.rs b/src/tools/miri/tests/pass/concurrency/sync.rs
new file mode 100644
index 0000000000000..8bda32bb95a48
--- /dev/null
+++ b/src/tools/miri/tests/pass/concurrency/sync.rs
@@ -0,0 +1,238 @@
+//@ignore-target-windows: Concurrency on Windows is not supported yet.
+//@compile-flags: -Zmiri-disable-isolation -Zmiri-strict-provenance
+
+use std::sync::{Arc, Barrier, Condvar, Mutex, Once, RwLock};
+use std::thread;
+use std::time::{Duration, Instant};
+
+// Check if Rust barriers are working.
+
+/// This test is taken from the Rust documentation.
+fn check_barriers() {
+    let mut handles = Vec::with_capacity(10);
+    let barrier = Arc::new(Barrier::new(10));
+    for _ in 0..10 {
+        let c = barrier.clone();
+        // The same messages will be printed together.
+        // You will NOT see any interleaving.
+        handles.push(thread::spawn(move || {
+            println!("before wait");
+            c.wait();
+            println!("after wait");
+        }));
+    }
+    // Wait for other threads to finish.
+    for handle in handles {
+        handle.join().unwrap();
+    }
+}
+
+// Check if Rust conditional variables are working.
+
+/// The test taken from the Rust documentation.
+fn check_conditional_variables_notify_one() {
+    let pair = Arc::new((Mutex::new(false), Condvar::new()));
+    let pair2 = pair.clone();
+
+    // Spawn a new thread.
+    let t = thread::spawn(move || {
+        thread::yield_now();
+        let (lock, cvar) = &*pair2;
+        let mut started = lock.lock().unwrap();
+        *started = true;
+        // We notify the condvar that the value has changed.
+        cvar.notify_one();
+    });
+
+    // Wait for the thread to fully start up.
+    let (lock, cvar) = &*pair;
+    let mut started = lock.lock().unwrap();
+    while !*started {
+        started = cvar.wait(started).unwrap();
+    }
+
+    t.join().unwrap();
+}
+
+/// Test that waiting on a conditional variable with a timeout does not
+/// deadlock.
+fn check_conditional_variables_timed_wait_timeout() {
+    let lock = Mutex::new(());
+    let cvar = Condvar::new();
+    let guard = lock.lock().unwrap();
+    let now = Instant::now();
+    let (_guard, timeout) = cvar.wait_timeout(guard, Duration::from_millis(100)).unwrap();
+    assert!(timeout.timed_out());
+    let elapsed_time = now.elapsed().as_millis();
+    assert!(100 <= elapsed_time && elapsed_time <= 1000);
+}
+
+/// Test that signaling a conditional variable when waiting with a timeout works
+/// as expected.
+fn check_conditional_variables_timed_wait_notimeout() {
+    let pair = Arc::new((Mutex::new(()), Condvar::new()));
+    let pair2 = pair.clone();
+
+    let (lock, cvar) = &*pair;
+    let guard = lock.lock().unwrap();
+
+    let handle = thread::spawn(move || {
+        thread::sleep(Duration::from_millis(100)); // Make sure the other thread is waiting by the time we call `notify`.
+        let (_lock, cvar) = &*pair2;
+        cvar.notify_one();
+    });
+
+    let (_guard, timeout) = cvar.wait_timeout(guard, Duration::from_millis(500)).unwrap();
+    assert!(!timeout.timed_out());
+    handle.join().unwrap();
+}
+
+// Check if locks are working.
+
+fn check_mutex() {
+    let data = Arc::new(Mutex::new(0));
+    let mut threads = Vec::new();
+
+    for _ in 0..3 {
+        let data = Arc::clone(&data);
+        let thread = thread::spawn(move || {
+            let mut data = data.lock().unwrap();
+            thread::yield_now();
+            *data += 1;
+        });
+        threads.push(thread);
+    }
+
+    for thread in threads {
+        thread.join().unwrap();
+    }
+
+    assert!(data.try_lock().is_ok());
+
+    let data = Arc::try_unwrap(data).unwrap().into_inner().unwrap();
+    assert_eq!(data, 3);
+}
+
+fn check_rwlock_write() {
+    let data = Arc::new(RwLock::new(0));
+    let mut threads = Vec::new();
+
+    for _ in 0..3 {
+        let data = Arc::clone(&data);
+        let thread = thread::spawn(move || {
+            let mut data = data.write().unwrap();
+            thread::yield_now();
+            *data += 1;
+        });
+        threads.push(thread);
+    }
+
+    for thread in threads {
+        thread.join().unwrap();
+    }
+
+    assert!(data.try_write().is_ok());
+
+    let data = Arc::try_unwrap(data).unwrap().into_inner().unwrap();
+    assert_eq!(data, 3);
+}
+
+fn check_rwlock_read_no_deadlock() {
+    let l1 = Arc::new(RwLock::new(0));
+    let l2 = Arc::new(RwLock::new(0));
+
+    let l1_copy = Arc::clone(&l1);
+    let l2_copy = Arc::clone(&l2);
+    let _guard1 = l1.read().unwrap();
+    let handle = thread::spawn(move || {
+        let _guard2 = l2_copy.read().unwrap();
+        thread::yield_now();
+        let _guard1 = l1_copy.read().unwrap();
+    });
+    thread::yield_now();
+    let _guard2 = l2.read().unwrap();
+    handle.join().unwrap();
+}
+
+// Check if Rust once statics are working.
+
+static mut VAL: usize = 0;
+static INIT: Once = Once::new();
+
+fn get_cached_val() -> usize {
+    unsafe {
+        INIT.call_once(|| {
+            VAL = expensive_computation();
+        });
+        VAL
+    }
+}
+
+fn expensive_computation() -> usize {
+    let mut i = 1;
+    let mut c = 1;
+    while i < 1000 {
+        i *= c;
+        c += 1;
+    }
+    i
+}
+
+/// The test taken from the Rust documentation.
+fn check_once() {
+    let handles: Vec<_> = (0..10)
+        .map(|_| {
+            thread::spawn(|| {
+                thread::yield_now();
+                let val = get_cached_val();
+                assert_eq!(val, 5040);
+            })
+        })
+        .collect();
+    for handle in handles {
+        handle.join().unwrap();
+    }
+}
+
+fn park_timeout() {
+    let start = Instant::now();
+
+    thread::park_timeout(Duration::from_millis(200));
+    // Normally, waiting in park/park_timeout may spuriously wake up early, but we
+    // know Miri's timed synchronization primitives do not do that.
+
+    assert!((200..1000).contains(&start.elapsed().as_millis()));
+}
+
+fn park_unpark() {
+    let t1 = thread::current();
+    let t2 = thread::spawn(move || {
+        thread::park();
+        thread::sleep(Duration::from_millis(200));
+        t1.unpark();
+    });
+
+    let start = Instant::now();
+
+    t2.thread().unpark();
+    thread::park();
+    // Normally, waiting in park/park_timeout may spuriously wake up early, but we
+    // know Miri's timed synchronization primitives do not do that.
+
+    assert!((200..1000).contains(&start.elapsed().as_millis()));
+
+    t2.join().unwrap();
+}
+
+fn main() {
+    check_barriers();
+    check_conditional_variables_notify_one();
+    check_conditional_variables_timed_wait_timeout();
+    check_conditional_variables_timed_wait_notimeout();
+    check_mutex();
+    check_rwlock_write();
+    check_rwlock_read_no_deadlock();
+    check_once();
+    park_timeout();
+    park_unpark();
+}
diff --git a/src/tools/miri/tests/pass/concurrency/sync.stdout b/src/tools/miri/tests/pass/concurrency/sync.stdout
new file mode 100644
index 0000000000000..f2c036a1735ed
--- /dev/null
+++ b/src/tools/miri/tests/pass/concurrency/sync.stdout
@@ -0,0 +1,20 @@
+before wait
+before wait
+before wait
+before wait
+before wait
+before wait
+before wait
+before wait
+before wait
+before wait
+after wait
+after wait
+after wait
+after wait
+after wait
+after wait
+after wait
+after wait
+after wait
+after wait
diff --git a/src/tools/miri/tests/pass/concurrency/sync_nopreempt.rs b/src/tools/miri/tests/pass/concurrency/sync_nopreempt.rs
new file mode 100644
index 0000000000000..3da33fee4c0ed
--- /dev/null
+++ b/src/tools/miri/tests/pass/concurrency/sync_nopreempt.rs
@@ -0,0 +1,88 @@
+//@ignore-target-windows: Concurrency on Windows is not supported yet.
+// We are making scheduler assumptions here.
+//@compile-flags: -Zmiri-strict-provenance -Zmiri-preemption-rate=0
+
+use std::sync::{Arc, Condvar, Mutex, RwLock};
+use std::thread;
+
+fn check_conditional_variables_notify_all() {
+    let pair = Arc::new(((Mutex::new(())), Condvar::new()));
+
+    // Spawn threads and block them on the conditional variable.
+    let handles: Vec<_> = (0..5)
+        .map(|_| {
+            let pair2 = pair.clone();
+            thread::spawn(move || {
+                let (lock, cvar) = &*pair2;
+                let guard = lock.lock().unwrap();
+                // Block waiting on the conditional variable.
+                let _guard = cvar.wait(guard).unwrap();
+            })
+        })
+        .inspect(|_| {
+            // Ensure the other threads all run and block on the `wait`.
+            thread::yield_now();
+            thread::yield_now();
+        })
+        .collect();
+
+    let (_, cvar) = &*pair;
+    // Unblock all threads.
+    cvar.notify_all();
+
+    for handle in handles {
+        handle.join().unwrap();
+    }
+}
+
+fn check_rwlock_unlock_bug1() {
+    // There was a bug where when un-read-locking an rwlock that still has other
+    // readers waiting, we'd accidentally also let a writer in.
+    // That caused an ICE.
+    let l = Arc::new(RwLock::new(0));
+
+    let r1 = l.read().unwrap();
+    let r2 = l.read().unwrap();
+
+    // Make a waiting writer.
+    let l2 = l.clone();
+    let t = thread::spawn(move || {
+        let mut w = l2.write().unwrap();
+        *w += 1;
+    });
+    thread::yield_now();
+
+    drop(r1);
+    assert_eq!(*r2, 0);
+    thread::yield_now();
+    thread::yield_now();
+    thread::yield_now();
+    assert_eq!(*r2, 0);
+    drop(r2);
+    t.join().unwrap();
+}
+
+fn check_rwlock_unlock_bug2() {
+    // There was a bug where when un-read-locking an rwlock by letting the last reader leaver,
+    // we'd forget to wake up a writer.
+    // That meant the writer thread could never run again.
+    let l = Arc::new(RwLock::new(0));
+
+    let r = l.read().unwrap();
+
+    // Make a waiting writer.
+    let l2 = l.clone();
+    let h = thread::spawn(move || {
+        let _w = l2.write().unwrap();
+    });
+    thread::yield_now();
+
+    drop(r);
+    h.join().unwrap();
+}
+
+fn main() {
+    check_conditional_variables_notify_all();
+    check_rwlock_unlock_bug1();
+    check_rwlock_unlock_bug2();
+}
diff --git a/src/tools/miri/tests/pass/concurrency/sync_singlethread.rs b/src/tools/miri/tests/pass/concurrency/sync_singlethread.rs
new file mode 100644
index 0000000000000..5663e1c1426c9
--- /dev/null
+++ b/src/tools/miri/tests/pass/concurrency/sync_singlethread.rs
@@ -0,0 +1,60 @@
+use std::hint;
+use std::sync::atomic;
+use std::sync::{Mutex, TryLockError};
+
+fn main() {
+    test_mutex_stdlib();
+    test_rwlock_stdlib();
+    test_spin_loop_hint();
+    test_thread_yield_now();
+}
+
+fn test_mutex_stdlib() {
+    let m = Mutex::new(0);
+    {
+        let _guard = m.lock();
+        assert!(m.try_lock().unwrap_err().would_block());
+    }
+    drop(m.try_lock().unwrap());
+    drop(m);
+}
+
+fn test_rwlock_stdlib() {
+    use std::sync::RwLock;
+    let rw = RwLock::new(0);
+    {
+        let _read_guard = rw.read().unwrap();
+        drop(rw.read().unwrap());
+        drop(rw.try_read().unwrap());
+        assert!(rw.try_write().unwrap_err().would_block());
+    }
+
+    {
+        let _write_guard = rw.write().unwrap();
+        assert!(rw.try_read().unwrap_err().would_block());
+        assert!(rw.try_write().unwrap_err().would_block());
+    }
+}
+
+trait TryLockErrorExt<T> {
+    fn would_block(&self) -> bool;
+}
+
+impl<T> TryLockErrorExt<T> for TryLockError<T> {
+    fn would_block(&self) -> bool {
+        match self {
+            TryLockError::WouldBlock => true,
+            TryLockError::Poisoned(_) => false,
+        }
+    }
+}
+
+fn test_spin_loop_hint() {
+    #[allow(deprecated)]
+    atomic::spin_loop_hint();
+    hint::spin_loop();
+}
+
+fn test_thread_yield_now() {
+    std::thread::yield_now();
+}
diff --git a/src/tools/miri/tests/pass/concurrency/thread_locals.rs b/src/tools/miri/tests/pass/concurrency/thread_locals.rs
new file mode 100644
index 0000000000000..b19e56312f304
--- /dev/null
+++ b/src/tools/miri/tests/pass/concurrency/thread_locals.rs
@@ -0,0 +1,65 @@
+//@compile-flags: -Zmiri-strict-provenance
+
+//! The main purpose of this test is to check that if we take a pointer to
+//! thread's `t1` thread-local `A` and send it to another thread `t2`,
+//! dereferencing the pointer on `t2` resolves to `t1`'s thread-local. In this
+//! test, we also check that thread-locals act as per-thread statics.
+
+#![feature(thread_local)]
+
+use std::thread;
+
+#[thread_local]
+static mut A: u8 = 0;
+#[thread_local]
+static mut B: u8 = 0;
+static mut C: u8 = 0;
+
+// Regression test for https://github.com/rust-lang/rust/issues/96191.
+#[thread_local]
+static READ_ONLY: u8 = 42;
+
+unsafe fn get_a_ref() -> *mut u8 {
+    &mut A
+}
+
+struct Sender(*mut u8);
+
+unsafe impl Send for Sender {}
+
+fn main() {
+    let _val = READ_ONLY;
+
+    let ptr = unsafe {
+        let x = get_a_ref();
+        *x = 5;
+        assert_eq!(A, 5);
+        B = 15;
+        C = 25;
+        Sender(&mut A)
+    };
+
+    thread::spawn(move || unsafe {
+        assert_eq!(*ptr.0, 5);
+        assert_eq!(A, 0);
+        assert_eq!(B, 0);
+        assert_eq!(C, 25);
+        B = 14;
+        C = 24;
+        let y = get_a_ref();
+        assert_eq!(*y, 0);
+        *y = 4;
+        assert_eq!(*ptr.0, 5);
+        assert_eq!(A, 4);
+        assert_eq!(*get_a_ref(), 4);
+    })
+    .join()
+    .unwrap();
+
+    unsafe {
+        assert_eq!(*get_a_ref(), 5);
+        assert_eq!(A, 5);
+        assert_eq!(B, 15);
+        assert_eq!(C, 24);
+    }
+}
diff --git a/src/tools/miri/tests/pass/concurrency/tls_lib_drop.rs b/src/tools/miri/tests/pass/concurrency/tls_lib_drop.rs
new file mode 100644
index 0000000000000..04e89ec361b74
--- /dev/null
+++ b/src/tools/miri/tests/pass/concurrency/tls_lib_drop.rs
@@ -0,0 +1,193 @@
+use std::cell::RefCell;
+use std::thread;
+
+struct TestCell {
+    value: RefCell<u8>,
+}
+
+impl Drop for TestCell {
+    fn drop(&mut self) {
+        for _ in 0..10 {
+            thread::yield_now();
+        }
+        println!("Dropping: {} (should be before 'Continue main 1').", *self.value.borrow())
+    }
+}
+
+thread_local! {
+    static A: TestCell = TestCell { value: RefCell::new(0) };
+    static A_CONST: TestCell = const { TestCell { value: RefCell::new(10) } };
+}
+
+/// Check that destructors of the library thread locals are executed immediately
+/// after a thread terminates.
+fn check_destructors() {
+    // We use the same value for both of them, since destructor order differs between Miri on Linux
+    // (which uses `register_dtor_fallback`, in the end using a single pthread_key to manage a
+    // thread-local linked list of dtors to call), real Linux rustc (which uses
+    // `__cxa_thread_atexit_impl`), and Miri on Windows.
+    thread::spawn(|| {
+        A.with(|f| {
+            assert_eq!(*f.value.borrow(), 0);
+            *f.value.borrow_mut() = 8;
+        });
+        A_CONST.with(|f| {
+            assert_eq!(*f.value.borrow(), 10);
+            *f.value.borrow_mut() = 8;
+        });
+    })
+    .join()
+    .unwrap();
+    println!("Continue main 1.")
+}
+
+struct JoinCell {
+    value: RefCell<Option<thread::JoinHandle<u8>>>,
+}
+
+impl Drop for JoinCell {
+    fn drop(&mut self) {
+        for _ in 0..10 {
+            thread::yield_now();
+        }
+        let join_handle = self.value.borrow_mut().take().unwrap();
+        println!("Joining: {} (should be before 'Continue main 2').", join_handle.join().unwrap());
+    }
+}
+
+thread_local! {
+    static B: JoinCell = JoinCell { value: RefCell::new(None) };
+}
+
+/// Check that the destructor can be blocked joining another thread.
+fn check_blocking() {
+    thread::spawn(|| {
+        B.with(|f| {
+            assert!(f.value.borrow().is_none());
+            let handle = thread::spawn(|| 7);
+            *f.value.borrow_mut() = Some(handle);
+        });
+    })
+    .join()
+    .unwrap();
+    println!("Continue main 2.");
+    // Preempt the main thread so that the destructor gets executed and can join
+    // the thread.
+    thread::yield_now();
+    thread::yield_now();
+}
+
+// This test tests that TLS destructors have run before the thread joins. The
+// test has no false positives (meaning: if the test fails, there's actually
+// an ordering problem). It may have false negatives, where the test passes but
+// join is not guaranteed to be after the TLS destructors. However, false
+// negatives should be exceedingly rare due to judicious use of
+// thread::yield_now and running the test several times.
+fn join_orders_after_tls_destructors() {
+    use std::sync::atomic::{AtomicU8, Ordering};
+
+    // We emulate a synchronous MPSC rendezvous channel using only atomics and
+    // thread::yield_now. We can't use std::mpsc as the implementation itself
+    // may rely on thread locals.
+    //
+    // The basic state machine for an SPSC rendezvous channel is:
+    //           FRESH -> THREAD1_WAITING -> MAIN_THREAD_RENDEZVOUS
+    // where the first transition is done by the “receiving” thread and the 2nd
+    // transition is done by the “sending” thread.
+    //
+    // We add an additional state `THREAD2_LAUNCHED` between `FRESH` and
+    // `THREAD1_WAITING` to block until all threads are actually running.
+    //
+    // A thread that joins on the “receiving” thread completion should never
+    // observe the channel in the `THREAD1_WAITING` state. If this does occur,
+    // we switch to the “poison” state `THREAD2_JOINED` and panic all around.
+    // (This is equivalent to “sending” from an alternate producer thread.)
+    const FRESH: u8 = 0;
+    const THREAD2_LAUNCHED: u8 = 1;
+    const THREAD1_WAITING: u8 = 2;
+    const MAIN_THREAD_RENDEZVOUS: u8 = 3;
+    const THREAD2_JOINED: u8 = 4;
+    static SYNC_STATE: AtomicU8 = AtomicU8::new(FRESH);
+
+    for _ in 0..10 {
+        SYNC_STATE.store(FRESH, Ordering::SeqCst);
+
+        let jh = thread::Builder::new()
+            .name("thread1".into())
+            .spawn(move || {
+                struct TlDrop;
+
+                impl Drop for TlDrop {
+                    fn drop(&mut self) {
+                        let mut sync_state = SYNC_STATE.swap(THREAD1_WAITING, Ordering::SeqCst);
+                        loop {
+                            match sync_state {
+                                THREAD2_LAUNCHED | THREAD1_WAITING => thread::yield_now(),
+                                MAIN_THREAD_RENDEZVOUS => break,
+                                THREAD2_JOINED =>
+                                    panic!(
+                                        "Thread 1 still running after thread 2 joined on thread 1"
+                                    ),
+                                v => unreachable!("sync state: {}", v),
+                            }
+                            sync_state = SYNC_STATE.load(Ordering::SeqCst);
+                        }
+                    }
+                }
+
+                thread_local! {
+                    static TL_DROP: TlDrop = TlDrop;
+                }
+
+                TL_DROP.with(|_| {});
+
+                loop {
+                    match SYNC_STATE.load(Ordering::SeqCst) {
+                        FRESH => thread::yield_now(),
+                        THREAD2_LAUNCHED => break,
+                        v => unreachable!("sync state: {}", v),
+                    }
+                }
+            })
+            .unwrap();
+
+        let jh2 = thread::Builder::new()
+            .name("thread2".into())
+            .spawn(move || {
+                assert_eq!(SYNC_STATE.swap(THREAD2_LAUNCHED, Ordering::SeqCst), FRESH);
+                jh.join().unwrap();
+                match SYNC_STATE.swap(THREAD2_JOINED, Ordering::SeqCst) {
+                    MAIN_THREAD_RENDEZVOUS => return,
+                    THREAD2_LAUNCHED | THREAD1_WAITING => {
+                        panic!("Thread 2 running after thread 1 join before main thread rendezvous")
+                    }
+                    v => unreachable!("sync state: {:?}", v),
+                }
+            })
+            .unwrap();
+
+        loop {
+            match SYNC_STATE.compare_exchange(
+                THREAD1_WAITING,
+                MAIN_THREAD_RENDEZVOUS,
+                Ordering::SeqCst,
+                Ordering::SeqCst,
+            ) {
+                Ok(_) => break,
+                Err(FRESH) => thread::yield_now(),
+                Err(THREAD2_LAUNCHED) => thread::yield_now(),
+                Err(THREAD2_JOINED) => {
+                    panic!("Main thread rendezvous after thread 2 joined thread 1")
+                }
+                v => unreachable!("sync state: {:?}", v),
+            }
+        }
+        jh2.join().unwrap();
+    }
+}
+
+fn main() {
+    check_destructors();
+    check_blocking();
+    join_orders_after_tls_destructors();
+}
diff --git a/src/tools/miri/tests/pass/concurrency/tls_lib_drop.stdout b/src/tools/miri/tests/pass/concurrency/tls_lib_drop.stdout
new file mode 100644
index 0000000000000..b7877820a0ca9
--- /dev/null
+++ b/src/tools/miri/tests/pass/concurrency/tls_lib_drop.stdout
@@ -0,0 +1,5 @@
+Dropping: 8 (should be before 'Continue main 1').
+Dropping: 8 (should be before 'Continue main 1').
+Continue main 1.
+Joining: 7 (should be before 'Continue main 2').
+Continue main 2.
diff --git a/src/tools/miri/tests/pass/concurrency/tls_lib_drop_single_thread.rs b/src/tools/miri/tests/pass/concurrency/tls_lib_drop_single_thread.rs
new file mode 100644
index 0000000000000..2766ba36d12b6
--- /dev/null
+++ b/src/tools/miri/tests/pass/concurrency/tls_lib_drop_single_thread.rs
@@ -0,0 +1,30 @@
+//! Check that destructors of the thread locals are executed on all OSes.
+
+use std::cell::RefCell;
+
+struct TestCell {
+    value: RefCell<u8>,
+}
+
+impl Drop for TestCell {
+    fn drop(&mut self) {
+        eprintln!("Dropping: {}", *self.value.borrow())
+    }
+}
+
+thread_local! {
+    static A: TestCell = TestCell { value: RefCell::new(0) };
+    static A_CONST: TestCell = const { TestCell { value: RefCell::new(10) } };
+}
+
+fn main() {
+    A.with(|f| {
+        assert_eq!(*f.value.borrow(), 0);
+        *f.value.borrow_mut() = 5;
+    });
+    A_CONST.with(|f| {
+        assert_eq!(*f.value.borrow(), 10);
+        *f.value.borrow_mut() = 5; // Same value as above since the drop order is different on different platforms
+    });
+    eprintln!("Continue main.")
+}
diff --git a/src/tools/miri/tests/pass/concurrency/tls_lib_drop_single_thread.stderr b/src/tools/miri/tests/pass/concurrency/tls_lib_drop_single_thread.stderr
new file mode 100644
index 0000000000000..09ec1c3c2c511
--- /dev/null
+++ b/src/tools/miri/tests/pass/concurrency/tls_lib_drop_single_thread.stderr
@@ -0,0 +1,3 @@
+Continue main.
+Dropping: 5
+Dropping: 5
diff --git a/src/tools/miri/tests/pass/concurrency/windows_detach_terminated.rs b/src/tools/miri/tests/pass/concurrency/windows_detach_terminated.rs
new file mode 100644
index 0000000000000..91088ce6aef9b
--- /dev/null
+++ b/src/tools/miri/tests/pass/concurrency/windows_detach_terminated.rs
@@ -0,0 +1,21 @@
+//@only-target-windows: Uses win32 api functions
+// We are making scheduler assumptions here.
+//@compile-flags: -Zmiri-preemption-rate=0
+
+use std::os::windows::io::IntoRawHandle;
+use std::thread;
+
+extern "system" {
+    fn CloseHandle(handle: usize) -> i32;
+}
+
+fn main() {
+    let thread = thread::spawn(|| {}).into_raw_handle() as usize;
+
+    // this yield ensures that `thread` is terminated by this point
+    thread::yield_now();
+
+    unsafe {
+        assert_ne!(CloseHandle(thread), 0);
+    }
+}
diff --git a/src/tools/miri/tests/pass/concurrency/windows_join_multiple.rs b/src/tools/miri/tests/pass/concurrency/windows_join_multiple.rs
new file mode 100644
index 0000000000000..986e2b8cc10f7
--- /dev/null
+++ b/src/tools/miri/tests/pass/concurrency/windows_join_multiple.rs
@@ -0,0 +1,41 @@
+//@only-target-windows: Uses win32 api functions
+// We are making scheduler assumptions here.
+//@compile-flags: -Zmiri-preemption-rate=0
+
+use std::os::windows::io::IntoRawHandle;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::thread;
+
+extern "system" {
+    fn WaitForSingleObject(handle: usize, timeout: u32) -> u32;
+}
+
+const INFINITE: u32 = u32::MAX;
+
+fn main() {
+    static FLAG: AtomicBool = AtomicBool::new(false);
+
+    let blocker = thread::spawn(|| {
+        while !FLAG.load(Ordering::Relaxed) {
+            thread::yield_now();
+        }
+    })
+    .into_raw_handle() as usize;
+
+    let waiter = move || {
+        unsafe {
+            assert_eq!(WaitForSingleObject(blocker, INFINITE), 0);
+        }
+    };
+
+    let waiter1 = thread::spawn(waiter);
+    let waiter2 = thread::spawn(waiter);
+
+    // this yield ensures `waiter1` & `waiter2` are blocked on `blocker` by this point
+    thread::yield_now();
+
+    FLAG.store(true, Ordering::Relaxed);
+
+    waiter1.join().unwrap();
+    waiter2.join().unwrap();
+}
diff --git a/src/tools/miri/tests/pass/const-vec-of-fns.rs b/src/tools/miri/tests/pass/const-vec-of-fns.rs
new file mode 100644
index 0000000000000..7f0782fe32247
--- /dev/null
+++ b/src/tools/miri/tests/pass/const-vec-of-fns.rs
@@ -0,0 +1,17 @@
+/*!
+ * Try to double-check that static fns have the right size (with or
+ * without dummy env ptr, as appropriate) by iterating a size-2 array.
+ * If the static size differs from the runtime size, the second element
+ * should be read as a null or otherwise wrong pointer and crash.
+ */
+
+fn f() {}
+static mut CLOSURES: &'static mut [fn()] = &mut [f as fn(), f as fn()];
+
+pub fn main() {
+    unsafe {
+        for closure in &mut *CLOSURES {
+            (*closure)()
+        }
+    }
+}
diff --git a/src/tools/miri/tests/pass/constants.rs b/src/tools/miri/tests/pass/constants.rs
new file mode 100644
index 0000000000000..718c852601420
--- /dev/null
+++ b/src/tools/miri/tests/pass/constants.rs
@@ -0,0 +1,9 @@
+const A: usize = *&5;
+
+fn foo() -> usize {
+    A
+}
+
+fn main() {
+    assert_eq!(foo(), A);
+}
diff --git a/src/tools/miri/tests/pass/deriving-associated-types.rs b/src/tools/miri/tests/pass/deriving-associated-types.rs
new file mode 100644
index 0000000000000..4803792a97cee
--- /dev/null
+++ b/src/tools/miri/tests/pass/deriving-associated-types.rs
@@ -0,0 +1,159 @@
+pub trait DeclaredTrait {
+    type Type;
+}
+
+impl DeclaredTrait for i32 {
+    type Type = i32;
+}
+
+pub trait WhereTrait {
+    type Type;
+}
+
+impl WhereTrait for i32 {
+    type Type = i32;
+}
+
+// Make sure we don't add a bound that just shares a name with an associated
+// type.
+pub mod module {
+    pub type Type = i32;
+}
+
+#[derive(PartialEq, Debug)]
+struct PrivateStruct<T>(T);
+
+#[derive(PartialEq, Debug)]
+struct TupleStruct<A, B: DeclaredTrait, C>(
+    module::Type,
+    Option<module::Type>,
+    A,
+    PrivateStruct<A>,
+    B,
+    B::Type,
+    Option<B::Type>,
+    <B as DeclaredTrait>::Type,
+    Option<<B as DeclaredTrait>::Type>,
+    C,
+    C::Type,
+    Option<C::Type>,
+    <C as WhereTrait>::Type,
+    Option<<C as WhereTrait>::Type>,
+    <i32 as DeclaredTrait>::Type,
+)
+where
+    C: WhereTrait;
+
+#[derive(PartialEq, Debug)]
+pub struct Struct<A, B: DeclaredTrait, C>
+where
+    C: WhereTrait,
+{
+    m1: module::Type,
+    m2: Option<module::Type>,
+    a1: A,
+    a2: PrivateStruct<A>,
+    b: B,
+    b1: B::Type,
+    b2: Option<B::Type>,
+    b3: <B as DeclaredTrait>::Type,
+    b4: Option<<B as DeclaredTrait>::Type>,
+    c: C,
+    c1: C::Type,
+    c2: Option<C::Type>,
+    c3: <C as WhereTrait>::Type,
+    c4: Option<<C as WhereTrait>::Type>,
+    d: <i32 as DeclaredTrait>::Type,
+}
+
+#[derive(PartialEq, Debug)]
+enum Enum<A, B: DeclaredTrait, C>
+where
+    C: WhereTrait,
+{
+    Unit,
+    Seq(
+        module::Type,
+        Option<module::Type>,
+        A,
+        PrivateStruct<A>,
+        B,
+        B::Type,
+        Option<B::Type>,
+        <B as DeclaredTrait>::Type,
+        Option<<B as DeclaredTrait>::Type>,
+        C,
+        C::Type,
+        Option<C::Type>,
+        <C as WhereTrait>::Type,
+        Option<<C as WhereTrait>::Type>,
+        <i32 as DeclaredTrait>::Type,
+    ),
+    Map {
+        m1: module::Type,
+        m2: Option<module::Type>,
+        a1: A,
+        a2: PrivateStruct<A>,
+        b: B,
+        b1: B::Type,
+        b2: Option<B::Type>,
+        b3: <B as DeclaredTrait>::Type,
+        b4: Option<<B as DeclaredTrait>::Type>,
+        c: C,
+        c1: C::Type,
+        c2: Option<C::Type>,
+        c3: <C as WhereTrait>::Type,
+        c4: Option<<C as WhereTrait>::Type>,
+        d: <i32 as DeclaredTrait>::Type,
+    },
+}
+
+fn main() {
+    let e: Enum<i32, i32, i32> =
+        Enum::Seq(0, None, 0, PrivateStruct(0), 0, 0, None, 0, None, 0, 0, None, 0, None, 0);
+    assert_eq!(e, e);
+
+    let e: Enum<i32, i32, i32> = Enum::Map {
+        m1: 0,
+        m2: None,
+        a1: 0,
+        a2: PrivateStruct(0),
+        b: 0,
+        b1: 0,
+        b2: None,
+        b3: 0,
+        b4: None,
+        c: 0,
+        c1: 0,
+        c2: None,
+        c3: 0,
+        c4: None,
+        d: 0,
+    };
+    assert_eq!(e, e);
+    let e: TupleStruct<i32, i32, i32> =
+        TupleStruct(0, None, 0, PrivateStruct(0), 0, 0, None, 0, None, 0, 0, None, 0, None, 0);
+    assert_eq!(e, e);
+
+    let e: Struct<i32, i32, i32> = Struct {
+        m1: 0,
+        m2: None,
+        a1: 0,
+        a2: PrivateStruct(0),
+        b: 0,
+        b1: 0,
+        b2: None,
+        b3: 0,
+        b4: None,
+        c: 0,
+        c1: 0,
+        c2: None,
+        c3: 0,
+        c4: None,
+        d: 0,
+    };
+    assert_eq!(e, e);
+
+    let e = Enum::Unit::<i32, i32, i32>;
+    assert_eq!(e, e);
+}
diff --git a/src/tools/miri/tests/pass/disable-alignment-check.rs b/src/tools/miri/tests/pass/disable-alignment-check.rs
new file mode 100644
index 0000000000000..366aff4a9f8ec
--- /dev/null
+++ b/src/tools/miri/tests/pass/disable-alignment-check.rs
@@ -0,0 +1,11 @@
+//@compile-flags: -Zmiri-disable-alignment-check
+
+fn main() {
+    let mut x = [0u8; 20];
+    let x_ptr: *mut u8 = x.as_mut_ptr();
+    // At least one of these is definitely unaligned.
+    unsafe {
+        *(x_ptr as *mut u64) = 42;
+        *(x_ptr.add(1) as *mut u64) = 42;
+    }
+}
diff --git a/src/tools/miri/tests/pass/drop_empty_slice.rs b/src/tools/miri/tests/pass/drop_empty_slice.rs
new file mode 100644
index 0000000000000..9805ce0ace3f0
--- /dev/null
+++ b/src/tools/miri/tests/pass/drop_empty_slice.rs
@@ -0,0 +1,7 @@
+#![feature(box_syntax)]
+
+fn main() {
+    // With the nested Vec, this is calling Offset(Unique::empty(), 0) on drop.
+    let args: Vec<Vec<i32>> = Vec::new();
+    let _val = box args;
+}
diff --git a/src/tools/miri/tests/pass/drop_on_array_elements.rs b/src/tools/miri/tests/pass/drop_on_array_elements.rs
new file mode 100644
index 0000000000000..ae1ef036267e2
--- /dev/null
+++ b/src/tools/miri/tests/pass/drop_on_array_elements.rs
@@ -0,0 +1,24 @@
+struct Bar(u16); // ZSTs are tested separately
+
+static mut DROP_COUNT: usize = 0;
+
+impl Drop for Bar {
+    fn drop(&mut self) {
+        assert_eq!(self.0 as usize, unsafe { DROP_COUNT }); // tests whether we are called at a valid address
+        unsafe {
+            DROP_COUNT += 1;
+        }
+    }
+}
+
+fn main() {
+    let b = [Bar(0), Bar(1), Bar(2), Bar(3)];
+    assert_eq!(unsafe { DROP_COUNT }, 0);
+    drop(b);
+    assert_eq!(unsafe { DROP_COUNT }, 4);
+
+    // check empty case
+    let b: [Bar; 0] = [];
+    drop(b);
+    assert_eq!(unsafe { DROP_COUNT }, 4);
+}
diff --git a/src/tools/miri/tests/pass/drop_on_fat_ptr_array_elements.rs b/src/tools/miri/tests/pass/drop_on_fat_ptr_array_elements.rs
new file mode 100644
index 0000000000000..40025cd07f7e0
--- /dev/null
+++ b/src/tools/miri/tests/pass/drop_on_fat_ptr_array_elements.rs
@@ -0,0 +1,22 @@
+trait Foo {}
+
+struct Bar;
+
+impl Foo for Bar {}
+
+static mut DROP_COUNT: usize = 0;
+
+impl Drop for Bar {
+    fn drop(&mut self) {
+        unsafe {
+            DROP_COUNT += 1;
+        }
+    }
+}
+
+fn main() {
+    let b: [Box<dyn Foo>; 4] = [Box::new(Bar), Box::new(Bar), Box::new(Bar), Box::new(Bar)];
+    assert_eq!(unsafe { DROP_COUNT }, 0);
+    drop(b);
+    assert_eq!(unsafe { DROP_COUNT }, 4);
+}
diff --git a/src/tools/miri/tests/pass/drop_on_zst_array_elements.rs b/src/tools/miri/tests/pass/drop_on_zst_array_elements.rs
new file mode 100644
index 0000000000000..babe098e4e6f5
--- /dev/null
+++ b/src/tools/miri/tests/pass/drop_on_zst_array_elements.rs
@@ -0,0 +1,23 @@
+struct Bar;
+
+static mut DROP_COUNT: usize = 0;
+
+impl Drop for Bar {
+    fn drop(&mut self) {
+        unsafe {
+            DROP_COUNT += 1;
+        }
+    }
+}
+
+fn main() {
+    let b = [Bar, Bar, Bar, Bar];
+    assert_eq!(unsafe { DROP_COUNT }, 0);
+    drop(b);
+    assert_eq!(unsafe { DROP_COUNT }, 4);
+
+    // check empty case
+    let b: [Bar; 0] = [];
+    drop(b);
+    assert_eq!(unsafe { DROP_COUNT }, 4);
+}
diff --git a/src/tools/miri/tests/pass/drop_through_owned_slice.rs b/src/tools/miri/tests/pass/drop_through_owned_slice.rs
new file mode 100644
index 0000000000000..8cdeb57d02f79
--- /dev/null
+++ b/src/tools/miri/tests/pass/drop_through_owned_slice.rs
@@ -0,0 +1,18 @@
+struct Bar;
+
+static mut DROP_COUNT: usize = 0;
+
+impl Drop for Bar {
+    fn drop(&mut self) {
+        unsafe {
+            DROP_COUNT += 1;
+        }
+    }
+}
+
+fn main() {
+    let b: Box<[Bar]> = vec![Bar, Bar, Bar, Bar].into_boxed_slice();
+    assert_eq!(unsafe { DROP_COUNT }, 0);
+    drop(b);
+    assert_eq!(unsafe { DROP_COUNT }, 4);
+}
diff --git a/src/tools/miri/tests/pass/drop_through_trait_object.rs b/src/tools/miri/tests/pass/drop_through_trait_object.rs
new file mode 100644
index 0000000000000..8d22ca9ceb4ab
--- /dev/null
+++ b/src/tools/miri/tests/pass/drop_through_trait_object.rs
@@ -0,0 +1,22 @@
+trait Foo {}
+
+struct Bar;
+
+static mut DROP_CALLED: bool = false;
+
+impl Drop for Bar {
+    fn drop(&mut self) {
+        unsafe {
+            DROP_CALLED = true;
+        }
+    }
+}
+
+impl Foo for Bar {}
+
+fn main() {
+    let b: Box<dyn Foo> = Box::new(Bar);
+    assert!(unsafe { !DROP_CALLED });
+    drop(b);
+    assert!(unsafe { DROP_CALLED });
+}
diff --git a/src/tools/miri/tests/pass/drop_through_trait_object_rc.rs b/src/tools/miri/tests/pass/drop_through_trait_object_rc.rs
new file mode 100644
index 0000000000000..7806c0252d270
--- /dev/null
+++ b/src/tools/miri/tests/pass/drop_through_trait_object_rc.rs
@@ -0,0 +1,24 @@
+trait Foo {}
+
+struct Bar;
+
+static mut DROP_CALLED: bool = false;
+
+impl Drop for Bar {
+    fn drop(&mut self) {
+        unsafe {
+            DROP_CALLED = true;
+        }
+    }
+}
+
+impl Foo for Bar {}
+
+use std::rc::Rc;
+
+fn main() {
+    let b: Rc<dyn Foo> = Rc::new(Bar);
+    assert!(unsafe { !DROP_CALLED });
+    drop(b);
+    assert!(unsafe { DROP_CALLED });
+}
diff --git a/src/tools/miri/tests/pass/dst-field-align.rs b/src/tools/miri/tests/pass/dst-field-align.rs
new file mode 100644
index 0000000000000..4d1db03643f79
--- /dev/null
+++ b/src/tools/miri/tests/pass/dst-field-align.rs
@@ -0,0 +1,66 @@
+#[allow(dead_code)]
+struct Foo<T: ?Sized> {
+    a: u16,
+    b: T,
+}
+
+trait Bar {
+    fn get(&self) -> usize;
+}
+
+impl Bar for usize {
+    fn get(&self) -> usize {
+        *self
+    }
+}
+
+struct Baz<T: ?Sized> {
+    a: T,
+}
+
+#[allow(dead_code)]
+struct HasDrop<T: ?Sized> {
+    ptr: Box<usize>,
+    data: T,
+}
+
+fn main() {
+    // Test that zero-offset works properly
+    let b: Baz<usize> = Baz { a: 7 };
+    assert_eq!(b.a.get(), 7);
+    let b: &Baz<dyn Bar> = &b;
+    assert_eq!(b.a.get(), 7);
+
+    // Test that the field is aligned properly
+    let f: Foo<usize> = Foo { a: 0, b: 11 };
+    assert_eq!(f.b.get(), 11);
+    let ptr1: *const u8 = &f.b as *const _ as *const u8;
+
+    let f: &Foo<dyn Bar> = &f;
+    let ptr2: *const u8 = &f.b as *const _ as *const u8;
+    assert_eq!(f.b.get(), 11);
+
+    // The pointers should be the same
+    assert_eq!(ptr1, ptr2);
+
+    // Test that nested DSTs work properly
+    let f: Foo<Foo<usize>> = Foo { a: 0, b: Foo { a: 1, b: 17 } };
+    assert_eq!(f.b.b.get(), 17);
+    let f: &Foo<Foo<dyn Bar>> = &f;
+    assert_eq!(f.b.b.get(), 17);
+
+    // Test that get the pointer via destructuring works
+
+    let f: Foo<usize> = Foo { a: 0, b: 11 };
+    let f: &Foo<dyn Bar> = &f;
+    let &Foo { a: _, b: ref bar } = f;
+    assert_eq!(bar.get(), 11);
+
+    // Make sure that drop flags don't screw things up
+
+    let d: HasDrop<Baz<[i32; 4]>> = HasDrop { ptr: Box::new(0), data: Baz { a: [1, 2, 3, 4] } };
+    assert_eq!([1, 2, 3, 4], d.data.a);
+
+    let d: &HasDrop<Baz<[i32]>> = &d;
+    assert_eq!(&[1, 2, 3, 4], &d.data.a);
+}
diff --git a/src/tools/miri/tests/pass/dst-irrefutable-bind.rs b/src/tools/miri/tests/pass/dst-irrefutable-bind.rs
new file mode 100644
index 0000000000000..fe7335c0c6564
--- /dev/null
+++ b/src/tools/miri/tests/pass/dst-irrefutable-bind.rs
@@ -0,0 +1,14 @@
+struct Test<T: ?Sized>(T);
+
+fn main() {
+    let x = Test([1, 2, 3]);
+    let x: &Test<[i32]> = &x;
+
+    let &ref _y = x;
+
+    // Make sure binding to a fat pointer behind a reference
+    // still works
+    let slice = &[1, 2, 3];
+    let x = Test(&slice);
+    let Test(&_slice) = x;
+}
diff --git a/src/tools/miri/tests/pass/dst-raw.rs b/src/tools/miri/tests/pass/dst-raw.rs
new file mode 100644
index 0000000000000..f26191a1d5998
--- /dev/null
+++ b/src/tools/miri/tests/pass/dst-raw.rs
@@ -0,0 +1,94 @@
+// Test DST raw pointers
+
+trait Trait {
+    fn foo(&self) -> isize;
+}
+
+struct A {
+    f: isize,
+}
+impl Trait for A {
+    fn foo(&self) -> isize {
+        self.f
+    }
+}
+
+struct Foo<T: ?Sized> {
+    f: T,
+}
+
+pub fn main() {
+    // raw trait object
+    let x = A { f: 42 };
+    let z: *const dyn Trait = &x;
+    let r = unsafe { (&*z).foo() };
+    assert_eq!(r, 42);
+
+    // raw DST struct
+    let p = Foo { f: A { f: 42 } };
+    let o: *const Foo<dyn Trait> = &p;
+    let r = unsafe { (&*o).f.foo() };
+    assert_eq!(r, 42);
+
+    // raw slice
+    let a: *const [_] = &[1, 2, 3];
+    unsafe {
+        let b = (*a)[2];
+        assert_eq!(b, 3);
+        let len = (*a).len();
+        assert_eq!(len, 3);
+    }
+
+    // raw slice with explicit cast
+    let a = &[1, 2, 3] as *const [i32];
+    unsafe {
+        let b = (*a)[2];
+        assert_eq!(b, 3);
+        let len = (*a).len();
+        assert_eq!(len, 3);
+    }
+
+    // raw DST struct with slice
+    let c: *const Foo<[_]> = &Foo { f: [1, 2, 3] };
+    unsafe {
+        let b = (&*c).f[0];
+        assert_eq!(b, 1);
+        let len = (&*c).f.len();
+        assert_eq!(len, 3);
+    }
+
+    // all of the above with *mut
+    let mut x = A { f: 42 };
+    let z: *mut dyn Trait = &mut x;
+    let r = unsafe { (&*z).foo() };
+    assert_eq!(r, 42);
+
+    let mut p = Foo { f: A { f: 42 } };
+    let o: *mut Foo<dyn Trait> = &mut p;
+    let r = unsafe { (&*o).f.foo() };
+    assert_eq!(r, 42);
+
+    let a: *mut [_] = &mut [1, 2, 3];
+    unsafe {
+        let b = (*a)[2];
+        assert_eq!(b, 3);
+        let len = (*a).len();
+        assert_eq!(len, 3);
+    }
+
+    let a = &mut [1, 2, 3] as *mut [i32];
+    unsafe {
+        let b = (*a)[2];
+        assert_eq!(b, 3);
+        let len = (*a).len();
+        assert_eq!(len, 3);
+    }
+
+    let c: *mut Foo<[_]> = &mut Foo { f: [1, 2, 3] };
+    unsafe {
+        let b = (&*c).f[0];
+        assert_eq!(b, 1);
+        let len = (&*c).f.len();
+        assert_eq!(len, 3);
+    }
+}
diff --git a/src/tools/miri/tests/pass/dst-struct-sole.rs b/src/tools/miri/tests/pass/dst-struct-sole.rs
new file mode 100644
index 0000000000000..4b25fbb063006
--- /dev/null
+++ b/src/tools/miri/tests/pass/dst-struct-sole.rs
@@ -0,0 +1,74 @@
+// As dst-struct.rs, but the unsized field is the only field in the struct.
+
+struct Fat<T: ?Sized> {
+    ptr: T,
+}
+
+// x is a fat pointer
+fn foo(x: &Fat<[isize]>) {
+    let y = &x.ptr;
+    assert_eq!(x.ptr.len(), 3);
+    assert_eq!(y[0], 1);
+    assert_eq!(x.ptr[1], 2);
+}
+
+fn foo2<T: ToBar>(x: &Fat<[T]>) {
+    let y = &x.ptr;
+    let bar = Bar;
+    assert_eq!(x.ptr.len(), 3);
+    assert_eq!(y[0].to_bar(), bar);
+    assert_eq!(x.ptr[1].to_bar(), bar);
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+struct Bar;
+
+trait ToBar {
+    fn to_bar(&self) -> Bar;
+}
+
+impl ToBar for Bar {
+    fn to_bar(&self) -> Bar {
+        *self
+    }
+}
+
+pub fn main() {
+    // With a vec of ints.
+    let f1 = Fat { ptr: [1, 2, 3] };
+    foo(&f1);
+    let f2 = &f1;
+    foo(f2);
+    let f3: &Fat<[isize]> = f2;
+    foo(f3);
+    let f4: &Fat<[isize]> = &f1;
+    foo(f4);
+    let f5: &Fat<[isize]> = &Fat { ptr: [1, 2, 3] };
+    foo(f5);
+
+    // With a vec of Bars.
+    let bar = Bar;
+    let f1 = Fat { ptr: [bar, bar, bar] };
+    foo2(&f1);
+    let f2 = &f1;
+    foo2(f2);
+    let f3: &Fat<[Bar]> = f2;
+    foo2(f3);
+    let f4: &Fat<[Bar]> = &f1;
+    foo2(f4);
+    let f5: &Fat<[Bar]> = &Fat { ptr: [bar, bar, bar] };
+    foo2(f5);
+
+    // Assignment.
+    let f5: &mut Fat<[isize]> = &mut Fat { ptr: [1, 2, 3] };
+    f5.ptr[1] = 34;
+    assert_eq!(f5.ptr[0], 1);
+    assert_eq!(f5.ptr[1], 34);
+    assert_eq!(f5.ptr[2], 3);
+
+    // Zero size vec.
+    let f5: &Fat<[isize]> = &Fat { ptr: [] };
+    assert!(f5.ptr.is_empty());
+    let f5: &Fat<[Bar]> = &Fat { ptr: [] };
+    assert!(f5.ptr.is_empty());
+}
diff --git a/src/tools/miri/tests/pass/dst-struct.rs b/src/tools/miri/tests/pass/dst-struct.rs
new file mode 100644
index 0000000000000..7191068eb2c40
--- /dev/null
+++ b/src/tools/miri/tests/pass/dst-struct.rs
@@ -0,0 +1,122 @@
+#![feature(box_syntax)]
+
+struct Fat<T: ?Sized> {
+    f1: isize,
+    f2: &'static str,
+    ptr: T,
+}
+
+// x is a fat pointer
+fn foo(x: &Fat<[isize]>) {
+    let y = &x.ptr;
+    assert_eq!(x.ptr.len(), 3);
+    assert_eq!(y[0], 1);
+    assert_eq!(x.ptr[1], 2);
+    assert_eq!(x.f1, 5);
+    assert_eq!(x.f2, "some str");
+}
+
+fn foo2<T: ToBar>(x: &Fat<[T]>) {
+    let y = &x.ptr;
+    let bar = Bar;
+    assert_eq!(x.ptr.len(), 3);
+    assert_eq!(y[0].to_bar(), bar);
+    assert_eq!(x.ptr[1].to_bar(), bar);
+    assert_eq!(x.f1, 5);
+    assert_eq!(x.f2, "some str");
+}
+
+fn foo3(x: &Fat<Fat<[isize]>>) {
+    let y = &x.ptr.ptr;
+    assert_eq!(x.f1, 5);
+    assert_eq!(x.f2, "some str");
+    assert_eq!(x.ptr.f1, 8);
+    assert_eq!(x.ptr.f2, "deep str");
+    assert_eq!(x.ptr.ptr.len(), 3);
+    assert_eq!(y[0], 1);
+    assert_eq!(x.ptr.ptr[1], 2);
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+struct Bar;
+
+trait ToBar {
+    fn to_bar(&self) -> Bar;
+}
+
+impl ToBar for Bar {
+    fn to_bar(&self) -> Bar {
+        *self
+    }
+}
+
+pub fn main() {
+    // With a vec of ints.
+    let f1: Fat<[isize; 3]> = Fat { f1: 5, f2: "some str", ptr: [1, 2, 3] };
+    foo(&f1);
+    let f2: &Fat<[isize; 3]> = &f1;
+    foo(f2);
+    let f3: &Fat<[isize]> = f2;
+    foo(f3);
+    let f4: &Fat<[isize]> = &f1;
+    foo(f4);
+    let f5: &Fat<[isize]> = &Fat { f1: 5, f2: "some str", ptr: [1, 2, 3] };
+    foo(f5);
+
+    // With a vec of Bars.
+    let bar = Bar;
+    let f1 = Fat { f1: 5, f2: "some str", ptr: [bar, bar, bar] };
+    foo2(&f1);
+    let f2 = &f1;
+    foo2(f2);
+    let f3: &Fat<[Bar]> = f2;
+    foo2(f3);
+    let f4: &Fat<[Bar]> = &f1;
+    foo2(f4);
+    let f5: &Fat<[Bar]> = &Fat { f1: 5, f2: "some str", ptr: [bar, bar, bar] };
+    foo2(f5);
+
+    // Assignment.
+    let f5: &mut Fat<[isize]> = &mut Fat { f1: 5, f2: "some str", ptr: [1, 2, 3] };
+    f5.ptr[1] = 34;
+    assert_eq!(f5.ptr[0], 1);
+    assert_eq!(f5.ptr[1], 34);
+    assert_eq!(f5.ptr[2], 3);
+
+    // Zero size vec.
+    let f5: &Fat<[isize]> = &Fat { f1: 5, f2: "some str", ptr: [] };
+    assert!(f5.ptr.is_empty());
+    let f5: &Fat<[Bar]> = &Fat { f1: 5, f2: "some str", ptr: [] };
+    assert!(f5.ptr.is_empty());
+
+    // Deeply nested.
+    let f1 = Fat { f1: 5, f2: "some str", ptr: Fat { f1: 8, f2: "deep str", ptr: [1, 2, 3] } };
+    foo3(&f1);
+    let f2 = &f1;
+    foo3(f2);
+    let f3: &Fat<Fat<[isize]>> = f2;
+    foo3(f3);
+    let f4: &Fat<Fat<[isize]>> = &f1;
+    foo3(f4);
+    let f5: &Fat<Fat<[isize]>> =
+        &Fat { f1: 5, f2: "some str", ptr: Fat { f1: 8, f2: "deep str", ptr: [1, 2, 3] } };
+    foo3(f5);
+
+    // Box.
+    let f1 = Box::new([1, 2, 3]);
+    assert_eq!((*f1)[1], 2);
+    let f2: Box<[isize]> = f1;
+    assert_eq!((*f2)[1], 2);
+
+    // Nested Box.
+    let f1: Box<Fat<[isize; 3]>> = box Fat { f1: 5, f2: "some str", ptr: [1, 2, 3] };
+    foo(&*f1);
+    let f2: Box<Fat<[isize]>> = f1;
+    foo(&*f2);
+
+    let f3: Box<Fat<[isize]>> =
+        Box::<Fat<[_; 3]>>::new(Fat { f1: 5, f2: "some str", ptr: [1, 2, 3] });
+    foo(&*f3);
+    let f4: Box<Fat<[isize]>> = box Fat { f1: 5, f2: "some str", ptr: [1, 2, 3] };
+    foo(&*f4);
+}
diff --git a/src/tools/miri/tests/pass/dyn-arbitrary-self.rs b/src/tools/miri/tests/pass/dyn-arbitrary-self.rs
new file mode 100644
index 0000000000000..256c72add9220
--- /dev/null
+++ b/src/tools/miri/tests/pass/dyn-arbitrary-self.rs
@@ -0,0 +1,128 @@
+#![feature(arbitrary_self_types, unsize, coerce_unsized, dispatch_from_dyn)]
+#![feature(rustc_attrs)]
+
+fn pin_box_dyn() {
+    use std::pin::Pin;
+
+    trait Foo {
+        fn bar(self: Pin<&mut Self>) -> bool;
+    }
+
+    impl Foo for &'static str {
+        fn bar(self: Pin<&mut Self>) -> bool {
+            true
+        }
+    }
+
+    let mut test: Pin<Box<dyn Foo>> = Box::pin("foo");
+    test.as_mut().bar();
+}
+
+fn stdlib_pointers() {
+    use std::{pin::Pin, rc::Rc, sync::Arc};
+
+    trait Trait {
+        fn by_rc(self: Rc<Self>) -> i64;
+        fn by_arc(self: Arc<Self>) -> i64;
+        fn by_pin_mut(self: Pin<&mut Self>) -> i64;
+        fn by_pin_box(self: Pin<Box<Self>>) -> i64;
+    }
+
+    impl Trait for i64 {
+        fn by_rc(self: Rc<Self>) -> i64 {
+            *self
+        }
+        fn by_arc(self: Arc<Self>) -> i64 {
+            *self
+        }
+        fn by_pin_mut(self: Pin<&mut Self>) -> i64 {
+            *self
+        }
+        fn by_pin_box(self: Pin<Box<Self>>) -> i64 {
+            *self
+        }
+    }
+
+    let rc = Rc::new(1i64) as Rc<dyn Trait>;
+    assert_eq!(1, rc.by_rc());
+
+    let arc = Arc::new(2i64) as Arc<dyn Trait>;
+    assert_eq!(2, arc.by_arc());
+
+    let mut value = 3i64;
+    let pin_mut = Pin::new(&mut value) as Pin<&mut dyn Trait>;
+    assert_eq!(3, pin_mut.by_pin_mut());
+
+    let pin_box = Into::<Pin<Box<i64>>>::into(Box::new(4i64)) as Pin<Box<dyn Trait>>;
+    assert_eq!(4, pin_box.by_pin_box());
+}
+
+fn pointers_and_wrappers() {
+    use std::{
+        marker::Unsize,
+        ops::{CoerceUnsized, Deref, DispatchFromDyn},
+    };
+
+    struct Ptr<T: ?Sized>(Box<T>);
+
+    impl<T: ?Sized> Deref for Ptr<T> {
+        type Target = T;
+
+        fn deref(&self) -> &T {
+            &*self.0
+        }
+    }
+
+    impl<T: Unsize<U> + ?Sized, U: ?Sized> CoerceUnsized<Ptr<U>> for Ptr<T> {}
+    impl<T: Unsize<U> + ?Sized, U: ?Sized> DispatchFromDyn<Ptr<U>> for Ptr<T> {}
+
+    struct Wrapper<T: ?Sized>(T);
+
+    impl<T: ?Sized> Deref for Wrapper<T> {
+        type Target = T;
+
+        fn deref(&self) -> &T {
+            &self.0
+        }
+    }
+
+    impl<T: CoerceUnsized<U>, U> CoerceUnsized<Wrapper<U>> for Wrapper<T> {}
+    impl<T: DispatchFromDyn<U>, U> DispatchFromDyn<Wrapper<U>> for Wrapper<T> {}
+
+    trait Trait {
+        // This method isn't object-safe yet. Unsized by-value `self` is object-safe (but not callable
+        // without unsized_locals), but wrappers arond `Self` currently are not.
+        // FIXME (mikeyhew) uncomment this when unsized rvalues object-safety is implemented
+        // fn wrapper(self: Wrapper<Self>) -> i32;
+        fn ptr_wrapper(self: Ptr<Wrapper<Self>>) -> i32;
+        fn wrapper_ptr(self: Wrapper<Ptr<Self>>) -> i32;
+        fn wrapper_ptr_wrapper(self: Wrapper<Ptr<Wrapper<Self>>>) -> i32;
+    }
+
+    impl Trait for i32 {
+        fn ptr_wrapper(self: Ptr<Wrapper<Self>>) -> i32 {
+            **self
+        }
+        fn wrapper_ptr(self: Wrapper<Ptr<Self>>) -> i32 {
+            **self
+        }
+        fn wrapper_ptr_wrapper(self: Wrapper<Ptr<Wrapper<Self>>>) -> i32 {
+            ***self
+        }
+    }
+
+    let pw = Ptr(Box::new(Wrapper(5))) as Ptr<Wrapper<dyn Trait>>;
+    assert_eq!(pw.ptr_wrapper(), 5);
+
+    let wp = Wrapper(Ptr(Box::new(6))) as Wrapper<Ptr<dyn Trait>>;
+    assert_eq!(wp.wrapper_ptr(), 6);
+
+    let wpw = Wrapper(Ptr(Box::new(Wrapper(7)))) as Wrapper<Ptr<Wrapper<dyn Trait>>>;
+    assert_eq!(wpw.wrapper_ptr_wrapper(), 7);
+}
+
+fn main() {
+    pin_box_dyn();
+    stdlib_pointers();
+    pointers_and_wrappers();
+}
diff --git a/src/tools/miri/tests/pass/dyn-traits.rs b/src/tools/miri/tests/pass/dyn-traits.rs
new file mode 100644
index 0000000000000..908d521a0d816
--- /dev/null
+++ b/src/tools/miri/tests/pass/dyn-traits.rs
@@ -0,0 +1,147 @@
+fn ref_box_dyn() {
+    struct Struct(i32);
+
+    trait Trait {
+        fn method(&self);
+
+        fn box_method(self: Box<Self>);
+    }
+
+    impl Trait for Struct {
+        fn method(&self) {
+            assert_eq!(self.0, 42);
+        }
+
+        fn box_method(self: Box<Self>) {
+            assert_eq!(self.0, 7);
+        }
+    }
+
+    struct Foo<T: ?Sized>(T);
+
+    let y: &dyn Trait = &Struct(42);
+    y.method();
+
+    let x: Foo<Struct> = Foo(Struct(42));
+    let y: &Foo<dyn Trait> = &x;
+    y.0.method();
+
+    let y: Box<dyn Trait> = Box::new(Struct(42));
+    y.method();
+
+    let y = &y;
+    y.method();
+
+    let y: Box<dyn Trait> = Box::new(Struct(7));
+    y.box_method();
+}
+
+fn box_box_trait() {
+    struct DroppableStruct;
+
+    static mut DROPPED: bool = false;
+
+    impl Drop for DroppableStruct {
+        fn drop(&mut self) {
+            unsafe {
+                DROPPED = true;
+            }
+        }
+    }
+
+    trait MyTrait {
+        fn dummy(&self) {}
+    }
+    impl MyTrait for Box<DroppableStruct> {}
+
+    struct Whatever {
+        w: Box<dyn MyTrait + 'static>,
+    }
+
+    impl Whatever {
+        fn new(w: Box<dyn MyTrait + 'static>) -> Whatever {
+            Whatever { w: w }
+        }
+    }
+
+    {
+        let f = Box::new(DroppableStruct);
+        let a = Whatever::new(Box::new(f) as Box<dyn MyTrait>);
+        a.w.dummy();
+    }
+    assert!(unsafe { DROPPED });
+}
+
+// Disabled for now: unsized locals are not supported,
+// their current MIR encoding is just not great.
+/*
+fn unsized_dyn() {
+    pub trait Foo {
+        fn foo(self) -> String;
+    }
+
+    struct A;
+
+    impl Foo for A {
+        fn foo(self) -> String {
+            format!("hello")
+        }
+    }
+
+    let x = *(Box::new(A) as Box<dyn Foo>);
+    assert_eq!(x.foo(), format!("hello"));
+
+    // I'm not sure whether we want this to work
+    let x = Box::new(A) as Box<dyn Foo>;
+    assert_eq!(x.foo(), format!("hello"));
+}
+fn unsized_dyn_autoderef() {
+    pub trait Foo {
+        fn foo(self) -> String;
+    }
+
+    impl Foo for [char] {
+        fn foo(self) -> String {
+            self.iter().collect()
+        }
+    }
+
+    impl Foo for str {
+        fn foo(self) -> String {
+            self.to_owned()
+        }
+    }
+
+    impl Foo for dyn FnMut() -> String {
+        fn foo(mut self) -> String {
+            self()
+        }
+    }
+
+    let x = *(Box::new(['h', 'e', 'l', 'l', 'o']) as Box<[char]>);
+    assert_eq!(&x.foo() as &str, "hello");
+
+    let x = Box::new(['h', 'e', 'l', 'l', 'o']) as Box<[char]>;
+    assert_eq!(&x.foo() as &str, "hello");
+
+    let x = "hello".to_owned().into_boxed_str();
+    assert_eq!(&x.foo() as &str, "hello");
+
+    let x = *("hello".to_owned().into_boxed_str());
+    assert_eq!(&x.foo() as &str, "hello");
+
+    let x = "hello".to_owned().into_boxed_str();
+    assert_eq!(&x.foo() as &str, "hello");
+
+    let x = *(Box::new(|| "hello".to_owned()) as Box<dyn FnMut() -> String>);
+    assert_eq!(&x.foo() as &str, "hello");
+
+    let x = Box::new(|| "hello".to_owned()) as Box<dyn FnMut() -> String>;
+    assert_eq!(&x.foo() as &str, "hello");
+}
+*/
+
+fn main() {
+    ref_box_dyn();
+    box_box_trait();
+}
diff --git a/src/tools/miri/tests/pass/dyn-upcast.rs b/src/tools/miri/tests/pass/dyn-upcast.rs
new file mode 100644
index 0000000000000..8432012a9ba52
--- /dev/null
+++ b/src/tools/miri/tests/pass/dyn-upcast.rs
@@ -0,0 +1,427 @@
+#![feature(trait_upcasting)]
+#![allow(incomplete_features)]
+
+fn main() {
+    basic();
+    diamond();
+    struct_();
+    replace_vptr();
+    vtable_mismatch_nop_cast();
+}
+
+fn vtable_mismatch_nop_cast() {
+    let ptr: &dyn std::fmt::Display = &0;
+    // Even though the vtable is for the wrong trait, this cast doesn't actually change the needed
+    // vtable so it should still be allowed.
+    let ptr: *const (dyn std::fmt::Debug + Send + Sync) = unsafe { std::mem::transmute(ptr) };
+    let _ptr2 = ptr as *const dyn std::fmt::Debug;
+}
+
+fn basic() {
+    trait Foo: PartialEq<i32> + std::fmt::Debug + Send + Sync {
+        fn a(&self) -> i32 {
+            10
+        }
+
+        fn z(&self) -> i32 {
+            11
+        }
+
+        fn y(&self) -> i32 {
+            12
+        }
+    }
+
+    trait Bar: Foo {
+        fn b(&self) -> i32 {
+            20
+        }
+
+        fn w(&self) -> i32 {
+            21
+        }
+    }
+
+    trait Baz: Bar {
+        fn c(&self) -> i32 {
+            30
+        }
+    }
+
+    impl Foo for i32 {
+        fn a(&self) -> i32 {
+            100
+        }
+    }
+
+    impl Bar for i32 {
+        fn b(&self) -> i32 {
+            200
+        }
+    }
+
+    impl Baz for i32 {
+        fn c(&self) -> i32 {
+            300
+        }
+    }
+
+    let baz: &dyn Baz = &1;
+    let _: &dyn std::fmt::Debug = baz;
+    assert_eq!(*baz, 1);
+    assert_eq!(baz.a(), 100);
+    assert_eq!(baz.b(), 200);
+    assert_eq!(baz.c(), 300);
+    assert_eq!(baz.z(), 11);
+    assert_eq!(baz.y(), 12);
+    assert_eq!(baz.w(), 21);
+
+    let bar: &dyn Bar = baz;
+    let _: &dyn std::fmt::Debug = bar;
+    assert_eq!(*bar, 1);
+    assert_eq!(bar.a(), 100);
+    assert_eq!(bar.b(), 200);
+    assert_eq!(bar.z(), 11);
+    assert_eq!(bar.y(), 12);
+    assert_eq!(bar.w(), 21);
+
+    let foo: &dyn Foo = baz;
+    let _: &dyn std::fmt::Debug = foo;
+    assert_eq!(*foo, 1);
+    assert_eq!(foo.a(), 100);
+    assert_eq!(foo.z(), 11);
+    assert_eq!(foo.y(), 12);
+
+    let foo: &dyn Foo = bar;
+    let _: &dyn std::fmt::Debug = foo;
+    assert_eq!(*foo, 1);
+    assert_eq!(foo.a(), 100);
+    assert_eq!(foo.z(), 11);
+    assert_eq!(foo.y(), 12);
+}
+
+fn diamond() {
+    trait Foo: PartialEq<i32> + std::fmt::Debug + Send + Sync {
+        fn a(&self) -> i32 {
+            10
+        }
+
+        fn z(&self) -> i32 {
+            11
+        }
+
+        fn y(&self) -> i32 {
+            12
+        }
+    }
+
+    trait Bar1: Foo {
+        fn b(&self) -> i32 {
+            20
+        }
+
+        fn w(&self) -> i32 {
+            21
+        }
+    }
+
+    trait Bar2: Foo {
+        fn c(&self) -> i32 {
+            30
+        }
+
+        fn v(&self) -> i32 {
+            31
+        }
+    }
+
+    trait Baz: Bar1 + Bar2 {
+        fn d(&self) -> i32 {
+            40
+        }
+    }
+
+    impl Foo for i32 {
+        fn a(&self) -> i32 {
+            100
+        }
+    }
+
+    impl Bar1 for i32 {
+        fn b(&self) -> i32 {
+            200
+        }
+    }
+
+    impl Bar2 for i32 {
+        fn c(&self) -> i32 {
+            300
+        }
+    }
+
+    impl Baz for i32 {
+        fn d(&self) -> i32 {
+            400
+        }
+    }
+
+    let baz: &dyn Baz = &1;
+    let _: &dyn std::fmt::Debug = baz;
+    assert_eq!(*baz, 1);
+    assert_eq!(baz.a(), 100);
+    assert_eq!(baz.b(), 200);
+    assert_eq!(baz.c(), 300);
+    assert_eq!(baz.d(), 400);
+    assert_eq!(baz.z(), 11);
+    assert_eq!(baz.y(), 12);
+    assert_eq!(baz.w(), 21);
+    assert_eq!(baz.v(), 31);
+
+    let bar1: &dyn Bar1 = baz;
+    let _: &dyn std::fmt::Debug = bar1;
+    assert_eq!(*bar1, 1);
+    assert_eq!(bar1.a(), 100);
+    assert_eq!(bar1.b(), 200);
+    assert_eq!(bar1.z(), 11);
+    assert_eq!(bar1.y(), 12);
+    assert_eq!(bar1.w(), 21);
+
+    let bar2: &dyn Bar2 = baz;
+    let _: &dyn std::fmt::Debug = bar2;
+    assert_eq!(*bar2, 1);
+    assert_eq!(bar2.a(), 100);
+    assert_eq!(bar2.c(), 300);
+    assert_eq!(bar2.z(), 11);
+    assert_eq!(bar2.y(), 12);
+    assert_eq!(bar2.v(), 31);
+
+    let foo: &dyn Foo = baz;
+    let _: &dyn std::fmt::Debug = foo;
+    assert_eq!(*foo, 1);
+    assert_eq!(foo.a(), 100);
+
+    let foo: &dyn Foo = bar1;
+    let _: &dyn std::fmt::Debug = foo;
+    assert_eq!(*foo, 1);
+    assert_eq!(foo.a(), 100);
+
+    let foo: &dyn Foo = bar2;
+    let _: &dyn std::fmt::Debug = foo;
+    assert_eq!(*foo, 1);
+    assert_eq!(foo.a(), 100);
+}
+
+fn struct_() {
+    use std::rc::Rc;
+    use std::sync::Arc;
+
+    trait Foo: PartialEq<i32> + std::fmt::Debug + Send + Sync {
+        fn a(&self) -> i32 {
+            10
+        }
+
+        fn z(&self) -> i32 {
+            11
+        }
+
+        fn y(&self) -> i32 {
+            12
+        }
+    }
+
+    trait Bar: Foo {
+        fn b(&self) -> i32 {
+            20
+        }
+
+        fn w(&self) -> i32 {
+            21
+        }
+    }
+
+    trait Baz: Bar {
+        fn c(&self) -> i32 {
+            30
+        }
+    }
+
+    impl Foo for i32 {
+        fn a(&self) -> i32 {
+            100
+        }
+    }
+
+    impl Bar for i32 {
+        fn b(&self) -> i32 {
+            200
+        }
+    }
+
+    impl Baz for i32 {
+        fn c(&self) -> i32 {
+            300
+        }
+    }
+
+    fn test_box() {
+        let v = Box::new(1);
+
+        let baz: Box<dyn Baz> = v.clone();
+        assert_eq!(*baz, 1);
+        assert_eq!(baz.a(), 100);
+        assert_eq!(baz.b(), 200);
+        assert_eq!(baz.c(), 300);
+        assert_eq!(baz.z(), 11);
+        assert_eq!(baz.y(), 12);
+        assert_eq!(baz.w(), 21);
+
+        let baz: Box<dyn Baz> = v.clone();
+        let bar: Box<dyn Bar> = baz;
+        assert_eq!(*bar, 1);
+        assert_eq!(bar.a(), 100);
+        assert_eq!(bar.b(), 200);
+        assert_eq!(bar.z(), 11);
+        assert_eq!(bar.y(), 12);
+        assert_eq!(bar.w(), 21);
+
+        let baz: Box<dyn Baz> = v.clone();
+        let foo: Box<dyn Foo> = baz;
+        assert_eq!(*foo, 1);
+        assert_eq!(foo.a(), 100);
+        assert_eq!(foo.z(), 11);
+        assert_eq!(foo.y(), 12);
+
+        let baz: Box<dyn Baz> = v.clone();
+        let bar: Box<dyn Bar> = baz;
+        let foo: Box<dyn Foo> = bar;
+        assert_eq!(*foo, 1);
+        assert_eq!(foo.a(), 100);
+        assert_eq!(foo.z(), 11);
+        assert_eq!(foo.y(), 12);
+    }
+
+    fn test_rc() {
+        let v = Rc::new(1);
+
+        let baz: Rc<dyn Baz> = v.clone();
+        assert_eq!(*baz, 1);
+        assert_eq!(baz.a(), 100);
+        assert_eq!(baz.b(), 200);
+        assert_eq!(baz.c(), 300);
+        assert_eq!(baz.z(), 11);
+        assert_eq!(baz.y(), 12);
+        assert_eq!(baz.w(), 21);
+
+        let baz: Rc<dyn Baz> = v.clone();
+        let bar: Rc<dyn Bar> = baz;
+        assert_eq!(*bar, 1);
+        assert_eq!(bar.a(), 100);
+        assert_eq!(bar.b(), 200);
+        assert_eq!(bar.z(), 11);
+        assert_eq!(bar.y(), 12);
+        assert_eq!(bar.w(), 21);
+
+        let baz: Rc<dyn Baz> = v.clone();
+        let foo: Rc<dyn Foo> = baz;
+        assert_eq!(*foo, 1);
+        assert_eq!(foo.a(), 100);
+        assert_eq!(foo.z(), 11);
+        assert_eq!(foo.y(), 12);
+
+        let baz: Rc<dyn Baz> = v.clone();
+        let bar: Rc<dyn Bar> = baz;
+        let foo: Rc<dyn Foo> = bar;
+        assert_eq!(*foo, 1);
+        assert_eq!(foo.a(), 100);
+        assert_eq!(foo.z(), 11);
+        assert_eq!(foo.y(), 12);
+        assert_eq!(foo.z(), 11);
+        assert_eq!(foo.y(), 12);
+    }
+
+    fn test_arc() {
+        let v = Arc::new(1);
+
+        let baz: Arc<dyn Baz> = v.clone();
+        assert_eq!(*baz, 1);
+        assert_eq!(baz.a(), 100);
+        assert_eq!(baz.b(), 200);
+        assert_eq!(baz.c(), 300);
+        assert_eq!(baz.z(), 11);
+        assert_eq!(baz.y(), 12);
+        assert_eq!(baz.w(), 21);
+
+        let baz: Arc<dyn Baz> = v.clone();
+        let bar: Arc<dyn Bar> = baz;
+        assert_eq!(*bar, 1);
+        assert_eq!(bar.a(), 100);
+        assert_eq!(bar.b(), 200);
+        assert_eq!(bar.z(), 11);
+        assert_eq!(bar.y(), 12);
+        assert_eq!(bar.w(), 21);
+
+        let baz: Arc<dyn Baz> = v.clone();
+        let foo: Arc<dyn Foo> = baz;
+        assert_eq!(*foo, 1);
+        assert_eq!(foo.a(), 100);
+        assert_eq!(foo.z(), 11);
+        assert_eq!(foo.y(), 12);
+
+        let baz: Arc<dyn Baz> = v.clone();
+        let bar: Arc<dyn Bar> = baz;
+        let foo: Arc<dyn Foo> = bar;
+        assert_eq!(*foo, 1);
+        assert_eq!(foo.a(), 100);
+        assert_eq!(foo.z(), 11);
+        assert_eq!(foo.y(), 12);
+    }
+
+    test_box();
+    test_rc();
+    test_arc();
+}
+
+fn replace_vptr() {
+    trait A {
+        fn foo_a(&self);
+    }
+
+    trait B {
+        fn foo_b(&self);
+    }
+
+    trait C: A + B {
+        fn foo_c(&self);
+    }
+
+    struct S(i32);
+
+    impl A for S {
+        fn foo_a(&self) {
+            unreachable!();
+        }
+    }
+
+    impl B for S {
+        fn foo_b(&self) {
+            assert_eq!(42, self.0);
+        }
+    }
+
+    impl C for S {
+        fn foo_c(&self) {
+            unreachable!();
+        }
+    }
+
+    fn invoke_inner(b: &dyn B) {
+        b.foo_b();
+    }
+
+    fn invoke_outer(c: &dyn C) {
+        invoke_inner(c);
+    }
+
+    let s = S(42);
+    invoke_outer(&s);
+}
diff --git a/src/tools/miri/tests/pass/enum-nullable-const-null-with-fields.rs b/src/tools/miri/tests/pass/enum-nullable-const-null-with-fields.rs
new file mode 100644
index 0000000000000..8385cc5d880c6
--- /dev/null
+++ b/src/tools/miri/tests/pass/enum-nullable-const-null-with-fields.rs
@@ -0,0 +1,11 @@
+use std::result::Result;
+use std::result::Result::Ok;
+
+static C: Result<(), Box<isize>> = Ok(());
+
+// This is because of yet another bad assertion (ICE) about the null side of a nullable enum.
+// So we won't actually compile if the bug is present, but we check the value in main anyway.
+
+pub fn main() {
+    assert!(C.is_ok());
+}
diff --git a/src/tools/miri/tests/pass/enum_discriminant_ptr_value.rs b/src/tools/miri/tests/pass/enum_discriminant_ptr_value.rs
new file mode 100644
index 0000000000000..4a3820777cff7
--- /dev/null
+++ b/src/tools/miri/tests/pass/enum_discriminant_ptr_value.rs
@@ -0,0 +1,9 @@
+// A niche-optimized enum where the discriminant is a pointer value -- relies on ptr-to-int casts in
+// the niche handling code.
+//@compile-flags: -Zmiri-disable-stacked-borrows -Zmiri-disable-validation
+
+fn main() {
+    let x = 42;
+    let val: Option<&i32> = unsafe { std::mem::transmute((&x as *const i32).wrapping_offset(2)) };
+    assert!(val.is_some());
+}
diff --git a/src/tools/miri/tests/pass/enums.rs b/src/tools/miri/tests/pass/enums.rs
new file mode 100644
index 0000000000000..ac7aafc1bb2e3
--- /dev/null
+++ b/src/tools/miri/tests/pass/enums.rs
@@ -0,0 +1,143 @@
+enum MyEnum {
+    MyEmptyVariant,
+    MyNewtypeVariant(i32),
+    MyTupleVariant(i32, i32),
+    MyStructVariant { my_first_field: i32, my_second_field: i32 },
+}
+
+fn test(me: MyEnum) {
+    match me {
+        MyEnum::MyEmptyVariant => {}
+        MyEnum::MyNewtypeVariant(ref val) => assert_eq!(val, &42),
+        MyEnum::MyTupleVariant(ref a, ref b) => {
+            assert_eq!(a, &43);
+            assert_eq!(b, &44);
+        }
+        MyEnum::MyStructVariant { ref my_first_field, ref my_second_field } => {
+            assert_eq!(my_first_field, &45);
+            assert_eq!(my_second_field, &46);
+        }
+    }
+}
+
+fn discriminant_overflow() {
+    // Tests for https://github.com/rust-lang/rust/issues/62138.
+    #[repr(u8)]
+    #[allow(dead_code)]
+    enum WithWraparoundInvalidValues {
+        X = 1,
+        Y = 254,
+    }
+
+    #[allow(dead_code)]
+    enum Foo {
+        A,
+        B,
+        C(WithWraparoundInvalidValues),
+    }
+
+    let x = Foo::B;
+    match x {
+        Foo::B => {}
+        _ => panic!(),
+    }
+}
+
+fn more_discriminant_overflow() {
+    pub enum Infallible {}
+
+    // The check that the `bool` field of `V1` is encoding a "niche variant"
+    // (i.e. not `V1`, so `V3` or `V4`) used to be mathematically incorrect,
+    // causing valid `V1` values to be interpreted as other variants.
+    #[allow(dead_code)]
+    pub enum E1 {
+        V1 { f: bool },
+        V2 { f: Infallible },
+        V3,
+        V4,
+    }
+
+    // Computing the discriminant used to be done using the niche type (here `u8`,
+    // from the `bool` field of `V1`), overflowing for variants with large enough
+    // indices (`V3` and `V4`), causing them to be interpreted as other variants.
+    #[allow(dead_code)]
+    #[rustfmt::skip] // rustfmt prefers every variant on its own line
+    pub enum E2<X> {
+        V1 { f: bool },
+
+        /*_00*/ _01(X), _02(X), _03(X), _04(X), _05(X), _06(X), _07(X),
+        _08(X), _09(X), _0A(X), _0B(X), _0C(X), _0D(X), _0E(X), _0F(X),
+        _10(X), _11(X), _12(X), _13(X), _14(X), _15(X), _16(X), _17(X),
+        _18(X), _19(X), _1A(X), _1B(X), _1C(X), _1D(X), _1E(X), _1F(X),
+        _20(X), _21(X), _22(X), _23(X), _24(X), _25(X), _26(X), _27(X),
+        _28(X), _29(X), _2A(X), _2B(X), _2C(X), _2D(X), _2E(X), _2F(X),
+        _30(X), _31(X), _32(X), _33(X), _34(X), _35(X), _36(X), _37(X),
+        _38(X), _39(X), _3A(X), _3B(X), _3C(X), _3D(X), _3E(X), _3F(X),
+        _40(X), _41(X), _42(X), _43(X), _44(X), _45(X), _46(X), _47(X),
+        _48(X), _49(X), _4A(X), _4B(X), _4C(X), _4D(X), _4E(X), _4F(X),
+        _50(X), _51(X), _52(X), _53(X), _54(X), _55(X), _56(X), _57(X),
+        _58(X), _59(X), _5A(X), _5B(X), _5C(X), _5D(X), _5E(X), _5F(X),
+        _60(X), _61(X), _62(X), _63(X), _64(X), _65(X), _66(X), _67(X),
+        _68(X), _69(X), _6A(X), _6B(X), _6C(X), _6D(X), _6E(X), _6F(X),
+        _70(X), _71(X), _72(X), _73(X), _74(X), _75(X), _76(X), _77(X),
+        _78(X), _79(X), _7A(X), _7B(X), _7C(X), _7D(X), _7E(X), _7F(X),
+        _80(X), _81(X), _82(X), _83(X), _84(X), _85(X), _86(X), _87(X),
+        _88(X), _89(X), _8A(X), _8B(X), _8C(X), _8D(X), _8E(X), _8F(X),
+        _90(X), _91(X), _92(X), _93(X), _94(X), _95(X), _96(X), _97(X),
+        _98(X), _99(X), _9A(X), _9B(X), _9C(X), _9D(X), _9E(X), _9F(X),
+        _A0(X), _A1(X), _A2(X), _A3(X), _A4(X), _A5(X), _A6(X), _A7(X),
+        _A8(X), _A9(X), _AA(X), _AB(X), _AC(X), _AD(X), _AE(X), _AF(X),
+        _B0(X), _B1(X), _B2(X), _B3(X), _B4(X), _B5(X), _B6(X), _B7(X),
+        _B8(X), _B9(X), _BA(X), _BB(X), _BC(X), _BD(X), _BE(X), _BF(X),
+        _C0(X), _C1(X), _C2(X), _C3(X), _C4(X), _C5(X), _C6(X), _C7(X),
+        _C8(X), _C9(X), _CA(X), _CB(X), _CC(X), _CD(X), _CE(X), _CF(X),
+        _D0(X), _D1(X), _D2(X), _D3(X), _D4(X), _D5(X), _D6(X), _D7(X),
+        _D8(X), _D9(X), _DA(X), _DB(X), _DC(X), _DD(X), _DE(X), _DF(X),
+        _E0(X), _E1(X), _E2(X), _E3(X), _E4(X), _E5(X), _E6(X), _E7(X),
+        _E8(X), _E9(X), _EA(X), _EB(X), _EC(X), _ED(X), _EE(X), _EF(X),
+        _F0(X), _F1(X), _F2(X), _F3(X), _F4(X), _F5(X), _F6(X), _F7(X),
+        _F8(X), _F9(X), _FA(X), _FB(X), _FC(X), _FD(X), _FE(X), _FF(X),
+
+        V3,
+        V4,
+    }
+
+    if let E1::V2 { .. } = (E1::V1 { f: true }) {
+        unreachable!()
+    }
+    if let E1::V1 { .. } = (E1::V1 { f: true }) {
+    } else {
+        unreachable!()
+    }
+
+    if let E2::V1 { .. } = E2::V3::<Infallible> {
+        unreachable!()
+    }
+    if let E2::V3 { .. } = E2::V3::<Infallible> {
+    } else {
+        unreachable!()
+    }
+}
+
+fn overaligned_casts() {
+    #[allow(dead_code)]
+    #[repr(align(8))]
+    enum Aligned {
+        Zero = 0,
+        One = 1,
+    }
+
+    let aligned = Aligned::Zero;
+    assert_eq!(aligned as u8, 0);
+}
+
+fn main() {
+    test(MyEnum::MyEmptyVariant);
+    test(MyEnum::MyNewtypeVariant(42));
+    test(MyEnum::MyTupleVariant(43, 44));
+    test(MyEnum::MyStructVariant { my_first_field: 45, my_second_field: 46 });
+
+    discriminant_overflow();
+    more_discriminant_overflow();
+    overaligned_casts();
+}
diff --git a/src/tools/miri/tests/pass/extern_crate_std_in_main.rs b/src/tools/miri/tests/pass/extern_crate_std_in_main.rs
new file mode 100644
index 0000000000000..8f71e613375e3
--- /dev/null
+++ b/src/tools/miri/tests/pass/extern_crate_std_in_main.rs
@@ -0,0 +1,5 @@
+#![no_std]
+
+fn main() {
+    extern crate std;
+}
diff --git a/src/tools/miri/tests/pass/extern_types.rs b/src/tools/miri/tests/pass/extern_types.rs
new file mode 100644
index 0000000000000..aa4c65ea8928a
--- /dev/null
+++ b/src/tools/miri/tests/pass/extern_types.rs
@@ -0,0 +1,10 @@
+#![feature(extern_types)]
+
+extern "C" {
+    type Foo;
+}
+
+fn main() {
+    let x: &Foo = unsafe { &*(16 as *const Foo) };
+    let _y: &Foo = &*x;
+}
diff --git a/src/tools/miri/tests/pass/extern_types.stderr b/src/tools/miri/tests/pass/extern_types.stderr
new file mode 100644
index 0000000000000..2e18f69305896
--- /dev/null
+++ b/src/tools/miri/tests/pass/extern_types.stderr
@@ -0,0 +1,15 @@
+warning: integer-to-pointer cast
+  --> $DIR/extern_types.rs:LL:CC
+   |
+LL |     let x: &Foo = unsafe { &*(16 as *const Foo) };
+   |                              ^^^^^^^^^^^^^^^^^^ integer-to-pointer cast
+   |
+   = help: This program is using integer-to-pointer casts or (equivalently) `ptr::from_exposed_addr`,
+   = help: which means that Miri might miss pointer bugs in this program.
+   = help: See https://doc.rust-lang.org/nightly/std/ptr/fn.from_exposed_addr.html for more details on that operation.
+   = help: To ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead.
+   = help: You can then pass the `-Zmiri-strict-provenance` flag to Miri, to ensure you are not relying on `from_exposed_addr` semantics.
+   = help: Alternatively, the `-Zmiri-permissive-provenance` flag disables this warning.
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/extern_types.rs:LL:CC
+
diff --git a/src/tools/miri/tests/pass/fat_ptr.rs b/src/tools/miri/tests/pass/fat_ptr.rs
new file mode 100644
index 0000000000000..8317156a218dd
--- /dev/null
+++ b/src/tools/miri/tests/pass/fat_ptr.rs
@@ -0,0 +1,51 @@
+// test that ordinary fat pointer operations work.
+
+struct Wrapper<T: ?Sized>(u32, T);
+
+struct FatPtrContainer<'a> {
+    ptr: &'a [u8],
+}
+
+fn fat_ptr_project(a: &Wrapper<[u8]>) -> &[u8] {
+    &a.1
+}
+
+fn fat_ptr_simple(a: &[u8]) -> &[u8] {
+    a
+}
+
+fn fat_ptr_via_local(a: &[u8]) -> &[u8] {
+    let x = a;
+    x
+}
+
+fn fat_ptr_from_struct(s: FatPtrContainer) -> &[u8] {
+    s.ptr
+}
+
+fn fat_ptr_to_struct(a: &[u8]) -> FatPtrContainer {
+    FatPtrContainer { ptr: a }
+}
+
+fn fat_ptr_store_to<'a>(a: &'a [u8], b: &mut &'a [u8]) {
+    *b = a;
+}
+
+fn fat_ptr_constant() -> &'static str {
+    "HELLO"
+}
+
+fn main() {
+    let a = Wrapper(4, [7, 6, 5]);
+
+    let p = fat_ptr_project(&a);
+    let p = fat_ptr_simple(p);
+    let p = fat_ptr_via_local(p);
+    let p = fat_ptr_from_struct(fat_ptr_to_struct(p));
+
+    let mut target: &[u8] = &[42];
+    fat_ptr_store_to(p, &mut target);
+    assert_eq!(target, &a.1);
+
+    assert_eq!(fat_ptr_constant(), "HELLO");
+}
diff --git a/src/tools/miri/tests/pass/float.rs b/src/tools/miri/tests/pass/float.rs
new file mode 100644
index 0000000000000..48dd99441ebff
--- /dev/null
+++ b/src/tools/miri/tests/pass/float.rs
@@ -0,0 +1,555 @@
+#![feature(stmt_expr_attributes, bench_black_box)]
+#![allow(arithmetic_overflow)]
+use std::fmt::Debug;
+use std::hint::black_box;
+
+fn main() {
+    basic();
+    casts();
+    more_casts();
+    ops();
+    nan_casts();
+}
+
+// Helper function to avoid promotion so that this tests "run-time" casts, not CTFE.
+// Doesn't make a big difference when running this in Miri, but it means we can compare this
+// with the LLVM backend by running `rustc -Zmir-opt-level=0 -Zsaturating-float-casts`.
+#[track_caller]
+#[inline(never)]
+fn assert_eq<T: PartialEq + Debug>(x: T, y: T) {
+    assert_eq!(x, y);
+}
+
+trait FloatToInt<Int>: Copy {
+    fn cast(self) -> Int;
+    unsafe fn cast_unchecked(self) -> Int;
+}
+
+impl FloatToInt<i8> for f32 {
+    fn cast(self) -> i8 {
+        self as _
+    }
+    unsafe fn cast_unchecked(self) -> i8 {
+        self.to_int_unchecked()
+    }
+}
+impl FloatToInt<i32> for f32 {
+    fn cast(self) -> i32 {
+        self as _
+    }
+    unsafe fn cast_unchecked(self) -> i32 {
+        self.to_int_unchecked()
+    }
+}
+impl FloatToInt<u32> for f32 {
+    fn cast(self) -> u32 {
+        self as _
+    }
+    unsafe fn cast_unchecked(self) -> u32 {
+        self.to_int_unchecked()
+    }
+}
+impl FloatToInt<i64> for f32 {
+    fn cast(self) -> i64 {
+        self as _
+    }
+    unsafe fn cast_unchecked(self) -> i64 {
+        self.to_int_unchecked()
+    }
+}
+impl FloatToInt<u64> for f32 {
+    fn cast(self) -> u64 {
+        self as _
+    }
+    unsafe fn cast_unchecked(self) -> u64 {
+        self.to_int_unchecked()
+    }
+}
+
+impl FloatToInt<i8> for f64 {
+    fn cast(self) -> i8 {
+        self as _
+    }
+    unsafe fn cast_unchecked(self) -> i8 {
+        self.to_int_unchecked()
+    }
+}
+impl FloatToInt<i32> for f64 {
+    fn cast(self) -> i32 {
+        self as _
+    }
+    unsafe fn cast_unchecked(self) -> i32 {
+        self.to_int_unchecked()
+    }
+}
+impl FloatToInt<u32> for f64 {
+    fn cast(self) -> u32 {
+        self as _
+    }
+    unsafe fn cast_unchecked(self) -> u32 {
+        self.to_int_unchecked()
+    }
+}
+impl FloatToInt<i64> for f64 {
+    fn cast(self) -> i64 {
+        self as _
+    }
+    unsafe fn cast_unchecked(self) -> i64 {
+        self.to_int_unchecked()
+    }
+}
+impl FloatToInt<u64> for f64 {
+    fn cast(self) -> u64 {
+        self as _
+    }
+    unsafe fn cast_unchecked(self) -> u64 {
+        self.to_int_unchecked()
+    }
+}
+impl FloatToInt<i128> for f64 {
+    fn cast(self) -> i128 {
+        self as _
+    }
+    unsafe fn cast_unchecked(self) -> i128 {
+        self.to_int_unchecked()
+    }
+}
+impl FloatToInt<u128> for f64 {
+    fn cast(self) -> u128 {
+        self as _
+    }
+    unsafe fn cast_unchecked(self) -> u128 {
+        self.to_int_unchecked()
+    }
+}
+
+/// Test this cast both via `as` and via `approx_unchecked` (i.e., it must not saturate).
+#[track_caller]
+#[inline(never)]
+fn test_both_cast<F, I>(x: F, y: I)
+where
+    F: FloatToInt<I>,
+    I: PartialEq + Debug,
+{
+    assert_eq!(x.cast(), y);
+    assert_eq!(unsafe { x.cast_unchecked() }, y);
+}
+
+fn basic() {
+    // basic arithmetic
+    assert_eq(6.0_f32 * 6.0_f32, 36.0_f32);
+    assert_eq(6.0_f64 * 6.0_f64, 36.0_f64);
+    assert_eq(-{ 5.0_f32 }, -5.0_f32);
+    assert_eq(-{ 5.0_f64 }, -5.0_f64);
+    // infinities, NaN
+    assert!((5.0_f32 / 0.0).is_infinite());
+    assert_ne!({ 5.0_f32 / 0.0 }, { -5.0_f32 / 0.0 });
+    assert!((5.0_f64 / 0.0).is_infinite());
+    assert_ne!({ 5.0_f64 / 0.0 }, { 5.0_f64 / -0.0 });
+    assert!((-5.0_f32).sqrt().is_nan());
+    assert!((-5.0_f64).sqrt().is_nan());
+    assert_ne!(f32::NAN, f32::NAN);
+    assert_ne!(f64::NAN, f64::NAN);
+    // negative zero
+    let posz = 0.0f32;
+    let negz = -0.0f32;
+    assert_eq(posz, negz);
+    assert_ne!(posz.to_bits(), negz.to_bits());
+    let posz = 0.0f64;
+    let negz = -0.0f64;
+    assert_eq(posz, negz);
+    assert_ne!(posz.to_bits(), negz.to_bits());
+    // byte-level transmute
+    let x: u64 = unsafe { std::mem::transmute(42.0_f64) };
+    let y: f64 = unsafe { std::mem::transmute(x) };
+    assert_eq(y, 42.0_f64);
+    let x: u32 = unsafe { std::mem::transmute(42.0_f32) };
+    let y: f32 = unsafe { std::mem::transmute(x) };
+    assert_eq(y, 42.0_f32);
+}
+
+/// Many of these test values are taken from
+/// https://github.com/WebAssembly/testsuite/blob/master/conversions.wast.
+fn casts() {
+    // f32 -> i8
+    test_both_cast::<f32, i8>(127.99, 127);
+    test_both_cast::<f32, i8>(-128.99, -128);
+
+    // f32 -> i32
+    test_both_cast::<f32, i32>(0.0, 0);
+    test_both_cast::<f32, i32>(-0.0, 0);
+    test_both_cast::<f32, i32>(/*0x1p-149*/ f32::from_bits(0x00000001), 0);
+    test_both_cast::<f32, i32>(/*-0x1p-149*/ f32::from_bits(0x80000001), 0);
+    test_both_cast::<f32, i32>(/*0x1.19999ap+0*/ f32::from_bits(0x3f8ccccd), 1);
+    test_both_cast::<f32, i32>(/*-0x1.19999ap+0*/ f32::from_bits(0xbf8ccccd), -1);
+    test_both_cast::<f32, i32>(1.9, 1);
+    test_both_cast::<f32, i32>(-1.9, -1);
+    test_both_cast::<f32, i32>(5.0, 5);
+    test_both_cast::<f32, i32>(-5.0, -5);
+    test_both_cast::<f32, i32>(2147483520.0, 2147483520);
+    test_both_cast::<f32, i32>(-2147483648.0, -2147483648);
+    // unrepresentable casts
+    assert_eq::<i32>(2147483648.0f32 as i32, i32::MAX);
+    assert_eq::<i32>(-2147483904.0f32 as i32, i32::MIN);
+    assert_eq::<i32>(f32::MAX as i32, i32::MAX);
+    assert_eq::<i32>(f32::MIN as i32, i32::MIN);
+    assert_eq::<i32>(f32::INFINITY as i32, i32::MAX);
+    assert_eq::<i32>(f32::NEG_INFINITY as i32, i32::MIN);
+    assert_eq::<i32>(f32::NAN as i32, 0);
+    assert_eq::<i32>((-f32::NAN) as i32, 0);
+
+    // f32 -> u32
+    test_both_cast::<f32, u32>(0.0, 0);
+    test_both_cast::<f32, u32>(-0.0, 0);
+    test_both_cast::<f32, u32>(-0.9999999, 0);
+    test_both_cast::<f32, u32>(/*0x1p-149*/ f32::from_bits(0x1), 0);
+    test_both_cast::<f32, u32>(/*-0x1p-149*/ f32::from_bits(0x80000001), 0);
+    test_both_cast::<f32, u32>(/*0x1.19999ap+0*/ f32::from_bits(0x3f8ccccd), 1);
+    test_both_cast::<f32, u32>(1.9, 1);
+    test_both_cast::<f32, u32>(5.0, 5);
+    test_both_cast::<f32, u32>(2147483648.0, 0x8000_0000);
+    test_both_cast::<f32, u32>(4294967040.0, 0u32.wrapping_sub(256));
+    test_both_cast::<f32, u32>(/*-0x1.ccccccp-1*/ f32::from_bits(0xbf666666), 0);
+    test_both_cast::<f32, u32>(/*-0x1.fffffep-1*/ f32::from_bits(0xbf7fffff), 0);
+    test_both_cast::<f32, u32>((u32::MAX - 128) as f32, u32::MAX - 255); // rounding loss
+    // unrepresentable casts
+    assert_eq::<u32>((u32::MAX - 127) as f32 as u32, u32::MAX); // rounds up and then becomes unrepresentable
+    assert_eq::<u32>(4294967296.0f32 as u32, u32::MAX);
+    assert_eq::<u32>(-5.0f32 as u32, 0);
+    assert_eq::<u32>(f32::MAX as u32, u32::MAX);
+    assert_eq::<u32>(f32::MIN as u32, 0);
+    assert_eq::<u32>(f32::INFINITY as u32, u32::MAX);
+    assert_eq::<u32>(f32::NEG_INFINITY as u32, 0);
+    assert_eq::<u32>(f32::NAN as u32, 0);
+    assert_eq::<u32>((-f32::NAN) as u32, 0);
+
+    // f32 -> i64
+    test_both_cast::<f32, i64>(4294967296.0, 4294967296);
+    test_both_cast::<f32, i64>(-4294967296.0, -4294967296);
+    test_both_cast::<f32, i64>(9223371487098961920.0, 9223371487098961920);
+    test_both_cast::<f32, i64>(-9223372036854775808.0, -9223372036854775808);
+
+    // f64 -> i8
+    test_both_cast::<f64, i8>(127.99, 127);
+    test_both_cast::<f64, i8>(-128.99, -128);
+
+    // f64 -> i32
+    test_both_cast::<f64, i32>(0.0, 0);
+    test_both_cast::<f64, i32>(-0.0, 0);
+    test_both_cast::<f64, i32>(/*0x1.199999999999ap+0*/ f64::from_bits(0x3ff199999999999a), 1);
+    test_both_cast::<f64, i32>(
+        /*-0x1.199999999999ap+0*/ f64::from_bits(0xbff199999999999a),
+        -1,
+    );
+    test_both_cast::<f64, i32>(1.9, 1);
+    test_both_cast::<f64, i32>(-1.9, -1);
+    test_both_cast::<f64, i32>(1e8, 100_000_000);
+    test_both_cast::<f64, i32>(2147483647.0, 2147483647);
+    test_both_cast::<f64, i32>(-2147483648.0, -2147483648);
+    // unrepresentable casts
+    assert_eq::<i32>(2147483648.0f64 as i32, i32::MAX);
+    assert_eq::<i32>(-2147483649.0f64 as i32, i32::MIN);
+
+    // f64 -> i64
+    test_both_cast::<f64, i64>(0.0, 0);
+    test_both_cast::<f64, i64>(-0.0, 0);
+    test_both_cast::<f64, i64>(/*0x0.0000000000001p-1022*/ f64::from_bits(0x1), 0);
+    test_both_cast::<f64, i64>(
+        /*-0x0.0000000000001p-1022*/ f64::from_bits(0x8000000000000001),
+        0,
+    );
+    test_both_cast::<f64, i64>(/*0x1.199999999999ap+0*/ f64::from_bits(0x3ff199999999999a), 1);
+    test_both_cast::<f64, i64>(
+        /*-0x1.199999999999ap+0*/ f64::from_bits(0xbff199999999999a),
+        -1,
+    );
+    test_both_cast::<f64, i64>(5.0, 5);
+    test_both_cast::<f64, i64>(5.9, 5);
+    test_both_cast::<f64, i64>(-5.0, -5);
+    test_both_cast::<f64, i64>(-5.9, -5);
+    test_both_cast::<f64, i64>(4294967296.0, 4294967296);
+    test_both_cast::<f64, i64>(-4294967296.0, -4294967296);
+    test_both_cast::<f64, i64>(9223372036854774784.0, 9223372036854774784);
+    test_both_cast::<f64, i64>(-9223372036854775808.0, -9223372036854775808);
+    // unrepresentable casts
+    assert_eq::<i64>(9223372036854775808.0f64 as i64, i64::MAX);
+    assert_eq::<i64>(-9223372036854777856.0f64 as i64, i64::MIN);
+    assert_eq::<i64>(f64::MAX as i64, i64::MAX);
+    assert_eq::<i64>(f64::MIN as i64, i64::MIN);
+    assert_eq::<i64>(f64::INFINITY as i64, i64::MAX);
+    assert_eq::<i64>(f64::NEG_INFINITY as i64, i64::MIN);
+    assert_eq::<i64>(f64::NAN as i64, 0);
+    assert_eq::<i64>((-f64::NAN) as i64, 0);
+
+    // f64 -> u64
+    test_both_cast::<f64, u64>(0.0, 0);
+    test_both_cast::<f64, u64>(-0.0, 0);
+    test_both_cast::<f64, u64>(-0.99999999999, 0);
+    test_both_cast::<f64, u64>(5.0, 5);
+    test_both_cast::<f64, u64>(1e16, 10000000000000000);
+    test_both_cast::<f64, u64>((u64::MAX - 1024) as f64, u64::MAX - 2047); // rounding loss
+    test_both_cast::<f64, u64>(9223372036854775808.0, 9223372036854775808);
+    // unrepresentable casts
+    assert_eq::<u64>(-5.0f64 as u64, 0);
+    assert_eq::<u64>((u64::MAX - 1023) as f64 as u64, u64::MAX); // rounds up and then becomes unrepresentable
+    assert_eq::<u64>(18446744073709551616.0f64 as u64, u64::MAX);
+    assert_eq::<u64>(f64::MAX as u64, u64::MAX);
+    assert_eq::<u64>(f64::MIN as u64, 0);
+    assert_eq::<u64>(f64::INFINITY as u64, u64::MAX);
+    assert_eq::<u64>(f64::NEG_INFINITY as u64, 0);
+    assert_eq::<u64>(f64::NAN as u64, 0);
+    assert_eq::<u64>((-f64::NAN) as u64, 0);
+
+    // f64 -> i128
+    assert_eq::<i128>(f64::MAX as i128, i128::MAX);
+    assert_eq::<i128>(f64::MIN as i128, i128::MIN);
+
+    // f64 -> u128
+    assert_eq::<u128>(f64::MAX as u128, u128::MAX);
+    assert_eq::<u128>(f64::MIN as u128, 0);
+
+    // int -> f32
+    assert_eq::<f32>(127i8 as f32, 127.0);
+    assert_eq::<f32>(2147483647i32 as f32, 2147483648.0);
+    assert_eq::<f32>((-2147483648i32) as f32, -2147483648.0);
+    assert_eq::<f32>(1234567890i32 as f32, /*0x1.26580cp+30*/ f32::from_bits(0x4e932c06));
+    assert_eq::<f32>(16777217i32 as f32, 16777216.0);
+    assert_eq::<f32>((-16777217i32) as f32, -16777216.0);
+    assert_eq::<f32>(16777219i32 as f32, 16777220.0);
+    assert_eq::<f32>((-16777219i32) as f32, -16777220.0);
+    assert_eq::<f32>(
+        0x7fffff4000000001i64 as f32,
+        /*0x1.fffffep+62*/ f32::from_bits(0x5effffff),
+    );
+    assert_eq::<f32>(
+        0x8000004000000001u64 as i64 as f32,
+        /*-0x1.fffffep+62*/ f32::from_bits(0xdeffffff),
+    );
+    assert_eq::<f32>(
+        0x0020000020000001i64 as f32,
+        /*0x1.000002p+53*/ f32::from_bits(0x5a000001),
+    );
+    assert_eq::<f32>(
+        0xffdfffffdfffffffu64 as i64 as f32,
+        /*-0x1.000002p+53*/ f32::from_bits(0xda000001),
+    );
+    assert_eq::<f32>(i128::MIN as f32, -170141183460469231731687303715884105728.0f32);
+    assert_eq::<f32>(u128::MAX as f32, f32::INFINITY); // saturation
+
+    // int -> f64
+    assert_eq::<f64>(127i8 as f64, 127.0);
+    assert_eq::<f64>(i16::MIN as f64, -32768.0f64);
+    assert_eq::<f64>(2147483647i32 as f64, 2147483647.0);
+    assert_eq::<f64>(-2147483648i32 as f64, -2147483648.0);
+    assert_eq::<f64>(987654321i32 as f64, 987654321.0);
+    assert_eq::<f64>(9223372036854775807i64 as f64, 9223372036854775807.0);
+    assert_eq::<f64>(-9223372036854775808i64 as f64, -9223372036854775808.0);
+    assert_eq::<f64>(4669201609102990i64 as f64, 4669201609102990.0); // Feigenbaum (?)
+    assert_eq::<f64>(9007199254740993i64 as f64, 9007199254740992.0);
+    assert_eq::<f64>(-9007199254740993i64 as f64, -9007199254740992.0);
+    assert_eq::<f64>(9007199254740995i64 as f64, 9007199254740996.0);
+    assert_eq::<f64>(-9007199254740995i64 as f64, -9007199254740996.0);
+    assert_eq::<f64>(u128::MAX as f64, 340282366920938463463374607431768211455.0f64); // even that fits...
+
+    // f32 -> f64
+    assert_eq::<u64>((0.0f32 as f64).to_bits(), 0.0f64.to_bits());
+    assert_eq::<u64>(((-0.0f32) as f64).to_bits(), (-0.0f64).to_bits());
+    assert_eq::<f64>(5.0f32 as f64, 5.0f64);
+    assert_eq::<f64>(
+        /*0x1p-149*/ f32::from_bits(0x1) as f64,
+        /*0x1p-149*/ f64::from_bits(0x36a0000000000000),
+    );
+    assert_eq::<f64>(
+        /*-0x1p-149*/ f32::from_bits(0x80000001) as f64,
+        /*-0x1p-149*/ f64::from_bits(0xb6a0000000000000),
+    );
+    assert_eq::<f64>(
+        /*0x1.fffffep+127*/ f32::from_bits(0x7f7fffff) as f64,
+        /*0x1.fffffep+127*/ f64::from_bits(0x47efffffe0000000),
+    );
+    assert_eq::<f64>(
+        /*-0x1.fffffep+127*/ (-f32::from_bits(0x7f7fffff)) as f64,
+        /*-0x1.fffffep+127*/ -f64::from_bits(0x47efffffe0000000),
+    );
+    assert_eq::<f64>(
+        /*0x1p-119*/ f32::from_bits(0x4000000) as f64,
+        /*0x1p-119*/ f64::from_bits(0x3880000000000000),
+    );
+    assert_eq::<f64>(
+        /*0x1.8f867ep+125*/ f32::from_bits(0x7e47c33f) as f64,
+        6.6382536710104395e+37,
+    );
+    assert_eq::<f64>(f32::INFINITY as f64, f64::INFINITY);
+    assert_eq::<f64>(f32::NEG_INFINITY as f64, f64::NEG_INFINITY);
+
+    // f64 -> f32
+    assert_eq::<u32>((0.0f64 as f32).to_bits(), 0.0f32.to_bits());
+    assert_eq::<u32>(((-0.0f64) as f32).to_bits(), (-0.0f32).to_bits());
+    assert_eq::<f32>(5.0f64 as f32, 5.0f32);
+    assert_eq::<f32>(/*0x0.0000000000001p-1022*/ f64::from_bits(0x1) as f32, 0.0);
+    assert_eq::<f32>(/*-0x0.0000000000001p-1022*/ (-f64::from_bits(0x1)) as f32, -0.0);
+    assert_eq::<f32>(
+        /*0x1.fffffe0000000p-127*/ f64::from_bits(0x380fffffe0000000) as f32,
+        /*0x1p-149*/ f32::from_bits(0x800000),
+    );
+    assert_eq::<f32>(
+        /*0x1.4eae4f7024c7p+108*/ f64::from_bits(0x46b4eae4f7024c70) as f32,
+        /*0x1.4eae5p+108*/ f32::from_bits(0x75a75728),
+    );
+    assert_eq::<f32>(f64::MAX as f32, f32::INFINITY);
+    assert_eq::<f32>(f64::MIN as f32, f32::NEG_INFINITY);
+    assert_eq::<f32>(f64::INFINITY as f32, f32::INFINITY);
+    assert_eq::<f32>(f64::NEG_INFINITY as f32, f32::NEG_INFINITY);
+}
+
+fn ops() {
+    // f32 min/max
+    assert_eq((1.0 as f32).max(-1.0), 1.0);
+    assert_eq((1.0 as f32).min(-1.0), -1.0);
+    assert_eq(f32::NAN.min(9.0), 9.0);
+    assert_eq(f32::NAN.max(-9.0), -9.0);
+    assert_eq((9.0 as f32).min(f32::NAN), 9.0);
+    assert_eq((-9.0 as f32).max(f32::NAN), -9.0);
+
+    // f64 min/max
+    assert_eq((1.0 as f64).max(-1.0), 1.0);
+    assert_eq((1.0 as f64).min(-1.0), -1.0);
+    assert_eq(f64::NAN.min(9.0), 9.0);
+    assert_eq(f64::NAN.max(-9.0), -9.0);
+    assert_eq((9.0 as f64).min(f64::NAN), 9.0);
+    assert_eq((-9.0 as f64).max(f64::NAN), -9.0);
+
+    // f32 copysign
+    assert_eq(3.5_f32.copysign(0.42), 3.5_f32);
+    assert_eq(3.5_f32.copysign(-0.42), -3.5_f32);
+    assert_eq((-3.5_f32).copysign(0.42), 3.5_f32);
+    assert_eq((-3.5_f32).copysign(-0.42), -3.5_f32);
+    assert!(f32::NAN.copysign(1.0).is_nan());
+
+    // f64 copysign
+    assert_eq(3.5_f64.copysign(0.42), 3.5_f64);
+    assert_eq(3.5_f64.copysign(-0.42), -3.5_f64);
+    assert_eq((-3.5_f64).copysign(0.42), 3.5_f64);
+    assert_eq((-3.5_f64).copysign(-0.42), -3.5_f64);
+    assert!(f64::NAN.copysign(1.0).is_nan());
+}
+
+/// Tests taken from rustc test suite.
+///
+
+macro_rules! test {
+    ($val:expr, $src_ty:ident -> $dest_ty:ident, $expected:expr) => (
+        // black_box disables constant evaluation to test run-time conversions:
+        assert_eq!(black_box::<$src_ty>($val) as $dest_ty, $expected,
+                    "run-time {} -> {}", stringify!($src_ty), stringify!($dest_ty));
+
+        {
+            const X: $src_ty = $val;
+            const Y: $dest_ty = X as $dest_ty;
+            assert_eq!(Y, $expected,
+                        "const eval {} -> {}", stringify!($src_ty), stringify!($dest_ty));
+        }
+    );
+
+    ($fval:expr, f* -> $ity:ident, $ival:expr) => (
+        test!($fval, f32 -> $ity, $ival);
+        test!($fval, f64 -> $ity, $ival);
+    )
+}
+
+macro_rules! common_fptoi_tests {
+    ($fty:ident -> $($ity:ident)+) => ({ $(
+        test!($fty::NAN, $fty -> $ity, 0);
+        test!($fty::INFINITY, $fty -> $ity, $ity::MAX);
+        test!($fty::NEG_INFINITY, $fty -> $ity, $ity::MIN);
+        // These two tests are not solely float->int tests, in particular the latter relies on
+        // `u128::MAX as f32` not being UB. But that's okay, since this file tests int->float
+        // as well, the test is just slightly misplaced.
+        test!($ity::MIN as $fty, $fty -> $ity, $ity::MIN);
+        test!($ity::MAX as $fty, $fty -> $ity, $ity::MAX);
+        test!(0., $fty -> $ity, 0);
+        test!($fty::MIN_POSITIVE, $fty -> $ity, 0);
+        test!(-0.9, $fty -> $ity, 0);
+        test!(1., $fty -> $ity, 1);
+        test!(42., $fty -> $ity, 42);
+    )+ });
+
+    (f* -> $($ity:ident)+) => ({
+        common_fptoi_tests!(f32 -> $($ity)+);
+        common_fptoi_tests!(f64 -> $($ity)+);
+    })
+}
+
+macro_rules! fptoui_tests {
+    ($fty: ident -> $($ity: ident)+) => ({ $(
+        test!(-0., $fty -> $ity, 0);
+        test!(-$fty::MIN_POSITIVE, $fty -> $ity, 0);
+        test!(-0.99999994, $fty -> $ity, 0);
+        test!(-1., $fty -> $ity, 0);
+        test!(-100., $fty -> $ity, 0);
+        test!(#[allow(overflowing_literals)] -1e50, $fty -> $ity, 0);
+        test!(#[allow(overflowing_literals)] -1e130, $fty -> $ity, 0);
+    )+ });
+
+    (f* -> $($ity:ident)+) => ({
+        fptoui_tests!(f32 -> $($ity)+);
+        fptoui_tests!(f64 -> $($ity)+);
+    })
+}
+
+fn more_casts() {
+    common_fptoi_tests!(f* -> i8 i16 i32 i64 u8 u16 u32 u64);
+    fptoui_tests!(f* -> u8 u16 u32 u64);
+    common_fptoi_tests!(f* -> i128 u128);
+    fptoui_tests!(f* -> u128);
+
+    // The following tests cover edge cases for some integer types.
+
+    // # u8
+    test!(254., f* -> u8, 254);
+    test!(256., f* -> u8, 255);
+
+    // # i8
+    test!(-127., f* -> i8, -127);
+    test!(-129., f* -> i8, -128);
+    test!(126., f* -> i8, 126);
+    test!(128., f* -> i8, 127);
+
+    // # i32
+    // -2147483648. is i32::MIN (exactly)
+    test!(-2147483648., f* -> i32, i32::MIN);
+    // 2147483648. is i32::MAX rounded up
+    test!(2147483648., f32 -> i32, 2147483647);
+    // With 24 significand bits, floats with magnitude in [2^30 + 1, 2^31] are rounded to
+    // multiples of 2^7. Therefore, nextDown(round(i32::MAX)) is 2^31 - 128:
+    test!(2147483520., f32 -> i32, 2147483520);
+    // Similarly, nextUp(i32::MIN) is i32::MIN + 2^8 and nextDown(i32::MIN) is i32::MIN - 2^7
+    test!(-2147483904., f* -> i32, i32::MIN);
+    test!(-2147483520., f* -> i32, -2147483520);
+
+    // # u32
+    // round(MAX) and nextUp(round(MAX))
+    test!(4294967040., f* -> u32, 4294967040);
+    test!(4294967296., f* -> u32, 4294967295);
+
+    // # u128
+    // float->int:
+    test!(f32::MAX, f32 -> u128, 0xffffff00000000000000000000000000);
+    // nextDown(f32::MAX) = 2^128 - 2 * 2^104
+    const SECOND_LARGEST_F32: f32 = 340282326356119256160033759537265639424.;
+    test!(SECOND_LARGEST_F32, f32 -> u128, 0xfffffe00000000000000000000000000);
+}
+
+fn nan_casts() {
+    let nan1 = f64::from_bits(0x7FF0_0001_0000_0001u64);
+    let nan2 = f64::from_bits(0x7FF0_0000_0000_0001u64);
+
+    assert!(nan1.is_nan());
+    assert!(nan2.is_nan());
+
+    let nan1_32 = nan1 as f32;
+    let nan2_32 = nan2 as f32;
+
+    assert!(nan1_32.is_nan());
+    assert!(nan2_32.is_nan());
+}
diff --git a/src/tools/miri/tests/pass/float_fast_math.rs b/src/tools/miri/tests/pass/float_fast_math.rs
new file mode 100644
index 0000000000000..52d985667df2d
--- /dev/null
+++ b/src/tools/miri/tests/pass/float_fast_math.rs
@@ -0,0 +1,34 @@
+#![feature(core_intrinsics)]
+
+use std::intrinsics::{fadd_fast, fdiv_fast, fmul_fast, frem_fast, fsub_fast};
+
+#[inline(never)]
+pub fn test_operations_f64(a: f64, b: f64) {
+    // make sure they all map to the correct operation
+    unsafe {
+        assert_eq!(fadd_fast(a, b), a + b);
+        assert_eq!(fsub_fast(a, b), a - b);
+        assert_eq!(fmul_fast(a, b), a * b);
+        assert_eq!(fdiv_fast(a, b), a / b);
+        assert_eq!(frem_fast(a, b), a % b);
+    }
+}
+
+#[inline(never)]
+pub fn test_operations_f32(a: f32, b: f32) {
+    // make sure they all map to the correct operation
+    unsafe {
+        assert_eq!(fadd_fast(a, b), a + b);
+        assert_eq!(fsub_fast(a, b), a - b);
+        assert_eq!(fmul_fast(a, b), a * b);
+        assert_eq!(fdiv_fast(a, b), a / b);
+        assert_eq!(frem_fast(a, b), a % b);
+    }
+}
+
+fn main() {
+    test_operations_f64(1., 2.);
+    test_operations_f64(10., 5.);
+    test_operations_f32(11., 2.);
+    test_operations_f32(10., 15.);
+}
diff --git a/src/tools/miri/tests/pass/format.rs b/src/tools/miri/tests/pass/format.rs
new file mode 100644
index 0000000000000..053cce36130c7
--- /dev/null
+++ b/src/tools/miri/tests/pass/format.rs
@@ -0,0 +1,4 @@
+fn main() {
+    println!("Hello {}", 13);
+    println!("{:0<width$}", "hello", width = 10);
+}
diff --git a/src/tools/miri/tests/pass/format.stdout b/src/tools/miri/tests/pass/format.stdout
new file mode 100644
index 0000000000000..3c9a55d0d4c1a
--- /dev/null
+++ b/src/tools/miri/tests/pass/format.stdout
@@ -0,0 +1,2 @@
+Hello 13
+hello00000
diff --git a/src/tools/miri/tests/pass/from_utf8.rs b/src/tools/miri/tests/pass/from_utf8.rs
new file mode 100644
index 0000000000000..ce59e60a932d5
--- /dev/null
+++ b/src/tools/miri/tests/pass/from_utf8.rs
@@ -0,0 +1,3 @@
+fn main() {
+    let _val = ::std::str::from_utf8(b"a");
+}
diff --git a/src/tools/miri/tests/pass/function_calls/disable_abi_check.rs b/src/tools/miri/tests/pass/function_calls/disable_abi_check.rs
new file mode 100644
index 0000000000000..e6251b5355886
--- /dev/null
+++ b/src/tools/miri/tests/pass/function_calls/disable_abi_check.rs
@@ -0,0 +1,24 @@
+//@compile-flags: -Zmiri-disable-abi-check
+#![feature(core_intrinsics)]
+
+fn main() {
+    fn foo() {}
+
+    extern "C" fn try_fn(ptr: *mut u8) {
+        assert!(ptr.is_null());
+    }
+
+    extern "Rust" {
+        fn malloc(size: usize) -> *mut std::ffi::c_void;
+    }
+
+    unsafe {
+        let _ = malloc(0);
+        std::mem::transmute::<fn(), extern "C" fn()>(foo)();
+        std::intrinsics::r#try(
+            std::mem::transmute::<extern "C" fn(*mut u8), _>(try_fn),
+            std::ptr::null_mut(),
+            |_, _| unreachable!(),
+        );
+    }
+}
diff --git a/src/tools/miri/tests/pass/function_calls/exported_symbol.rs b/src/tools/miri/tests/pass/function_calls/exported_symbol.rs
new file mode 100644
index 0000000000000..27aee9c883588
--- /dev/null
+++ b/src/tools/miri/tests/pass/function_calls/exported_symbol.rs
@@ -0,0 +1,79 @@
+#![feature(rustc_attrs)]
+
+#[no_mangle]
+extern "C" fn foo() -> i32 {
+    -1
+}
+
+#[export_name = "bar"]
+fn bar() -> i32 {
+    -2
+}
+
+#[rustc_std_internal_symbol]
+fn baz() -> i32 {
+    -3
+}
+
+struct AssocFn;
+
+impl AssocFn {
+    #[no_mangle]
+    fn qux() -> i32 {
+        -4
+    }
+}
+
+fn main() {
+    // Repeat calls to make sure the `Instance` cache is not broken.
+    for _ in 0..3 {
+        extern "C" {
+            fn foo() -> i32;
+            fn free(_: *mut std::ffi::c_void);
+        }
+
+        assert_eq!(unsafe { foo() }, -1);
+
+        // `free()` is a built-in shim, so calling it will add ("free", None) to the cache.
+        // Test that the cache is not broken with ("free", None).
+        unsafe { free(std::ptr::null_mut()) }
+
+        extern "Rust" {
+            fn bar() -> i32;
+            fn baz() -> i32;
+            fn qux() -> i32;
+        }
+
+        assert_eq!(unsafe { bar() }, -2);
+        assert_eq!(unsafe { baz() }, -3);
+        assert_eq!(unsafe { qux() }, -4);
+
+        #[allow(clashing_extern_declarations)]
+        {
+            extern "Rust" {
+                fn foo() -> i32;
+            }
+
+            assert_eq!(
+                unsafe {
+                    std::mem::transmute::<unsafe fn() -> i32, unsafe extern "C" fn() -> i32>(foo)()
+                },
+                -1
+            );
+
+            extern "C" {
+                fn bar() -> i32;
+                fn baz() -> i32;
+                fn qux() -> i32;
+            }
+
+            unsafe {
+                let transmute =
+                    |f| std::mem::transmute::<unsafe extern "C" fn() -> i32, unsafe fn() -> i32>(f);
+                assert_eq!(transmute(bar)(), -2);
+                assert_eq!(transmute(baz)(), -3);
+                assert_eq!(transmute(qux)(), -4);
+            }
+        }
+    }
+}
diff --git a/src/tools/miri/tests/pass/function_pointers.rs b/src/tools/miri/tests/pass/function_pointers.rs
new file mode 100644
index 0000000000000..b66826e3fcdfb
--- /dev/null
+++ b/src/tools/miri/tests/pass/function_pointers.rs
@@ -0,0 +1,89 @@
+use std::mem;
+
+trait Answer {
+    fn answer() -> Self;
+}
+
+impl Answer for i32 {
+    fn answer() -> i32 {
+        42
+    }
+}
+
+// A generic function, to make its address unstable
+fn f<T: Answer>() -> T {
+    Answer::answer()
+}
+
+fn g(i: i32) -> i32 {
+    i * 42
+}
+
+fn h(i: i32, j: i32) -> i32 {
+    j * i * 7
+}
+
+fn return_fn_ptr(f: fn() -> i32) -> fn() -> i32 {
+    f
+}
+
+fn call_fn_ptr() -> i32 {
+    return_fn_ptr(f)()
+}
+
+fn indirect<F: Fn() -> i32>(f: F) -> i32 {
+    f()
+}
+fn indirect_mut<F: FnMut() -> i32>(mut f: F) -> i32 {
+    f()
+}
+fn indirect_once<F: FnOnce() -> i32>(f: F) -> i32 {
+    f()
+}
+
+fn indirect2<F: Fn(i32) -> i32>(f: F) -> i32 {
+    f(10)
+}
+fn indirect_mut2<F: FnMut(i32) -> i32>(mut f: F) -> i32 {
+    f(10)
+}
+fn indirect_once2<F: FnOnce(i32) -> i32>(f: F) -> i32 {
+    f(10)
+}
+
+fn indirect3<F: Fn(i32, i32) -> i32>(f: F) -> i32 {
+    f(10, 3)
+}
+fn indirect_mut3<F: FnMut(i32, i32) -> i32>(mut f: F) -> i32 {
+    f(10, 3)
+}
+fn indirect_once3<F: FnOnce(i32, i32) -> i32>(f: F) -> i32 {
+    f(10, 3)
+}
+
+fn main() {
+    assert_eq!(call_fn_ptr(), 42);
+    assert_eq!(indirect(f), 42);
+    assert_eq!(indirect_mut(f), 42);
+    assert_eq!(indirect_once(f), 42);
+    assert_eq!(indirect2(g), 420);
+    assert_eq!(indirect_mut2(g), 420);
+    assert_eq!(indirect_once2(g), 420);
+    assert_eq!(indirect3(h), 210);
+    assert_eq!(indirect_mut3(h), 210);
+    assert_eq!(indirect_once3(h), 210);
+    let g = f as fn() -> i32;
+    assert!(return_fn_ptr(g) == g);
+    assert!(return_fn_ptr(g) as unsafe fn() -> i32 == g as fn() -> i32 as unsafe fn() -> i32);
+    assert!(return_fn_ptr(f) != f);
+
+    // Any non-null value is okay for function pointers.
+    unsafe {
+        let _x: fn() = mem::transmute(1usize);
+        let mut b = Box::new(42u8);
+        let ptr = &mut *b as *mut u8;
+        drop(b);
+        let _x: fn() = mem::transmute(ptr);
+        let _x: fn() = mem::transmute(ptr.wrapping_offset(1));
+    }
+}
diff --git a/src/tools/miri/tests/pass/generator.rs b/src/tools/miri/tests/pass/generator.rs
new file mode 100644
index 0000000000000..06f48666c557f
--- /dev/null
+++ b/src/tools/miri/tests/pass/generator.rs
@@ -0,0 +1,210 @@
+#![feature(generators, generator_trait, never_type)]
+
+use std::fmt::Debug;
+use std::mem::ManuallyDrop;
+use std::ops::{
+    Generator,
+    GeneratorState::{self, *},
+};
+use std::pin::Pin;
+use std::ptr;
+use std::sync::atomic::{AtomicUsize, Ordering};
+
+fn basic() {
+    fn finish<T>(mut amt: usize, mut t: T) -> T::Return
+    where
+        T: Generator<Yield = usize>,
+    {
+        // We are not moving the `t` around until it gets dropped, so this is okay.
+        let mut t = unsafe { Pin::new_unchecked(&mut t) };
+        loop {
+            let state = t.as_mut().resume(());
+            // Test if the generator is valid (according to type invariants).
+            let _ = unsafe { ManuallyDrop::new(ptr::read(t.as_mut().get_unchecked_mut())) };
+            match state {
+                GeneratorState::Yielded(y) => {
+                    amt -= y;
+                }
+                GeneratorState::Complete(ret) => {
+                    assert_eq!(amt, 0);
+                    return ret;
+                }
+            }
+        }
+    }
+
+    enum Never {}
+    fn never() -> Never {
+        panic!()
+    }
+
+    finish(1, || yield 1);
+
+    finish(3, || {
+        let mut x = 0;
+        yield 1;
+        x += 1;
+        yield 1;
+        x += 1;
+        yield 1;
+        assert_eq!(x, 2);
+    });
+
+    finish(7 * 8 / 2, || {
+        for i in 0..8 {
+            yield i;
+        }
+    });
+
+    finish(1, || {
+        if true {
+            yield 1;
+        } else {
+        }
+    });
+
+    finish(1, || {
+        if false {
+        } else {
+            yield 1;
+        }
+    });
+
+    finish(2, || {
+        if {
+            yield 1;
+            false
+        } {
+            yield 1;
+            panic!()
+        }
+        yield 1;
+    });
+
+    // also test a self-referential generator
+    assert_eq!(
+        finish(5, || {
+            let mut x = Box::new(5);
+            let y = &mut *x;
+            *y = 5;
+            yield *y;
+            *y = 10;
+            *x
+        }),
+        10
+    );
+
+    let b = true;
+    finish(1, || {
+        yield 1;
+        if b {
+            return;
+        }
+        #[allow(unused)]
+        let x = never();
+        #[allow(unreachable_code)]
+        yield 2;
+        drop(x);
+    });
+
+    finish(3, || {
+        yield 1;
+        #[allow(unreachable_code)]
+        let _x: (String, !) = (String::new(), {
+            yield 2;
+            return;
+        });
+    });
+}
+
+fn smoke_resume_arg() {
+    fn drain<G: Generator<R, Yield = Y> + Unpin, R, Y>(
+        gen: &mut G,
+        inout: Vec<(R, GeneratorState<Y, G::Return>)>,
+    ) where
+        Y: Debug + PartialEq,
+        G::Return: Debug + PartialEq,
+    {
+        let mut gen = Pin::new(gen);
+
+        for (input, out) in inout {
+            assert_eq!(gen.as_mut().resume(input), out);
+            // Test if the generator is valid (according to type invariants).
+            let _ = unsafe { ManuallyDrop::new(ptr::read(gen.as_mut().get_unchecked_mut())) };
+        }
+    }
+
+    static DROPS: AtomicUsize = AtomicUsize::new(0);
+
+    #[derive(Debug, PartialEq)]
+    struct DropMe;
+
+    impl Drop for DropMe {
+        fn drop(&mut self) {
+            DROPS.fetch_add(1, Ordering::SeqCst);
+        }
+    }
+
+    fn expect_drops<T>(expected_drops: usize, f: impl FnOnce() -> T) -> T {
+        DROPS.store(0, Ordering::SeqCst);
+
+        let res = f();
+
+        let actual_drops = DROPS.load(Ordering::SeqCst);
+        assert_eq!(actual_drops, expected_drops);
+        res
+    }
+
+    drain(
+        &mut |mut b| {
+            while b != 0 {
+                b = yield (b + 1);
+            }
+            -1
+        },
+        vec![(1, Yielded(2)), (-45, Yielded(-44)), (500, Yielded(501)), (0, Complete(-1))],
+    );
+
+    expect_drops(2, || drain(&mut |a| yield a, vec![(DropMe, Yielded(DropMe))]));
+
+    expect_drops(6, || {
+        drain(
+            &mut |a| yield yield a,
+            vec![(DropMe, Yielded(DropMe)), (DropMe, Yielded(DropMe)), (DropMe, Complete(DropMe))],
+        )
+    });
+
+    #[allow(unreachable_code)]
+    expect_drops(2, || drain(&mut |a| yield return a, vec![(DropMe, Complete(DropMe))]));
+
+    expect_drops(2, || {
+        drain(
+            &mut |a: DropMe| {
+                if false { yield () } else { a }
+            },
+            vec![(DropMe, Complete(DropMe))],
+        )
+    });
+
+    expect_drops(4, || {
+        drain(
+            #[allow(unused_assignments, unused_variables)]
+            &mut |mut a: DropMe| {
+                a = yield;
+                a = yield;
+                a = yield;
+            },
+            vec![
+                (DropMe, Yielded(())),
+                (DropMe, Yielded(())),
+                (DropMe, Yielded(())),
+                (DropMe, Complete(())),
+            ],
+        )
+    });
+}
+
+fn main() {
+    basic();
+    smoke_resume_arg();
+}
diff --git a/src/tools/miri/tests/pass/getpid.rs b/src/tools/miri/tests/pass/getpid.rs
new file mode 100644
index 0000000000000..733545462ebc0
--- /dev/null
+++ b/src/tools/miri/tests/pass/getpid.rs
@@ -0,0 +1,9 @@
+//@compile-flags: -Zmiri-disable-isolation
+
+fn getpid() -> u32 {
+    std::process::id()
+}
+
+fn main() {
+    getpid();
+}
diff --git a/src/tools/miri/tests/pass/global_allocator.rs b/src/tools/miri/tests/pass/global_allocator.rs
new file mode 100644
index 0000000000000..24a56c663f060
--- /dev/null
+++ b/src/tools/miri/tests/pass/global_allocator.rs
@@ -0,0 +1,41 @@
+#![feature(allocator_api, slice_ptr_get)]
+
+use std::alloc::{Allocator as _, Global, GlobalAlloc, Layout, System};
+
+#[global_allocator]
+static ALLOCATOR: Allocator = Allocator;
+
+struct Allocator;
+
+unsafe impl GlobalAlloc for Allocator {
+    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
+        // use specific size to avoid getting triggered by rt
+        if layout.size() == 123 {
+            println!("Allocated!")
+        }
+
+        System.alloc(layout)
+    }
+
+    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
+        if layout.size() == 123 {
+            println!("Dellocated!")
+        }
+
+        System.dealloc(ptr, layout)
+    }
+}
+
+fn main() {
+    // Only okay because we explicitly set a global allocator that uses the system allocator!
+    let l = Layout::from_size_align(123, 1).unwrap();
+    let ptr = Global.allocate(l).unwrap().as_non_null_ptr(); // allocating with Global...
+    unsafe {
+        System.deallocate(ptr, l);
+    } // ... and deallocating with System.
+
+    let ptr = System.allocate(l).unwrap().as_non_null_ptr(); // allocating with System...
+    unsafe {
+        Global.deallocate(ptr, l);
+    } // ... and deallocating with Global.
+}
diff --git a/src/tools/miri/tests/pass/global_allocator.stdout b/src/tools/miri/tests/pass/global_allocator.stdout
new file mode 100644
index 0000000000000..411a4cdd1467e
--- /dev/null
+++ b/src/tools/miri/tests/pass/global_allocator.stdout
@@ -0,0 +1,2 @@
+Allocated!
+Dellocated!
diff --git a/src/tools/miri/tests/pass/hashmap.rs b/src/tools/miri/tests/pass/hashmap.rs
new file mode 100644
index 0000000000000..29ddd6c59a1a6
--- /dev/null
+++ b/src/tools/miri/tests/pass/hashmap.rs
@@ -0,0 +1,40 @@
+use std::collections::HashMap;
+use std::hash::BuildHasher;
+
+// Gather all references from a mutable iterator and make sure Miri notices if
+// using them is dangerous.
+fn test_all_refs<'a, T: 'a>(dummy: &mut T, iter: impl Iterator<Item = &'a mut T>) {
+    // Gather all those references.
+    let mut refs: Vec<&mut T> = iter.collect();
+    // Use them all. Twice, to be sure we got all interleavings.
+    for r in refs.iter_mut() {
+        std::mem::swap(dummy, r);
+    }
+    for r in refs {
+        std::mem::swap(dummy, r);
+    }
+}
+
+fn smoketest_map<S: BuildHasher>(mut map: HashMap<i32, i32, S>) {
+    map.insert(0, 0);
+    assert_eq!(map.values().fold(0, |x, y| x + y), 0);
+
+    let num = 25;
+    for i in 1..num {
+        map.insert(i, i);
+    }
+    assert_eq!(map.values().fold(0, |x, y| x + y), num * (num - 1) / 2); // check the right things are in the table now
+
+    // Inserting again replaces the existing entries
+    for i in 0..num {
+        map.insert(i, num - 1 - i);
+    }
+    assert_eq!(map.values().fold(0, |x, y| x + y), num * (num - 1) / 2);
+
+    test_all_refs(&mut 13, map.values_mut());
+}
+
+fn main() {
+    // hashbrown uses Miri on its own CI; we just do a smoketest here.
+    smoketest_map(HashMap::new());
+}
diff --git a/src/tools/miri/tests/pass/heap.rs b/src/tools/miri/tests/pass/heap.rs
new file mode 100644
index 0000000000000..44537e74b5a44
--- /dev/null
+++ b/src/tools/miri/tests/pass/heap.rs
@@ -0,0 +1,34 @@
+#![feature(box_syntax)]
+
+fn make_box() -> Box<(i16, i16)> {
+    Box::new((1, 2))
+}
+
+fn make_box_syntax() -> Box<(i16, i16)> {
+    box (1, 2)
+}
+
+fn allocate_reallocate() {
+    let mut s = String::new();
+
+    // 6 byte heap alloc (__rust_allocate)
+    s.push_str("foobar");
+    assert_eq!(s.len(), 6);
+    assert_eq!(s.capacity(), 8);
+
+    // heap size doubled to 12 (__rust_reallocate)
+    s.push_str("baz");
+    assert_eq!(s.len(), 9);
+    assert_eq!(s.capacity(), 16);
+
+    // heap size reduced to 9  (__rust_reallocate)
+    s.shrink_to_fit();
+    assert_eq!(s.len(), 9);
+    assert_eq!(s.capacity(), 9);
+}
+
+fn main() {
+    assert_eq!(*make_box(), (1, 2));
+    assert_eq!(*make_box_syntax(), (1, 2));
+    allocate_reallocate();
+}
diff --git a/src/tools/miri/tests/pass/heap_allocator.rs b/src/tools/miri/tests/pass/heap_allocator.rs
new file mode 100644
index 0000000000000..2c38dcb49f1ce
--- /dev/null
+++ b/src/tools/miri/tests/pass/heap_allocator.rs
@@ -0,0 +1,127 @@
+#![feature(allocator_api, slice_ptr_get)]
+
+use std::alloc::{Allocator, Global, Layout, System};
+use std::ptr::NonNull;
+use std::slice;
+
+fn check_alloc<T: Allocator>(allocator: T) {
+    unsafe {
+        for &align in &[4, 8, 16, 32] {
+            let layout_20 = Layout::from_size_align(20, align).unwrap();
+            let layout_40 = Layout::from_size_align(40, 4 * align).unwrap();
+            let layout_10 = Layout::from_size_align(10, align / 2).unwrap();
+
+            for _ in 0..32 {
+                let a = allocator.allocate(layout_20).unwrap().as_non_null_ptr();
+                assert_eq!(
+                    a.as_ptr() as usize % layout_20.align(),
+                    0,
+                    "pointer is incorrectly aligned",
+                );
+                allocator.deallocate(a, layout_20);
+            }
+
+            let p1 = allocator.allocate_zeroed(layout_20).unwrap().as_non_null_ptr();
+            assert_eq!(
+                p1.as_ptr() as usize % layout_20.align(),
+                0,
+                "pointer is incorrectly aligned",
+            );
+            assert_eq!(*p1.as_ptr(), 0);
+
+            // old size < new size
+            let p2 = allocator.grow(p1, layout_20, layout_40).unwrap().as_non_null_ptr();
+            assert_eq!(
+                p2.as_ptr() as usize % layout_40.align(),
+                0,
+                "pointer is incorrectly aligned",
+            );
+            let slice = slice::from_raw_parts(p2.as_ptr(), 20);
+            assert_eq!(&slice, &[0_u8; 20]);
+
+            // old size == new size
+            let p3 = allocator.grow(p2, layout_40, layout_40).unwrap().as_non_null_ptr();
+            assert_eq!(
+                p3.as_ptr() as usize % layout_40.align(),
+                0,
+                "pointer is incorrectly aligned",
+            );
+            let slice = slice::from_raw_parts(p3.as_ptr(), 20);
+            assert_eq!(&slice, &[0_u8; 20]);
+
+            // old size > new size
+            let p4 = allocator.shrink(p3, layout_40, layout_10).unwrap().as_non_null_ptr();
+            assert_eq!(
+                p4.as_ptr() as usize % layout_10.align(),
+                0,
+                "pointer is incorrectly aligned",
+            );
+            let slice = slice::from_raw_parts(p4.as_ptr(), 10);
+            assert_eq!(&slice, &[0_u8; 10]);
+
+            allocator.deallocate(p4, layout_10);
+        }
+    }
+}
+
+fn check_align_requests<T: Allocator>(allocator: T) {
+    #[rustfmt::skip] // https://github.com/rust-lang/rustfmt/issues/3255
+    for &size in &[2, 8, 64] { // size less than and bigger than alignment
+        for &align in &[4, 8, 16, 32] { // Be sure to cover less than and bigger than `MIN_ALIGN` for all architectures
+            let iterations = 32;
+            unsafe {
+                let pointers: Vec<_> = (0..iterations)
+                    .map(|_| {
+                        allocator
+                            .allocate(Layout::from_size_align(size, align).unwrap())
+                            .unwrap()
+                            .as_non_null_ptr()
+                    })
+                    .collect();
+                for &ptr in &pointers {
+                    assert_eq!(
+                        (ptr.as_ptr() as usize) % align,
+                        0,
+                        "Got a pointer less aligned than requested",
+                    )
+                }
+
+                // Clean up.
+                for &ptr in &pointers {
+                    allocator.deallocate(ptr, Layout::from_size_align(size, align).unwrap())
+                }
+            }
+        }
+    };
+}
+
+fn global_to_box() {
+    type T = [i32; 4];
+    let l = Layout::new::<T>();
+    // allocate manually with global allocator, then turn into Box and free there
+    unsafe {
+        let ptr = Global.allocate(l).unwrap().as_non_null_ptr().as_ptr() as *mut T;
+        let b = Box::from_raw(ptr);
+        drop(b);
+    }
+}
+
+fn box_to_global() {
+    type T = [i32; 4];
+    let l = Layout::new::<T>();
+    // allocate with the Box, then deallocate manually with global allocator
+    unsafe {
+        let b = Box::new(T::default());
+        let ptr = Box::into_raw(b);
+        Global.deallocate(NonNull::new(ptr as *mut u8).unwrap(), l);
+    }
+}
+
+fn main() {
+    check_alloc(System);
+    check_alloc(Global);
+    check_align_requests(System);
+    check_align_requests(Global);
+    global_to_box();
+    box_to_global();
+}
diff --git a/src/tools/miri/tests/pass/hello.rs b/src/tools/miri/tests/pass/hello.rs
new file mode 100644
index 0000000000000..e7a11a969c037
--- /dev/null
+++ b/src/tools/miri/tests/pass/hello.rs
@@ -0,0 +1,3 @@
+fn main() {
+    println!("Hello, world!");
+}
diff --git a/src/tools/miri/tests/pass/hello.stdout b/src/tools/miri/tests/pass/hello.stdout
new file mode 100644
index 0000000000000..af5626b4a114a
--- /dev/null
+++ b/src/tools/miri/tests/pass/hello.stdout
@@ -0,0 +1 @@
+Hello, world!
diff --git a/src/tools/miri/tests/pass/hide_stdout.rs b/src/tools/miri/tests/pass/hide_stdout.rs
new file mode 100644
index 0000000000000..cfd05a8396cb5
--- /dev/null
+++ b/src/tools/miri/tests/pass/hide_stdout.rs
@@ -0,0 +1,6 @@
+//@compile-flags: -Zmiri-mute-stdout-stderr
+
+fn main() {
+    println!("print to stdout");
+    eprintln!("print to stderr");
+}
diff --git a/src/tools/miri/tests/pass/integer-ops.rs b/src/tools/miri/tests/pass/integer-ops.rs
new file mode 100644
index 0000000000000..724be9efc9f82
--- /dev/null
+++ b/src/tools/miri/tests/pass/integer-ops.rs
@@ -0,0 +1,205 @@
+//@compile-flags: -Coverflow-checks=off
+#![feature(int_log)]
+#![allow(arithmetic_overflow)]
+
+pub fn main() {
+    // This tests that we do (not) do sign extension properly when loading integers
+    assert_eq!(u32::MAX as i64, 4294967295);
+    assert_eq!(i32::MIN as i64, -2147483648);
+
+    assert_eq!(i8::MAX, 127);
+    assert_eq!(i8::MIN, -128);
+
+    // Shifts with negative offsets are subtle.
+    assert_eq!(13 << -2i8, 13 << 254);
+    assert_eq!(13 << i8::MIN, 13);
+    assert_eq!(13 << -1i16, 13 << u16::MAX);
+    assert_eq!(13 << i16::MIN, 13);
+    assert_eq!(13i128 << -2i8, 13i128 << 254);
+    assert_eq!(13i128 << i8::MIN, 13);
+    assert_eq!(13i128 << -1i16, 13i128 << u16::MAX);
+    assert_eq!(13i128 << i16::MIN, 13);
+
+    assert_eq!(i32::from_str_radix("A", 16), Ok(10));
+
+    let n = -0b1000_0000i8;
+    assert_eq!(n.count_ones(), 1);
+
+    let n = -0b1000_0000i8;
+    assert_eq!(n.count_zeros(), 7);
+
+    let n = -1i16;
+    assert_eq!(n.leading_zeros(), 0);
+
+    let n = -4i8;
+    assert_eq!(n.trailing_zeros(), 2);
+
+    let n = 0x0123456789ABCDEFi64;
+    let m = -0x76543210FEDCBA99i64;
+    assert_eq!(n.rotate_left(32), m);
+
+    let n = 0x0123456789ABCDEFi64;
+    let m = -0xFEDCBA987654322i64;
+    assert_eq!(n.rotate_right(4), m);
+
+    let n = 0x0123456789ABCDEFi64;
+    let m = -0x1032547698BADCFFi64;
+    assert_eq!(n.swap_bytes(), m);
+
+    let n = 0x0123456789ABCDEFi64;
+    if cfg!(target_endian = "big") {
+        assert_eq!(i64::from_be(n), n)
+    } else {
+        assert_eq!(i64::from_be(n), n.swap_bytes())
+    }
+
+    let n = 0x0123456789ABCDEFi64;
+    if cfg!(target_endian = "little") {
+        assert_eq!(i64::from_le(n), n)
+    } else {
+        assert_eq!(i64::from_le(n), n.swap_bytes())
+    }
+
+    let n = 0x0123456789ABCDEFi64;
+    if cfg!(target_endian = "big") {
+        assert_eq!(n.to_be(), n)
+    } else {
+        assert_eq!(n.to_be(), n.swap_bytes())
+    }
+
+    let n = 0x0123456789ABCDEFi64;
+    if cfg!(target_endian = "little") {
+        assert_eq!(n.to_le(), n)
+    } else {
+        assert_eq!(n.to_le(), n.swap_bytes())
+    }
+
+    assert_eq!(7i16.checked_add(32760), Some(32767));
+    assert_eq!(8i16.checked_add(32760), None);
+
+    assert_eq!((-127i8).checked_sub(1), Some(-128));
+    assert_eq!((-128i8).checked_sub(1), None);
+
+    assert_eq!(6i8.checked_mul(21), Some(126));
+    assert_eq!(6i8.checked_mul(22), None);
+
+    assert_eq!((-127i8).checked_div(-1), Some(127));
+    assert_eq!((-128i8).checked_div(-1), None);
+    assert_eq!((1i8).checked_div(0), None);
+
+    assert_eq!(5i32.checked_rem(2), Some(1));
+    assert_eq!(5i32.checked_rem(0), None);
+    assert_eq!(i32::MIN.checked_rem(-1), None);
+
+    assert_eq!(5i32.checked_neg(), Some(-5));
+    assert_eq!(i32::MIN.checked_neg(), None);
+
+    assert_eq!(0x10i32.checked_shl(4), Some(0x100));
+    assert_eq!(0x10i32.checked_shl(33), None);
+
+    assert_eq!(0x10i32.checked_shr(4), Some(0x1));
+    assert_eq!(0x10i32.checked_shr(33), None);
+
+    assert_eq!((-5i32).checked_abs(), Some(5));
+    assert_eq!(i32::MIN.checked_abs(), None);
+
+    assert_eq!(100i8.saturating_add(1), 101);
+    assert_eq!(100i8.saturating_add(127), 127);
+
+    assert_eq!(100i8.saturating_sub(127), -27);
+    assert_eq!((-100i8).saturating_sub(127), -128);
+
+    assert_eq!(100i32.saturating_mul(127), 12700);
+    assert_eq!((1i32 << 23).saturating_mul(1 << 23), i32::MAX);
+    assert_eq!((-1i32 << 23).saturating_mul(1 << 23), i32::MIN);
+
+    assert_eq!(100i8.wrapping_add(27), 127);
+    assert_eq!(100i8.wrapping_add(127), -29);
+
+    assert_eq!(0i8.wrapping_sub(127), -127);
+    assert_eq!((-2i8).wrapping_sub(127), 127);
+
+    assert_eq!(10i8.wrapping_mul(12), 120);
+    assert_eq!(11i8.wrapping_mul(12), -124);
+
+    assert_eq!(100u8.wrapping_div(10), 10);
+    assert_eq!((-128i8).wrapping_div(-1), -128);
+
+    assert_eq!(100i8.wrapping_rem(10), 0);
+    assert_eq!((-128i8).wrapping_rem(-1), 0);
+
+    assert_eq!(i32::MIN.wrapping_div(-1), i32::MIN);
+    assert_eq!(i32::MIN.wrapping_rem(-1), 0);
+
+    assert_eq!(100i8.wrapping_neg(), -100);
+    assert_eq!((-128i8).wrapping_neg(), -128);
+
+    assert_eq!((-1i8).wrapping_shl(7), -128);
+    assert_eq!((-1i8).wrapping_shl(8), -1);
+
+    assert_eq!((-128i8).wrapping_shr(7), -1);
+    assert_eq!((-128i8).wrapping_shr(8), -128);
+
+    assert_eq!(100i8.wrapping_abs(), 100);
+    assert_eq!((-100i8).wrapping_abs(), 100);
+    assert_eq!((-128i8).wrapping_abs(), -128);
+    assert_eq!((-128i8).wrapping_abs() as u8, 128);
+
+    assert_eq!(5i32.overflowing_add(2), (7, false));
+    assert_eq!(i32::MAX.overflowing_add(1), (i32::MIN, true));
+
+    assert_eq!(5i32.overflowing_sub(2), (3, false));
+    assert_eq!(i32::MIN.overflowing_sub(1), (i32::MAX, true));
+
+    assert_eq!(5i32.overflowing_mul(2), (10, false));
+    assert_eq!(1_000_000_000i32.overflowing_mul(10), (1410065408, true));
+
+    assert_eq!(5i32.overflowing_div(2), (2, false));
+    assert_eq!(i32::MIN.overflowing_div(-1), (i32::MIN, true));
+
+    assert_eq!(5i32.overflowing_rem(2), (1, false));
+    assert_eq!(i32::MIN.overflowing_rem(-1), (0, true));
+
+    assert_eq!(2i32.overflowing_neg(), (-2, false));
+    assert_eq!(i32::MIN.overflowing_neg(), (i32::MIN, true));
+
+    assert_eq!(0x10i32.overflowing_shl(4), (0x100, false));
+    assert_eq!(0x10i32.overflowing_shl(36), (0x100, true));
+
+    assert_eq!(0x10i32.overflowing_shr(4), (0x1, false));
+    assert_eq!(0x10i32.overflowing_shr(36), (0x1, true));
+
+    assert_eq!(10i8.overflowing_abs(), (10, false));
+    assert_eq!((-10i8).overflowing_abs(), (10, false));
+    assert_eq!((-128i8).overflowing_abs(), (-128, true));
+
+    // Logarithms
+    macro_rules! test_log {
+        ($type:ident, $max_log2:expr, $max_log10:expr) => {
+            assert_eq!($type::MIN.checked_ilog2(), None);
+            assert_eq!($type::MIN.checked_ilog10(), None);
+            assert_eq!($type::MAX.checked_ilog2(), Some($max_log2));
+            assert_eq!($type::MAX.checked_ilog10(), Some($max_log10));
+            assert_eq!($type::MAX.ilog2(), $max_log2);
+            assert_eq!($type::MAX.ilog10(), $max_log10);
+        };
+    }
+
+    test_log!(i8, 6, 2);
+    test_log!(u8, 7, 2);
+    test_log!(i16, 14, 4);
+    test_log!(u16, 15, 4);
+    test_log!(i32, 30, 9);
+    test_log!(u32, 31, 9);
+    test_log!(i64, 62, 18);
+    test_log!(u64, 63, 19);
+    test_log!(i128, 126, 38);
+    test_log!(u128, 127, 38);
+
+    for i in (1..=i16::MAX).step_by(i8::MAX as usize) {
+        assert_eq!(i.checked_ilog(13), Some((i as f32).log(13.0) as u32));
+    }
+    for i in (1..=u16::MAX).step_by(i8::MAX as usize) {
+        assert_eq!(i.checked_ilog(13), Some((i as f32).log(13.0) as u32));
+    }
+}
diff --git a/src/tools/miri/tests/pass/intptrcast.rs b/src/tools/miri/tests/pass/intptrcast.rs
new file mode 100644
index 0000000000000..e7ff90cb6bf09
--- /dev/null
+++ b/src/tools/miri/tests/pass/intptrcast.rs
@@ -0,0 +1,163 @@
+//@compile-flags: -Zmiri-permissive-provenance
+
+use std::mem;
+
+// This strips provenance
+fn transmute_ptr_to_int<T>(x: *const T) -> usize {
+    unsafe { std::mem::transmute(x) }
+}
+
+fn cast() {
+    // Some casting-to-int with arithmetic.
+    let x = &42 as *const i32 as usize;
+    let y = x * 2;
+    assert_eq!(y, x + x);
+    let z = y as u8 as usize;
+    assert_eq!(z, y % 256);
+}
+
+/// Test usize->ptr cast for dangling and OOB address.
+/// That is safe, and thus has to work.
+fn cast_dangling() {
+    let b = Box::new(0);
+    let x = &*b as *const i32 as usize;
+    drop(b);
+    let _val = x as *const i32;
+
+    let b = Box::new(0);
+    let mut x = &*b as *const i32 as usize;
+    x += 0x100;
+    let _val = x as *const i32;
+}
+
+fn format() {
+    // Pointer string formatting! We can't check the output as it changes when libstd changes,
+    // but we can make sure Miri does not error.
+    format!("{:?}", &mut 13 as *mut _);
+}
+
+fn transmute() {
+    // Check that intptrcast is triggered for explicit casts and that it is consistent with
+    // transmuting.
+    let a: *const i32 = &42;
+    let b = transmute_ptr_to_int(a) as u8;
+    let c = a as u8;
+    assert_eq!(b, c);
+}
+
+fn ptr_bitops1() {
+    let bytes = [0i8, 1, 2, 3, 4, 5, 6, 7, 8, 9];
+    let one = bytes.as_ptr().wrapping_offset(1);
+    let three = bytes.as_ptr().wrapping_offset(3);
+    let res = (one as usize) | (three as usize);
+    format!("{}", res);
+}
+
+fn ptr_bitops2() {
+    let val = 13usize;
+    let addr = &val as *const _ as usize;
+    let _val = addr & 13;
+}
+
+fn ptr_eq_dangling() {
+    let b = Box::new(0);
+    let x = &*b as *const i32; // soon-to-be dangling
+    drop(b);
+    let b = Box::new(0);
+    let y = &*b as *const i32; // different allocation
+    // They *could* be equal if memory was reused, but probably are not.
+    assert!(x != y);
+}
+
+fn ptr_eq_out_of_bounds() {
+    let b = Box::new(0);
+    let x = (&*b as *const i32).wrapping_sub(0x800); // out-of-bounds
+    let b = Box::new(0);
+    let y = &*b as *const i32; // different allocation
+    // They *could* be equal (with the right base addresses), but probably are not.
+    assert!(x != y);
+}
+
+fn ptr_eq_out_of_bounds_null() {
+    let b = Box::new(0);
+    let x = (&*b as *const i32).wrapping_sub(0x800); // out-of-bounds
+    // This *could* be NULL (with the right base address), but probably is not.
+    assert!(x != std::ptr::null());
+}
+
+fn ptr_eq_integer() {
+    let b = Box::new(0);
+    let x = &*b as *const i32;
+    // These *could* be equal (with the right base address), but probably are not.
+    assert!(x != 64 as *const i32);
+}
+
+fn zst_deref_of_dangling() {
+    let b = Box::new(0);
+    let addr = &*b as *const _ as usize;
+    drop(b);
+    // Now if we cast `addr` to a ptr it might pick up the dangling provenance.
+    // But if we only do a ZST deref there is no UB here!
+    let zst = addr as *const ();
+    let _val = unsafe { *zst };
+}
+
+fn functions() {
+    // Roundtrip a few functions through integers. Do this multiple times to make sure this does not
+    // work by chance. If we did not give unique addresses to ZST allocations -- which fn
+    // allocations are -- then we might be unable to cast back, or we might call the wrong function!
+    // Every function gets at most one address so doing a loop would not help...
+    fn fn0() -> i32 {
+        0
+    }
+    fn fn1() -> i32 {
+        1
+    }
+    fn fn2() -> i32 {
+        2
+    }
+    fn fn3() -> i32 {
+        3
+    }
+    fn fn4() -> i32 {
+        4
+    }
+    fn fn5() -> i32 {
+        5
+    }
+    fn fn6() -> i32 {
+        6
+    }
+    fn fn7() -> i32 {
+        7
+    }
+    let fns = [
+        fn0 as fn() -> i32 as *const () as usize,
+        fn1 as fn() -> i32 as *const () as usize,
+        fn2 as fn() -> i32 as *const () as usize,
+        fn3 as fn() -> i32 as *const () as usize,
+        fn4 as fn() -> i32 as *const () as usize,
+        fn5 as fn() -> i32 as *const () as usize,
+        fn6 as fn() -> i32 as *const () as usize,
+        fn7 as fn() -> i32 as *const () as usize,
+    ];
+    for (idx, &addr) in fns.iter().enumerate() {
+        let fun: fn() -> i32 = unsafe { mem::transmute(addr as *const ()) };
+        assert_eq!(fun(), idx as i32);
+    }
+}
+
+fn main() {
+    cast();
+    cast_dangling();
+    format();
+    transmute();
+    ptr_bitops1();
+    ptr_bitops2();
+    ptr_eq_dangling();
+    ptr_eq_out_of_bounds();
+    ptr_eq_out_of_bounds_null();
+    ptr_eq_integer();
+    zst_deref_of_dangling();
+    functions();
+}
diff --git a/src/tools/miri/tests/pass/intrinsics-integer.rs b/src/tools/miri/tests/pass/intrinsics-integer.rs
new file mode 100644
index 0000000000000..546931f6ff875
--- /dev/null
+++ b/src/tools/miri/tests/pass/intrinsics-integer.rs
@@ -0,0 +1,154 @@
+// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![feature(core_intrinsics)]
+use std::intrinsics::*;
+
+pub fn main() {
+    unsafe {
+        [assert_eq!(ctpop(0u8), 0), assert_eq!(ctpop(0i8), 0)];
+        [assert_eq!(ctpop(0u16), 0), assert_eq!(ctpop(0i16), 0)];
+        [assert_eq!(ctpop(0u32), 0), assert_eq!(ctpop(0i32), 0)];
+        [assert_eq!(ctpop(0u64), 0), assert_eq!(ctpop(0i64), 0)];
+
+        [assert_eq!(ctpop(1u8), 1), assert_eq!(ctpop(1i8), 1)];
+        [assert_eq!(ctpop(1u16), 1), assert_eq!(ctpop(1i16), 1)];
+        [assert_eq!(ctpop(1u32), 1), assert_eq!(ctpop(1i32), 1)];
+        [assert_eq!(ctpop(1u64), 1), assert_eq!(ctpop(1i64), 1)];
+
+        [assert_eq!(ctpop(10u8), 2), assert_eq!(ctpop(10i8), 2)];
+        [assert_eq!(ctpop(10u16), 2), assert_eq!(ctpop(10i16), 2)];
+        [assert_eq!(ctpop(10u32), 2), assert_eq!(ctpop(10i32), 2)];
+        [assert_eq!(ctpop(10u64), 2), assert_eq!(ctpop(10i64), 2)];
+
+        [assert_eq!(ctpop(100u8), 3), assert_eq!(ctpop(100i8), 3)];
+        [assert_eq!(ctpop(100u16), 3), assert_eq!(ctpop(100i16), 3)];
+        [assert_eq!(ctpop(100u32), 3), assert_eq!(ctpop(100i32), 3)];
+        [assert_eq!(ctpop(100u64), 3), assert_eq!(ctpop(100i64), 3)];
+
+        [assert_eq!(ctpop(-1i8 as u8), 8), assert_eq!(ctpop(-1i8), 8)];
+        [assert_eq!(ctpop(-1i16 as u16), 16), assert_eq!(ctpop(-1i16), 16)];
+        [assert_eq!(ctpop(-1i32 as u32), 32), assert_eq!(ctpop(-1i32), 32)];
+        [assert_eq!(ctpop(-1i64 as u64), 64), assert_eq!(ctpop(-1i64), 64)];
+
+        [assert_eq!(ctlz(0u8), 8), assert_eq!(ctlz(0i8), 8)];
+        [assert_eq!(ctlz(0u16), 16), assert_eq!(ctlz(0i16), 16)];
+        [assert_eq!(ctlz(0u32), 32), assert_eq!(ctlz(0i32), 32)];
+        [assert_eq!(ctlz(0u64), 64), assert_eq!(ctlz(0i64), 64)];
+
+        [assert_eq!(ctlz(1u8), 7), assert_eq!(ctlz(1i8), 7)];
+        [assert_eq!(ctlz(1u16), 15), assert_eq!(ctlz(1i16), 15)];
+        [assert_eq!(ctlz(1u32), 31), assert_eq!(ctlz(1i32), 31)];
+        [assert_eq!(ctlz(1u64), 63), assert_eq!(ctlz(1i64), 63)];
+
+        [assert_eq!(ctlz(10u8), 4), assert_eq!(ctlz(10i8), 4)];
+        [assert_eq!(ctlz(10u16), 12), assert_eq!(ctlz(10i16), 12)];
+        [assert_eq!(ctlz(10u32), 28), assert_eq!(ctlz(10i32), 28)];
+        [assert_eq!(ctlz(10u64), 60), assert_eq!(ctlz(10i64), 60)];
+
+        [assert_eq!(ctlz(100u8), 1), assert_eq!(ctlz(100i8), 1)];
+        [assert_eq!(ctlz(100u16), 9), assert_eq!(ctlz(100i16), 9)];
+        [assert_eq!(ctlz(100u32), 25), assert_eq!(ctlz(100i32), 25)];
+        [assert_eq!(ctlz(100u64), 57), assert_eq!(ctlz(100i64), 57)];
+
+        [assert_eq!(ctlz_nonzero(1u8), 7), assert_eq!(ctlz_nonzero(1i8), 7)];
+        [assert_eq!(ctlz_nonzero(1u16), 15), assert_eq!(ctlz_nonzero(1i16), 15)];
+        [assert_eq!(ctlz_nonzero(1u32), 31), assert_eq!(ctlz_nonzero(1i32), 31)];
+        [assert_eq!(ctlz_nonzero(1u64), 63), assert_eq!(ctlz_nonzero(1i64), 63)];
+
+        [assert_eq!(ctlz_nonzero(10u8), 4), assert_eq!(ctlz_nonzero(10i8), 4)];
+        [assert_eq!(ctlz_nonzero(10u16), 12), assert_eq!(ctlz_nonzero(10i16), 12)];
+        [assert_eq!(ctlz_nonzero(10u32), 28), assert_eq!(ctlz_nonzero(10i32), 28)];
+        [assert_eq!(ctlz_nonzero(10u64), 60), assert_eq!(ctlz_nonzero(10i64), 60)];
+
+        [assert_eq!(ctlz_nonzero(100u8), 1), assert_eq!(ctlz_nonzero(100i8), 1)];
+        [assert_eq!(ctlz_nonzero(100u16), 9), assert_eq!(ctlz_nonzero(100i16), 9)];
+        [assert_eq!(ctlz_nonzero(100u32), 25), assert_eq!(ctlz_nonzero(100i32), 25)];
+        [assert_eq!(ctlz_nonzero(100u64), 57), assert_eq!(ctlz_nonzero(100i64), 57)];
+
+        [assert_eq!(cttz(-1i8 as u8), 0), assert_eq!(cttz(-1i8), 0)];
+        [assert_eq!(cttz(-1i16 as u16), 0), assert_eq!(cttz(-1i16), 0)];
+        [assert_eq!(cttz(-1i32 as u32), 0), assert_eq!(cttz(-1i32), 0)];
+        [assert_eq!(cttz(-1i64 as u64), 0), assert_eq!(cttz(-1i64), 0)];
+
+        [assert_eq!(cttz(0u8), 8), assert_eq!(cttz(0i8), 8)];
+        [assert_eq!(cttz(0u16), 16), assert_eq!(cttz(0i16), 16)];
+        [assert_eq!(cttz(0u32), 32), assert_eq!(cttz(0i32), 32)];
+        [assert_eq!(cttz(0u64), 64), assert_eq!(cttz(0i64), 64)];
+
+        [assert_eq!(cttz(1u8), 0), assert_eq!(cttz(1i8), 0)];
+        [assert_eq!(cttz(1u16), 0), assert_eq!(cttz(1i16), 0)];
+        [assert_eq!(cttz(1u32), 0), assert_eq!(cttz(1i32), 0)];
+        [assert_eq!(cttz(1u64), 0), assert_eq!(cttz(1i64), 0)];
+
+        [assert_eq!(cttz(10u8), 1), assert_eq!(cttz(10i8), 1)];
+        [assert_eq!(cttz(10u16), 1), assert_eq!(cttz(10i16), 1)];
+        [assert_eq!(cttz(10u32), 1), assert_eq!(cttz(10i32), 1)];
+        [assert_eq!(cttz(10u64), 1), assert_eq!(cttz(10i64), 1)];
+
+        [assert_eq!(cttz(100u8), 2), assert_eq!(cttz(100i8), 2)];
+        [assert_eq!(cttz(100u16), 2), assert_eq!(cttz(100i16), 2)];
+        [assert_eq!(cttz(100u32), 2), assert_eq!(cttz(100i32), 2)];
+        [assert_eq!(cttz(100u64), 2), assert_eq!(cttz(100i64), 2)];
+
+        [assert_eq!(cttz_nonzero(-1i8 as u8), 0), assert_eq!(cttz_nonzero(-1i8), 0)];
+        [assert_eq!(cttz_nonzero(-1i16 as u16), 0), assert_eq!(cttz_nonzero(-1i16), 0)];
+        [assert_eq!(cttz_nonzero(-1i32 as u32), 0), assert_eq!(cttz_nonzero(-1i32), 0)];
+        [assert_eq!(cttz_nonzero(-1i64 as u64), 0), assert_eq!(cttz_nonzero(-1i64), 0)];
+
+        [assert_eq!(cttz_nonzero(1u8), 0), assert_eq!(cttz_nonzero(1i8), 0)];
+        [assert_eq!(cttz_nonzero(1u16), 0), assert_eq!(cttz_nonzero(1i16), 0)];
+        [assert_eq!(cttz_nonzero(1u32), 0), assert_eq!(cttz_nonzero(1i32), 0)];
+        [assert_eq!(cttz_nonzero(1u64), 0), assert_eq!(cttz_nonzero(1i64), 0)];
+
+        [assert_eq!(cttz_nonzero(10u8), 1), assert_eq!(cttz_nonzero(10i8), 1)];
+        [assert_eq!(cttz_nonzero(10u16), 1), assert_eq!(cttz_nonzero(10i16), 1)];
+        [assert_eq!(cttz_nonzero(10u32), 1), assert_eq!(cttz_nonzero(10i32), 1)];
+        [assert_eq!(cttz_nonzero(10u64), 1), assert_eq!(cttz_nonzero(10i64), 1)];
+
+        [assert_eq!(cttz_nonzero(100u8), 2), assert_eq!(cttz_nonzero(100i8), 2)];
+        [assert_eq!(cttz_nonzero(100u16), 2), assert_eq!(cttz_nonzero(100i16), 2)];
+        [assert_eq!(cttz_nonzero(100u32), 2), assert_eq!(cttz_nonzero(100i32), 2)];
+        [assert_eq!(cttz_nonzero(100u64), 2), assert_eq!(cttz_nonzero(100i64), 2)];
+
+        assert_eq!(bswap(0x0Au8), 0x0A); // no-op
+        assert_eq!(bswap(0x0Ai8), 0x0A); // no-op
+        assert_eq!(bswap(0x0A0Bu16), 0x0B0A);
+        assert_eq!(bswap(0x0A0Bi16), 0x0B0A);
+        assert_eq!(bswap(0x0ABBCC0Du32), 0x0DCCBB0A);
+        assert_eq!(bswap(0x0ABBCC0Di32), 0x0DCCBB0A);
+        assert_eq!(bswap(0x0122334455667708u64), 0x0877665544332201);
+        assert_eq!(bswap(0x0122334455667708i64), 0x0877665544332201);
+
+        assert_eq!(exact_div(9 * 9u32, 3), 27);
+        assert_eq!(exact_div(-9 * 9i32, 3), -27);
+        assert_eq!(exact_div(9 * 9i8, -3), -27);
+        assert_eq!(exact_div(-9 * 9i64, -3), 27);
+
+        assert_eq!(unchecked_div(9 * 9u32, 2), 40);
+        assert_eq!(unchecked_div(-9 * 9i32, 2), -40);
+        assert_eq!(unchecked_div(9 * 9i8, -2), -40);
+        assert_eq!(unchecked_div(-9 * 9i64, -2), 40);
+
+        assert_eq!(unchecked_rem(9 * 9u32, 2), 1);
+        assert_eq!(unchecked_rem(-9 * 9i32, 2), -1);
+        assert_eq!(unchecked_rem(9 * 9i8, -2), 1);
+        assert_eq!(unchecked_rem(-9 * 9i64, -2), -1);
+
+        assert_eq!(unchecked_add(23u8, 19), 42);
+        assert_eq!(unchecked_add(5, -10), -5);
+
+        assert_eq!(unchecked_sub(23u8, 19), 4);
+        assert_eq!(unchecked_sub(-17, -27), 10);
+
+        assert_eq!(unchecked_mul(6u8, 7), 42);
+        assert_eq!(unchecked_mul(13, -5), -65);
+    }
+}
diff --git a/src/tools/miri/tests/pass/intrinsics-math.rs b/src/tools/miri/tests/pass/intrinsics-math.rs
new file mode 100644
index 0000000000000..5973f4cd197fe
--- /dev/null
+++ b/src/tools/miri/tests/pass/intrinsics-math.rs
@@ -0,0 +1,140 @@
+// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+macro_rules! assert_approx_eq {
+    ($a:expr, $b:expr) => {{
+        let (a, b) = (&$a, &$b);
+        assert!((*a - *b).abs() < 1.0e-6, "{} is not approximately equal to {}", *a, *b);
+    }};
+}
+
+fn ldexp(a: f64, b: i32) -> f64 {
+    extern "C" {
+        fn ldexp(x: f64, n: i32) -> f64;
+    }
+    unsafe { ldexp(a, b) }
+}
+
+pub fn main() {
+    use std::f32;
+    use std::f64;
+
+    assert_approx_eq!(64f32.sqrt(), 8f32);
+    assert_approx_eq!(64f64.sqrt(), 8f64);
+
+    assert_approx_eq!(25f32.powi(-2), 0.0016f32);
+    assert_approx_eq!(23.2f64.powi(2), 538.24f64);
+
+    assert_approx_eq!(25f32.powf(-2f32), 0.0016f32);
+    assert_approx_eq!(400f64.powf(0.5f64), 20f64);
+
+    assert_approx_eq!(1f32.exp(), f32::consts::E);
+    assert_approx_eq!(1f64.exp(), f64::consts::E);
+
+    assert_approx_eq!(1f32.exp_m1(), f32::consts::E - 1.0);
+    assert_approx_eq!(1f64.exp_m1(), f64::consts::E - 1.0);
+
+    assert_approx_eq!(10f32.exp2(), 1024f32);
+    assert_approx_eq!(50f64.exp2(), 1125899906842624f64);
+
+    assert_approx_eq!(f32::consts::E.ln(), 1f32);
+    assert_approx_eq!(1f64.ln(), 0f64);
+
+    assert_approx_eq!(0f32.ln_1p(), 0f32);
+    assert_approx_eq!(0f64.ln_1p(), 0f64);
+
+    assert_approx_eq!(10f32.log10(), 1f32);
+    assert_approx_eq!(f64::consts::E.log10(), f64::consts::LOG10_E);
+
+    assert_approx_eq!(8f32.log2(), 3f32);
+    assert_approx_eq!(f64::consts::E.log2(), f64::consts::LOG2_E);
+
+    assert_approx_eq!(3.0f32.mul_add(2.0f32, 5.0f32), 11.0);
+    assert_eq!(0.0f32.mul_add(-2.0, f32::consts::E), f32::consts::E);
+    assert_approx_eq!(3.0f64.mul_add(2.0, 5.0), 11.0);
+    assert_eq!(0.0f64.mul_add(-2.0f64, f64::consts::E), f64::consts::E);
+    assert_eq!((-3.2f32).mul_add(2.4, f32::NEG_INFINITY), f32::NEG_INFINITY);
+    assert_eq!((-3.2f64).mul_add(2.4, f64::NEG_INFINITY), f64::NEG_INFINITY);
+
+    assert_approx_eq!((-1.0f32).abs(), 1.0f32);
+    assert_approx_eq!(34.2f64.abs(), 34.2f64);
+
+    #[allow(deprecated)]
+    {
+        assert_approx_eq!(5.0f32.abs_sub(3.0), 2.0);
+        assert_approx_eq!(3.0f64.abs_sub(5.0), 0.0);
+    }
+
+    assert_approx_eq!(3.8f32.floor(), 3.0f32);
+    assert_approx_eq!((-1.1f64).floor(), -2.0f64);
+
+    assert_approx_eq!((-2.3f32).ceil(), -2.0f32);
+    assert_approx_eq!(3.8f64.ceil(), 4.0f64);
+
+    assert_approx_eq!(0.1f32.trunc(), 0.0f32);
+    assert_approx_eq!((-0.1f64).trunc(), 0.0f64);
+
+    assert_approx_eq!(27.0f32.cbrt(), 3.0f32);
+    assert_approx_eq!(27.0f64.cbrt(), 3.0f64);
+
+    assert_approx_eq!(3.0f32.hypot(4.0f32), 5.0f32);
+    assert_approx_eq!(3.0f64.hypot(4.0f64), 5.0f64);
+
+    assert_eq!(3.3_f32.round(), 3.0);
+    assert_eq!(3.3_f64.round(), 3.0);
+
+    assert_eq!(ldexp(0.65f64, 3i32), 5.2f64);
+    assert_eq!(ldexp(1.42, 0xFFFF), f64::INFINITY);
+    assert_eq!(ldexp(1.42, -0xFFFF), 0f64);
+
+    // Trigonometric functions.
+
+    assert_approx_eq!(0f32.sin(), 0f32);
+    assert_approx_eq!((f64::consts::PI / 2f64).sin(), 1f64);
+    assert_approx_eq!(f32::consts::FRAC_PI_6.sin(), 0.5);
+    assert_approx_eq!(f64::consts::FRAC_PI_6.sin(), 0.5);
+    assert_approx_eq!(f32::consts::FRAC_PI_4.sin().asin(), f32::consts::FRAC_PI_4);
+    assert_approx_eq!(f64::consts::FRAC_PI_4.sin().asin(), f64::consts::FRAC_PI_4);
+
+    assert_approx_eq!(1.0f32.sinh(), 1.1752012f32);
+    assert_approx_eq!(1.0f64.sinh(), 1.1752012f64);
+    assert_approx_eq!(2.0f32.asinh(), 1.443635475178810342493276740273105f32);
+    assert_approx_eq!((-2.0f64).asinh(), -1.443635475178810342493276740273105f64);
+
+    assert_approx_eq!(0f32.cos(), 1f32);
+    assert_approx_eq!((f64::consts::PI * 2f64).cos(), 1f64);
+    assert_approx_eq!(f32::consts::FRAC_PI_3.cos(), 0.5);
+    assert_approx_eq!(f64::consts::FRAC_PI_3.cos(), 0.5);
+    assert_approx_eq!(f32::consts::FRAC_PI_4.cos().acos(), f32::consts::FRAC_PI_4);
+    assert_approx_eq!(f64::consts::FRAC_PI_4.cos().acos(), f64::consts::FRAC_PI_4);
+
+    assert_approx_eq!(1.0f32.cosh(), 1.54308f32);
+    assert_approx_eq!(1.0f64.cosh(), 1.54308f64);
+    assert_approx_eq!(2.0f32.acosh(), 1.31695789692481670862504634730796844f32);
+    assert_approx_eq!(3.0f64.acosh(), 1.76274717403908605046521864995958461f64);
+
+    assert_approx_eq!(1.0f32.tan(), 1.557408f32);
+    assert_approx_eq!(1.0f64.tan(), 1.557408f64);
+    assert_approx_eq!(1.0_f32, 1.0_f32.tan().atan());
+    assert_approx_eq!(1.0_f64, 1.0_f64.tan().atan());
+    assert_approx_eq!(1.0f32.atan2(2.0f32), 0.46364761f32);
+    assert_approx_eq!(1.0f32.atan2(2.0f32), 0.46364761f32);
+
+    assert_approx_eq!(
+        1.0f32.tanh(),
+        (1.0 - f32::consts::E.powi(-2)) / (1.0 + f32::consts::E.powi(-2))
+    );
+    assert_approx_eq!(
+        1.0f64.tanh(),
+        (1.0 - f64::consts::E.powi(-2)) / (1.0 + f64::consts::E.powi(-2))
+    );
+    assert_approx_eq!(0.5f32.atanh(), 0.54930614433405484569762261846126285f32);
+    assert_approx_eq!(0.5f64.atanh(), 0.54930614433405484569762261846126285f64);
+}
diff --git a/src/tools/miri/tests/pass/intrinsics-x86.rs b/src/tools/miri/tests/pass/intrinsics-x86.rs
new file mode 100644
index 0000000000000..88cd782e70a46
--- /dev/null
+++ b/src/tools/miri/tests/pass/intrinsics-x86.rs
@@ -0,0 +1,22 @@
+#[cfg(target_arch = "x86_64")]
+mod x86_64 {
+    use core::arch::x86_64 as arch;
+
+    fn adc(c_in: u8, a: u64, b: u64) -> (u8, u64) {
+        let mut sum = 0;
+        // SAFETY: There are no safety requirements for calling `_addcarry_u64`.
+        // It's just unsafe for API consistency with other intrinsics.
+        let c_out = unsafe { arch::_addcarry_u64(c_in, a, b, &mut sum) };
+        (c_out, sum)
+    }
+
+    pub fn main() {
+        assert_eq!(adc(1, 1, 1), (0, 3));
+        assert_eq!(adc(3, u64::MAX, u64::MAX), (2, 1));
+    }
+}
+
+fn main() {
+    #[cfg(target_arch = "x86_64")]
+    x86_64::main();
+}
diff --git a/src/tools/miri/tests/pass/intrinsics.rs b/src/tools/miri/tests/pass/intrinsics.rs
new file mode 100644
index 0000000000000..6267e6e6bc111
--- /dev/null
+++ b/src/tools/miri/tests/pass/intrinsics.rs
@@ -0,0 +1,42 @@
+//@compile-flags: -Zmiri-permissive-provenance
+#![feature(core_intrinsics, layout_for_ptr)]
+//! Tests for various intrinsics that do not fit anywhere else.
+
+use std::intrinsics;
+use std::mem::{size_of, size_of_val, size_of_val_raw};
+
+struct Bomb;
+
+impl Drop for Bomb {
+    fn drop(&mut self) {
+        eprintln!("BOOM!");
+    }
+}
+
+fn main() {
+    assert_eq!(size_of::<Option<i32>>(), 8);
+    assert_eq!(size_of_val(&()), 0);
+    assert_eq!(size_of_val(&42), 4);
+    assert_eq!(size_of_val(&[] as &[i32]), 0);
+    assert_eq!(size_of_val(&[1, 2, 3] as &[i32]), 12);
+    assert_eq!(size_of_val("foobar"), 6);
+
+    unsafe {
+        assert_eq!(size_of_val_raw(&[1] as &[i32] as *const [i32]), 4);
+    }
+    unsafe {
+        assert_eq!(size_of_val_raw(0x100 as *const i32), 4);
+    }
+
+    assert_eq!(intrinsics::type_name::<Option<i32>>(), "core::option::Option<i32>");
+
+    assert_eq!(intrinsics::likely(false), false);
+    assert_eq!(intrinsics::unlikely(true), true);
+
+    intrinsics::forget(Bomb);
+
+    let _v = intrinsics::discriminant_value(&Some(()));
+    let _v = intrinsics::discriminant_value(&0);
+    let _v = intrinsics::discriminant_value(&true);
+    let _v = intrinsics::discriminant_value(&vec![1, 2, 3]);
+}
diff --git a/src/tools/miri/tests/pass/ints.rs b/src/tools/miri/tests/pass/ints.rs
new file mode 100644
index 0000000000000..c04c6921f3c42
--- /dev/null
+++ b/src/tools/miri/tests/pass/ints.rs
@@ -0,0 +1,58 @@
+fn ret() -> i64 {
+    1
+}
+
+fn neg() -> i64 {
+    -1
+}
+
+fn add() -> i64 {
+    1 + 2
+}
+
+fn indirect_add() -> i64 {
+    let x = 1;
+    let y = 2;
+    x + y
+}
+
+fn arith() -> i32 {
+    3 * 3 + 4 * 4
+}
+
+fn match_int() -> i16 {
+    let n = 2;
+    match n {
+        0 => 0,
+        1 => 10,
+        2 => 20,
+        3 => 30,
+        _ => 100,
+    }
+}
+
+fn match_int_range() -> i64 {
+    let n = 42;
+    match n {
+        0..=9 => 0,
+        10..=19 => 1,
+        20..=29 => 2,
+        30..=39 => 3,
+        40..=42 => 4,
+        _ => 5,
+    }
+}
+
+fn main() {
+    assert_eq!(ret(), 1);
+    assert_eq!(neg(), -1);
+    assert_eq!(add(), 3);
+    assert_eq!(indirect_add(), 3);
+    assert_eq!(arith(), 5 * 5);
+    assert_eq!(match_int(), 20);
+    assert_eq!(match_int_range(), 4);
+    assert_eq!(i64::MIN.overflowing_mul(-1), (i64::MIN, true));
+    assert_eq!(i32::MIN.overflowing_mul(-1), (i32::MIN, true));
+    assert_eq!(i16::MIN.overflowing_mul(-1), (i16::MIN, true));
+    assert_eq!(i8::MIN.overflowing_mul(-1), (i8::MIN, true));
+}
diff --git a/src/tools/miri/tests/pass/issues/issue-15063.rs b/src/tools/miri/tests/pass/issues/issue-15063.rs
new file mode 100644
index 0000000000000..cbb1b90f68629
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-15063.rs
@@ -0,0 +1,11 @@
+#[allow(dead_code)]
+enum Two {
+    A,
+    B,
+}
+impl Drop for Two {
+    fn drop(&mut self) {}
+}
+fn main() {
+    let _k = Two::A;
+}
diff --git a/src/tools/miri/tests/pass/issues/issue-15080.rs b/src/tools/miri/tests/pass/issues/issue-15080.rs
new file mode 100644
index 0000000000000..4a360993116c3
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-15080.rs
@@ -0,0 +1,19 @@
+fn main() {
+    let mut x: &[_] = &[1, 2, 3, 4];
+
+    let mut result = vec![];
+    loop {
+        x = match *x {
+            [1, n, 3, ref rest @ ..] => {
+                result.push(n);
+                rest
+            }
+            [n, ref rest @ ..] => {
+                result.push(n);
+                rest
+            }
+            [] => break,
+        }
+    }
+    assert_eq!(result, [2, 4]);
+}
diff --git a/src/tools/miri/tests/pass/issues/issue-15523-big.rs b/src/tools/miri/tests/pass/issues/issue-15523-big.rs
new file mode 100644
index 0000000000000..7c9fec3ab04b0
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-15523-big.rs
@@ -0,0 +1,37 @@
+// Issue 15523: derive(PartialOrd) should use the provided
+// discriminant values for the derived ordering.
+//
+// This test is checking corner cases that arise when you have
+// 64-bit values in the variants.
+
+#[derive(PartialEq, PartialOrd)]
+#[repr(u64)]
+enum Eu64 {
+    Pos2 = 2,
+    PosMax = !0,
+    Pos1 = 1,
+}
+
+#[derive(PartialEq, PartialOrd)]
+#[repr(i64)]
+enum Ei64 {
+    Pos2 = 2,
+    Neg1 = -1,
+    NegMin = 1 << 63,
+    PosMax = !(1 << 63),
+    Pos1 = 1,
+}
+
+fn main() {
+    assert!(Eu64::Pos2 > Eu64::Pos1);
+    assert!(Eu64::Pos2 < Eu64::PosMax);
+    assert!(Eu64::Pos1 < Eu64::PosMax);
+
+    assert!(Ei64::Pos2 > Ei64::Pos1);
+    assert!(Ei64::Pos2 > Ei64::Neg1);
+    assert!(Ei64::Pos1 > Ei64::Neg1);
+    assert!(Ei64::Pos2 > Ei64::NegMin);
+    assert!(Ei64::Pos1 > Ei64::NegMin);
+    assert!(Ei64::Pos2 < Ei64::PosMax);
+    assert!(Ei64::Pos1 < Ei64::PosMax);
+}
diff --git a/src/tools/miri/tests/pass/issues/issue-17877.rs b/src/tools/miri/tests/pass/issues/issue-17877.rs
new file mode 100644
index 0000000000000..64d397f91aeec
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-17877.rs
@@ -0,0 +1,17 @@
+fn main() {
+    assert_eq!(
+        match [0u8; 16 * 1024] {
+            _ => 42_usize,
+        },
+        42_usize,
+    );
+
+    assert_eq!(
+        match [0u8; 16 * 1024] {
+            [1, ..] => 0_usize,
+            [0, ..] => 1_usize,
+            _ => 2_usize,
+        },
+        1_usize,
+    );
+}
diff --git a/src/tools/miri/tests/pass/issues/issue-20575.rs b/src/tools/miri/tests/pass/issues/issue-20575.rs
new file mode 100644
index 0000000000000..19049b9add5c1
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-20575.rs
@@ -0,0 +1,7 @@
+// Test that overloaded calls work with zero arity closures
+
+fn main() {
+    let functions: [Box<dyn Fn() -> Option<()>>; 1] = [Box::new(|| None)];
+
+    let _val: Option<Vec<()>> = functions.iter().map(|f| (*f)()).collect();
+}
diff --git a/src/tools/miri/tests/pass/issues/issue-23261.rs b/src/tools/miri/tests/pass/issues/issue-23261.rs
new file mode 100644
index 0000000000000..f98252c18bf5c
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-23261.rs
@@ -0,0 +1,60 @@
+// Matching on a DST struct should not trigger an LLVM assertion.
+
+struct Foo<T: ?Sized> {
+    a: i32,
+    inner: T,
+}
+
+trait Get {
+    fn get(&self) -> i32;
+}
+
+impl Get for i32 {
+    fn get(&self) -> i32 {
+        *self
+    }
+}
+
+fn check_val(val: &Foo<[u8]>) {
+    match *val {
+        Foo { a, .. } => {
+            assert_eq!(a, 32);
+        }
+    }
+}
+
+fn check_dst_val(val: &Foo<[u8]>) {
+    match *val {
+        Foo { ref inner, .. } => {
+            assert_eq!(inner, [1, 2, 3]);
+        }
+    }
+}
+
+fn check_both(val: &Foo<[u8]>) {
+    match *val {
+        Foo { a, ref inner } => {
+            assert_eq!(a, 32);
+            assert_eq!(inner, [1, 2, 3]);
+        }
+    }
+}
+
+fn check_trait_obj(val: &Foo<dyn Get>) {
+    match *val {
+        Foo { a, ref inner } => {
+            assert_eq!(a, 32);
+            assert_eq!(inner.get(), 32);
+        }
+    }
+}
+
+fn main() {
+    let foo: &Foo<[u8]> = &Foo { a: 32, inner: [1, 2, 3] };
+    check_val(foo);
+    check_dst_val(foo);
+    check_both(foo);
+
+    let foo: &Foo<dyn Get> = &Foo { a: 32, inner: 32 };
+    check_trait_obj(foo);
+}
diff --git a/src/tools/miri/tests/pass/issues/issue-26709.rs b/src/tools/miri/tests/pass/issues/issue-26709.rs
new file mode 100644
index 0000000000000..78f30e78db766
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-26709.rs
@@ -0,0 +1,16 @@
+struct Wrapper<'a, T: ?Sized>(&'a mut i32, T);
+
+impl<'a, T: ?Sized> Drop for Wrapper<'a, T> {
+    fn drop(&mut self) {
+        *self.0 = 432;
+    }
+}
+
+fn main() {
+    let mut x = 0;
+    {
+        let wrapper = Box::new(Wrapper(&mut x, 123));
+        let _val: Box<Wrapper<dyn Send>> = wrapper;
+    }
+    assert_eq!(432, x)
+}
diff --git a/src/tools/miri/tests/pass/issues/issue-27901.rs b/src/tools/miri/tests/pass/issues/issue-27901.rs
new file mode 100644
index 0000000000000..a9f5dc98720fc
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-27901.rs
@@ -0,0 +1,14 @@
+trait Stream {
+    type Item;
+}
+impl<'a> Stream for &'a str {
+    type Item = u8;
+}
+fn f<'s>(s: &'s str) -> (&'s str, <&'s str as Stream>::Item) {
+    (s, 42)
+}
+
+fn main() {
+    let fx = f as for<'t> fn(&'t str) -> (&'t str, <&'t str as Stream>::Item);
+    assert_eq!(fx("hi"), ("hi", 42));
+}
diff --git a/src/tools/miri/tests/pass/issues/issue-29746.rs b/src/tools/miri/tests/pass/issues/issue-29746.rs
new file mode 100644
index 0000000000000..43bed4464b9c8
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-29746.rs
@@ -0,0 +1,35 @@
+// zip!(a1,a2,a3,a4) is equivalent to:
+//  a1.zip(a2).zip(a3).zip(a4).map(|(((x1,x2),x3),x4)| (x1,x2,x3,x4))
+macro_rules! zip {
+    // Entry point
+    ([$a:expr, $b:expr, $($rest:expr),*]) => {
+        zip!([$($rest),*], $a.zip($b), (x,y), [x,y])
+    };
+
+    // Intermediate steps to build the zipped expression, the match pattern, and
+    //  and the output tuple of the closure, using macro hygene to repeatedly
+    //  introduce new variables named 'x'.
+    ([$a:expr, $($rest:expr),*], $zip:expr, $pat:pat, [$($flat:expr),*]) => {
+        zip!([$($rest),*], $zip.zip($a), ($pat,x), [$($flat),*, x])
+    };
+
+    // Final step
+    ([], $zip:expr, $pat:pat, [$($flat:expr),+]) => {
+        $zip.map(|$pat| ($($flat),+))
+    };
+
+    // Comma
+    ([$a:expr], $zip:expr, $pat:pat, [$($flat:expr),*]) => {
+        zip!([$a,], $zip, $pat, [$($flat),*])
+    };
+}
+
+fn main() {
+    let p1 = vec![1i32, 2].into_iter();
+    let p2 = vec!["10", "20"].into_iter();
+    let p3 = vec![100u16, 200].into_iter();
+    let p4 = vec![1000i64, 2000].into_iter();
+
+    let e = zip!([p1, p2, p3, p4]).collect::<Vec<_>>();
+    assert_eq!(e[0], (1i32, "10", 100u16, 1000i64));
+}
diff --git a/src/tools/miri/tests/pass/issues/issue-30530.rs b/src/tools/miri/tests/pass/issues/issue-30530.rs
new file mode 100644
index 0000000000000..472b42adaac85
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-30530.rs
@@ -0,0 +1,38 @@
+// Copyright 2012-2016 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Regression test for Issue #30530: alloca's created for storing
+// intermediate scratch values during brace-less match arms need to be
+// initialized with their drop-flag set to "dropped" (or else we end
+// up running the destructors on garbage data at the end of the
+// function).
+
+pub enum Handler {
+    Default,
+    #[allow(dead_code)]
+    Custom(*mut Box<dyn Fn()>),
+}
+
+fn main() {
+    #[allow(unused_must_use)]
+    {
+        take(Handler::Default, Box::new(main));
+    }
+}
+
+#[inline(never)]
+pub fn take(h: Handler, f: Box<dyn Fn()>) -> Box<dyn Fn()> {
+    unsafe {
+        match h {
+            Handler::Custom(ptr) => *Box::from_raw(ptr),
+            Handler::Default => f,
+        }
+    }
+}
diff --git a/src/tools/miri/tests/pass/issues/issue-31267-additional.rs b/src/tools/miri/tests/pass/issues/issue-31267-additional.rs
new file mode 100644
index 0000000000000..f6d7209369b70
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-31267-additional.rs
@@ -0,0 +1,17 @@
+#[derive(Clone, Copy, Debug)]
+struct Bar;
+
+const BAZ: Bar = Bar;
+
+#[derive(Debug)]
+struct Foo([Bar; 1]);
+
+struct Biz;
+
+impl Biz {
+    const BAZ: Foo = Foo([BAZ; 1]);
+}
+
+fn main() {
+    let _foo = Biz::BAZ;
+}
diff --git a/src/tools/miri/tests/pass/issues/issue-33387.rs b/src/tools/miri/tests/pass/issues/issue-33387.rs
new file mode 100644
index 0000000000000..36b58c642d7d5
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-33387.rs
@@ -0,0 +1,9 @@
+use std::sync::Arc;
+
+trait Foo {}
+
+impl Foo for [u8; 2] {}
+
+fn main() {
+    let _val: Arc<dyn Foo + Send> = Arc::new([3, 4]);
+}
diff --git a/src/tools/miri/tests/pass/issues/issue-34571.rs b/src/tools/miri/tests/pass/issues/issue-34571.rs
new file mode 100644
index 0000000000000..e1ed8d19e4ea6
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-34571.rs
@@ -0,0 +1,10 @@
+#[repr(u8)]
+enum Foo {
+    Foo(u8),
+}
+
+fn main() {
+    match Foo::Foo(1) {
+        _ => (),
+    }
+}
diff --git a/src/tools/miri/tests/pass/issues/issue-35815.rs b/src/tools/miri/tests/pass/issues/issue-35815.rs
new file mode 100644
index 0000000000000..62b3220967edd
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-35815.rs
@@ -0,0 +1,14 @@
+use std::mem;
+
+#[allow(dead_code)]
+struct Foo<T: ?Sized> {
+    a: i64,
+    b: bool,
+    c: T,
+}
+
+fn main() {
+    let foo: &Foo<i32> = &Foo { a: 1, b: false, c: 2i32 };
+    let foo_unsized: &Foo<dyn Send> = foo;
+    assert_eq!(mem::size_of_val(foo), mem::size_of_val(foo_unsized));
+}
diff --git a/src/tools/miri/tests/pass/issues/issue-36278-prefix-nesting.rs b/src/tools/miri/tests/pass/issues/issue-36278-prefix-nesting.rs
new file mode 100644
index 0000000000000..6bc8f02c3bafb
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-36278-prefix-nesting.rs
@@ -0,0 +1,22 @@
+// Issue 36278: On an unsized struct with >1 level of nontrivial
+// nesting, ensure we are computing dynamic size of prefix correctly.
+
+use std::mem;
+
+const SZ: usize = 100;
+struct P<T: ?Sized>([u8; SZ], T);
+
+type Ack<T> = P<P<T>>;
+
+fn main() {
+    let size_of_sized;
+    let size_of_unsized;
+    let x: Box<Ack<[u8; 0]>> = Box::new(P([0; SZ], P([0; SZ], [0; 0])));
+    size_of_sized = mem::size_of_val::<Ack<_>>(&x);
+    let align_of_sized = mem::align_of_val::<Ack<_>>(&x);
+    let y: Box<Ack<[u8]>> = x;
+    size_of_unsized = mem::size_of_val::<Ack<_>>(&y);
+    assert_eq!(size_of_sized, size_of_unsized);
+    assert_eq!(align_of_sized, 1);
+    assert_eq!(mem::align_of_val::<Ack<_>>(&y), 1);
+}
diff --git a/src/tools/miri/tests/pass/issues/issue-3794.rs b/src/tools/miri/tests/pass/issues/issue-3794.rs
new file mode 100644
index 0000000000000..5b5b22b54948d
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-3794.rs
@@ -0,0 +1,32 @@
+#![feature(box_syntax)]
+
+trait T {
+    fn print(&self);
+}
+
+#[derive(Debug)]
+struct S {
+    #[allow(dead_code)]
+    s: isize,
+}
+
+impl T for S {
+    fn print(&self) {
+        println!("{:?}", self);
+    }
+}
+
+fn print_t(t: &dyn T) {
+    t.print();
+}
+
+fn print_s(s: &S) {
+    s.print();
+}
+
+pub fn main() {
+    let s: Box<S> = box S { s: 5 };
+    print_s(&*s);
+    let t: Box<dyn T> = s as Box<dyn T>;
+    print_t(&*t);
+}
diff --git a/src/tools/miri/tests/pass/issues/issue-3794.stdout b/src/tools/miri/tests/pass/issues/issue-3794.stdout
new file mode 100644
index 0000000000000..e4afe6fa55f15
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-3794.stdout
@@ -0,0 +1,2 @@
+S { s: 5 }
+S { s: 5 }
diff --git a/src/tools/miri/tests/pass/issues/issue-53728.rs b/src/tools/miri/tests/pass/issues/issue-53728.rs
new file mode 100644
index 0000000000000..0c858d3444fb3
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-53728.rs
@@ -0,0 +1,18 @@
+#[repr(u16)]
+#[allow(dead_code)]
+enum DeviceKind {
+    Nil = 0,
+}
+
+#[repr(packed)]
+#[allow(dead_code)]
+struct DeviceInfo {
+    endianness: u8,
+    device_kind: DeviceKind,
+}
+
+fn main() {
+    let _x = None::<(DeviceInfo, u8)>;
+    let _y = None::<(DeviceInfo, u16)>;
+    let _z = None::<(DeviceInfo, u64)>;
+}
diff --git a/src/tools/miri/tests/pass/issues/issue-5917.rs b/src/tools/miri/tests/pass/issues/issue-5917.rs
new file mode 100644
index 0000000000000..f7bbb4350e2be
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-5917.rs
@@ -0,0 +1,6 @@
+struct T(&'static [isize]);
+static STATIC: T = T(&[5, 4, 3]);
+pub fn main() {
+    let T(ref v) = STATIC;
+    assert_eq!(v[0], 5);
+}
diff --git a/src/tools/miri/tests/pass/issues/issue-73223.rs b/src/tools/miri/tests/pass/issues/issue-73223.rs
new file mode 100644
index 0000000000000..df13787aad698
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-73223.rs
@@ -0,0 +1,23 @@
+fn main() {
+    let mut state = State { prev: None, next: Some(8) };
+    let path = "/nested/some/more";
+    assert_eq!(state.rest(path), "some/more");
+}
+
+#[allow(unused)]
+struct State {
+    prev: Option<usize>,
+    next: Option<usize>,
+}
+
+impl State {
+    fn rest<'r>(&mut self, path: &'r str) -> &'r str {
+        let start = match self.next.take() {
+            Some(v) => v,
+            None => return "",
+        };
+
+        self.prev = Some(start);
+        &path[start..]
+    }
+}
diff --git a/src/tools/miri/tests/pass/issues/issue-91636.rs b/src/tools/miri/tests/pass/issues/issue-91636.rs
new file mode 100644
index 0000000000000..21000bb68d2bc
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-91636.rs
@@ -0,0 +1,20 @@
+type BuiltIn = for<'a> fn(&str);
+
+struct Function {
+    inner: BuiltIn,
+}
+
+impl Function {
+    fn new(subr: BuiltIn) -> Self {
+        Self { inner: subr }
+    }
+}
+
+fn dummy(_: &str) {}
+
+fn main() {
+    let func1 = Function::new(dummy);
+    let func2 = Function::new(dummy);
+    let inner: fn(&'static _) -> _ = func1.inner;
+    assert!(inner == func2.inner);
+}
diff --git a/src/tools/miri/tests/pass/issues/issue-94371.rs b/src/tools/miri/tests/pass/issues/issue-94371.rs
new file mode 100644
index 0000000000000..1336348efb17a
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-94371.rs
@@ -0,0 +1,13 @@
+#[repr(C)]
+struct Demo(u64, bool, u64, u32, u64, u64, u64);
+
+fn test() -> (Demo, Demo) {
+    let mut x = Demo(1, true, 3, 4, 5, 6, 7);
+    let mut y = Demo(10, false, 12, 13, 14, 15, 16);
+    std::mem::swap(&mut x, &mut y);
+    (x, y)
+}
+
+fn main() {
+    drop(test());
+}
diff --git a/src/tools/miri/tests/pass/issues/issue-miri-1075.rs b/src/tools/miri/tests/pass/issues/issue-miri-1075.rs
new file mode 100644
index 0000000000000..8bacaca9c2ac2
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-miri-1075.rs
@@ -0,0 +1,6 @@
+fn main() {
+    let f: fn() -> ! = || std::process::exit(0);
+    f();
+
+    // FIXME: Also add a test for <https://github.com/rust-lang/rust/issues/66738>, once that is fixed.
+}
diff --git a/src/tools/miri/tests/pass/issues/issue-miri-133.rs b/src/tools/miri/tests/pass/issues/issue-miri-133.rs
new file mode 100644
index 0000000000000..02c9732571330
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-miri-133.rs
@@ -0,0 +1,25 @@
+use std::mem::size_of;
+
+struct S<U, V> {
+    _u: U,
+    size_of_u: usize,
+    _v: V,
+    size_of_v: usize,
+}
+
+impl<U, V> S<U, V> {
+    fn new(u: U, v: V) -> Self {
+        S { _u: u, size_of_u: size_of::<U>(), _v: v, size_of_v: size_of::<V>() }
+    }
+}
+
+impl<V, U> Drop for S<U, V> {
+    fn drop(&mut self) {
+        assert_eq!(size_of::<U>(), self.size_of_u);
+        assert_eq!(size_of::<V>(), self.size_of_v);
+    }
+}
+
+fn main() {
+    S::new(0u8, 1u16);
+}
diff --git a/src/tools/miri/tests/pass/issues/issue-miri-184.rs b/src/tools/miri/tests/pass/issues/issue-miri-184.rs
new file mode 100644
index 0000000000000..39c841403ef0c
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-miri-184.rs
@@ -0,0 +1,4 @@
+pub fn main() {
+    let bytes: [u8; 8] = unsafe { ::std::mem::transmute(0u64) };
+    let _val: &[u8] = &bytes;
+}
diff --git a/src/tools/miri/tests/pass/issues/issue-miri-1925.rs b/src/tools/miri/tests/pass/issues/issue-miri-1925.rs
new file mode 100644
index 0000000000000..8655681349194
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-miri-1925.rs
@@ -0,0 +1,33 @@
+//@compile-flags: -Zmiri-symbolic-alignment-check
+
+use std::mem::size_of;
+
+fn main() {
+    let mut a = Params::new();
+    a.key_block = [0; BLOCKBYTES];
+}
+
+#[repr(C)]
+#[derive(Clone)]
+#[allow(unused)]
+pub struct Params {
+    hash_length: u8,
+    key_length: u8,
+    key_block: [u8; BLOCKBYTES],
+    max_leaf_length: u32,
+}
+
+pub const OUTBYTES: usize = 8 * size_of::<u64>();
+pub const KEYBYTES: usize = 8 * size_of::<u64>();
+pub const BLOCKBYTES: usize = 16 * size_of::<u64>();
+
+impl Params {
+    pub fn new() -> Self {
+        Self {
+            hash_length: OUTBYTES as u8,
+            key_length: 0,
+            key_block: [0; BLOCKBYTES],
+            max_leaf_length: 0,
+        }
+    }
+}
diff --git a/src/tools/miri/tests/pass/issues/issue-miri-2068.rs b/src/tools/miri/tests/pass/issues/issue-miri-2068.rs
new file mode 100644
index 0000000000000..7576ba78f607d
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-miri-2068.rs
@@ -0,0 +1,48 @@
+#![feature(pin_macro)]
+
+use core::future::Future;
+use core::pin::Pin;
+use core::task::{Context, Poll};
+
+use std::sync::Arc;
+
+struct NopWaker;
+
+impl std::task::Wake for NopWaker {
+    fn wake(self: Arc<Self>) {}
+}
+
+pub fn fuzzing_block_on<O, F: Future<Output = O>>(fut: F) -> O {
+    let mut fut = std::pin::pin!(fut);
+    let waker = std::task::Waker::from(Arc::new(NopWaker));
+    let mut context = std::task::Context::from_waker(&waker);
+    loop {
+        match fut.as_mut().poll(&mut context) {
+            Poll::Ready(v) => return v,
+            Poll::Pending => {}
+        }
+    }
+}
+
+pub struct LastFuture<S> {
+    last: S,
+}
+
+impl<S> Future for LastFuture<S>
+where
+    Self: Unpin,
+    S: Unpin + Copy,
+{
+    type Output = S;
+
+    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
+        return Poll::Ready(self.last);
+    }
+}
+
+fn main() {
+    fuzzing_block_on(async {
+        LastFuture { last: &0u32 }.await;
+        LastFuture { last: Option::<u32>::None }.await;
+    });
+}
diff --git a/src/tools/miri/tests/pass/issues/issue-miri-2123.rs b/src/tools/miri/tests/pass/issues/issue-miri-2123.rs
new file mode 100644
index 0000000000000..e39e5fe454a2c
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-miri-2123.rs
@@ -0,0 +1,21 @@
+#![feature(ptr_metadata, layout_for_ptr)]
+
+use std::{mem, ptr};
+
+trait Foo {}
+
+impl Foo for u32 {}
+
+fn uwu(thin: *const (), meta: &'static ()) -> *const dyn Foo {
+    ptr::from_raw_parts(thin, unsafe { mem::transmute(meta) })
+}
+
+fn main() {
+    unsafe {
+        let orig = 1_u32;
+        let x = &orig as &dyn Foo;
+        let (ptr, meta) = (x as *const dyn Foo).to_raw_parts();
+        let ptr = uwu(ptr, mem::transmute(meta));
+        let _size = mem::size_of_val_raw(ptr);
+    }
+}
diff --git a/src/tools/miri/tests/pass/iter.rs b/src/tools/miri/tests/pass/iter.rs
new file mode 100644
index 0000000000000..31d0d7442d9d2
--- /dev/null
+++ b/src/tools/miri/tests/pass/iter.rs
@@ -0,0 +1,41 @@
+fn iter_empty_and_zst() {
+    // Iterate over a Unique::empty()
+    for _ in Vec::<u32>::new().iter() {
+        panic!("We should never be here.");
+    }
+
+    // Iterate over a ZST (uses arith_offset internally)
+    let mut count = 0;
+    for _ in &[(), (), ()] {
+        count += 1;
+    }
+    assert_eq!(count, 3);
+}
+
+fn test_iterator_step_by_nth() {
+    let mut it = (0..16).step_by(5);
+    assert_eq!(it.nth(0), Some(0));
+    assert_eq!(it.nth(0), Some(5));
+    assert_eq!(it.nth(0), Some(10));
+    assert_eq!(it.nth(0), Some(15));
+    assert_eq!(it.nth(0), None);
+}
+
+fn iter_any() {
+    let f = |x: &u8| 10u8 == *x;
+    f(&1u8);
+
+    let g = |(), x: &u8| 10u8 == *x;
+    g((), &1u8);
+
+    let h = |(), (), x: &u8| 10u8 == *x;
+    h((), (), &1u8);
+
+    [1, 2, 3u8].iter().any(|elt| 10 == *elt);
+}
+
+fn main() {
+    test_iterator_step_by_nth();
+    iter_any();
+    iter_empty_and_zst();
+}
diff --git a/src/tools/miri/tests/pass/last-use-in-cap-clause.rs b/src/tools/miri/tests/pass/last-use-in-cap-clause.rs
new file mode 100644
index 0000000000000..2160aea16346f
--- /dev/null
+++ b/src/tools/miri/tests/pass/last-use-in-cap-clause.rs
@@ -0,0 +1,17 @@
+// Make sure #1399 stays fixed
+
+#[allow(dead_code)]
+struct A {
+    a: Box<isize>,
+}
+
+fn foo() -> Box<dyn FnMut() -> isize + 'static> {
+    let k: Box<_> = Box::new(22);
+    let _u = A { a: k.clone() };
+    let result = || 22;
+    Box::new(result)
+}
+
+pub fn main() {
+    assert_eq!(foo()(), 22);
+}
diff --git a/src/tools/miri/tests/pass/leak-in-static.rs b/src/tools/miri/tests/pass/leak-in-static.rs
new file mode 100644
index 0000000000000..9523394408806
--- /dev/null
+++ b/src/tools/miri/tests/pass/leak-in-static.rs
@@ -0,0 +1,23 @@
+use std::{
+    ptr,
+    sync::atomic::{AtomicPtr, Ordering},
+};
+
+static mut LEAKER: Option<Box<Vec<i32>>> = None;
+
+fn main() {
+    // Having memory "leaked" in globals is allowed.
+    unsafe {
+        LEAKER = Some(Box::new(vec![0; 42]));
+    }
+
+    // Make sure this is allowed even when `AtomicPtr` is used.
+    {
+        static LEAK: AtomicPtr<usize> = AtomicPtr::new(ptr::null_mut());
+        LEAK.store(Box::into_raw(Box::new(0usize)), Ordering::SeqCst);
+
+        static LEAK2: AtomicPtr<usize> = AtomicPtr::new(ptr::null_mut());
+        // Make sure this also works when using 'swap'.
+        LEAK2.swap(Box::into_raw(Box::new(0usize)), Ordering::SeqCst);
+    }
+}
diff --git a/src/tools/miri/tests/pass/linked-list.rs b/src/tools/miri/tests/pass/linked-list.rs
new file mode 100644
index 0000000000000..7377f9f60b01e
--- /dev/null
+++ b/src/tools/miri/tests/pass/linked-list.rs
@@ -0,0 +1,51 @@
+#![feature(linked_list_cursors)]
+use std::collections::LinkedList;
+
+fn list_from<T: Clone>(v: &[T]) -> LinkedList<T> {
+    v.iter().cloned().collect()
+}
+
+// Gather all references from a mutable iterator and make sure Miri notices if
+// using them is dangerous.
+fn test_all_refs<'a, T: 'a>(dummy: &mut T, iter: impl Iterator<Item = &'a mut T>) {
+    // Gather all those references.
+    let mut refs: Vec<&mut T> = iter.collect();
+    // Use them all. Twice, to be sure we got all interleavings.
+    for r in refs.iter_mut() {
+        std::mem::swap(dummy, r);
+    }
+    for r in refs {
+        std::mem::swap(dummy, r);
+    }
+}
+
+fn main() {
+    let mut m = list_from(&[0, 2, 4, 6, 8]);
+    let len = m.len();
+    {
+        let mut it = m.cursor_front_mut();
+        it.insert_before(-2);
+        loop {
+            match it.current().copied() {
+                None => break,
+                Some(elt) => {
+                    match it.peek_next() {
+                        Some(x) => assert_eq!(*x, elt + 2),
+                        None => assert_eq!(8, elt),
+                    }
+                    it.insert_after(elt + 1);
+                    it.move_next(); // Move by 2 to skip the one we inserted.
+                    it.move_next();
+                }
+            }
+        }
+        it.insert_before(99);
+        it.insert_after(-10);
+    }
+
+    assert_eq!(m.len(), 3 + len * 2);
+    let mut m2 = m.clone();
+    assert_eq!(m.into_iter().collect::<Vec<_>>(), [-10, -2, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 99]);
+
+    test_all_refs(&mut 13, m2.iter_mut());
+}
diff --git a/src/tools/miri/tests/pass/loop-break-value.rs b/src/tools/miri/tests/pass/loop-break-value.rs
new file mode 100644
index 0000000000000..bc4c967d26a5b
--- /dev/null
+++ b/src/tools/miri/tests/pass/loop-break-value.rs
@@ -0,0 +1,125 @@
+#![feature(never_type)]
+#![allow(unreachable_code)]
+
+#[allow(unused)]
+fn never_returns() {
+    loop {
+        break loop {};
+    }
+}
+
+pub fn main() {
+    let value = 'outer: loop {
+        if 1 == 1 {
+            break 13;
+        } else {
+            let _never: ! = loop {
+                break loop {
+                    break 'outer panic!();
+                };
+            };
+        }
+    };
+    assert_eq!(value, 13);
+
+    let x = [1, 3u32, 5];
+    let y = [17];
+    let z = [];
+    let coerced: &[_] = loop {
+        match 2 {
+            1 => break &x,
+            2 => break &y,
+            3 => break &z,
+            _ => (),
+        }
+    };
+    assert_eq!(coerced, &[17u32]);
+
+    let trait_unified = loop {
+        break if true { break Default::default() } else { break [13, 14] };
+    };
+    assert_eq!(trait_unified, [0, 0]);
+
+    let trait_unified_2 = loop {
+        if false {
+            break [String::from("Hello")];
+        } else {
+            break Default::default();
+        };
+    };
+    // compare lengths; ptr comparison is not deterministic
+    assert_eq!(trait_unified_2.len(), 1);
+    assert_eq!(trait_unified_2[0].len(), 0);
+
+    let trait_unified_3 = loop {
+        break if false { break [String::from("Hello")] } else { ["Yes".into()] };
+    };
+    assert_eq!(trait_unified_3, ["Yes"]);
+
+    let regular_break = loop {
+        if true {
+            break;
+        } else {
+            break break Default::default();
+        }
+    };
+    assert_eq!(regular_break, ());
+
+    let regular_break_2 = loop {
+        if true {
+            break Default::default();
+        } else {
+            break;
+        }
+    };
+    assert_eq!(regular_break_2, ());
+
+    let regular_break_3 = loop {
+        break if true {
+            Default::default()
+        } else {
+            break;
+        };
+    };
+    assert_eq!(regular_break_3, ());
+
+    let regular_break_4 = loop {
+        break ();
+        break;
+    };
+    assert_eq!(regular_break_4, ());
+
+    let regular_break_5 = loop {
+        break;
+        break ();
+    };
+    assert_eq!(regular_break_5, ());
+
+    let nested_break_value = 'outer2: loop {
+        let _a: u32 = 'inner: loop {
+            if true {
+                break 'outer2 "hello";
+            } else {
+                break 'inner 17;
+            }
+        };
+        panic!();
+    };
+    assert_eq!(nested_break_value, "hello");
+
+    let break_from_while_cond = loop {
+        'inner_loop: while break 'inner_loop {
+            panic!();
+        }
+        break 123;
+    };
+    assert_eq!(break_from_while_cond, 123);
+
+    let break_from_while_to_outer = 'outer_loop: loop {
+        while break 'outer_loop 567 {
+            panic!("from_inner");
+        }
+        panic!("from outer");
+    };
+    assert_eq!(break_from_while_to_outer, 567);
+}
diff --git a/src/tools/miri/tests/pass/loops.rs b/src/tools/miri/tests/pass/loops.rs
new file mode 100644
index 0000000000000..222287cbe09ad
--- /dev/null
+++ b/src/tools/miri/tests/pass/loops.rs
@@ -0,0 +1,35 @@
+fn factorial_loop() -> i64 {
+    let mut product = 1;
+    let mut i = 1;
+
+    while i <= 10 {
+        product *= i;
+        i += 1;
+    }
+
+    product
+}
+
+fn index_for_loop() -> usize {
+    let mut sum = 0;
+    let a = [0, 10, 20, 30];
+    for i in 0..a.len() {
+        sum += a[i];
+    }
+    sum
+}
+
+fn for_loop() -> usize {
+    let mut sum = 0;
+    let a = [0, 10, 20, 30];
+    for &n in &a {
+        sum += n;
+    }
+    sum
+}
+
+fn main() {
+    assert_eq!(factorial_loop(), 3628800);
+    assert_eq!(index_for_loop(), 60);
+    assert_eq!(for_loop(), 60);
+}
diff --git a/src/tools/miri/tests/pass/main_fn.rs b/src/tools/miri/tests/pass/main_fn.rs
new file mode 100644
index 0000000000000..3b84d1abe6f3d
--- /dev/null
+++ b/src/tools/miri/tests/pass/main_fn.rs
@@ -0,0 +1,7 @@
+#![feature(imported_main)]
+
+mod foo {
+    pub(crate) fn bar() {}
+}
+
+use foo::bar as main;
diff --git a/src/tools/miri/tests/pass/main_result.rs b/src/tools/miri/tests/pass/main_result.rs
new file mode 100644
index 0000000000000..078760ee6667c
--- /dev/null
+++ b/src/tools/miri/tests/pass/main_result.rs
@@ -0,0 +1,3 @@
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+    Ok(())
+}
diff --git a/src/tools/miri/tests/pass/many_shr_bor.rs b/src/tools/miri/tests/pass/many_shr_bor.rs
new file mode 100644
index 0000000000000..376b41dd6e209
--- /dev/null
+++ b/src/tools/miri/tests/pass/many_shr_bor.rs
@@ -0,0 +1,36 @@
+// Make sure validation can handle many overlapping shared borrows for different parts of a data structure
+use std::cell::RefCell;
+
+#[allow(unused)]
+struct Test {
+    a: u32,
+    b: u32,
+}
+
+fn test1() {
+    let t = &mut Test { a: 0, b: 0 };
+    {
+        let x;
+        {
+            let y = &t.a;
+            x = &t;
+            let _y = *y;
+        }
+        let _x = x.a;
+    }
+    t.b = 42;
+}
+
+fn test2(r: &mut RefCell<i32>) {
+    let x = &*r; // releasing write lock, first suspension recorded
+    let mut x_ref = x.borrow_mut();
+    let x_inner: &mut i32 = &mut *x_ref; // new inner write lock, with same lifetime as outer lock
+    let _x_inner_shr = &*x_inner; // releasing inner write lock, recording suspension
+    let _y = &*r; // second suspension for the outer write lock
+    let _x_inner_shr2 = &*x_inner; // 2nd suspension for inner write lock
+}
+
+fn main() {
+    test1();
+    test2(&mut RefCell::new(0));
+}
diff --git a/src/tools/miri/tests/pass/match_slice.rs b/src/tools/miri/tests/pass/match_slice.rs
new file mode 100644
index 0000000000000..e40a63ef2003b
--- /dev/null
+++ b/src/tools/miri/tests/pass/match_slice.rs
@@ -0,0 +1,8 @@
+fn main() {
+    let x = "hello";
+    match x {
+        "foo" => {}
+        "bar" => {}
+        _ => {}
+    }
+}
diff --git a/src/tools/miri/tests/pass/memchr.rs b/src/tools/miri/tests/pass/memchr.rs
new file mode 100644
index 0000000000000..e92c37ff2a8c7
--- /dev/null
+++ b/src/tools/miri/tests/pass/memchr.rs
@@ -0,0 +1,89 @@
+#![feature(slice_internals)]
+
+use core::slice::memchr::{memchr, memrchr};
+
+// test fallback implementations on all targets
+fn matches_one() {
+    assert_eq!(Some(0), memchr(b'a', b"a"));
+}
+
+fn matches_begin() {
+    assert_eq!(Some(0), memchr(b'a', b"aaaa"));
+}
+
+fn matches_end() {
+    assert_eq!(Some(4), memchr(b'z', b"aaaaz"));
+}
+
+fn matches_nul() {
+    assert_eq!(Some(4), memchr(b'\x00', b"aaaa\x00"));
+}
+
+fn matches_past_nul() {
+    assert_eq!(Some(5), memchr(b'z', b"aaaa\x00z"));
+}
+
+fn no_match_empty() {
+    assert_eq!(None, memchr(b'a', b""));
+}
+
+fn no_match() {
+    assert_eq!(None, memchr(b'a', b"xyz"));
+}
+
+fn matches_one_reversed() {
+    assert_eq!(Some(0), memrchr(b'a', b"a"));
+}
+
+fn matches_begin_reversed() {
+    assert_eq!(Some(3), memrchr(b'a', b"aaaa"));
+}
+
+fn matches_end_reversed() {
+    assert_eq!(Some(0), memrchr(b'z', b"zaaaa"));
+}
+
+fn matches_nul_reversed() {
+    assert_eq!(Some(4), memrchr(b'\x00', b"aaaa\x00"));
+}
+
+fn matches_past_nul_reversed() {
+    assert_eq!(Some(0), memrchr(b'z', b"z\x00aaaa"));
+}
+
+fn no_match_empty_reversed() {
+    assert_eq!(None, memrchr(b'a', b""));
+}
+
+fn no_match_reversed() {
+    assert_eq!(None, memrchr(b'a', b"xyz"));
+}
+
+fn each_alignment_reversed() {
+    let mut data = [1u8; 64];
+    let needle = 2;
+    let pos = 40;
+    data[pos] = needle;
+    for start in 0..16 {
+        assert_eq!(Some(pos - start), memrchr(needle, &data[start..]));
+    }
+}
+
+fn main() {
+    matches_one();
+    matches_begin();
+    matches_end();
+    matches_nul();
+    matches_past_nul();
+    no_match_empty();
+    no_match();
+
+    matches_one_reversed();
+    matches_begin_reversed();
+    matches_end_reversed();
+    matches_nul_reversed();
+    matches_past_nul_reversed();
+    no_match_empty_reversed();
+    no_match_reversed();
+    each_alignment_reversed();
+}
diff --git a/src/tools/miri/tests/pass/memleak_ignored.rs b/src/tools/miri/tests/pass/memleak_ignored.rs
new file mode 100644
index 0000000000000..60e06094e1771
--- /dev/null
+++ b/src/tools/miri/tests/pass/memleak_ignored.rs
@@ -0,0 +1,5 @@
+//@compile-flags: -Zmiri-ignore-leaks
+
+fn main() {
+    std::mem::forget(Box::new(42));
+}
diff --git a/src/tools/miri/tests/pass/move-arg-2-unique.rs b/src/tools/miri/tests/pass/move-arg-2-unique.rs
new file mode 100644
index 0000000000000..669602ac70436
--- /dev/null
+++ b/src/tools/miri/tests/pass/move-arg-2-unique.rs
@@ -0,0 +1,11 @@
+#![feature(box_syntax)]
+
+fn test(foo: Box<Vec<isize>>) {
+    assert_eq!((*foo)[0], 10);
+}
+
+pub fn main() {
+    let x = box vec![10];
+    // Test forgetting a local by move-in
+    test(x);
+}
diff --git a/src/tools/miri/tests/pass/move-arg-3-unique.rs b/src/tools/miri/tests/pass/move-arg-3-unique.rs
new file mode 100644
index 0000000000000..3b5c7cbbd42ca
--- /dev/null
+++ b/src/tools/miri/tests/pass/move-arg-3-unique.rs
@@ -0,0 +1,7 @@
+#![feature(box_syntax)]
+
+pub fn main() {
+    let x = box 10;
+    let y = x;
+    assert_eq!(*y, 10);
+}
diff --git a/src/tools/miri/tests/pass/move-uninit-primval.rs b/src/tools/miri/tests/pass/move-uninit-primval.rs
new file mode 100644
index 0000000000000..f5fd27fa0d4ed
--- /dev/null
+++ b/src/tools/miri/tests/pass/move-uninit-primval.rs
@@ -0,0 +1,14 @@
+#![allow(deprecated)]
+
+use std::mem;
+
+struct Foo {
+    _inner: mem::MaybeUninit<i32>,
+}
+
+fn main() {
+    unsafe {
+        let foo = Foo { _inner: mem::uninitialized() };
+        let _bar = foo;
+    }
+}
diff --git a/src/tools/miri/tests/pass/mpsc.rs b/src/tools/miri/tests/pass/mpsc.rs
new file mode 100644
index 0000000000000..6e3c6e771ccb8
--- /dev/null
+++ b/src/tools/miri/tests/pass/mpsc.rs
@@ -0,0 +1,15 @@
+#![feature(box_syntax)]
+
+use std::sync::mpsc::channel;
+
+pub fn main() {
+    let (tx, rx) = channel::<Box<_>>();
+    tx.send(box 100).unwrap();
+    let v = rx.recv().unwrap();
+    assert_eq!(v, box 100);
+
+    tx.send(box 101).unwrap();
+    tx.send(box 102).unwrap();
+    assert_eq!(rx.recv().unwrap(), box 101);
+    assert_eq!(rx.recv().unwrap(), box 102);
+}
diff --git a/src/tools/miri/tests/pass/multi_arg_closure.rs b/src/tools/miri/tests/pass/multi_arg_closure.rs
new file mode 100644
index 0000000000000..02d53540b83c3
--- /dev/null
+++ b/src/tools/miri/tests/pass/multi_arg_closure.rs
@@ -0,0 +1,8 @@
+fn foo(f: &mut dyn FnMut(isize, isize) -> isize) -> isize {
+    f(1, 2)
+}
+
+fn main() {
+    let z = foo(&mut |x, y| x * 10 + y);
+    assert_eq!(z, 12);
+}
diff --git a/src/tools/miri/tests/pass/negative_discriminant.rs b/src/tools/miri/tests/pass/negative_discriminant.rs
new file mode 100644
index 0000000000000..5a58deeac0f31
--- /dev/null
+++ b/src/tools/miri/tests/pass/negative_discriminant.rs
@@ -0,0 +1,16 @@
+enum AB {
+    A = -1,
+    B = 1,
+}
+
+fn main() {
+    match AB::A {
+        AB::A => (),
+        AB::B => panic!(),
+    }
+
+    match AB::B {
+        AB::A => panic!(),
+        AB::B => (),
+    }
+}
diff --git a/src/tools/miri/tests/pass/no_std.rs b/src/tools/miri/tests/pass/no_std.rs
new file mode 100644
index 0000000000000..10632c2cce497
--- /dev/null
+++ b/src/tools/miri/tests/pass/no_std.rs
@@ -0,0 +1,21 @@
+#![feature(lang_items, start)]
+#![no_std]
+// windows tls dtors go through libstd right now, thus this test
+// cannot pass. When windows tls dtors go through the special magic
+// windows linker section, we can run this test on windows again.
+//@ignore-target-windows
+
+#[start]
+fn start(_: isize, _: *const *const u8) -> isize {
+    for _ in 0..10 {}
+
+    0
+}
+
+#[panic_handler]
+fn panic_handler(_: &core::panic::PanicInfo) -> ! {
+    loop {}
+}
+
+#[lang = "eh_personality"]
+fn eh_personality() {}
diff --git a/src/tools/miri/tests/pass/observed_local_mut.rs b/src/tools/miri/tests/pass/observed_local_mut.rs
new file mode 100644
index 0000000000000..ca0f569860b82
--- /dev/null
+++ b/src/tools/miri/tests/pass/observed_local_mut.rs
@@ -0,0 +1,24 @@
+// Stacked Borrows catches this (correctly) as UB.
+//@compile-flags: -Zmiri-disable-stacked-borrows
+
+// This test is intended to guard against the problem described in commit
+// 39bb1254d1eaf74f45a4e741097e33fc942168d5.
+//
+// As written, it might be considered UB in compiled Rust, but of course Miri gives it a safe,
+// deterministic behaviour (one that might not correspond with how an eventual Rust spec would
+// defined this).
+//
+// An alternative way to write the test without `unsafe` would be to use `Cell<i32>`, but it would
+// only surface the bug described by the above commit if `Cell<i32>` on the stack got represented
+// as a primitive `PrimVal::I32` which is not yet the case.
+
+fn main() {
+    let mut x = 0;
+    let y: *const i32 = &x;
+    x = 1;
+
+    // When the described bug is in place, this results in `0`, not observing the `x = 1` line.
+    assert_eq!(unsafe { *y }, 1);
+
+    assert_eq!(x, 1);
+}
diff --git a/src/tools/miri/tests/pass/option_box_transmute_ptr.rs b/src/tools/miri/tests/pass/option_box_transmute_ptr.rs
new file mode 100644
index 0000000000000..0786db1ef895a
--- /dev/null
+++ b/src/tools/miri/tests/pass/option_box_transmute_ptr.rs
@@ -0,0 +1,15 @@
+// This tests that the size of Option<Box<i32>> is the same as *const i32.
+fn option_box_deref() -> i32 {
+    let val = Some(Box::new(42));
+    unsafe {
+        let ptr: *const i32 = std::mem::transmute::<Option<Box<i32>>, *const i32>(val);
+        let ret = *ptr;
+        // unleak memory
+        std::mem::transmute::<*const i32, Option<Box<i32>>>(ptr);
+        ret
+    }
+}
+
+fn main() {
+    assert_eq!(option_box_deref(), 42);
+}
diff --git a/src/tools/miri/tests/pass/option_eq.rs b/src/tools/miri/tests/pass/option_eq.rs
new file mode 100644
index 0000000000000..e698f8767746c
--- /dev/null
+++ b/src/tools/miri/tests/pass/option_eq.rs
@@ -0,0 +1,3 @@
+fn main() {
+    assert_eq!(std::char::from_u32('x' as u32), Some('x'));
+}
diff --git a/src/tools/miri/tests/pass/overflow_checks_off.rs b/src/tools/miri/tests/pass/overflow_checks_off.rs
new file mode 100644
index 0000000000000..79aa510ef97fe
--- /dev/null
+++ b/src/tools/miri/tests/pass/overflow_checks_off.rs
@@ -0,0 +1,18 @@
+//@compile-flags: -C overflow-checks=off
+
+// Check that we correctly implement the intended behavior of these operators
+// when they are not being overflow-checked.
+
+// FIXME: if we call the functions in `std::ops`, we still get the panics.
+// Miri does not implement the codegen-time hack that backs `#[rustc_inherit_overflow_checks]`.
+// use std::ops::*;
+
+fn main() {
+    assert_eq!(-{ -0x80i8 }, -0x80);
+
+    assert_eq!(0xffu8 + 1, 0_u8);
+    assert_eq!(0u8 - 1, 0xff_u8);
+    assert_eq!(0xffu8 * 2, 0xfe_u8);
+    assert_eq!(1u8 << 9, 2_u8);
+    assert_eq!(2u8 >> 9, 1_u8);
+}
diff --git a/src/tools/miri/tests/pass/overloaded-calls-simple.rs b/src/tools/miri/tests/pass/overloaded-calls-simple.rs
new file mode 100644
index 0000000000000..9fcf7d4a819a5
--- /dev/null
+++ b/src/tools/miri/tests/pass/overloaded-calls-simple.rs
@@ -0,0 +1,19 @@
+#![feature(lang_items, unboxed_closures, fn_traits)]
+
+struct S3 {
+    x: i32,
+    y: i32,
+}
+
+impl FnOnce<(i32, i32)> for S3 {
+    type Output = i32;
+    extern "rust-call" fn call_once(self, (z, zz): (i32, i32)) -> i32 {
+        self.x * self.y * z * zz
+    }
+}
+
+fn main() {
+    let s = S3 { x: 3, y: 3 };
+    let ans = s(3, 1);
+    assert_eq!(ans, 27);
+}
diff --git a/src/tools/miri/tests/pass/packed_struct.rs b/src/tools/miri/tests/pass/packed_struct.rs
new file mode 100644
index 0000000000000..85acab858aab9
--- /dev/null
+++ b/src/tools/miri/tests/pass/packed_struct.rs
@@ -0,0 +1,151 @@
+#![feature(unsize, coerce_unsized)]
+
+use std::collections::hash_map::DefaultHasher;
+use std::hash::Hash;
+use std::ptr;
+
+fn test_basic() {
+    #[repr(packed)]
+    struct S {
+        fill: u8,
+        a: i32,
+        b: i64,
+    }
+
+    #[repr(packed)]
+    #[allow(dead_code)]
+    struct Test1<'a> {
+        x: u8,
+        other: &'a u32,
+    }
+
+    #[repr(packed)]
+    #[allow(dead_code)]
+    struct Test2<'a> {
+        x: u8,
+        other: &'a Test1<'a>,
+    }
+
+    fn test(t: Test2) {
+        let x = *t.other.other;
+        assert_eq!(x, 42);
+    }
+
+    let mut x = S { fill: 0, a: 42, b: 99 };
+    let a = x.a;
+    let b = x.b;
+    assert_eq!(a, 42);
+    assert_eq!(b, 99);
+    assert_eq!(&x.fill, &0); // `fill` just requirs 1-byte-align, so this is fine
+    // can't do `assert_eq!(x.a, 42)`, because `assert_eq!` takes a reference
+    assert_eq!({ x.a }, 42);
+    assert_eq!({ x.b }, 99);
+    // but we *can* take a raw pointer!
+    assert_eq!(unsafe { ptr::addr_of!(x.a).read_unaligned() }, 42);
+    assert_eq!(unsafe { ptr::addr_of!(x.b).read_unaligned() }, 99);
+
+    x.b = 77;
+    assert_eq!({ x.b }, 77);
+
+    test(Test2 { x: 0, other: &Test1 { x: 0, other: &42 } });
+}
+
+fn test_unsizing() {
+    #[repr(packed)]
+    #[allow(dead_code)]
+    struct UnalignedPtr<'a, T: ?Sized>
+    where
+        T: 'a,
+    {
+        data: &'a T,
+    }
+
+    impl<'a, T, U> std::ops::CoerceUnsized<UnalignedPtr<'a, U>> for UnalignedPtr<'a, T>
+    where
+        T: std::marker::Unsize<U> + ?Sized,
+        U: ?Sized,
+    {
+    }
+
+    let arr = [1, 2, 3];
+    let arr_unaligned: UnalignedPtr<[i32; 3]> = UnalignedPtr { data: &arr };
+    let arr_unaligned: UnalignedPtr<[i32]> = arr_unaligned;
+    let _unused = &arr_unaligned; // forcing an allocation, which could also yield "unaligned write"-errors
+}
+
+fn test_drop() {
+    struct Wrap(u32);
+    impl Drop for Wrap {
+        fn drop(&mut self) {
+            // Do an (aligned) load
+            let _test = self.0;
+            // For the fun of it, test alignment
+            assert_eq!(&self.0 as *const _ as usize % std::mem::align_of::<u32>(), 0);
+        }
+    }
+
+    #[repr(packed, C)]
+    struct Packed<T> {
+        f1: u8, // this should move the second field to something not very aligned
+        f2: T,
+    }
+
+    let p = Packed { f1: 42, f2: Wrap(23) };
+    drop(p);
+}
+
+fn test_inner_packed() {
+    // Even if just the inner struct is packed, accesses to the outer field can get unaligned.
+    // Make sure that works.
+    #[repr(packed)]
+    #[derive(Clone, Copy)]
+    struct Inner(u32);
+
+    #[derive(Clone, Copy)]
+    struct Outer(u8, Inner);
+
+    let o = Outer(0, Inner(42));
+    let _x = o.1;
+    let _y = (o.1).0;
+    let _o2 = o.clone();
+}
+
+fn test_static() {
+    #[repr(packed)]
+    struct Foo {
+        i: i32,
+    }
+
+    static FOO: Foo = Foo { i: 42 };
+
+    assert_eq!({ FOO.i }, 42);
+}
+
+fn test_derive() {
+    #[repr(packed)]
+    #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
+    struct P {
+        a: usize,
+        b: u8,
+        c: usize,
+    }
+
+    let x = P { a: 1usize, b: 2u8, c: 3usize };
+    let y = P { a: 1usize, b: 2u8, c: 4usize };
+
+    let _clone = x.clone();
+    assert!(x != y);
+    assert_eq!(x.partial_cmp(&y).unwrap(), x.cmp(&y));
+    x.hash(&mut DefaultHasher::new());
+    P::default();
+    format!("{:?}", x);
+}
+
+fn main() {
+    test_basic();
+    test_unsizing();
+    test_drop();
+    test_inner_packed();
+    test_static();
+    test_derive();
+}
diff --git a/src/tools/miri/tests/pass/panic/catch_panic.rs b/src/tools/miri/tests/pass/panic/catch_panic.rs
new file mode 100644
index 0000000000000..308904406538c
--- /dev/null
+++ b/src/tools/miri/tests/pass/panic/catch_panic.rs
@@ -0,0 +1,119 @@
+// We test the `align_offset` panic below, make sure we test the interpreter impl and not the "real" one.
+//@compile-flags: -Zmiri-symbolic-alignment-check -Zmiri-permissive-provenance
+#![feature(never_type)]
+#![allow(unconditional_panic, non_fmt_panics)]
+
+use std::cell::Cell;
+use std::panic::{catch_unwind, AssertUnwindSafe};
+
+thread_local! {
+    static MY_COUNTER: Cell<usize> = Cell::new(0);
+    static DROPPED: Cell<bool> = Cell::new(false);
+    static HOOK_CALLED: Cell<bool> = Cell::new(false);
+}
+
+struct DropTester;
+impl Drop for DropTester {
+    fn drop(&mut self) {
+        DROPPED.with(|c| {
+            c.set(true);
+        });
+    }
+}
+
+fn do_panic_counter(do_panic: impl FnOnce(usize) -> !) {
+    // If this gets leaked, it will be easy to spot
+    // in Miri's leak report
+    let _string = "LEAKED FROM do_panic_counter".to_string();
+
+    // When we panic, this should get dropped during unwinding
+    let _drop_tester = DropTester;
+
+    // Check for bugs in Miri's panic implementation.
+    // If do_panic_counter() somehow gets called more than once,
+    // we'll generate a different panic message and stderr will differ.
+    let old_val = MY_COUNTER.with(|c| {
+        let val = c.get();
+        c.set(val + 1);
+        val
+    });
+    do_panic(old_val);
+}
+
+fn main() {
+    let prev = std::panic::take_hook();
+    std::panic::set_hook(Box::new(move |panic_info| {
+        HOOK_CALLED.with(|h| h.set(true));
+        prev(panic_info)
+    }));
+
+    // Std panics
+    test(None, |_old_val| std::panic!("Hello from panic: std"));
+    test(None, |old_val| std::panic!(format!("Hello from panic: {:?}", old_val)));
+    test(None, |old_val| std::panic!("Hello from panic: {:?}", old_val));
+    test(None, |_old_val| std::panic!(1337));
+
+    // Core panics
+    test(None, |_old_val| core::panic!("Hello from panic: core"));
+    test(None, |old_val| core::panic!(&format!("Hello from panic: {:?}", old_val)));
+    test(None, |old_val| core::panic!("Hello from panic: {:?}", old_val));
+
+    // Built-in panics; also make sure the message is right.
+    test(Some("index out of bounds: the len is 3 but the index is 4"), |_old_val| {
+        let _val = [0, 1, 2][4];
+        loop {}
+    });
+    test(Some("attempt to divide by zero"), |_old_val| {
+        let _val = 1 / 0;
+        loop {}
+    });
+
+    test(Some("align_offset: align is not a power-of-two"), |_old_val| {
+        (0usize as *const u8).align_offset(3);
+        loop {}
+    });
+
+    // Assertion and debug assertion
+    test(None, |_old_val| {
+        assert!(false);
+        loop {}
+    });
+    test(None, |_old_val| {
+        debug_assert!(false);
+        loop {}
+    });
+
+    eprintln!("Success!"); // Make sure we get this in stderr
+}
+
+fn test(expect_msg: Option<&str>, do_panic: impl FnOnce(usize) -> !) {
+    // Reset test flags.
+    DROPPED.with(|c| c.set(false));
+    HOOK_CALLED.with(|c| c.set(false));
+
+    // Cause and catch a panic.
+    let res = catch_unwind(AssertUnwindSafe(|| {
+        let _string = "LEAKED FROM CLOSURE".to_string();
+        do_panic_counter(do_panic)
+    }))
+    .expect_err("do_panic() did not panic!");
+
+    // See if we can extract the panic message.
+    let msg = if let Some(s) = res.downcast_ref::<String>() {
+        eprintln!("Caught panic message (String): {}", s);
+        Some(s.as_str())
+    } else if let Some(s) = res.downcast_ref::<&str>() {
+        eprintln!("Caught panic message (&str): {}", s);
+        Some(*s)
+    } else {
+        eprintln!("Failed to get caught panic message.");
+        None
+    };
+    if let Some(expect_msg) = expect_msg {
+        assert_eq!(expect_msg, msg.unwrap());
+    }
+
+    // Test flags.
+    assert!(DROPPED.with(|c| c.get()));
+    assert!(HOOK_CALLED.with(|c| c.get()));
+}
diff --git a/src/tools/miri/tests/pass/panic/catch_panic.stderr b/src/tools/miri/tests/pass/panic/catch_panic.stderr
new file mode 100644
index 0000000000000..0ced5588cc12c
--- /dev/null
+++ b/src/tools/miri/tests/pass/panic/catch_panic.stderr
@@ -0,0 +1,26 @@
+thread 'main' panicked at 'Hello from panic: std', $DIR/catch_panic.rs:LL:CC
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
+Caught panic message (&str): Hello from panic: std
+thread 'main' panicked at 'Hello from panic: 1', $DIR/catch_panic.rs:LL:CC
+Caught panic message (String): Hello from panic: 1
+thread 'main' panicked at 'Hello from panic: 2', $DIR/catch_panic.rs:LL:CC
+Caught panic message (String): Hello from panic: 2
+thread 'main' panicked at 'Box<dyn Any>', $DIR/catch_panic.rs:LL:CC
+Failed to get caught panic message.
+thread 'main' panicked at 'Hello from panic: core', $DIR/catch_panic.rs:LL:CC
+Caught panic message (&str): Hello from panic: core
+thread 'main' panicked at 'Hello from panic: 5', $DIR/catch_panic.rs:LL:CC
+Caught panic message (String): Hello from panic: 5
+thread 'main' panicked at 'Hello from panic: 6', $DIR/catch_panic.rs:LL:CC
+Caught panic message (String): Hello from panic: 6
+thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 4', $DIR/catch_panic.rs:LL:CC
+Caught panic message (String): index out of bounds: the len is 3 but the index is 4
+thread 'main' panicked at 'attempt to divide by zero', $DIR/catch_panic.rs:LL:CC
+Caught panic message (&str): attempt to divide by zero
+thread 'main' panicked at 'align_offset: align is not a power-of-two', RUSTLIB/core/src/ptr/const_ptr.rs:LL:CC
+Caught panic message (&str): align_offset: align is not a power-of-two
+thread 'main' panicked at 'assertion failed: false', $DIR/catch_panic.rs:LL:CC
+Caught panic message (&str): assertion failed: false
+thread 'main' panicked at 'assertion failed: false', $DIR/catch_panic.rs:LL:CC
+Caught panic message (&str): assertion failed: false
+Success!
diff --git a/src/tools/miri/tests/pass/panic/concurrent-panic.rs b/src/tools/miri/tests/pass/panic/concurrent-panic.rs
new file mode 100644
index 0000000000000..342269c6acbe3
--- /dev/null
+++ b/src/tools/miri/tests/pass/panic/concurrent-panic.rs
@@ -0,0 +1,93 @@
+//@ignore-target-windows: Condvars on Windows are not supported yet.
+// We are making scheduler assumptions here.
+//@compile-flags: -Zmiri-preemption-rate=0
+
+//! Cause a panic in one thread while another thread is unwinding. This checks
+//! that separate threads have their own panicking state.
+
+use std::sync::{Arc, Condvar, Mutex};
+use std::thread::{spawn, JoinHandle};
+
+struct BlockOnDrop(Option<JoinHandle<()>>);
+
+impl BlockOnDrop {
+    fn new(handle: JoinHandle<()>) -> BlockOnDrop {
+        BlockOnDrop(Some(handle))
+    }
+}
+
+impl Drop for BlockOnDrop {
+    fn drop(&mut self) {
+        eprintln!("Thread 2 blocking on thread 1");
+        let _ = self.0.take().unwrap().join();
+        eprintln!("Thread 1 has exited");
+    }
+}
+
+fn main() {
+    let t1_started_pair = Arc::new((Mutex::new(false), Condvar::new()));
+    let t2_started_pair = Arc::new((Mutex::new(false), Condvar::new()));
+
+    let t1_continue_mutex = Arc::new(Mutex::new(()));
+    let t1_continue_guard = t1_continue_mutex.lock();
+
+    let t1 = {
+        let t1_started_pair = t1_started_pair.clone();
+        let t1_continue_mutex = t1_continue_mutex.clone();
+        spawn(move || {
+            eprintln!("Thread 1 starting, will block on mutex");
+            let (mutex, condvar) = &*t1_started_pair;
+            *mutex.lock().unwrap() = true;
+            condvar.notify_one();
+
+            drop(t1_continue_mutex.lock());
+            panic!("panic in thread 1");
+        })
+    };
+
+    // Wait for thread 1 to signal it has started.
+    let (t1_started_mutex, t1_started_condvar) = &*t1_started_pair;
+    let mut t1_started_guard = t1_started_mutex.lock().unwrap();
+    while !*t1_started_guard {
+        t1_started_guard = t1_started_condvar.wait(t1_started_guard).unwrap();
+    }
+    eprintln!("Thread 1 reported it has started");
+    // Thread 1 should now be blocked waiting on t1_continue_mutex.
+
+    let t2 = {
+        let t2_started_pair = t2_started_pair.clone();
+        let block_on_drop = BlockOnDrop::new(t1);
+        spawn(move || {
+            let _ = block_on_drop;
+
+            let (mutex, condvar) = &*t2_started_pair;
+            *mutex.lock().unwrap() = true;
+            condvar.notify_one();
+
+            panic!("panic in thread 2");
+        })
+    };
+
+    // Wait for thread 2 to signal it has started.
+    let (t2_started_mutex, t2_started_condvar) = &*t2_started_pair;
+    let mut t2_started_guard = t2_started_mutex.lock().unwrap();
+    while !*t2_started_guard {
+        t2_started_guard = t2_started_condvar.wait(t2_started_guard).unwrap();
+    }
+    eprintln!("Thread 2 reported it has started");
+    // Thread 2 should now have already panicked and be in the middle of
+    // unwinding. It should now be blocked on joining thread 1.
+
+    // Unlock t1_continue_mutex, and allow thread 1 to proceed.
+    eprintln!("Unlocking mutex");
+    drop(t1_continue_guard);
+    // Thread 1 will panic the next time it is scheduled. This will test the
+    // behavior of interest to this test, whether Miri properly handles
+    // concurrent panics in two different threads.
+
+    // Block the main thread on waiting to join thread 2. Thread 2 should
+    // already be blocked on joining thread 1, so thread 1 will be scheduled
+    // to run next, as it is the only ready thread.
+    assert!(t2.join().is_err());
+    eprintln!("Thread 2 has exited");
+}
diff --git a/src/tools/miri/tests/pass/panic/concurrent-panic.stderr b/src/tools/miri/tests/pass/panic/concurrent-panic.stderr
new file mode 100644
index 0000000000000..fd8fabc89cccf
--- /dev/null
+++ b/src/tools/miri/tests/pass/panic/concurrent-panic.stderr
@@ -0,0 +1,10 @@
+Thread 1 starting, will block on mutex
+Thread 1 reported it has started
+thread '<unnamed>' panicked at 'panic in thread 2', $DIR/concurrent-panic.rs:LL:CC
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
+Thread 2 blocking on thread 1
+Thread 2 reported it has started
+Unlocking mutex
+thread '<unnamed>' panicked at 'panic in thread 1', $DIR/concurrent-panic.rs:LL:CC
+Thread 1 has exited
+Thread 2 has exited
diff --git a/src/tools/miri/tests/pass/panic/std-panic-locations.rs b/src/tools/miri/tests/pass/panic/std-panic-locations.rs
new file mode 100644
index 0000000000000..8781a371d51fa
--- /dev/null
+++ b/src/tools/miri/tests/pass/panic/std-panic-locations.rs
@@ -0,0 +1,39 @@
+//! Test that panic locations for `#[track_caller]` functions in std have the correct
+//! location reported.
+
+use std::sync::atomic::{AtomicUsize, Ordering};
+
+static HOOK_COUNT: AtomicUsize = AtomicUsize::new(0);
+
+fn main() {
+    // inspect the `PanicInfo` we receive to ensure the right file is the source
+    std::panic::set_hook(Box::new(|info| {
+        HOOK_COUNT.fetch_add(1, Ordering::Relaxed);
+        let actual = info.location().unwrap();
+        if actual.file() != file!() {
+            eprintln!("expected a location in the test file, found {:?}", actual);
+            panic!();
+        }
+    }));
+
+    fn assert_panicked(f: impl FnOnce() + std::panic::UnwindSafe) {
+        std::panic::catch_unwind(f).unwrap_err();
+    }
+
+    let nope: Option<()> = None;
+    assert_panicked(|| nope.unwrap());
+    assert_panicked(|| nope.expect(""));
+
+    let oops: Result<(), ()> = Err(());
+    assert_panicked(|| oops.unwrap());
+    assert_panicked(|| oops.expect(""));
+
+    let fine: Result<(), ()> = Ok(());
+    assert_panicked(|| fine.unwrap_err());
+    assert_panicked(|| fine.expect_err(""));
+
+    // Cleanup: reset to default hook.
+    drop(std::panic::take_hook());
+
+    assert_eq!(HOOK_COUNT.load(Ordering::Relaxed), 6);
+}
diff --git a/src/tools/miri/tests/pass/partially-uninit.rs b/src/tools/miri/tests/pass/partially-uninit.rs
new file mode 100644
index 0000000000000..db26a2084b1c0
--- /dev/null
+++ b/src/tools/miri/tests/pass/partially-uninit.rs
@@ -0,0 +1,15 @@
+use std::mem::{self, MaybeUninit};
+
+#[repr(C)]
+#[derive(Copy, Clone, Debug, PartialEq)]
+struct Demo(bool, u16);
+
+fn main() {
+    unsafe {
+        // Transmute-round-trip through a type with Scalar layout is lossless.
+        // This is tricky because that 'scalar' is *partially* uninitialized.
+        let x = Demo(true, 3);
+        let y: MaybeUninit<u32> = mem::transmute(x);
+        assert_eq!(x, mem::transmute(y));
+    }
+}
diff --git a/src/tools/miri/tests/pass/pointers.rs b/src/tools/miri/tests/pass/pointers.rs
new file mode 100644
index 0000000000000..d1340a04e0498
--- /dev/null
+++ b/src/tools/miri/tests/pass/pointers.rs
@@ -0,0 +1,143 @@
+//@compile-flags: -Zmiri-permissive-provenance
+#![feature(ptr_metadata, const_raw_ptr_comparison)]
+
+use std::mem::{self, transmute};
+use std::ptr;
+
+fn one_line_ref() -> i16 {
+    *&1
+}
+
+fn basic_ref() -> i16 {
+    let x = &1;
+    *x
+}
+
+fn basic_ref_mut() -> i16 {
+    let x = &mut 1;
+    *x += 2;
+    *x
+}
+
+fn basic_ref_mut_var() -> i16 {
+    let mut a = 1;
+    {
+        let x = &mut a;
+        *x += 2;
+    }
+    a
+}
+
+fn tuple_ref_mut() -> (i8, i8) {
+    let mut t = (10, 20);
+    {
+        let x = &mut t.1;
+        *x += 2;
+    }
+    t
+}
+
+fn match_ref_mut() -> i8 {
+    let mut t = (20, 22);
+    {
+        let opt = Some(&mut t);
+        match opt {
+            Some(&mut (ref mut x, ref mut y)) => *x += *y,
+            None => {}
+        }
+    }
+    t.0
+}
+
+fn dangling_pointer() -> *const i32 {
+    let b = Box::new((42, 42)); // make it bigger than the alignment, so that there is some "room" after this pointer
+    &b.0 as *const i32
+}
+
+fn wide_ptr_ops() {
+    let a: *const dyn Send = &1 as &dyn Send;
+    let b: *const dyn Send = &1 as &dyn Send;
+    let _val = a == b;
+    let _val = a != b;
+    let _val = a < b;
+    let _val = a <= b;
+    let _val = a > b;
+    let _val = a >= b;
+
+    let a: *const [u8] = unsafe { transmute((1usize, 1usize)) };
+    let b: *const [u8] = unsafe { transmute((1usize, 2usize)) };
+    // confirmed with rustc.
+    assert!(!(a == b));
+    assert!(a != b);
+    assert!(a <= b);
+    assert!(a < b);
+    assert!(!(a >= b));
+    assert!(!(a > b));
+}
+
+fn metadata_vtable() {
+    let p = &0i32 as &dyn std::fmt::Debug;
+    let meta: ptr::DynMetadata<_> = ptr::metadata(p as *const _);
+    assert_eq!(meta.size_of(), mem::size_of::<i32>());
+    assert_eq!(meta.align_of(), mem::align_of::<i32>());
+
+    type T = [i32; 16];
+    let p = &T::default() as &dyn std::fmt::Debug;
+    let meta: ptr::DynMetadata<_> = ptr::metadata(p as *const _);
+    assert_eq!(meta.size_of(), mem::size_of::<T>());
+    assert_eq!(meta.align_of(), mem::align_of::<T>());
+}
+
+fn main() {
+    assert_eq!(one_line_ref(), 1);
+    assert_eq!(basic_ref(), 1);
+    assert_eq!(basic_ref_mut(), 3);
+    assert_eq!(basic_ref_mut_var(), 3);
+    assert_eq!(tuple_ref_mut(), (10, 22));
+    assert_eq!(match_ref_mut(), 42);
+
+    // Compare even dangling pointers with NULL, and with others in the same allocation, including
+    // out-of-bounds.
+    assert!(dangling_pointer() != std::ptr::null());
+    assert!(match dangling_pointer() as usize {
+        0 => false,
+        _ => true,
+    });
+    let dangling = dangling_pointer();
+    assert!(dangling == dangling);
+    assert!(dangling.wrapping_add(1) != dangling);
+    assert!(dangling.wrapping_sub(1) != dangling);
+
+    // Compare pointer with BIG integers
+    let dangling = dangling as usize;
+    assert!(dangling != usize::MAX);
+    assert!(dangling != usize::MAX - 1);
+    assert!(dangling != usize::MAX - 2);
+    assert!(dangling != usize::MAX - 3); // this is even 4-aligned, but it still cannot be equal because of the extra "room" after this pointer
+    assert_eq!((usize::MAX - 3) % 4, 0); // just to be sure we got this right
+
+    // Compare pointer with unaligned integers
+    assert!(dangling != 1usize);
+    assert!(dangling != 2usize);
+    assert!(dangling != 3usize);
+    // 4 is a possible choice! So we cannot compare with that.
+    assert!(dangling != 5usize);
+    assert!(dangling != 6usize);
+    assert!(dangling != 7usize);
+
+    // Using inequality to do the comparison.
+    assert!(dangling > 0);
+    assert!(dangling > 1);
+    assert!(dangling > 2);
+    assert!(dangling > 3);
+    assert!(dangling >= 4);
+
+    // CTFE-specific equality tests, need to also work at runtime.
+    let addr = &13 as *const i32;
+    let addr2 = (addr as usize).wrapping_add(usize::MAX).wrapping_add(1);
+    assert_eq!(addr.guaranteed_eq(addr2 as *const _), Some(true));
+    assert_eq!(addr.guaranteed_ne(0x100 as *const _), Some(true));
+
+    wide_ptr_ops();
+    metadata_vtable();
+}
diff --git a/src/tools/miri/tests/pass/portable-simd.rs b/src/tools/miri/tests/pass/portable-simd.rs
new file mode 100644
index 0000000000000..173ac654b03d5
--- /dev/null
+++ b/src/tools/miri/tests/pass/portable-simd.rs
@@ -0,0 +1,396 @@
+//@compile-flags: -Zmiri-strict-provenance
+#![feature(portable_simd, platform_intrinsics)]
+use std::simd::*;
+
+fn simd_ops_f32() {
+    let a = f32x4::splat(10.0);
+    let b = f32x4::from_array([1.0, 2.0, 3.0, -4.0]);
+    assert_eq!(-b, f32x4::from_array([-1.0, -2.0, -3.0, 4.0]));
+    assert_eq!(a + b, f32x4::from_array([11.0, 12.0, 13.0, 6.0]));
+    assert_eq!(a - b, f32x4::from_array([9.0, 8.0, 7.0, 14.0]));
+    assert_eq!(a * b, f32x4::from_array([10.0, 20.0, 30.0, -40.0]));
+    assert_eq!(b / a, f32x4::from_array([0.1, 0.2, 0.3, -0.4]));
+    assert_eq!(a / f32x4::splat(2.0), f32x4::splat(5.0));
+    assert_eq!(a % b, f32x4::from_array([0.0, 0.0, 1.0, 2.0]));
+    assert_eq!(b.abs(), f32x4::from_array([1.0, 2.0, 3.0, 4.0]));
+    assert_eq!(a.simd_max(b * f32x4::splat(4.0)), f32x4::from_array([10.0, 10.0, 12.0, 10.0]));
+    assert_eq!(a.simd_min(b * f32x4::splat(4.0)), f32x4::from_array([4.0, 8.0, 10.0, -16.0]));
+
+    assert_eq!(a.mul_add(b, a), (a * b) + a);
+    assert_eq!(b.mul_add(b, a), (b * b) + a);
+    assert_eq!(a.mul_add(b, b), (a * b) + b);
+    assert_eq!(
+        f32x4::splat(-3.2).mul_add(b, f32x4::splat(f32::NEG_INFINITY)),
+        f32x4::splat(f32::NEG_INFINITY)
+    );
+    assert_eq!((a * a).sqrt(), a);
+    assert_eq!((b * b).sqrt(), b.abs());
+
+    assert_eq!(a.simd_eq(f32x4::splat(5.0) * b), Mask::from_array([false, true, false, false]));
+    assert_eq!(a.simd_ne(f32x4::splat(5.0) * b), Mask::from_array([true, false, true, true]));
+    assert_eq!(a.simd_le(f32x4::splat(5.0) * b), Mask::from_array([false, true, true, false]));
+    assert_eq!(a.simd_lt(f32x4::splat(5.0) * b), Mask::from_array([false, false, true, false]));
+    assert_eq!(a.simd_ge(f32x4::splat(5.0) * b), Mask::from_array([true, true, false, true]));
+    assert_eq!(a.simd_gt(f32x4::splat(5.0) * b), Mask::from_array([true, false, false, true]));
+
+    assert_eq!(a.reduce_sum(), 40.0);
+    assert_eq!(b.reduce_sum(), 2.0);
+    assert_eq!(a.reduce_product(), 100.0 * 100.0);
+    assert_eq!(b.reduce_product(), -24.0);
+    assert_eq!(a.reduce_max(), 10.0);
+    assert_eq!(b.reduce_max(), 3.0);
+    assert_eq!(a.reduce_min(), 10.0);
+    assert_eq!(b.reduce_min(), -4.0);
+
+    assert_eq!(
+        f32x2::from_array([0.0, f32::NAN]).simd_max(f32x2::from_array([f32::NAN, 0.0])),
+        f32x2::from_array([0.0, 0.0])
+    );
+    assert_eq!(f32x2::from_array([0.0, f32::NAN]).reduce_max(), 0.0);
+    assert_eq!(f32x2::from_array([f32::NAN, 0.0]).reduce_max(), 0.0);
+    assert_eq!(
+        f32x2::from_array([0.0, f32::NAN]).simd_min(f32x2::from_array([f32::NAN, 0.0])),
+        f32x2::from_array([0.0, 0.0])
+    );
+    assert_eq!(f32x2::from_array([0.0, f32::NAN]).reduce_min(), 0.0);
+    assert_eq!(f32x2::from_array([f32::NAN, 0.0]).reduce_min(), 0.0);
+}
+
+fn simd_ops_f64() {
+    let a = f64x4::splat(10.0);
+    let b = f64x4::from_array([1.0, 2.0, 3.0, -4.0]);
+    assert_eq!(-b, f64x4::from_array([-1.0, -2.0, -3.0, 4.0]));
+    assert_eq!(a + b, f64x4::from_array([11.0, 12.0, 13.0, 6.0]));
+    assert_eq!(a - b, f64x4::from_array([9.0, 8.0, 7.0, 14.0]));
+    assert_eq!(a * b, f64x4::from_array([10.0, 20.0, 30.0, -40.0]));
+    assert_eq!(b / a, f64x4::from_array([0.1, 0.2, 0.3, -0.4]));
+    assert_eq!(a / f64x4::splat(2.0), f64x4::splat(5.0));
+    assert_eq!(a % b, f64x4::from_array([0.0, 0.0, 1.0, 2.0]));
+    assert_eq!(b.abs(), f64x4::from_array([1.0, 2.0, 3.0, 4.0]));
+    assert_eq!(a.simd_max(b * f64x4::splat(4.0)), f64x4::from_array([10.0, 10.0, 12.0, 10.0]));
+    assert_eq!(a.simd_min(b * f64x4::splat(4.0)), f64x4::from_array([4.0, 8.0, 10.0, -16.0]));
+
+    assert_eq!(a.mul_add(b, a), (a * b) + a);
+    assert_eq!(b.mul_add(b, a), (b * b) + a);
+    assert_eq!(a.mul_add(b, b), (a * b) + b);
+    assert_eq!(
+        f64x4::splat(-3.2).mul_add(b, f64x4::splat(f64::NEG_INFINITY)),
+        f64x4::splat(f64::NEG_INFINITY)
+    );
+    assert_eq!((a * a).sqrt(), a);
+    assert_eq!((b * b).sqrt(), b.abs());
+
+    assert_eq!(a.simd_eq(f64x4::splat(5.0) * b), Mask::from_array([false, true, false, false]));
+    assert_eq!(a.simd_ne(f64x4::splat(5.0) * b), Mask::from_array([true, false, true, true]));
+    assert_eq!(a.simd_le(f64x4::splat(5.0) * b), Mask::from_array([false, true, true, false]));
+    assert_eq!(a.simd_lt(f64x4::splat(5.0) * b), Mask::from_array([false, false, true, false]));
+    assert_eq!(a.simd_ge(f64x4::splat(5.0) * b), Mask::from_array([true, true, false, true]));
+    assert_eq!(a.simd_gt(f64x4::splat(5.0) * b), Mask::from_array([true, false, false, true]));
+
+    assert_eq!(a.reduce_sum(), 40.0);
+    assert_eq!(b.reduce_sum(), 2.0);
+    assert_eq!(a.reduce_product(), 100.0 * 100.0);
+    assert_eq!(b.reduce_product(), -24.0);
+    assert_eq!(a.reduce_max(), 10.0);
+    assert_eq!(b.reduce_max(), 3.0);
+    assert_eq!(a.reduce_min(), 10.0);
+    assert_eq!(b.reduce_min(), -4.0);
+
+    assert_eq!(
+        f64x2::from_array([0.0, f64::NAN]).simd_max(f64x2::from_array([f64::NAN, 0.0])),
+        f64x2::from_array([0.0, 0.0])
+    );
+    assert_eq!(f64x2::from_array([0.0, f64::NAN]).reduce_max(), 0.0);
+    assert_eq!(f64x2::from_array([f64::NAN, 0.0]).reduce_max(), 0.0);
+    assert_eq!(
+        f64x2::from_array([0.0, f64::NAN]).simd_min(f64x2::from_array([f64::NAN, 0.0])),
+        f64x2::from_array([0.0, 0.0])
+    );
+    assert_eq!(f64x2::from_array([0.0, f64::NAN]).reduce_min(), 0.0);
+    assert_eq!(f64x2::from_array([f64::NAN, 0.0]).reduce_min(), 0.0);
+}
+
+fn simd_ops_i32() {
+    let a = i32x4::splat(10);
+    let b = i32x4::from_array([1, 2, 3, -4]);
+    assert_eq!(-b, i32x4::from_array([-1, -2, -3, 4]));
+    assert_eq!(a + b, i32x4::from_array([11, 12, 13, 6]));
+    assert_eq!(a - b, i32x4::from_array([9, 8, 7, 14]));
+    assert_eq!(a * b, i32x4::from_array([10, 20, 30, -40]));
+    assert_eq!(a / b, i32x4::from_array([10, 5, 3, -2]));
+    assert_eq!(a / i32x4::splat(2), i32x4::splat(5));
+    assert_eq!(i32x2::splat(i32::MIN) / i32x2::splat(-1), i32x2::splat(i32::MIN));
+    assert_eq!(a % b, i32x4::from_array([0, 0, 1, 2]));
+    assert_eq!(i32x2::splat(i32::MIN) % i32x2::splat(-1), i32x2::splat(0));
+    assert_eq!(b.abs(), i32x4::from_array([1, 2, 3, 4]));
+    assert_eq!(a.simd_max(b * i32x4::splat(4)), i32x4::from_array([10, 10, 12, 10]));
+    assert_eq!(a.simd_min(b * i32x4::splat(4)), i32x4::from_array([4, 8, 10, -16]));
+
+    assert_eq!(
+        i8x4::from_array([i8::MAX, -23, 23, i8::MIN]).saturating_add(i8x4::from_array([
+            1,
+            i8::MIN,
+            i8::MAX,
+            28
+        ])),
+        i8x4::from_array([i8::MAX, i8::MIN, i8::MAX, -100])
+    );
+    assert_eq!(
+        i8x4::from_array([i8::MAX, -28, 27, 42]).saturating_sub(i8x4::from_array([
+            1,
+            i8::MAX,
+            i8::MAX,
+            -80
+        ])),
+        i8x4::from_array([126, i8::MIN, -100, 122])
+    );
+    assert_eq!(
+        u8x4::from_array([u8::MAX, 0, 23, 42]).saturating_add(u8x4::from_array([
+            1,
+            1,
+            u8::MAX,
+            200
+        ])),
+        u8x4::from_array([u8::MAX, 1, u8::MAX, 242])
+    );
+    assert_eq!(
+        u8x4::from_array([u8::MAX, 0, 23, 42]).saturating_sub(u8x4::from_array([
+            1,
+            1,
+            u8::MAX,
+            200
+        ])),
+        u8x4::from_array([254, 0, 0, 0])
+    );
+
+    assert_eq!(!b, i32x4::from_array([!1, !2, !3, !-4]));
+    assert_eq!(b << i32x4::splat(2), i32x4::from_array([4, 8, 12, -16]));
+    assert_eq!(b >> i32x4::splat(1), i32x4::from_array([0, 1, 1, -2]));
+    assert_eq!(b & i32x4::splat(2), i32x4::from_array([0, 2, 2, 0]));
+    assert_eq!(b | i32x4::splat(2), i32x4::from_array([3, 2, 3, -2]));
+    assert_eq!(b ^ i32x4::splat(2), i32x4::from_array([3, 0, 1, -2]));
+
+    assert_eq!(a.simd_eq(i32x4::splat(5) * b), Mask::from_array([false, true, false, false]));
+    assert_eq!(a.simd_ne(i32x4::splat(5) * b), Mask::from_array([true, false, true, true]));
+    assert_eq!(a.simd_le(i32x4::splat(5) * b), Mask::from_array([false, true, true, false]));
+    assert_eq!(a.simd_lt(i32x4::splat(5) * b), Mask::from_array([false, false, true, false]));
+    assert_eq!(a.simd_ge(i32x4::splat(5) * b), Mask::from_array([true, true, false, true]));
+    assert_eq!(a.simd_gt(i32x4::splat(5) * b), Mask::from_array([true, false, false, true]));
+
+    assert_eq!(a.reduce_sum(), 40);
+    assert_eq!(b.reduce_sum(), 2);
+    assert_eq!(a.reduce_product(), 100 * 100);
+    assert_eq!(b.reduce_product(), -24);
+    assert_eq!(a.reduce_max(), 10);
+    assert_eq!(b.reduce_max(), 3);
+    assert_eq!(a.reduce_min(), 10);
+    assert_eq!(b.reduce_min(), -4);
+
+    assert_eq!(a.reduce_and(), 10);
+    assert_eq!(b.reduce_and(), 0);
+    assert_eq!(a.reduce_or(), 10);
+    assert_eq!(b.reduce_or(), -1);
+    assert_eq!(a.reduce_xor(), 0);
+    assert_eq!(b.reduce_xor(), -4);
+}
+
+fn simd_mask() {
+    let intmask = Mask::from_int(i32x4::from_array([0, -1, 0, 0]));
+    assert_eq!(intmask, Mask::from_array([false, true, false, false]));
+    assert_eq!(intmask.to_array(), [false, true, false, false]);
+
+    let values = [
+        true, false, false, true, false, false, true, false, true, true, false, false, false, true,
+        false, true,
+    ];
+    let mask = Mask::<i64, 16>::from_array(values);
+    let bitmask = mask.to_bitmask();
+    assert_eq!(bitmask, 0b1010001101001001);
+    assert_eq!(Mask::<i64, 16>::from_bitmask(bitmask), mask);
+
+    let values = [false, false, false, true];
+    let mask = Mask::<i64, 4>::from_array(values);
+    let bitmask = mask.to_bitmask();
+    assert_eq!(bitmask, 0b1000);
+    assert_eq!(Mask::<i64, 4>::from_bitmask(bitmask), mask);
+}
+
+fn simd_cast() {
+    // between integer types
+    assert_eq!(i32x4::from_array([1, 2, 3, -4]), i16x4::from_array([1, 2, 3, -4]).cast());
+    assert_eq!(i16x4::from_array([1, 2, 3, -4]), i32x4::from_array([1, 2, 3, -4]).cast());
+    assert_eq!(i32x4::from_array([1, -1, 3, 4]), u64x4::from_array([1, u64::MAX, 3, 4]).cast());
+
+    // float -> int
+    assert_eq!(
+        i8x4::from_array([127, -128, 127, -128]),
+        f32x4::from_array([127.99, -128.99, 999.0, -999.0]).cast()
+    );
+    assert_eq!(
+        i32x4::from_array([0, 1, -1, 2147483520]),
+        f32x4::from_array([
+            -0.0,
+            /*0x1.19999ap+0*/ f32::from_bits(0x3f8ccccd),
+            /*-0x1.19999ap+0*/ f32::from_bits(0xbf8ccccd),
+            2147483520.0
+        ])
+        .cast()
+    );
+    assert_eq!(
+        i32x8::from_array([i32::MAX, i32::MIN, i32::MAX, i32::MIN, i32::MAX, i32::MIN, 0, 0]),
+        f32x8::from_array([
+            2147483648.0f32,
+            -2147483904.0f32,
+            f32::MAX,
+            f32::MIN,
+            f32::INFINITY,
+            f32::NEG_INFINITY,
+            f32::NAN,
+            -f32::NAN,
+        ])
+        .cast()
+    );
+
+    // int -> float
+    assert_eq!(
+        f32x4::from_array([
+            -2147483648.0,
+            /*0x1.26580cp+30*/ f32::from_bits(0x4e932c06),
+            16777220.0,
+            -16777220.0,
+        ]),
+        i32x4::from_array([-2147483647i32, 1234567890i32, 16777219i32, -16777219i32]).cast()
+    );
+
+    // float -> float
+    assert_eq!(
+        f32x4::from_array([f32::INFINITY, f32::INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY]),
+        f64x4::from_array([f64::MAX, f64::INFINITY, f64::MIN, f64::NEG_INFINITY]).cast()
+    );
+
+    // unchecked casts
+    unsafe {
+        assert_eq!(
+            i32x4::from_array([0, 1, -1, 2147483520]),
+            f32x4::from_array([
+                -0.0,
+                /*0x1.19999ap+0*/ f32::from_bits(0x3f8ccccd),
+                /*-0x1.19999ap+0*/ f32::from_bits(0xbf8ccccd),
+                2147483520.0
+            ])
+            .to_int_unchecked()
+        );
+        assert_eq!(
+            u64x4::from_array([0, 10000000000000000, u64::MAX - 2047, 9223372036854775808]),
+            f64x4::from_array([
+                -0.99999999999,
+                1e16,
+                (u64::MAX - 1024) as f64,
+                9223372036854775808.0
+            ])
+            .to_int_unchecked()
+        );
+    }
+}
+
+fn simd_swizzle() {
+    use Which::*;
+
+    let a = f32x4::splat(10.0);
+    let b = f32x4::from_array([1.0, 2.0, 3.0, -4.0]);
+
+    assert_eq!(simd_swizzle!(b, [3, 0, 0, 2]), f32x4::from_array([-4.0, 1.0, 1.0, 3.0]));
+    assert_eq!(simd_swizzle!(b, [1, 2]), f32x2::from_array([2.0, 3.0]));
+    assert_eq!(simd_swizzle!(b, a, [First(3), Second(0)]), f32x2::from_array([-4.0, 10.0]));
+}
+
+fn simd_gather_scatter() {
+    let mut vec: Vec<i16> = vec![10, 11, 12, 13, 14, 15, 16, 17, 18];
+    let idxs = Simd::from_array([9, 3, 0, 17]);
+    let result = Simd::gather_or_default(&vec, idxs); // Note the lane that is out-of-bounds.
+    assert_eq!(result, Simd::from_array([0, 13, 10, 0]));
+
+    let idxs = Simd::from_array([9, 3, 0, 0]);
+    Simd::from_array([-27, 82, -41, 124]).scatter(&mut vec, idxs);
+    assert_eq!(vec, vec![124, 11, 12, 82, 14, 15, 16, 17, 18]);
+}
+
+fn simd_round() {
+    assert_eq!(
+        f32x4::from_array([0.9, 1.001, 2.0, -4.5]).ceil(),
+        f32x4::from_array([1.0, 2.0, 2.0, -4.0])
+    );
+    assert_eq!(
+        f32x4::from_array([0.9, 1.001, 2.0, -4.5]).floor(),
+        f32x4::from_array([0.0, 1.0, 2.0, -5.0])
+    );
+    assert_eq!(
+        f32x4::from_array([0.9, 1.001, 2.0, -4.5]).round(),
+        f32x4::from_array([1.0, 1.0, 2.0, -5.0])
+    );
+    assert_eq!(
+        f32x4::from_array([0.9, 1.001, 2.0, -4.5]).trunc(),
+        f32x4::from_array([0.0, 1.0, 2.0, -4.0])
+    );
+
+    assert_eq!(
+        f64x4::from_array([0.9, 1.001, 2.0, -4.5]).ceil(),
+        f64x4::from_array([1.0, 2.0, 2.0, -4.0])
+    );
+    assert_eq!(
+        f64x4::from_array([0.9, 1.001, 2.0, -4.5]).floor(),
+        f64x4::from_array([0.0, 1.0, 2.0, -5.0])
+    );
+    assert_eq!(
+        f64x4::from_array([0.9, 1.001, 2.0, -4.5]).round(),
+        f64x4::from_array([1.0, 1.0, 2.0, -5.0])
+    );
+    assert_eq!(
+        f64x4::from_array([0.9, 1.001, 2.0, -4.5]).trunc(),
+        f64x4::from_array([0.0, 1.0, 2.0, -4.0])
+    );
+}
+
+fn simd_intrinsics() {
+    extern "platform-intrinsic" {
+        fn simd_eq<T, U>(x: T, y: T) -> U;
+        fn simd_reduce_any<T>(x: T) -> bool;
+        fn simd_reduce_all<T>(x: T) -> bool;
+        fn simd_select<M, T>(m: M, yes: T, no: T) -> T;
+    }
+    unsafe {
+        // Make sure simd_eq returns all-1 for `true`
+        let a = i32x4::splat(10);
+        let b = i32x4::from_array([1, 2, 10, 4]);
+        let c: i32x4 = simd_eq(a, b);
+        assert_eq!(c, i32x4::from_array([0, 0, -1, 0]));
+
+        assert!(!simd_reduce_any(i32x4::splat(0)));
+        assert!(simd_reduce_any(i32x4::splat(-1)));
+        assert!(simd_reduce_any(i32x2::from_array([0, -1])));
+        assert!(!simd_reduce_all(i32x4::splat(0)));
+        assert!(simd_reduce_all(i32x4::splat(-1)));
+        assert!(!simd_reduce_all(i32x2::from_array([0, -1])));
+
+        assert_eq!(
+            simd_select(i8x4::from_array([0, -1, -1, 0]), a, b),
+            i32x4::from_array([1, 10, 10, 4])
+        );
+        assert_eq!(
+            simd_select(i8x4::from_array([0, -1, -1, 0]), b, a),
+            i32x4::from_array([10, 2, 10, 10])
+        );
+    }
+}
+
+fn main() {
+    simd_mask();
+    simd_ops_f32();
+    simd_ops_f64();
+    simd_ops_i32();
+    simd_cast();
+    simd_swizzle();
+    simd_gather_scatter();
+    simd_round();
+    simd_intrinsics();
+}
diff --git a/src/tools/miri/tests/pass/products.rs b/src/tools/miri/tests/pass/products.rs
new file mode 100644
index 0000000000000..767d756e5ae18
--- /dev/null
+++ b/src/tools/miri/tests/pass/products.rs
@@ -0,0 +1,35 @@
+fn tuple() -> (i16,) {
+    (1,)
+}
+
+fn tuple_2() -> (i16, i16) {
+    (1, 2)
+}
+
+fn tuple_5() -> (i16, i16, i16, i16, i16) {
+    (1, 2, 3, 4, 5)
+}
+
+#[derive(Debug, PartialEq)]
+struct Pair {
+    x: i8,
+    y: i8,
+}
+
+fn pair() -> Pair {
+    Pair { x: 10, y: 20 }
+}
+
+fn field_access() -> (i8, i8) {
+    let mut p = Pair { x: 10, y: 20 };
+    p.x += 5;
+    (p.x, p.y)
+}
+
+fn main() {
+    assert_eq!(tuple(), (1,));
+    assert_eq!(tuple_2(), (1, 2));
+    assert_eq!(tuple_5(), (1, 2, 3, 4, 5));
+    assert_eq!(pair(), Pair { x: 10, y: 20 });
+    assert_eq!(field_access(), (15, 20));
+}
diff --git a/src/tools/miri/tests/pass/ptr_int_casts.rs b/src/tools/miri/tests/pass/ptr_int_casts.rs
new file mode 100644
index 0000000000000..3044ac092b7d4
--- /dev/null
+++ b/src/tools/miri/tests/pass/ptr_int_casts.rs
@@ -0,0 +1,73 @@
+//@compile-flags: -Zmiri-permissive-provenance
+use std::mem;
+use std::ptr;
+
+fn eq_ref<T>(x: &T, y: &T) -> bool {
+    x as *const _ == y as *const _
+}
+
+fn f() -> i32 {
+    42
+}
+
+fn ptr_int_casts() {
+    // int-ptr-int
+    assert_eq!(1 as *const i32 as usize, 1);
+    assert_eq!((1 as *const i32).wrapping_offset(4) as usize, 1 + 4 * 4);
+
+    // negative overflowing wrapping_offset (going through memory because
+    // this used to trigger an ICE on 32bit)
+    let val = &mut ptr::null();
+    *val = (1 as *const u8).wrapping_offset(-4);
+    assert_eq!(*val as usize, usize::MAX - 2);
+
+    // ptr-int-ptr
+    {
+        let x = 13;
+        let mut y = &x as &_ as *const _ as usize;
+        y += 13;
+        y -= 13;
+        let y = y as *const _;
+        assert!(eq_ref(&x, unsafe { &*y }));
+    }
+
+    // fnptr-int-fnptr
+    {
+        let x: fn() -> i32 = f;
+        let y: *mut u8 = unsafe { mem::transmute(x as fn() -> i32) };
+        let mut y = y as usize;
+        y += 13;
+        y -= 13;
+        let x: fn() -> i32 = unsafe { mem::transmute(y as *mut u8) };
+        assert_eq!(x(), 42);
+    }
+
+    // involving types other than usize
+    assert_eq!((-1i32) as usize as *const i32 as usize, (-1i32) as usize);
+}
+
+fn ptr_int_ops() {
+    let v = [1i16, 2];
+    let x = &v[1] as *const i16 as usize;
+    // arithmetic
+    let _y = x + 4;
+    let _y = 4 + x;
+    let _y = x - 2;
+    // bit-operations, covered by alignment
+    assert_eq!(x & 1, 0);
+    assert_eq!(x & 0, 0);
+    assert_eq!(1 & (x + 1), 1);
+    let _y = !1 & x;
+    let _y = !0 & x;
+    let _y = x & !1;
+    // remainder, covered by alignment
+    assert_eq!(x % 2, 0);
+    assert_eq!((x + 1) % 2, 1);
+    // remainder with 1 is always 0
+    assert_eq!(x % 1, 0);
+}
+
+fn main() {
+    ptr_int_casts();
+    ptr_int_ops();
+}
diff --git a/src/tools/miri/tests/pass/ptr_int_from_exposed.rs b/src/tools/miri/tests/pass/ptr_int_from_exposed.rs
new file mode 100644
index 0000000000000..ef7ff34d26b23
--- /dev/null
+++ b/src/tools/miri/tests/pass/ptr_int_from_exposed.rs
@@ -0,0 +1,62 @@
+//@compile-flags: -Zmiri-permissive-provenance
+#![feature(strict_provenance)]
+
+use std::ptr;
+
+/// Ensure we can expose the address of a pointer that is out-of-bounds
+fn ptr_roundtrip_out_of_bounds() {
+    let x: i32 = 3;
+    let x_ptr = &x as *const i32;
+
+    let x_usize = x_ptr.wrapping_offset(128).expose_addr();
+
+    let ptr = ptr::from_exposed_addr::<i32>(x_usize).wrapping_offset(-128);
+    assert_eq!(unsafe { *ptr }, 3);
+}
+
+/// Ensure that we can move between allocations after casting back to a ptr
+fn ptr_roundtrip_confusion() {
+    let x: i32 = 0;
+    let y: i32 = 1;
+
+    let x_ptr = &x as *const i32;
+    let y_ptr = &y as *const i32;
+
+    let x_usize = x_ptr.expose_addr();
+    let y_usize = y_ptr.expose_addr();
+
+    let ptr = ptr::from_exposed_addr::<i32>(y_usize);
+    let ptr = ptr.with_addr(x_usize);
+    assert_eq!(unsafe { *ptr }, 0);
+}
+
+/// Ensure we can cast back a different integer than the one we got when exposing.
+fn ptr_roundtrip_imperfect() {
+    let x: u8 = 3;
+    let x_ptr = &x as *const u8;
+
+    let x_usize = x_ptr.expose_addr() + 128;
+
+    let ptr = ptr::from_exposed_addr::<u8>(x_usize).wrapping_offset(-128);
+    assert_eq!(unsafe { *ptr }, 3);
+}
+
+/// Ensure that we can roundtrip through a pointer with an address of 0
+fn ptr_roundtrip_null() {
+    let x = &42;
+    let x_ptr = x as *const i32;
+    let x_null_ptr = x_ptr.with_addr(0); // addr 0, but still the provenance of x
+    let null = x_null_ptr.expose_addr();
+    assert_eq!(null, 0);
+
+    let x_null_ptr_copy = ptr::from_exposed_addr::<i32>(null); // just a roundtrip, so has provenance of x (angelically)
+    let x_ptr_copy = x_null_ptr_copy.with_addr(x_ptr.addr()); // addr of x and provenance of x
+    assert_eq!(unsafe { *x_ptr_copy }, 42);
+}
+
+fn main() {
+    ptr_roundtrip_out_of_bounds();
+    ptr_roundtrip_confusion();
+    ptr_roundtrip_imperfect();
+    ptr_roundtrip_null();
+}
diff --git a/src/tools/miri/tests/pass/ptr_int_transmute.rs b/src/tools/miri/tests/pass/ptr_int_transmute.rs
new file mode 100644
index 0000000000000..ba50480c5399a
--- /dev/null
+++ b/src/tools/miri/tests/pass/ptr_int_transmute.rs
@@ -0,0 +1,22 @@
+// Test what happens when we read parts of a pointer.
+// Related to <https://github.com/rust-lang/rust/issues/69488>.
+fn ptr_partial_read() {
+    let x = 13;
+    let y = &x;
+    let z = &y as *const &i32 as *const u8;
+
+    // This just strips provenance, but should work fine otherwise.
+    let _val = unsafe { *z };
+}
+
+fn transmute_strip_provenance() {
+    let r = &mut 42;
+    let addr = r as *mut _ as usize;
+    let i: usize = unsafe { std::mem::transmute(r) };
+    assert_eq!(i, addr);
+}
+
+fn main() {
+    ptr_partial_read();
+    transmute_strip_provenance();
+}
diff --git a/src/tools/miri/tests/pass/ptr_offset.rs b/src/tools/miri/tests/pass/ptr_offset.rs
new file mode 100644
index 0000000000000..95eac8522fb40
--- /dev/null
+++ b/src/tools/miri/tests/pass/ptr_offset.rs
@@ -0,0 +1,92 @@
+//@compile-flags: -Zmiri-permissive-provenance
+#![feature(ptr_sub_ptr)]
+use std::{mem, ptr};
+
+fn main() {
+    smoke();
+    test_offset_from();
+    test_vec_into_iter();
+    ptr_arith_offset();
+    ptr_arith_offset_overflow();
+    ptr_offset();
+}
+
+fn smoke() {
+    // Smoke-test various offsetting operations.
+    let ptr = &5;
+    let ptr = ptr as *const i32;
+    let _val = ptr.wrapping_offset(0);
+    let _val = unsafe { ptr.offset(0) };
+    let _val = ptr.wrapping_add(0);
+    let _val = unsafe { ptr.add(0) };
+    let _val = ptr.wrapping_sub(0);
+    let _val = unsafe { ptr.sub(0) };
+    let _val = unsafe { ptr.offset_from(ptr) };
+    let _val = unsafe { ptr.sub_ptr(ptr) };
+}
+
+fn test_offset_from() {
+    unsafe {
+        let buf = [0u32; 4];
+
+        let x = buf.as_ptr() as *const u8;
+        let y = x.offset(12);
+
+        assert_eq!(y.offset_from(x), 12);
+        assert_eq!(y.sub_ptr(x), 12);
+        assert_eq!(x.offset_from(y), -12);
+        assert_eq!((y as *const u32).offset_from(x as *const u32), 12 / 4);
+        assert_eq!((x as *const u32).offset_from(y as *const u32), -12 / 4);
+
+        let x = (((x as usize) * 2) / 2) as *const u8;
+        assert_eq!(y.offset_from(x), 12);
+        assert_eq!(y.sub_ptr(x), 12);
+        assert_eq!(x.offset_from(y), -12);
+    }
+}
+
+// This also internally uses offset_from.
+fn test_vec_into_iter() {
+    let v = Vec::<i32>::new();
+    let i = v.into_iter();
+    i.size_hint();
+}
+
+fn ptr_arith_offset() {
+    let v = [1i16, 2];
+    let x = &v as *const [i16] as *const i16;
+    let x = x.wrapping_offset(1);
+    assert_eq!(unsafe { *x }, 2);
+}
+
+fn ptr_arith_offset_overflow() {
+    let v = [1i16, 2];
+    let x = &mut ptr::null(); // going through memory as there are more sanity checks along that path
+    *x = v.as_ptr().wrapping_offset(1); // ptr to the 2nd element
+    // Adding 2*isize::max and then 1 is like substracting 1
+    *x = x.wrapping_offset(isize::MAX);
+    *x = x.wrapping_offset(isize::MAX);
+    *x = x.wrapping_offset(1);
+    assert_eq!(unsafe { **x }, 1);
+}
+
+fn ptr_offset() {
+    fn f() -> i32 {
+        42
+    }
+
+    let v = [1i16, 2];
+    let x = &v as *const [i16; 2] as *const i16;
+    let x = unsafe { x.offset(1) };
+    assert_eq!(unsafe { *x }, 2);
+
+    // fn ptr offset
+    unsafe {
+        let p = f as fn() -> i32 as usize;
+        let x = (p as *mut u32).offset(0) as usize;
+        // *cast* to ptr, then transmute to fn ptr.
+        // (transmuting int to [fn]ptr causes trouble.)
+        let f: fn() -> i32 = mem::transmute(x as *const ());
+        assert_eq!(f(), 42);
+    }
+}
diff --git a/src/tools/miri/tests/pass/ptr_raw.rs b/src/tools/miri/tests/pass/ptr_raw.rs
new file mode 100644
index 0000000000000..3ba0fba9a9412
--- /dev/null
+++ b/src/tools/miri/tests/pass/ptr_raw.rs
@@ -0,0 +1,25 @@
+fn basic_raw() {
+    let mut x = 12;
+    let x = &mut x;
+
+    assert_eq!(*x, 12);
+
+    let raw = x as *mut i32;
+    unsafe {
+        *raw = 42;
+    }
+
+    assert_eq!(*x, 42);
+
+    let raw = x as *mut i32;
+    unsafe {
+        *raw = 12;
+    }
+    *x = 23;
+
+    assert_eq!(*x, 23);
+}
+
+fn main() {
+    basic_raw();
+}
diff --git a/src/tools/miri/tests/pass/rc.rs b/src/tools/miri/tests/pass/rc.rs
new file mode 100644
index 0000000000000..569dbc459a5b5
--- /dev/null
+++ b/src/tools/miri/tests/pass/rc.rs
@@ -0,0 +1,153 @@
+//@compile-flags: -Zmiri-strict-provenance
+#![feature(new_uninit)]
+#![feature(get_mut_unchecked)]
+
+use std::cell::{Cell, RefCell};
+use std::fmt::Debug;
+use std::rc::{Rc, Weak};
+use std::sync::{Arc, Weak as ArcWeak};
+
+fn rc_refcell() {
+    let r = Rc::new(RefCell::new(42));
+    let r2 = r.clone();
+    *r.borrow_mut() += 10;
+    let x = *r2.borrow();
+    assert_eq!(x, 52);
+}
+
+fn rc_cell() {
+    let r = Rc::new(Cell::new(42));
+    let r2 = r.clone();
+    let x = r.get();
+    r2.set(x + x);
+    assert_eq!(r.get(), 84);
+}
+
+fn rc_refcell2() {
+    let r = Rc::new(RefCell::new(42));
+    let r2 = r.clone();
+    *r.borrow_mut() += 10;
+    let x = r2.borrow();
+    let r3 = r.clone();
+    let y = r3.borrow();
+    assert_eq!((*x + *y) / 2, 52);
+}
+
+fn rc_raw() {
+    let r = Rc::new(0);
+    let r2 = Rc::into_raw(r.clone());
+    let r2 = unsafe { Rc::from_raw(r2) };
+    assert!(Rc::ptr_eq(&r, &r2));
+    drop(r);
+    assert!(Rc::try_unwrap(r2).is_ok());
+}
+
+fn arc() {
+    fn test() -> Arc<i32> {
+        let a = Arc::new(42);
+        a
+    }
+    assert_eq!(*test(), 42);
+
+    let raw = ArcWeak::into_raw(ArcWeak::<usize>::new());
+    drop(unsafe { ArcWeak::from_raw(raw) });
+}
+
+// Make sure this Rc doesn't fall apart when touched
+fn check_unique_rc<T: ?Sized>(mut r: Rc<T>) {
+    let r2 = r.clone();
+    assert!(Rc::get_mut(&mut r).is_none());
+    drop(r2);
+    assert!(Rc::get_mut(&mut r).is_some());
+}
+
+fn rc_from() {
+    check_unique_rc::<[_]>(Rc::from(&[1, 2, 3] as &[_]));
+    check_unique_rc::<[_]>(Rc::from(vec![1, 2, 3]));
+    check_unique_rc::<[_]>(Rc::from(Box::new([1, 2, 3]) as Box<[_]>));
+    check_unique_rc::<str>(Rc::from("Hello, World!"));
+}
+
+fn rc_fat_ptr_eq() {
+    let p = Rc::new(1) as Rc<dyn Debug>;
+    let a: *const dyn Debug = &*p;
+    let r = Rc::into_raw(p);
+    assert!(a == r);
+    drop(unsafe { Rc::from_raw(r) });
+}
+
+/// Taken from the `Weak::into_raw` doctest.
+fn weak_into_raw() {
+    let strong = Rc::new(42);
+    let weak = Rc::downgrade(&strong);
+    let raw = Weak::into_raw(weak);
+
+    assert_eq!(1, Rc::weak_count(&strong));
+    assert_eq!(42, unsafe { *raw });
+
+    drop(unsafe { Weak::from_raw(raw) });
+    assert_eq!(0, Rc::weak_count(&strong));
+
+    let raw = Weak::into_raw(Weak::<usize>::new());
+    drop(unsafe { Weak::from_raw(raw) });
+}
+
+/// Taken from the `Weak::from_raw` doctest.
+fn weak_from_raw() {
+    let strong = Rc::new(42);
+
+    let raw_1 = Weak::into_raw(Rc::downgrade(&strong));
+    let raw_2 = Weak::into_raw(Rc::downgrade(&strong));
+
+    assert_eq!(2, Rc::weak_count(&strong));
+
+    assert_eq!(42, *Weak::upgrade(&unsafe { Weak::from_raw(raw_1) }).unwrap());
+    assert_eq!(1, Rc::weak_count(&strong));
+
+    drop(strong);
+
+    // Decrement the last weak count.
+    assert!(Weak::upgrade(&unsafe { Weak::from_raw(raw_2) }).is_none());
+}
+
+fn rc_uninit() {
+    let mut five = Rc::<Box<u32>>::new_uninit();
+    let five = unsafe {
+        // Deferred initialization:
+        Rc::get_mut_unchecked(&mut five).as_mut_ptr().write(Box::new(5));
+        five.assume_init()
+    };
+    assert_eq!(**five, 5)
+}
+
+fn rc_uninit_slice() {
+    let mut values = Rc::<[Box<usize>]>::new_uninit_slice(3);
+
+    let values = unsafe {
+        // Deferred initialization:
+        Rc::get_mut_unchecked(&mut values)[0].as_mut_ptr().write(Box::new(0));
+        Rc::get_mut_unchecked(&mut values)[1].as_mut_ptr().write(Box::new(1));
+        Rc::get_mut_unchecked(&mut values)[2].as_mut_ptr().write(Box::new(2));
+
+        values.assume_init()
+    };
+
+    for (idx, i) in values.iter().enumerate() {
+        assert_eq!(idx, **i);
+    }
+}
+
+fn main() {
+    rc_fat_ptr_eq();
+    rc_refcell();
+    rc_refcell2();
+    rc_cell();
+    rc_raw();
+    rc_from();
+    weak_into_raw();
+    weak_from_raw();
+    rc_uninit();
+    rc_uninit_slice();
+
+    arc();
+}
diff --git a/src/tools/miri/tests/pass/recursive_static.rs b/src/tools/miri/tests/pass/recursive_static.rs
new file mode 100644
index 0000000000000..77f2902917a1c
--- /dev/null
+++ b/src/tools/miri/tests/pass/recursive_static.rs
@@ -0,0 +1,9 @@
+struct S(&'static S);
+static S1: S = S(&S2);
+static S2: S = S(&S1);
+
+fn main() {
+    let p: *const S = S2.0;
+    let q: *const S = &S1;
+    assert_eq!(p, q);
+}
diff --git a/src/tools/miri/tests/pass/reentrant-println.rs b/src/tools/miri/tests/pass/reentrant-println.rs
new file mode 100644
index 0000000000000..e73e82b8ec9ed
--- /dev/null
+++ b/src/tools/miri/tests/pass/reentrant-println.rs
@@ -0,0 +1,17 @@
+use std::fmt::{Display, Error, Formatter};
+
+// This test case exercises std::sys_common::remutex::ReentrantMutex
+// by calling println!() from inside fmt.
+
+struct InterruptingCow;
+
+impl Display for InterruptingCow {
+    fn fmt(&self, _f: &mut Formatter<'_>) -> Result<(), Error> {
+        println!("Moo");
+        Ok(())
+    }
+}
+
+fn main() {
+    println!("\"Knock knock\" \"Who's {} there?\"", InterruptingCow);
+}
diff --git a/src/tools/miri/tests/pass/reentrant-println.stdout b/src/tools/miri/tests/pass/reentrant-println.stdout
new file mode 100644
index 0000000000000..8a57d32f84ca6
--- /dev/null
+++ b/src/tools/miri/tests/pass/reentrant-println.stdout
@@ -0,0 +1,2 @@
+"Knock knock" "Who's Moo
+ there?"
diff --git a/src/tools/miri/tests/pass/regions-lifetime-nonfree-late-bound.rs b/src/tools/miri/tests/pass/regions-lifetime-nonfree-late-bound.rs
new file mode 100644
index 0000000000000..c91ac36ed6b41
--- /dev/null
+++ b/src/tools/miri/tests/pass/regions-lifetime-nonfree-late-bound.rs
@@ -0,0 +1,40 @@
+// This is a regression test for the ICE from issue #10846.
+//
+// The original issue causing the ICE: the LUB-computations during
+// type inference were encountering late-bound lifetimes, and
+// asserting that such lifetimes should have already been substituted
+// with a concrete lifetime.
+//
+// However, those encounters were occurring within the lexical scope
+// of the binding for the late-bound lifetime; that is, the late-bound
+// lifetimes were perfectly valid. The core problem was that the type
+// folding code was over-zealously passing back all lifetimes when
+// doing region-folding, when really all clients of the region-folding
+// case only want to see *free* lifetime variables, not bound ones.
+
+#![feature(box_syntax)]
+
+pub fn main() {
+    fn explicit() {
+        fn test<F>(_x: Option<Box<F>>)
+        where
+            F: FnMut(Box<dyn for<'a> FnMut(&'a isize)>),
+        {
+        }
+        test(Some(box |_f: Box<dyn for<'a> FnMut(&'a isize)>| {}));
+    }
+
+    // The code below is shorthand for the code above (and more likely
+    // to represent what one encounters in practice).
+    fn implicit() {
+        fn test<F>(_x: Option<Box<F>>)
+        where
+            F: FnMut(Box<dyn FnMut(&isize)>),
+        {
+        }
+        test(Some(box |_f: Box<dyn FnMut(&isize)>| {}));
+    }
+
+    explicit();
+    implicit();
+}
diff --git a/src/tools/miri/tests/pass/rename_std.rs b/src/tools/miri/tests/pass/rename_std.rs
new file mode 100644
index 0000000000000..7e82e53e6be85
--- /dev/null
+++ b/src/tools/miri/tests/pass/rename_std.rs
@@ -0,0 +1,5 @@
+#![no_std]
+
+extern crate std as foo;
+
+fn main() {}
diff --git a/src/tools/miri/tests/pass/rfc1623.rs b/src/tools/miri/tests/pass/rfc1623.rs
new file mode 100644
index 0000000000000..76e2c01e74505
--- /dev/null
+++ b/src/tools/miri/tests/pass/rfc1623.rs
@@ -0,0 +1,71 @@
+#![allow(dead_code)] // tons of unused statics here...
+
+// very simple test for a 'static static with default lifetime
+static STATIC_STR: &str = "&'static str";
+const CONST_STR: &str = "&'static str";
+
+// this should be the same as without default:
+static EXPLICIT_STATIC_STR: &'static str = "&'static str";
+const EXPLICIT_CONST_STR: &'static str = "&'static str";
+
+// a function that elides to an unbound lifetime for both in- and output
+fn id_u8_slice(arg: &[u8]) -> &[u8] {
+    arg
+}
+
+// one with a function, argument elided
+static STATIC_SIMPLE_FN: &fn(&[u8]) -> &[u8] = &(id_u8_slice as fn(&[u8]) -> &[u8]);
+const CONST_SIMPLE_FN: &fn(&[u8]) -> &[u8] = &(id_u8_slice as fn(&[u8]) -> &[u8]);
+
+// this should be the same as without elision
+static STATIC_NON_ELIDED_FN: &for<'a> fn(&'a [u8]) -> &'a [u8] =
+    &(id_u8_slice as for<'a> fn(&'a [u8]) -> &'a [u8]);
+const CONST_NON_ELIDED_FN: &for<'a> fn(&'a [u8]) -> &'a [u8] =
+    &(id_u8_slice as for<'a> fn(&'a [u8]) -> &'a [u8]);
+
+// another function that elides, each to a different unbound lifetime
+fn multi_args(_a: &u8, _b: &u8, _c: &u8) {}
+
+static STATIC_MULTI_FN: &fn(&u8, &u8, &u8) = &(multi_args as fn(&u8, &u8, &u8));
+const CONST_MULTI_FN: &fn(&u8, &u8, &u8) = &(multi_args as fn(&u8, &u8, &u8));
+
+struct Foo<'a> {
+    bools: &'a [bool],
+}
+
+static STATIC_FOO: Foo = Foo { bools: &[true, false] };
+const CONST_FOO: Foo = Foo { bools: &[true, false] };
+
+type Bar<'a> = Foo<'a>;
+
+static STATIC_BAR: Bar = Bar { bools: &[true, false] };
+const CONST_BAR: Bar = Bar { bools: &[true, false] };
+
+type Baz<'a> = fn(&'a [u8]) -> Option<u8>;
+
+fn baz(e: &[u8]) -> Option<u8> {
+    e.first().map(|x| *x)
+}
+
+static STATIC_BAZ: &Baz = &(baz as Baz);
+const CONST_BAZ: &Baz = &(baz as Baz);
+
+static BYTES: &[u8] = &[1, 2, 3];
+
+fn main() {
+    // make sure that the lifetime is actually elided (and not defaulted)
+    let x = &[1u8, 2, 3];
+    STATIC_SIMPLE_FN(x);
+    CONST_SIMPLE_FN(x);
+
+    STATIC_BAZ(BYTES); // neees static lifetime
+    CONST_BAZ(BYTES);
+
+    // make sure this works with different lifetimes
+    let a = &1;
+    {
+        let b = &2;
+        let c = &3;
+        CONST_MULTI_FN(a, b, c);
+    }
+}
diff --git a/src/tools/miri/tests/pass/rust-lang-org.rs b/src/tools/miri/tests/pass/rust-lang-org.rs
new file mode 100644
index 0000000000000..7ba68e6b239c0
--- /dev/null
+++ b/src/tools/miri/tests/pass/rust-lang-org.rs
@@ -0,0 +1,21 @@
+// This code is editable and runnable!
+fn main() {
+    // A simple integer calculator:
+    // `+` or `-` means add or subtract by 1
+    // `*` or `/` means multiply or divide by 2
+
+    let program = "+ + * - /";
+    let mut accumulator = 0;
+
+    for token in program.chars() {
+        match token {
+            '+' => accumulator += 1,
+            '-' => accumulator -= 1,
+            '*' => accumulator *= 2,
+            '/' => accumulator /= 2,
+            _ => { /* ignore everything else */ }
+        }
+    }
+
+    assert_eq!(accumulator, 1);
+}
diff --git a/src/tools/miri/tests/pass/send-is-not-static-par-for.rs b/src/tools/miri/tests/pass/send-is-not-static-par-for.rs
new file mode 100644
index 0000000000000..642f75ecc09bf
--- /dev/null
+++ b/src/tools/miri/tests/pass/send-is-not-static-par-for.rs
@@ -0,0 +1,28 @@
+use std::sync::Mutex;
+
+fn par_for<I, F>(iter: I, f: F)
+where
+    I: Iterator,
+    I::Item: Send,
+    F: Fn(I::Item) + Sync,
+{
+    for item in iter {
+        f(item)
+    }
+}
+
+fn sum(x: &[i32]) {
+    let sum_lengths = Mutex::new(0);
+    par_for(x.windows(4), |x| *sum_lengths.lock().unwrap() += x.len());
+
+    assert_eq!(*sum_lengths.lock().unwrap(), (x.len() - 3) * 4);
+}
+
+fn main() {
+    let mut elements = [0; 20];
+
+    // iterators over references into this stack frame
+    par_for(elements.iter_mut().enumerate(), |(i, x)| *x = i as i32);
+
+    sum(&elements)
+}
diff --git a/src/tools/miri/tests/pass/sendable-class.rs b/src/tools/miri/tests/pass/sendable-class.rs
new file mode 100644
index 0000000000000..a05278f1855a2
--- /dev/null
+++ b/src/tools/miri/tests/pass/sendable-class.rs
@@ -0,0 +1,19 @@
+// Test that a class with only sendable fields can be sent
+
+use std::sync::mpsc::channel;
+
+#[allow(dead_code)]
+struct Foo {
+    i: isize,
+    j: char,
+}
+
+fn foo(i: isize, j: char) -> Foo {
+    Foo { i: i, j: j }
+}
+
+pub fn main() {
+    let (tx, rx) = channel();
+    tx.send(foo(42, 'c')).unwrap();
+    let _val = rx;
+}
diff --git a/src/tools/miri/tests/pass/shims/env/args.rs b/src/tools/miri/tests/pass/shims/env/args.rs
new file mode 100644
index 0000000000000..0116dce4992dc
--- /dev/null
+++ b/src/tools/miri/tests/pass/shims/env/args.rs
@@ -0,0 +1,5 @@
+fn main() {
+    for arg in std::env::args() {
+        println!("{}", arg);
+    }
+}
diff --git a/src/tools/miri/tests/pass/shims/env/args.stdout b/src/tools/miri/tests/pass/shims/env/args.stdout
new file mode 100644
index 0000000000000..9564f5a1aa056
--- /dev/null
+++ b/src/tools/miri/tests/pass/shims/env/args.stdout
@@ -0,0 +1 @@
+args
diff --git a/src/tools/miri/tests/pass/shims/env/current_dir.rs b/src/tools/miri/tests/pass/shims/env/current_dir.rs
new file mode 100644
index 0000000000000..069b462ab371a
--- /dev/null
+++ b/src/tools/miri/tests/pass/shims/env/current_dir.rs
@@ -0,0 +1,18 @@
+//@compile-flags: -Zmiri-disable-isolation
+use std::env;
+use std::io::ErrorKind;
+
+fn main() {
+    // Test that `getcwd` is available
+    let cwd = env::current_dir().unwrap();
+    // Test that changing dir to `..` actually sets the current directory to the parent of `cwd`.
+    // The only exception here is if `cwd` is the root directory, then changing directory must
+    // keep the current directory equal to `cwd`.
+    let parent = cwd.parent().unwrap_or(&cwd);
+    // Test that `chdir` is available
+    assert!(env::set_current_dir("..").is_ok());
+    // Test that `..` goes to the parent directory
+    assert_eq!(env::current_dir().unwrap(), parent);
+    // Test that `chdir` to a non-existing directory returns a proper error
+    assert_eq!(env::set_current_dir("thisdoesnotexist").unwrap_err().kind(), ErrorKind::NotFound);
+}
diff --git a/src/tools/miri/tests/pass/shims/env/current_dir_with_isolation.rs b/src/tools/miri/tests/pass/shims/env/current_dir_with_isolation.rs
new file mode 100644
index 0000000000000..9dbcfeae2d644
--- /dev/null
+++ b/src/tools/miri/tests/pass/shims/env/current_dir_with_isolation.rs
@@ -0,0 +1,21 @@
+//@compile-flags: -Zmiri-isolation-error=warn-nobacktrace
+//@normalize-stderr-test: "(getcwd|GetCurrentDirectoryW)" -> "$$GETCWD"
+//@normalize-stderr-test: "(chdir|SetCurrentDirectoryW)" -> "$$SETCWD"
+
+use std::env;
+use std::io::ErrorKind;
+
+fn main() {
+    // Test that current dir operations return a proper error instead
+    // of stopping the machine in isolation mode
+    assert_eq!(env::current_dir().unwrap_err().kind(), ErrorKind::PermissionDenied);
+    for _i in 0..3 {
+        // Ensure we get no repeated warnings when doing this multiple times.
+        assert_eq!(env::current_dir().unwrap_err().kind(), ErrorKind::PermissionDenied);
+    }
+
+    assert_eq!(env::set_current_dir("..").unwrap_err().kind(), ErrorKind::PermissionDenied);
+    for _i in 0..3 {
+        assert_eq!(env::set_current_dir("..").unwrap_err().kind(), ErrorKind::PermissionDenied);
+    }
+}
diff --git a/src/tools/miri/tests/pass/shims/env/current_dir_with_isolation.stderr b/src/tools/miri/tests/pass/shims/env/current_dir_with_isolation.stderr
new file mode 100644
index 0000000000000..589ca65a1e47e
--- /dev/null
+++ b/src/tools/miri/tests/pass/shims/env/current_dir_with_isolation.stderr
@@ -0,0 +1,4 @@
+warning: `$GETCWD` was made to return an error due to isolation
+
+warning: `$SETCWD` was made to return an error due to isolation
+
diff --git a/src/tools/miri/tests/pass/shims/env/current_exe.rs b/src/tools/miri/tests/pass/shims/env/current_exe.rs
new file mode 100644
index 0000000000000..15ea6a52b7b6b
--- /dev/null
+++ b/src/tools/miri/tests/pass/shims/env/current_exe.rs
@@ -0,0 +1,9 @@
+//@ignore-target-windows
+//@only-on-host: the Linux std implementation opens /proc/self/exe, which doesn't work cross-target
+//@compile-flags: -Zmiri-disable-isolation
+use std::env;
+
+fn main() {
+    // The actual value we get is a bit odd: we get the Miri binary that interprets us.
+    env::current_exe().unwrap();
+}
diff --git a/src/tools/miri/tests/pass/shims/env/home.rs b/src/tools/miri/tests/pass/shims/env/home.rs
new file mode 100644
index 0000000000000..9eb9c3af569dd
--- /dev/null
+++ b/src/tools/miri/tests/pass/shims/env/home.rs
@@ -0,0 +1,9 @@
+//@ignore-target-windows: home_dir is not supported on Windows
+//@compile-flags: -Zmiri-disable-isolation
+use std::env;
+
+fn main() {
+    env::remove_var("HOME"); // make sure we enter the interesting codepath
+    #[allow(deprecated)]
+    env::home_dir().unwrap();
+}
diff --git a/src/tools/miri/tests/pass/shims/env/var-forward.rs b/src/tools/miri/tests/pass/shims/env/var-forward.rs
new file mode 100644
index 0000000000000..da7730b00f089
--- /dev/null
+++ b/src/tools/miri/tests/pass/shims/env/var-forward.rs
@@ -0,0 +1,5 @@
+//@compile-flags: -Zmiri-env-forward=MIRI_ENV_VAR_TEST
+
+fn main() {
+    assert_eq!(std::env::var("MIRI_ENV_VAR_TEST"), Ok("0".to_owned()));
+}
diff --git a/src/tools/miri/tests/pass/shims/env/var-without-isolation.rs b/src/tools/miri/tests/pass/shims/env/var-without-isolation.rs
new file mode 100644
index 0000000000000..3d7461eecfe88
--- /dev/null
+++ b/src/tools/miri/tests/pass/shims/env/var-without-isolation.rs
@@ -0,0 +1,5 @@
+//@compile-flags: -Zmiri-disable-isolation
+
+fn main() {
+    assert_eq!(std::env::var("MIRI_ENV_VAR_TEST"), Ok("0".to_owned()));
+}
diff --git a/src/tools/miri/tests/pass/shims/env/var.rs b/src/tools/miri/tests/pass/shims/env/var.rs
new file mode 100644
index 0000000000000..23a3724ff7fd3
--- /dev/null
+++ b/src/tools/miri/tests/pass/shims/env/var.rs
@@ -0,0 +1,26 @@
+use std::env;
+
+fn main() {
+    // Test that miri environment is isolated when communication is disabled.
+    // (`MIRI_ENV_VAR_TEST` is set by the test harness.)
+    assert_eq!(env::var("MIRI_ENV_VAR_TEST"), Err(env::VarError::NotPresent));
+
+    // Test base state.
+    println!("{:#?}", env::vars().collect::<Vec<_>>());
+    assert_eq!(env::var("MIRI_TEST"), Err(env::VarError::NotPresent));
+
+    // Set the variable.
+    env::set_var("MIRI_TEST", "the answer");
+    assert_eq!(env::var("MIRI_TEST"), Ok("the answer".to_owned()));
+    println!("{:#?}", env::vars().collect::<Vec<_>>());
+
+    // Change the variable.
+    env::set_var("MIRI_TEST", "42");
+    assert_eq!(env::var("MIRI_TEST"), Ok("42".to_owned()));
+    println!("{:#?}", env::vars().collect::<Vec<_>>());
+
+    // Remove the variable.
+    env::remove_var("MIRI_TEST");
+    assert_eq!(env::var("MIRI_TEST"), Err(env::VarError::NotPresent));
+    println!("{:#?}", env::vars().collect::<Vec<_>>());
+}
diff --git a/src/tools/miri/tests/pass/shims/env/var.stdout b/src/tools/miri/tests/pass/shims/env/var.stdout
new file mode 100644
index 0000000000000..9a8f979598ebc
--- /dev/null
+++ b/src/tools/miri/tests/pass/shims/env/var.stdout
@@ -0,0 +1,14 @@
+[]
+[
+    (
+        "MIRI_TEST",
+        "the answer",
+    ),
+]
+[
+    (
+        "MIRI_TEST",
+        "42",
+    ),
+]
+[]
diff --git a/src/tools/miri/tests/pass/shims/exit.rs b/src/tools/miri/tests/pass/shims/exit.rs
new file mode 100644
index 0000000000000..d93f0045377ef
--- /dev/null
+++ b/src/tools/miri/tests/pass/shims/exit.rs
@@ -0,0 +1,3 @@
+fn main() {
+    std::process::exit(0)
+}
diff --git a/src/tools/miri/tests/pass/shims/sleep_long.rs b/src/tools/miri/tests/pass/shims/sleep_long.rs
new file mode 100644
index 0000000000000..dd4a1843942c4
--- /dev/null
+++ b/src/tools/miri/tests/pass/shims/sleep_long.rs
@@ -0,0 +1,18 @@
+//@ignore-target-windows: no threads nor sleep on Windows
+//@compile-flags: -Zmiri-ignore-leaks -Zmiri-disable-isolation
+use std::sync::{Arc, Mutex};
+use std::thread;
+use std::time::Duration;
+
+fn main() {
+    let finished = Arc::new(Mutex::new(false));
+    let t_finished = finished.clone();
+    thread::spawn(move || {
+        // Sleep very, very long.
+        thread::sleep(Duration::new(u64::MAX, 0));
+        *t_finished.lock().unwrap() = true;
+    });
+    thread::sleep(Duration::from_millis(100));
+    assert_eq!(*finished.lock().unwrap(), false);
+    // Stopping the main thread will also kill the sleeper.
+}
diff --git a/src/tools/miri/tests/pass/shims/time-with-isolation.rs b/src/tools/miri/tests/pass/shims/time-with-isolation.rs
new file mode 100644
index 0000000000000..b6444319b59b9
--- /dev/null
+++ b/src/tools/miri/tests/pass/shims/time-with-isolation.rs
@@ -0,0 +1,35 @@
+use std::time::{Duration, Instant};
+
+fn test_sleep() {
+    // We sleep a *long* time here -- but the clock is virtual so the test should still pass quickly.
+    let before = Instant::now();
+    std::thread::sleep(Duration::from_secs(3600));
+    let after = Instant::now();
+    assert!((after - before).as_secs() >= 3600);
+}
+
+/// Ensure that time passes even if we don't sleep (but just work).
+fn test_time_passes() {
+    // Check `Instant`.
+    let now1 = Instant::now();
+    // Do some work to make time pass.
+    for _ in 0..10 {
+        drop(vec![42]);
+    }
+    let now2 = Instant::now();
+    assert!(now2 > now1);
+    // Sanity-check the difference we got.
+    let diff = now2.duration_since(now1);
+    assert_eq!(now1 + diff, now2);
+    assert_eq!(now2 - diff, now1);
+    // The virtual clock is deterministic and I got 29us on a 64-bit Linux machine. However, this
+    // changes according to the platform so we use an interval to be safe. This should be updated
+    // if `NANOSECONDS_PER_BASIC_BLOCK` changes.
+    assert!(diff.as_micros() > 10);
+    assert!(diff.as_micros() < 40);
+}
+
+fn main() {
+    test_time_passes();
+    test_sleep();
+}
diff --git a/src/tools/miri/tests/pass/shims/time.rs b/src/tools/miri/tests/pass/shims/time.rs
new file mode 100644
index 0000000000000..23b5ab57efa02
--- /dev/null
+++ b/src/tools/miri/tests/pass/shims/time.rs
@@ -0,0 +1,52 @@
+//@compile-flags: -Zmiri-disable-isolation
+
+use std::time::{Duration, Instant, SystemTime};
+
+fn duration_sanity(diff: Duration) {
+    // On my laptop, I observed times around 15-40ms. Add 10x lee-way both ways.
+    assert!(diff.as_millis() > 1);
+    assert!(diff.as_millis() < 500);
+}
+
+fn test_sleep() {
+    let before = Instant::now();
+    std::thread::sleep(Duration::from_millis(100));
+    let after = Instant::now();
+    assert!((after - before).as_millis() >= 100);
+}
+
+fn main() {
+    // Check `SystemTime`.
+    let now1 = SystemTime::now();
+    let seconds_since_epoch = now1.duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
+    let years_since_epoch = seconds_since_epoch / 3600 / 24 / 365;
+    let year = 1970 + years_since_epoch;
+    assert!(2020 <= year && year < 2100);
+    // Do some work to make time pass.
+    for _ in 0..10 {
+        drop(vec![42]);
+    }
+    let now2 = SystemTime::now();
+    assert!(now2 > now1);
+    // Sanity-check the difference we got.
+    let diff = now2.duration_since(now1).unwrap();
+    assert_eq!(now1 + diff, now2);
+    assert_eq!(now2 - diff, now1);
+    duration_sanity(diff);
+
+    // Check `Instant`.
+    let now1 = Instant::now();
+    // Do some work to make time pass.
+    for _ in 0..10 {
+        drop(vec![42]);
+    }
+    let now2 = Instant::now();
+    assert!(now2 > now1);
+    // Sanity-check the difference we got.
+    let diff = now2.duration_since(now1);
+    assert_eq!(now1 + diff, now2);
+    assert_eq!(now2 - diff, now1);
+    duration_sanity(diff);
+
+    test_sleep();
+}
diff --git a/src/tools/miri/tests/pass/simd-intrinsic-generic-elements.rs b/src/tools/miri/tests/pass/simd-intrinsic-generic-elements.rs
new file mode 100644
index 0000000000000..5958357c8b7b1
--- /dev/null
+++ b/src/tools/miri/tests/pass/simd-intrinsic-generic-elements.rs
@@ -0,0 +1,24 @@
+#![feature(repr_simd, platform_intrinsics)]
+
+#[repr(simd)]
+#[derive(Copy, Clone, Debug, PartialEq)]
+#[allow(non_camel_case_types)]
+struct i32x2(i32, i32);
+#[repr(simd)]
+#[derive(Copy, Clone, Debug, PartialEq)]
+#[allow(non_camel_case_types)]
+struct i32x4(i32, i32, i32, i32);
+#[repr(simd)]
+#[derive(Copy, Clone, Debug, PartialEq)]
+#[allow(non_camel_case_types)]
+struct i32x8(i32, i32, i32, i32, i32, i32, i32, i32);
+
+fn main() {
+    let _x2 = i32x2(20, 21);
+    let _x4 = i32x4(40, 41, 42, 43);
+    let _x8 = i32x8(80, 81, 82, 83, 84, 85, 86, 87);
+
+    let _y2 = i32x2(120, 121);
+    let _y4 = i32x4(140, 141, 142, 143);
+    let _y8 = i32x8(180, 181, 182, 183, 184, 185, 186, 187);
+}
diff --git a/src/tools/miri/tests/pass/slices.rs b/src/tools/miri/tests/pass/slices.rs
new file mode 100644
index 0000000000000..a56b97a5088fa
--- /dev/null
+++ b/src/tools/miri/tests/pass/slices.rs
@@ -0,0 +1,274 @@
+//@compile-flags: -Zmiri-strict-provenance
+#![feature(new_uninit)]
+#![feature(slice_as_chunks)]
+#![feature(slice_partition_dedup)]
+#![feature(layout_for_ptr)]
+#![feature(strict_provenance)]
+
+use std::ptr;
+use std::slice;
+
+fn slice_of_zst() {
+    fn foo<T>(v: &[T]) -> Option<&[T]> {
+        let mut it = v.iter();
+        for _ in 0..5 {
+            it.next();
+        }
+        Some(it.as_slice())
+    }
+
+    fn foo_mut<T>(v: &mut [T]) -> Option<&mut [T]> {
+        let mut it = v.iter_mut();
+        for _ in 0..5 {
+            it.next();
+        }
+        Some(it.into_slice())
+    }
+
+    // In a slice of zero-size elements the pointer is meaningless.
+    // Ensure iteration still works even if the pointer is at the end of the address space.
+    let slice: &[()] = unsafe { slice::from_raw_parts(ptr::invalid(-5isize as usize), 10) };
+    assert_eq!(slice.len(), 10);
+    assert_eq!(slice.iter().count(), 10);
+
+    // .nth() on the iterator should also behave correctly
+    let mut it = slice.iter();
+    assert!(it.nth(5).is_some());
+    assert_eq!(it.count(), 4);
+
+    // Converting Iter to a slice should never have a null pointer
+    assert!(foo(slice).is_some());
+
+    // Test mutable iterators as well
+    let slice: &mut [()] =
+        unsafe { slice::from_raw_parts_mut(ptr::invalid_mut(-5isize as usize), 10) };
+    assert_eq!(slice.len(), 10);
+    assert_eq!(slice.iter_mut().count(), 10);
+
+    {
+        let mut it = slice.iter_mut();
+        assert!(it.nth(5).is_some());
+        assert_eq!(it.count(), 4);
+    }
+
+    assert!(foo_mut(slice).is_some())
+}
+
+fn test_iter_ref_consistency() {
+    use std::fmt::Debug;
+
+    fn test<T: Copy + Debug + PartialEq>(x: T) {
+        let v: &[T] = &[x, x, x];
+        let v_ptrs: [*const T; 3] = match v {
+            [ref v1, ref v2, ref v3] => [v1 as *const _, v2 as *const _, v3 as *const _],
+            _ => unreachable!(),
+        };
+        let len = v.len();
+
+        // nth(i)
+        for i in 0..len {
+            assert_eq!(&v[i] as *const _, v_ptrs[i]); // check the v_ptrs array, just to be sure
+            let nth = v.iter().nth(i).unwrap();
+            assert_eq!(nth as *const _, v_ptrs[i]);
+        }
+        assert_eq!(v.iter().nth(len), None, "nth(len) should return None");
+
+        // stepping through with nth(0)
+        {
+            let mut it = v.iter();
+            for i in 0..len {
+                let next = it.nth(0).unwrap();
+                assert_eq!(next as *const _, v_ptrs[i]);
+            }
+            assert_eq!(it.nth(0), None);
+        }
+
+        // next()
+        {
+            let mut it = v.iter();
+            for i in 0..len {
+                let remaining = len - i;
+                assert_eq!(it.size_hint(), (remaining, Some(remaining)));
+
+                let next = it.next().unwrap();
+                assert_eq!(next as *const _, v_ptrs[i]);
+            }
+            assert_eq!(it.size_hint(), (0, Some(0)));
+            assert_eq!(it.next(), None, "The final call to next() should return None");
+        }
+
+        // next_back()
+        {
+            let mut it = v.iter();
+            for i in 0..len {
+                let remaining = len - i;
+                assert_eq!(it.size_hint(), (remaining, Some(remaining)));
+
+                let prev = it.next_back().unwrap();
+                assert_eq!(prev as *const _, v_ptrs[remaining - 1]);
+            }
+            assert_eq!(it.size_hint(), (0, Some(0)));
+            assert_eq!(it.next_back(), None, "The final call to next_back() should return None");
+        }
+    }
+
+    fn test_mut<T: Copy + Debug + PartialEq>(x: T) {
+        let v: &mut [T] = &mut [x, x, x];
+        let v_ptrs: [*mut T; 3] = match v {
+            [ref v1, ref v2, ref v3] =>
+                [v1 as *const _ as *mut _, v2 as *const _ as *mut _, v3 as *const _ as *mut _],
+            _ => unreachable!(),
+        };
+        let len = v.len();
+
+        // nth(i)
+        for i in 0..len {
+            assert_eq!(&mut v[i] as *mut _, v_ptrs[i]); // check the v_ptrs array, just to be sure
+            let nth = v.iter_mut().nth(i).unwrap();
+            assert_eq!(nth as *mut _, v_ptrs[i]);
+        }
+        assert_eq!(v.iter().nth(len), None, "nth(len) should return None");
+
+        // stepping through with nth(0)
+        {
+            let mut it = v.iter();
+            for i in 0..len {
+                let next = it.nth(0).unwrap();
+                assert_eq!(next as *const _, v_ptrs[i]);
+            }
+            assert_eq!(it.nth(0), None);
+        }
+
+        // next()
+        {
+            let mut it = v.iter_mut();
+            for i in 0..len {
+                let remaining = len - i;
+                assert_eq!(it.size_hint(), (remaining, Some(remaining)));
+
+                let next = it.next().unwrap();
+                assert_eq!(next as *mut _, v_ptrs[i]);
+            }
+            assert_eq!(it.size_hint(), (0, Some(0)));
+            assert_eq!(it.next(), None, "The final call to next() should return None");
+        }
+
+        // next_back()
+        {
+            let mut it = v.iter_mut();
+            for i in 0..len {
+                let remaining = len - i;
+                assert_eq!(it.size_hint(), (remaining, Some(remaining)));
+
+                let prev = it.next_back().unwrap();
+                assert_eq!(prev as *mut _, v_ptrs[remaining - 1]);
+            }
+            assert_eq!(it.size_hint(), (0, Some(0)));
+            assert_eq!(it.next_back(), None, "The final call to next_back() should return None");
+        }
+    }
+
+    // Make sure iterators and slice patterns yield consistent addresses for various types,
+    // including ZSTs.
+    test(0u32);
+    test(());
+    test([0u32; 0]); // ZST with alignment > 0
+    test_mut(0u32);
+    test_mut(());
+    test_mut([0u32; 0]); // ZST with alignment > 0
+}
+
+fn uninit_slice() {
+    let mut values = Box::<[Box<u32>]>::new_uninit_slice(3);
+
+    let values = unsafe {
+        // Deferred initialization:
+        values[0].as_mut_ptr().write(Box::new(1));
+        values[1].as_mut_ptr().write(Box::new(2));
+        values[2].as_mut_ptr().write(Box::new(3));
+
+        values.assume_init()
+    };
+
+    assert_eq!(values.iter().map(|x| **x).collect::<Vec<_>>(), vec![1, 2, 3])
+}
+
+/// Regression tests for slice methods in the Rust core library where raw pointers are obtained
+/// from mutable references.
+fn test_for_invalidated_pointers() {
+    let mut buffer = [0usize; 64];
+    let len = buffer.len();
+
+    // These regression tests (indirectly) call every slice method which contains a `buffer.as_mut_ptr()`.
+    // `<[T]>::as_mut_ptr(&mut self)` takes a mutable reference (tagged Unique), which will invalidate all
+    // the other pointers that were previously derived from it according to the Stacked Borrows model.
+    // An example of where this could go wrong is a prior bug inside `<[T]>::copy_within`:
+    //
+    //      unsafe {
+    //          core::ptr::copy(self.as_ptr().add(src_start), self.as_mut_ptr().add(dest), count);
+    //      }
+    //
+    // The arguments to `core::ptr::copy` are evaluated from left to right. `self.as_ptr()` creates
+    // an immutable reference (which is tagged as `SharedReadOnly` by Stacked Borrows) to the array
+    // and derives a valid `*const` pointer from it. When jumping to the next argument,
+    // `self.as_mut_ptr()` creates a mutable reference (tagged as `Unique`) to the array, which
+    // invalidates the existing `SharedReadOnly` reference and any pointers derived from it.
+    // The invalidated `*const` pointer (the first argument to `core::ptr::copy`) is then used
+    // after the fact when `core::ptr::copy` is called, which triggers undefined behavior.
+
+    unsafe {
+        assert_eq!(0, *buffer.as_mut_ptr_range().start);
+    }
+    // Check that the pointer range is in-bounds, while we're at it
+    let range = buffer.as_mut_ptr_range();
+    unsafe {
+        assert_eq!(*range.start, *range.end.sub(len));
+    }
+
+    buffer.reverse();
+
+    // Calls `fn as_chunks_unchecked_mut` internally (requires unstable `#![feature(slice_as_chunks)]`):
+    assert_eq!(2, buffer.as_chunks_mut::<32>().0.len());
+    for chunk in buffer.as_chunks_mut::<32>().0 {
+        for elem in chunk {
+            *elem += 1;
+        }
+    }
+
+    // Calls `fn split_at_mut_unchecked` internally:
+    let split_mut = buffer.split_at_mut(32);
+    assert_eq!(split_mut.0, split_mut.1);
+
+    // Calls `fn partition_dedup_by` internally (requires unstable `#![feature(slice_partition_dedup)]`):
+    let partition_dedup = buffer.partition_dedup();
+    assert_eq!(1, partition_dedup.0.len());
+    partition_dedup.0[0] += 1;
+    for elem in partition_dedup.1 {
+        *elem += 1;
+    }
+
+    buffer.rotate_left(8);
+    buffer.rotate_right(16);
+
+    buffer.copy_from_slice(&[1usize; 64]);
+    buffer.swap_with_slice(&mut [2usize; 64]);
+
+    assert_eq!(0, unsafe { buffer.align_to_mut::<u8>().1[1] });
+
+    buffer.copy_within(1.., 0);
+}
+
+fn large_raw_slice() {
+    let size = isize::MAX as usize;
+    // Creating a raw slice of size isize::MAX and asking for its size is okay.
+    let s = std::ptr::slice_from_raw_parts(ptr::invalid::<u8>(1), size);
+    assert_eq!(size, unsafe { std::mem::size_of_val_raw(s) });
+}
+
+fn main() {
+    slice_of_zst();
+    test_iter_ref_consistency();
+    uninit_slice();
+    test_for_invalidated_pointers();
+    large_raw_slice();
+}
diff --git a/src/tools/miri/tests/pass/small_enum_size_bug.rs b/src/tools/miri/tests/pass/small_enum_size_bug.rs
new file mode 100644
index 0000000000000..bb2f597444e77
--- /dev/null
+++ b/src/tools/miri/tests/pass/small_enum_size_bug.rs
@@ -0,0 +1,13 @@
+#[allow(dead_code)]
+enum E {
+    A = 1,
+    B = 2,
+    C = 3,
+}
+
+fn main() {
+    let enone = None::<E>;
+    if let Some(..) = enone {
+        panic!();
+    }
+}
diff --git a/src/tools/miri/tests/pass/specialization.rs b/src/tools/miri/tests/pass/specialization.rs
new file mode 100644
index 0000000000000..428dea073eb53
--- /dev/null
+++ b/src/tools/miri/tests/pass/specialization.rs
@@ -0,0 +1,26 @@
+#![allow(incomplete_features)]
+#![feature(specialization)]
+
+trait IsUnit {
+    fn is_unit() -> bool;
+}
+
+impl<T> IsUnit for T {
+    default fn is_unit() -> bool {
+        false
+    }
+}
+
+impl IsUnit for () {
+    fn is_unit() -> bool {
+        true
+    }
+}
+
+fn specialization() -> (bool, bool) {
+    (i32::is_unit(), <()>::is_unit())
+}
+
+fn main() {
+    assert_eq!(specialization(), (false, true));
+}
diff --git a/src/tools/miri/tests/pass/stacked-borrows/2phase.rs b/src/tools/miri/tests/pass/stacked-borrows/2phase.rs
new file mode 100644
index 0000000000000..eb543d691e10b
--- /dev/null
+++ b/src/tools/miri/tests/pass/stacked-borrows/2phase.rs
@@ -0,0 +1,87 @@
+trait S: Sized {
+    fn tpb(&mut self, _s: Self) {}
+}
+
+impl S for i32 {}
+
+fn two_phase1() {
+    let mut x = 3;
+    x.tpb(x);
+}
+
+fn two_phase2() {
+    let mut v = vec![];
+    v.push(v.len());
+}
+
+fn two_phase3(b: bool) {
+    let mut x = &mut vec![];
+    let mut y = vec![];
+    x.push((
+        {
+            if b {
+                x = &mut y;
+            }
+            22
+        },
+        x.len(),
+    ));
+}
+
+#[allow(unreachable_code)]
+fn two_phase_raw() {
+    let x: &mut Vec<i32> = &mut vec![];
+    x.push({
+        // Unfortunately this does not trigger the problem of creating a
+        // raw ponter from a pointer that had a two-phase borrow derived from
+        // it because of the implicit &mut reborrow.
+        let raw = x as *mut _;
+        unsafe {
+            *raw = vec![1];
+        }
+        return;
+    });
+}
+
+fn two_phase_overlapping1() {
+    let mut x = vec![];
+    let p = &x;
+    x.push(p.len());
+}
+
+fn two_phase_overlapping2() {
+    use std::ops::AddAssign;
+    let mut x = 1;
+    let l = &x;
+    x.add_assign(x + *l);
+}
+
+fn with_interior_mutability() {
+    use std::cell::Cell;
+
+    trait Thing: Sized {
+        fn do_the_thing(&mut self, _s: i32) {}
+    }
+
+    impl<T> Thing for Cell<T> {}
+
+    let mut x = Cell::new(1);
+    let l = &x;
+
+    x.do_the_thing({
+        x.set(3);
+        l.set(4);
+        x.get() + l.get()
+    });
+}
+
+fn main() {
+    two_phase1();
+    two_phase2();
+    two_phase3(false);
+    two_phase3(true);
+    two_phase_raw();
+    with_interior_mutability();
+    two_phase_overlapping1();
+    two_phase_overlapping2();
+}
diff --git a/src/tools/miri/tests/pass/stacked-borrows/generators-self-referential.rs b/src/tools/miri/tests/pass/stacked-borrows/generators-self-referential.rs
new file mode 100644
index 0000000000000..b71912882dd2a
--- /dev/null
+++ b/src/tools/miri/tests/pass/stacked-borrows/generators-self-referential.rs
@@ -0,0 +1,34 @@
+// See https://github.com/rust-lang/unsafe-code-guidelines/issues/148:
+// this fails when Stacked Borrows is strictly applied even to `!Unpin` types.
+#![feature(generators, generator_trait)]
+
+use std::{
+    ops::{Generator, GeneratorState},
+    pin::Pin,
+};
+
+fn firstn() -> impl Generator<Yield = u64, Return = ()> {
+    static move || {
+        let mut num = 0;
+        let num = &mut num;
+
+        yield *num;
+        *num += 1; // would fail here
+
+        yield *num;
+        *num += 1;
+
+        yield *num;
+        *num += 1;
+    }
+}
+
+fn main() {
+    let mut generator_iterator = firstn();
+    let mut pin = unsafe { Pin::new_unchecked(&mut generator_iterator) };
+    let mut sum = 0;
+    while let GeneratorState::Yielded(x) = pin.as_mut().resume(()) {
+        sum += x;
+    }
+    assert_eq!(sum, 3);
+}
diff --git a/src/tools/miri/tests/pass/stacked-borrows/int-to-ptr.rs b/src/tools/miri/tests/pass/stacked-borrows/int-to-ptr.rs
new file mode 100644
index 0000000000000..c3e30627a7ce3
--- /dev/null
+++ b/src/tools/miri/tests/pass/stacked-borrows/int-to-ptr.rs
@@ -0,0 +1,83 @@
+//@compile-flags: -Zmiri-permissive-provenance
+#![feature(strict_provenance)]
+use std::ptr;
+
+// Just to make sure that casting a ref to raw, to int and back to raw
+// and only then using it works. This rules out ideas like "do escape-to-raw lazily";
+// after casting to int and back, we lost the tag that could have let us do that.
+fn ref_raw_int_raw() {
+    let mut x = 3;
+    let xref = &mut x;
+    let xraw = xref as *mut i32 as usize as *mut i32;
+    assert_eq!(unsafe { *xraw }, 3);
+}
+
+/// Ensure that we do not just pick the topmost possible item on int2ptr casts.
+fn example(variant: bool) {
+    unsafe {
+        fn not_so_innocent(x: &mut u32) -> usize {
+            let x_raw4 = x as *mut u32;
+            x_raw4.expose_addr()
+        }
+
+        let mut c = 42u32;
+
+        let x_unique1 = &mut c;
+        // [..., Unique(1)]
+
+        let x_raw2 = x_unique1 as *mut u32;
+        let x_raw2_addr = x_raw2.expose_addr();
+        // [..., Unique(1), SharedRW(2)]
+
+        let x_unique3 = &mut *x_raw2;
+        // [.., Unique(1), SharedRW(2), Unique(3)]
+
+        assert_eq!(not_so_innocent(x_unique3), x_raw2_addr);
+        // [.., Unique(1), SharedRW(2), Unique(3), ..., SharedRW(4)]
+
+        // Do an int2ptr cast. This can pick tag 2 or 4 (the two previously exposed tags).
+        // 4 is the "obvious" choice (topmost tag, what we used to do with untagged pointers).
+        // And indeed if `variant == true` it is the only possible choice.
+        // But if `variant == false` then 2 is the only possible choice!
+        let x_wildcard = ptr::from_exposed_addr_mut::<i32>(x_raw2_addr);
+
+        if variant {
+            // If we picked 2, this will invalidate 3.
+            *x_wildcard = 10;
+            // Now we use 3. Only possible if above we picked 4.
+            *x_unique3 = 12;
+        } else {
+            // This invalidates tag 4.
+            *x_unique3 = 10;
+            // Now try to write with the "guessed" tag; it must be 2.
+            *x_wildcard = 12;
+        }
+    }
+}
+
+fn test() {
+    unsafe {
+        let root = &mut 42;
+        let root_raw = root as *mut i32;
+        let addr1 = root_raw as usize;
+        let child = &mut *root_raw;
+        let child_raw = child as *mut i32;
+        let addr2 = child_raw as usize;
+        assert_eq!(addr1, addr2);
+        // First use child.
+        *(addr2 as *mut i32) -= 2; // picks child_raw
+        *child -= 2;
+        // Then use root.
+        *(addr1 as *mut i32) += 2; // picks root_raw
+        *root += 2;
+        // Value should be unchanged.
+        assert_eq!(*root, 42);
+    }
+}
+
+fn main() {
+    ref_raw_int_raw();
+    example(false);
+    example(true);
+    test();
+}
diff --git a/src/tools/miri/tests/pass/stacked-borrows/interior_mutability.rs b/src/tools/miri/tests/pass/stacked-borrows/interior_mutability.rs
new file mode 100644
index 0000000000000..c6373a7eaf135
--- /dev/null
+++ b/src/tools/miri/tests/pass/stacked-borrows/interior_mutability.rs
@@ -0,0 +1,175 @@
+//@compile-flags: -Zmiri-retag-fields
+use std::cell::{Cell, Ref, RefCell, RefMut, UnsafeCell};
+use std::mem::{self, MaybeUninit};
+
+fn main() {
+    aliasing_mut_and_shr();
+    aliasing_frz_and_shr();
+    into_interior_mutability();
+    unsafe_cell_2phase();
+    unsafe_cell_deallocate();
+    unsafe_cell_invalidate();
+    refcell_basic();
+    ref_protector();
+    ref_mut_protector();
+    rust_issue_68303();
+}
+
+fn aliasing_mut_and_shr() {
+    fn inner(rc: &RefCell<i32>, aliasing: &mut i32) {
+        *aliasing += 4;
+        let _escape_to_raw = rc as *const _;
+        *aliasing += 4;
+        let _shr = &*rc;
+        *aliasing += 4;
+        // also turning this into a frozen ref now must work
+        let aliasing = &*aliasing;
+        let _val = *aliasing;
+        let _escape_to_raw = rc as *const _; // this must NOT unfreeze
+        let _val = *aliasing;
+        let _shr = &*rc; // this must NOT unfreeze
+        let _val = *aliasing;
+    }
+
+    let rc = RefCell::new(23);
+    let mut bmut = rc.borrow_mut();
+    inner(&rc, &mut *bmut);
+    drop(bmut);
+    assert_eq!(*rc.borrow(), 23 + 12);
+}
+
+fn aliasing_frz_and_shr() {
+    fn inner(rc: &RefCell<i32>, aliasing: &i32) {
+        let _val = *aliasing;
+        let _escape_to_raw = rc as *const _; // this must NOT unfreeze
+        let _val = *aliasing;
+        let _shr = &*rc; // this must NOT unfreeze
+        let _val = *aliasing;
+    }
+
+    let rc = RefCell::new(23);
+    let bshr = rc.borrow();
+    inner(&rc, &*bshr);
+    assert_eq!(*rc.borrow(), 23);
+}
+
+// Getting a pointer into a union with interior mutability used to be tricky
+// business (https://github.com/rust-lang/miri/issues/615), but it should work
+// now.
+fn into_interior_mutability() {
+    let mut x: MaybeUninit<(Cell<u32>, u32)> = MaybeUninit::uninit();
+    x.as_ptr();
+    x.write((Cell::new(0), 1));
+    let ptr = unsafe { x.assume_init_ref() };
+    assert_eq!(ptr.1, 1);
+}
+
+// Two-phase borrows of the pointer returned by UnsafeCell::get() should not
+// invalidate aliases.
+fn unsafe_cell_2phase() {
+    unsafe {
+        let x = &UnsafeCell::new(vec![]);
+        let x2 = &*x;
+        (*x.get()).push(0);
+        let _val = (*x2.get()).get(0);
+    }
+}
+
+/// Make sure we can deallocate an UnsafeCell that was passed to an active fn call.
+/// (This is the fix for https://github.com/rust-lang/rust/issues/55005.)
+fn unsafe_cell_deallocate() {
+    fn f(x: &UnsafeCell<i32>) {
+        let b: Box<i32> = unsafe { Box::from_raw(x as *const _ as *mut i32) };
+        drop(b)
+    }
+
+    let b = Box::new(0i32);
+    f(unsafe { mem::transmute(Box::into_raw(b)) });
+}
+
+/// As a side-effect of the above, we also allow this -- at least for now.
+fn unsafe_cell_invalidate() {
+    fn f(_x: &UnsafeCell<i32>, y: *mut i32) {
+        // Writing to y invalidates x, but that is okay.
+        unsafe {
+            *y += 1;
+        }
+    }
+
+    let mut x = 0i32;
+    let raw1 = &mut x as *mut _;
+    let ref1 = unsafe { &mut *raw1 };
+    let raw2 = ref1 as *mut _;
+    // Now the borrow stack is: raw1, ref2, raw2.
+    // So using raw1 invalidates raw2.
+    f(unsafe { mem::transmute(raw2) }, raw1);
+}
+
+fn refcell_basic() {
+    let c = RefCell::new(42);
+    {
+        let s1 = c.borrow();
+        let _x: i32 = *s1;
+        let s2 = c.borrow();
+        let _x: i32 = *s1;
+        let _y: i32 = *s2;
+        let _x: i32 = *s1;
+        let _y: i32 = *s2;
+    }
+    {
+        let mut m = c.borrow_mut();
+        let _z: i32 = *m;
+        {
+            let s: &i32 = &*m;
+            let _x = *s;
+        }
+        *m = 23;
+        let _z: i32 = *m;
+    }
+    {
+        let s1 = c.borrow();
+        let _x: i32 = *s1;
+        let s2 = c.borrow();
+        let _x: i32 = *s1;
+        let _y: i32 = *s2;
+        let _x: i32 = *s1;
+        let _y: i32 = *s2;
+    }
+}
+
+// Adding a Stacked Borrows protector for `Ref` would break this
+fn ref_protector() {
+    fn break_it(rc: &RefCell<i32>, r: Ref<'_, i32>) {
+        // `r` has a shared reference, it is passed in as argument and hence
+        // a protector is added that marks this memory as read-only for the entire
+        // duration of this function.
+        drop(r);
+        // *oops* here we can mutate that memory.
+        *rc.borrow_mut() = 2;
+    }
+
+    let rc = RefCell::new(0);
+    break_it(&rc, rc.borrow())
+}
+
+fn ref_mut_protector() {
+    fn break_it(rc: &RefCell<i32>, r: RefMut<'_, i32>) {
+        // `r` has a shared reference, it is passed in as argument and hence
+        // a protector is added that marks this memory as inaccessible for the entire
+        // duration of this function
+        drop(r);
+        // *oops* here we can mutate that memory.
+        *rc.borrow_mut() = 2;
+    }
+
+    let rc = RefCell::new(0);
+    break_it(&rc, rc.borrow_mut())
+}
+
+/// Make sure we do not have bad enum layout optimizations.
+fn rust_issue_68303() {
+    let optional = Some(RefCell::new(false));
+    let mut handle = optional.as_ref().unwrap().borrow_mut();
+    assert!(optional.is_some());
+    *handle = true;
+}
diff --git a/src/tools/miri/tests/pass/stacked-borrows/issue-miri-2389.rs b/src/tools/miri/tests/pass/stacked-borrows/issue-miri-2389.rs
new file mode 100644
index 0000000000000..469122095e512
--- /dev/null
+++ b/src/tools/miri/tests/pass/stacked-borrows/issue-miri-2389.rs
@@ -0,0 +1,17 @@
+use std::cell::Cell;
+
+fn main() {
+    unsafe {
+        let root0 = Cell::new(42);
+        let wildcard = &root0 as *const Cell<i32> as usize as *const Cell<i32>;
+        // empty the stack to unknown (via SRW reborrow from wildcard)
+        let _ref0 = &*wildcard;
+        // Do a non-SRW reborrow from wildcard to start building up a stack again.
+        // Now new refs start being inserted at idx 0, pushing the unique_range up.
+        let _refn = &*&*&*&*&*(wildcard.cast::<i32>());
+        // empty the stack again, but this time with unique_range.start sitting at some high index.
+        let _ref0 = &*wildcard;
+        // and do a read which tries to clear the uniques
+        wildcard.cast::<i32>().read();
+    }
+}
diff --git a/src/tools/miri/tests/pass/stacked-borrows/issue-miri-2389.stderr b/src/tools/miri/tests/pass/stacked-borrows/issue-miri-2389.stderr
new file mode 100644
index 0000000000000..f3ba052ae5130
--- /dev/null
+++ b/src/tools/miri/tests/pass/stacked-borrows/issue-miri-2389.stderr
@@ -0,0 +1,15 @@
+warning: integer-to-pointer cast
+  --> $DIR/issue-miri-2389.rs:LL:CC
+   |
+LL |         let wildcard = &root0 as *const Cell<i32> as usize as *const Cell<i32>;
+   |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ integer-to-pointer cast
+   |
+   = help: This program is using integer-to-pointer casts or (equivalently) `ptr::from_exposed_addr`,
+   = help: which means that Miri might miss pointer bugs in this program.
+   = help: See https://doc.rust-lang.org/nightly/std/ptr/fn.from_exposed_addr.html for more details on that operation.
+   = help: To ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead.
+   = help: You can then pass the `-Zmiri-strict-provenance` flag to Miri, to ensure you are not relying on `from_exposed_addr` semantics.
+   = help: Alternatively, the `-Zmiri-permissive-provenance` flag disables this warning.
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/issue-miri-2389.rs:LL:CC
+
diff --git a/src/tools/miri/tests/pass/stacked-borrows/stacked-borrows.rs b/src/tools/miri/tests/pass/stacked-borrows/stacked-borrows.rs
new file mode 100644
index 0000000000000..ef6eb346c17b1
--- /dev/null
+++ b/src/tools/miri/tests/pass/stacked-borrows/stacked-borrows.rs
@@ -0,0 +1,221 @@
+//@compile-flags: -Zmiri-retag-fields
+#![feature(allocator_api)]
+use std::ptr;
+
+// Test various stacked-borrows-related things.
+fn main() {
+    read_does_not_invalidate1();
+    read_does_not_invalidate2();
+    mut_raw_then_mut_shr();
+    mut_shr_then_mut_raw();
+    mut_raw_mut();
+    partially_invalidate_mut();
+    drop_after_sharing();
+    direct_mut_to_const_raw();
+    two_raw();
+    shr_and_raw();
+    disjoint_mutable_subborrows();
+    raw_ref_to_part();
+    array_casts();
+    mut_below_shr();
+    wide_raw_ptr_in_tuple();
+}
+
+// Make sure that reading from an `&mut` does, like reborrowing to `&`,
+// NOT invalidate other reborrows.
+fn read_does_not_invalidate1() {
+    fn foo(x: &mut (i32, i32)) -> &i32 {
+        let xraw = x as *mut (i32, i32);
+        let ret = unsafe { &(*xraw).1 };
+        let _val = x.1; // we just read, this does NOT invalidate the reborrows.
+        ret
+    }
+    assert_eq!(*foo(&mut (1, 2)), 2);
+}
+// Same as above, but this time we first create a raw, then read from `&mut`
+// and then freeze from the raw.
+fn read_does_not_invalidate2() {
+    fn foo(x: &mut (i32, i32)) -> &i32 {
+        let xraw = x as *mut (i32, i32);
+        let _val = x.1; // we just read, this does NOT invalidate the raw reborrow.
+        let ret = unsafe { &(*xraw).1 };
+        ret
+    }
+    assert_eq!(*foo(&mut (1, 2)), 2);
+}
+
+// Escape a mut to raw, then share the same mut and use the share, then the raw.
+// That should work.
+fn mut_raw_then_mut_shr() {
+    let mut x = 2;
+    let xref = &mut x;
+    let xraw = &mut *xref as *mut _;
+    let xshr = &*xref;
+    assert_eq!(*xshr, 2);
+    unsafe {
+        *xraw = 4;
+    }
+    assert_eq!(x, 4);
+}
+
+// Create first a shared reference and then a raw pointer from a `&mut`
+// should permit mutation through that raw pointer.
+fn mut_shr_then_mut_raw() {
+    let xref = &mut 2;
+    let _xshr = &*xref;
+    let xraw = xref as *mut _;
+    unsafe {
+        *xraw = 3;
+    }
+    assert_eq!(*xref, 3);
+}
+
+// Ensure that if we derive from a mut a raw, and then from that a mut,
+// and then read through the original mut, that does not invalidate the raw.
+// This shows that the read-exception for `&mut` applies even if the `Shr` item
+// on the stack is not at the top.
+fn mut_raw_mut() {
+    let mut x = 2;
+    {
+        let xref1 = &mut x;
+        let xraw = xref1 as *mut _;
+        let _xref2 = unsafe { &mut *xraw };
+        let _val = *xref1;
+        unsafe {
+            *xraw = 4;
+        }
+        // we can now use both xraw and xref1, for reading
+        assert_eq!(*xref1, 4);
+        assert_eq!(unsafe { *xraw }, 4);
+        assert_eq!(*xref1, 4);
+        assert_eq!(unsafe { *xraw }, 4);
+        // we cannot use xref2; see `compile-fail/stacked-borows/illegal_read4.rs`
+    }
+    assert_eq!(x, 4);
+}
+
+fn partially_invalidate_mut() {
+    let data = &mut (0u8, 0u8);
+    let reborrow = &mut *data as *mut (u8, u8);
+    let shard = unsafe { &mut (*reborrow).0 };
+    data.1 += 1; // the deref overlaps with `shard`, but that is ok; the access does not overlap.
+    *shard += 1; // so we can still use `shard`.
+    assert_eq!(*data, (1, 1));
+}
+
+// Make sure that we can handle the situation where a loaction is frozen when being dropped.
+fn drop_after_sharing() {
+    let x = String::from("hello!");
+    let _len = x.len();
+}
+
+// Make sure that coercing &mut T to *const T produces a writeable pointer.
+fn direct_mut_to_const_raw() {
+    // TODO: This is currently disabled, waiting on a decision on <https://github.com/rust-lang/rust/issues/56604>
+    /*let x = &mut 0;
+    let y: *const i32 = x;
+    unsafe { *(y as *mut i32) = 1; }
+    assert_eq!(*x, 1);
+    */
+}
+
+// Make sure that we can create two raw pointers from a mutable reference and use them both.
+fn two_raw() {
+    unsafe {
+        let x = &mut 0;
+        let y1 = x as *mut _;
+        let y2 = x as *mut _;
+        *y1 += 2;
+        *y2 += 1;
+    }
+}
+
+// Make sure that creating a *mut does not invalidate existing shared references.
+fn shr_and_raw() {
+    unsafe {
+        use std::mem;
+        let x = &mut 0;
+        let y1: &i32 = mem::transmute(&*x); // launder lifetimes
+        let y2 = x as *mut _;
+        let _val = *y1;
+        *y2 += 1;
+    }
+}
+
+fn disjoint_mutable_subborrows() {
+    struct Foo {
+        a: String,
+        b: Vec<u32>,
+    }
+
+    unsafe fn borrow_field_a<'a>(this: *mut Foo) -> &'a mut String {
+        &mut (*this).a
+    }
+
+    unsafe fn borrow_field_b<'a>(this: *mut Foo) -> &'a mut Vec<u32> {
+        &mut (*this).b
+    }
+
+    let mut foo = Foo { a: "hello".into(), b: vec![0, 1, 2] };
+
+    let ptr = &mut foo as *mut Foo;
+
+    let a = unsafe { borrow_field_a(ptr) };
+    let b = unsafe { borrow_field_b(ptr) };
+    b.push(4);
+    a.push_str(" world");
+    eprintln!("{:?} {:?}", a, b);
+}
+
+fn raw_ref_to_part() {
+    struct Part {
+        _lame: i32,
+    }
+
+    #[repr(C)]
+    struct Whole {
+        part: Part,
+        extra: i32,
+    }
+
+    let it = Box::new(Whole { part: Part { _lame: 0 }, extra: 42 });
+    let whole = ptr::addr_of_mut!(*Box::leak(it));
+    let part = unsafe { ptr::addr_of_mut!((*whole).part) };
+    let typed = unsafe { &mut *(part as *mut Whole) };
+    assert!(typed.extra == 42);
+    drop(unsafe { Box::from_raw(whole) });
+}
+
+/// When casting an array reference to a raw element ptr, that should cover the whole array.
+fn array_casts() {
+    let mut x: [usize; 2] = [0, 0];
+    let p = &mut x as *mut usize;
+    unsafe {
+        *p.add(1) = 1;
+    }
+
+    let x: [usize; 2] = [0, 1];
+    let p = &x as *const usize;
+    assert_eq!(unsafe { *p.add(1) }, 1);
+}
+
+/// Transmuting &&i32 to &&mut i32 is fine.
+fn mut_below_shr() {
+    let x = 0;
+    let y = &x;
+    let p = unsafe { core::mem::transmute::<&&i32, &&mut i32>(&y) };
+    let r = &**p;
+    let _val = *r;
+}
+
+fn wide_raw_ptr_in_tuple() {
+    let mut x: Box<dyn std::any::Any> = Box::new("ouch");
+    let r = &mut *x as *mut dyn std::any::Any;
+    // This triggers the visitor-based recursive retagging. It is *not* supposed to retag raw
+    // pointers, but then the visitor might recurse into the "fields" of a wide raw pointer and
+    // finds a reference (to a vtable) there that it wants to retag... and that would be Wrong.
+    let pair = (r, &0);
+    let r = unsafe { &mut *pair.0 };
+    // Make sure the fn ptr part of the vtable is still fine.
+    r.type_id();
+}
diff --git a/src/tools/miri/tests/pass/stacked-borrows/stacked-borrows.stderr b/src/tools/miri/tests/pass/stacked-borrows/stacked-borrows.stderr
new file mode 100644
index 0000000000000..8ee4e25dbef84
--- /dev/null
+++ b/src/tools/miri/tests/pass/stacked-borrows/stacked-borrows.stderr
@@ -0,0 +1 @@
+"hello world" [0, 1, 2, 4]
diff --git a/src/tools/miri/tests/pass/stacked-borrows/zst-field-retagging-terminates.rs b/src/tools/miri/tests/pass/stacked-borrows/zst-field-retagging-terminates.rs
new file mode 100644
index 0000000000000..ce3c8b7d5f1a1
--- /dev/null
+++ b/src/tools/miri/tests/pass/stacked-borrows/zst-field-retagging-terminates.rs
@@ -0,0 +1,6 @@
+//@compile-flags: -Zmiri-retag-fields
+// Checks that the test does not run forever (which relies on a fast path).
+fn main() {
+    let array = [(); usize::MAX];
+    drop(array); // Pass the array to a function, retagging its fields
+}
diff --git a/src/tools/miri/tests/pass/start.rs b/src/tools/miri/tests/pass/start.rs
new file mode 100644
index 0000000000000..f25d62fa8c335
--- /dev/null
+++ b/src/tools/miri/tests/pass/start.rs
@@ -0,0 +1,8 @@
+#![feature(start)]
+
+#[start]
+fn start(_: isize, _: *const *const u8) -> isize {
+    println!("Hello from start!");
+
+    0
+}
diff --git a/src/tools/miri/tests/pass/start.stdout b/src/tools/miri/tests/pass/start.stdout
new file mode 100644
index 0000000000000..d7f627d237c3e
--- /dev/null
+++ b/src/tools/miri/tests/pass/start.stdout
@@ -0,0 +1 @@
+Hello from start!
diff --git a/src/tools/miri/tests/pass/static_memory_modification.rs b/src/tools/miri/tests/pass/static_memory_modification.rs
new file mode 100644
index 0000000000000..84a524b1ed163
--- /dev/null
+++ b/src/tools/miri/tests/pass/static_memory_modification.rs
@@ -0,0 +1,14 @@
+use std::sync::atomic::{AtomicUsize, Ordering};
+
+static mut X: usize = 5;
+static Y: AtomicUsize = AtomicUsize::new(5);
+
+fn main() {
+    unsafe {
+        X = 6;
+        assert_eq!(X, 6);
+    }
+
+    Y.store(6, Ordering::Relaxed);
+    assert_eq!(Y.load(Ordering::Relaxed), 6);
+}
diff --git a/src/tools/miri/tests/pass/static_mut.rs b/src/tools/miri/tests/pass/static_mut.rs
new file mode 100644
index 0000000000000..218b02525bd55
--- /dev/null
+++ b/src/tools/miri/tests/pass/static_mut.rs
@@ -0,0 +1,16 @@
+static mut FOO: i32 = 42;
+static BAR: Foo = Foo(unsafe { &FOO as *const _ });
+
+#[allow(dead_code)]
+struct Foo(*const i32);
+
+unsafe impl Sync for Foo {}
+
+fn main() {
+    unsafe {
+        assert_eq!(*BAR.0, 42);
+        FOO = 5;
+        assert_eq!(FOO, 5);
+        assert_eq!(*BAR.0, 5);
+    }
+}
diff --git a/src/tools/miri/tests/pass/strings.rs b/src/tools/miri/tests/pass/strings.rs
new file mode 100644
index 0000000000000..5e2d2e9b5b5c1
--- /dev/null
+++ b/src/tools/miri/tests/pass/strings.rs
@@ -0,0 +1,63 @@
+//@compile-flags: -Zmiri-strict-provenance
+
+fn empty() -> &'static str {
+    ""
+}
+
+fn hello() -> &'static str {
+    "Hello, world!"
+}
+
+fn hello_bytes() -> &'static [u8; 13] {
+    b"Hello, world!"
+}
+
+fn hello_bytes_fat() -> &'static [u8] {
+    b"Hello, world!"
+}
+
+fn fat_pointer_on_32_bit() {
+    Some(5).expect("foo");
+}
+
+fn str_indexing() {
+    let mut x = "Hello".to_string();
+    let _v = &mut x[..3]; // Test IndexMut on String.
+}
+
+fn unique_aliasing() {
+    // This is a regression test for the aliasing rules of a `Unique<T>` pointer.
+    // At the time of writing this test case, Miri does not treat `Unique<T>`
+    // pointers as a special case, these are treated like any other raw pointer.
+    // However, there are existing Github issues which may lead to `Unique<T>`
+    // becoming a special case through asserting unique ownership over the pointee:
+    // - https://github.com/rust-lang/unsafe-code-guidelines/issues/258
+    // - https://github.com/rust-lang/unsafe-code-guidelines/issues/262
+    // Below, the calls to `String::remove` and `String::insert[_str]` follow
+    // code paths that would trigger undefined behavior in case `Unique<T>`
+    // would ever assert semantic ownership over the pointee. Internally,
+    // these methods call `self.vec.as_ptr()` and `self.vec.as_mut_ptr()` on
+    // the vector of bytes that are backing the `String`. That `Vec<u8>` holds a
+    // `Unique<u8>` internally. The second call to `Vec::as_mut_ptr(&mut self)`
+    // would then invalidate the pointers derived from `Vec::as_ptr(&self)`.
+    // Note that as long as `Unique<T>` is treated like any other raw pointer,
+    // this test case should pass. It is merely here as a canary test for
+    // potential future undefined behavior.
+    let mut x = String::from("Hello");
+    assert_eq!(x.remove(0), 'H');
+    x.insert(0, 'H');
+    assert_eq!(x, "Hello");
+    x.insert_str(x.len(), ", world!");
+    assert_eq!(x, "Hello, world!");
+}
+
+fn main() {
+    assert_eq!(empty(), "");
+    assert_eq!(hello(), "Hello, world!");
+    assert_eq!(hello_bytes(), b"Hello, world!");
+    assert_eq!(hello_bytes_fat(), b"Hello, world!");
+
+    fat_pointer_on_32_bit(); // Should run without crashing.
+    str_indexing();
+    unique_aliasing();
+}
diff --git a/src/tools/miri/tests/pass/subslice_array.rs b/src/tools/miri/tests/pass/subslice_array.rs
new file mode 100644
index 0000000000000..9b6b12d748adb
--- /dev/null
+++ b/src/tools/miri/tests/pass/subslice_array.rs
@@ -0,0 +1,11 @@
+fn bar(a: &'static str, b: &'static str) -> [&'static str; 4] {
+    [a, b, b, a]
+}
+
+fn main() {
+    let out = bar("baz", "foo");
+    let [a, xs @ .., d] = out;
+    assert_eq!(a, "baz");
+    assert_eq!(xs, ["foo", "foo"]);
+    assert_eq!(d, "baz");
+}
diff --git a/src/tools/miri/tests/pass/sums.rs b/src/tools/miri/tests/pass/sums.rs
new file mode 100644
index 0000000000000..3256d4c65116a
--- /dev/null
+++ b/src/tools/miri/tests/pass/sums.rs
@@ -0,0 +1,61 @@
+#[derive(Debug, PartialEq)]
+enum Unit {
+    Unit(()), // Force non-C-enum representation.
+}
+
+fn return_unit() -> Unit {
+    Unit::Unit(())
+}
+
+#[derive(Debug, PartialEq)]
+enum MyBool {
+    False(()), // Force non-C-enum representation.
+    True(()),
+}
+
+fn return_true() -> MyBool {
+    MyBool::True(())
+}
+
+fn return_false() -> MyBool {
+    MyBool::False(())
+}
+
+fn return_none() -> Option<i64> {
+    None
+}
+
+fn return_some() -> Option<i64> {
+    Some(42)
+}
+
+fn match_opt_none() -> i8 {
+    let x = None;
+    match x {
+        Some(data) => data,
+        None => 42,
+    }
+}
+
+fn match_opt_some() -> i8 {
+    let x = Some(13);
+    match x {
+        Some(data) => data,
+        None => 20,
+    }
+}
+
+fn two_nones() -> (Option<i16>, Option<i16>) {
+    (None, None)
+}
+
+fn main() {
+    assert_eq!(two_nones(), (None, None));
+    assert_eq!(match_opt_some(), 13);
+    assert_eq!(match_opt_none(), 42);
+    assert_eq!(return_some(), Some(42));
+    assert_eq!(return_none(), None);
+    assert_eq!(return_false(), MyBool::False(()));
+    assert_eq!(return_true(), MyBool::True(()));
+    assert_eq!(return_unit(), Unit::Unit(()));
+}
diff --git a/src/tools/miri/tests/pass/tag-align-dyn-u64.rs b/src/tools/miri/tests/pass/tag-align-dyn-u64.rs
new file mode 100644
index 0000000000000..72211a8d3f3af
--- /dev/null
+++ b/src/tools/miri/tests/pass/tag-align-dyn-u64.rs
@@ -0,0 +1,37 @@
+// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use std::mem;
+
+enum Tag<A> {
+    Tag2(A),
+}
+
+#[allow(dead_code)]
+struct Rec {
+    c8: u8,
+    t: Tag<u64>,
+}
+
+fn mk_rec() -> Rec {
+    return Rec { c8: 0, t: Tag::Tag2(0) };
+}
+
+fn is_u64_aligned(u: &Tag<u64>) -> bool {
+    let p: *const () = unsafe { mem::transmute(u) };
+    let p = p as usize;
+    let u64_align = std::mem::align_of::<u64>();
+    return (p & (u64_align - 1)) == 0;
+}
+
+pub fn main() {
+    let x = mk_rec();
+    assert!(is_u64_aligned(&x.t));
+}
diff --git a/src/tools/miri/tests/pass/threadleak_ignored.rs b/src/tools/miri/tests/pass/threadleak_ignored.rs
new file mode 100644
index 0000000000000..99bac7aa42a80
--- /dev/null
+++ b/src/tools/miri/tests/pass/threadleak_ignored.rs
@@ -0,0 +1,38 @@
+//@ignore-target-windows: Channels on Windows are not supported yet.
+// FIXME: disallow preemption to work around https://github.com/rust-lang/rust/issues/55005
+//@compile-flags: -Zmiri-ignore-leaks -Zmiri-preemption-rate=0
+
+//! Test that leaking threads works, and that their destructors are not executed.
+
+use std::cell::RefCell;
+
+struct LoudDrop(i32);
+impl Drop for LoudDrop {
+    fn drop(&mut self) {
+        eprintln!("Dropping {}", self.0);
+    }
+}
+
+thread_local! {
+    static X: RefCell<Option<LoudDrop>> = RefCell::new(None);
+}
+
+fn main() {
+    X.with(|x| *x.borrow_mut() = Some(LoudDrop(0)));
+
+    // Set up a channel so that we can learn when the other thread initialized `X`
+    // (so that we are sure there is something to drop).
+    let (send, recv) = std::sync::mpsc::channel::<()>();
+
+    let _detached = std::thread::spawn(move || {
+        X.with(|x| *x.borrow_mut() = Some(LoudDrop(1)));
+        send.send(()).unwrap();
+        std::thread::yield_now();
+        loop {}
+    });
+
+    std::thread::yield_now();
+
+    // Wait until child thread has initialized its `X`.
+    let () = recv.recv().unwrap();
+}
diff --git a/src/tools/miri/tests/pass/threadleak_ignored.stderr b/src/tools/miri/tests/pass/threadleak_ignored.stderr
new file mode 100644
index 0000000000000..7557f49c7584b
--- /dev/null
+++ b/src/tools/miri/tests/pass/threadleak_ignored.stderr
@@ -0,0 +1 @@
+Dropping 0
diff --git a/src/tools/miri/tests/pass/too-large-primval-write-problem.rs b/src/tools/miri/tests/pass/too-large-primval-write-problem.rs
new file mode 100644
index 0000000000000..f4c418bd78a99
--- /dev/null
+++ b/src/tools/miri/tests/pass/too-large-primval-write-problem.rs
@@ -0,0 +1,23 @@
+// `PrimVal`s in Miri are represented with 8 bytes (u64) and at the time of writing, the `-x`
+// will sign extend into the entire 8 bytes. Then, if you tried to write the `-x` into
+// something smaller than 8 bytes, like a 4 byte pointer, it would crash in byteorder crate
+// code that assumed only the low 4 bytes would be set. Actually, we were masking properly for
+// everything except pointers before I fixed it, so this was probably impossible to reproduce on
+// 64-bit.
+//
+// This is just intended as a regression test to make sure we don't reintroduce this problem.
+
+#[cfg(target_pointer_width = "32")]
+fn main() {
+    use std::mem::transmute;
+
+    // Make the weird PrimVal.
+    let x = 1i32;
+    let bad = unsafe { transmute::<i32, *const u8>(-x) };
+
+    // Force it through the Memory::write_primval code.
+    drop(Box::new(bad));
+}
+
+#[cfg(not(target_pointer_width = "32"))]
+fn main() {}
diff --git a/src/tools/miri/tests/pass/track-alloc-1.rs b/src/tools/miri/tests/pass/track-alloc-1.rs
new file mode 100644
index 0000000000000..427c800dc51d2
--- /dev/null
+++ b/src/tools/miri/tests/pass/track-alloc-1.rs
@@ -0,0 +1,6 @@
+// Ensure that tracking early allocations doesn't ICE Miri.
+// Early allocations are probably part of the runtime and therefore uninteresting, but they
+// shouldn't cause a crash.
+//@compile-flags: -Zmiri-track-alloc-id=1
+//@normalize-stderr-test: "[48] bytes" -> "SIZE bytes"
+fn main() {}
diff --git a/src/tools/miri/tests/pass/track-alloc-1.stderr b/src/tools/miri/tests/pass/track-alloc-1.stderr
new file mode 100644
index 0000000000000..7206edbb7010b
--- /dev/null
+++ b/src/tools/miri/tests/pass/track-alloc-1.stderr
@@ -0,0 +1,5 @@
+note: tracking was triggered
+   |
+   = note: created extern static allocation of SIZE bytes (alignment ALIGN bytes) with id 1
+   = note: (no span available)
+
diff --git a/src/tools/miri/tests/pass/track-caller-attribute.rs b/src/tools/miri/tests/pass/track-caller-attribute.rs
new file mode 100644
index 0000000000000..6694764a234d4
--- /dev/null
+++ b/src/tools/miri/tests/pass/track-caller-attribute.rs
@@ -0,0 +1,130 @@
+#![feature(core_intrinsics)]
+
+use std::panic::Location;
+
+#[track_caller]
+fn tracked() -> &'static Location<'static> {
+    Location::caller() // most importantly, we never get line 7
+}
+
+fn nested_intrinsic() -> &'static Location<'static> {
+    Location::caller()
+}
+
+fn nested_tracked() -> &'static Location<'static> {
+    tracked()
+}
+
+macro_rules! caller_location_from_macro {
+    () => {
+        core::panic::Location::caller()
+    };
+}
+
+fn test_fn_ptr() {
+    fn pass_to_ptr_call<T>(f: fn(T), x: T) {
+        f(x);
+    }
+
+    #[track_caller]
+    fn tracked_unit(_: ()) {
+        let expected_line = line!() - 1;
+        let location = std::panic::Location::caller();
+        assert_eq!(location.file(), file!());
+        assert_eq!(location.line(), expected_line, "call shims report location as fn definition");
+    }
+
+    pass_to_ptr_call(tracked_unit, ());
+}
+
+fn test_trait_obj() {
+    trait Tracked {
+        #[track_caller]
+        fn handle(&self) -> &'static Location<'static> {
+            std::panic::Location::caller()
+        }
+    }
+
+    impl Tracked for () {}
+    impl Tracked for u8 {}
+
+    // Test that we get the correct location
+    // even with a call through a trait object
+
+    let tracked: &dyn Tracked = &5u8;
+    let location = tracked.handle();
+    let expected_line = line!() - 1;
+    assert_eq!(location.file(), file!());
+    assert_eq!(location.line(), expected_line);
+    assert_eq!(location.column(), 28);
+
+    const TRACKED: &dyn Tracked = &();
+    let location = TRACKED.handle();
+    let expected_line = line!() - 1;
+    assert_eq!(location.file(), file!());
+    assert_eq!(location.line(), expected_line);
+    assert_eq!(location.column(), 28);
+}
+
+fn test_trait_obj2() {
+    // track_caller on the impl but not the trait.
+    pub trait Foo {
+        fn foo(&self) -> &'static Location<'static>;
+    }
+
+    struct Bar;
+    impl Foo for Bar {
+        #[track_caller]
+        fn foo(&self) -> &'static Location<'static> {
+            std::panic::Location::caller()
+        }
+    }
+    let expected_line = line!() - 4; // the `fn` signature above
+
+    let f = &Bar as &dyn Foo;
+    let loc = f.foo(); // trait doesn't track, so we don't point at this call site
+    assert_eq!(loc.file(), file!());
+    assert_eq!(loc.line(), expected_line);
+}
+
+fn main() {
+    let location = Location::caller();
+    let expected_line = line!() - 1;
+    assert_eq!(location.file(), file!());
+    assert_eq!(location.line(), expected_line);
+    assert_eq!(location.column(), 20);
+
+    let tracked = tracked();
+    let expected_line = line!() - 1;
+    assert_eq!(tracked.file(), file!());
+    assert_eq!(tracked.line(), expected_line);
+    assert_eq!(tracked.column(), 19);
+
+    let nested = nested_intrinsic();
+    assert_eq!(nested.file(), file!());
+    assert_eq!(nested.line(), 11);
+    assert_eq!(nested.column(), 5);
+
+    let contained = nested_tracked();
+    assert_eq!(contained.file(), file!());
+    assert_eq!(contained.line(), 15);
+    assert_eq!(contained.column(), 5);
+
+    // `Location::caller()` in a macro should behave similarly to `file!` and `line!`,
+    // i.e. point to where the macro was invoked, instead of the macro itself.
+    let inmacro = caller_location_from_macro!();
+    let expected_line = line!() - 1;
+    assert_eq!(inmacro.file(), file!());
+    assert_eq!(inmacro.line(), expected_line);
+    assert_eq!(inmacro.column(), 19);
+
+    let intrinsic = core::intrinsics::caller_location();
+    let expected_line = line!() - 1;
+    assert_eq!(intrinsic.file(), file!());
+    assert_eq!(intrinsic.line(), expected_line);
+    assert_eq!(intrinsic.column(), 21);
+
+    test_fn_ptr();
+    test_trait_obj();
+    test_trait_obj2();
+}
diff --git a/src/tools/miri/tests/pass/transmute_ptr.rs b/src/tools/miri/tests/pass/transmute_ptr.rs
new file mode 100644
index 0000000000000..fd9d457e440e9
--- /dev/null
+++ b/src/tools/miri/tests/pass/transmute_ptr.rs
@@ -0,0 +1,52 @@
+#![feature(strict_provenance)]
+use std::{mem, ptr};
+
+fn t1() {
+    // If we are careful, we can exploit data layout...
+    // This is a tricky case since we are transmuting a ScalarPair type to a non-ScalarPair type.
+    let raw = unsafe { mem::transmute::<&[u8], [*const u8; 2]>(&[42]) };
+    let ptr: *const u8 = unsafe { mem::transmute_copy(&raw) };
+    assert_eq!(unsafe { *ptr }, 42);
+}
+
+#[cfg(target_pointer_width = "64")]
+const PTR_SIZE: usize = 8;
+#[cfg(target_pointer_width = "32")]
+const PTR_SIZE: usize = 4;
+
+fn t2() {
+    let bad = unsafe { mem::transmute::<&[u8], [u8; 2 * PTR_SIZE]>(&[1u8]) };
+    let _val = bad[0] + bad[bad.len() - 1];
+}
+
+fn ptr_integer_array() {
+    let r = &mut 42;
+    let _i: [usize; 1] = unsafe { mem::transmute(r) };
+
+    let _x: [u8; PTR_SIZE] = unsafe { mem::transmute(&0) };
+}
+
+fn ptr_in_two_halves() {
+    unsafe {
+        let ptr = &0 as *const i32;
+        let arr = [ptr; 2];
+        // We want to do a scalar read of a pointer at offset PTR_SIZE/2 into this array. But we
+        // cannot use a packed struct or `read_unaligned`, as those use the memcpy code path in
+        // Miri. So instead we shift the entire array by a bit and then the actual read we want to
+        // do is perfectly aligned.
+        let mut target_arr = [ptr::null::<i32>(); 3];
+        let target = target_arr.as_mut_ptr().cast::<u8>();
+        target.add(PTR_SIZE / 2).cast::<[*const i32; 2]>().write_unaligned(arr);
+        // Now target_arr[1] is a mix of the two `ptr` we had stored in `arr`.
+        let strange_ptr = target_arr[1];
+        // Check that the provenance works out.
+        assert_eq!(*strange_ptr.with_addr(ptr.addr()), 0);
+    }
+}
+
+fn main() {
+    t1();
+    t2();
+    ptr_integer_array();
+    ptr_in_two_halves();
+}
diff --git a/src/tools/miri/tests/pass/trivial.rs b/src/tools/miri/tests/pass/trivial.rs
new file mode 100644
index 0000000000000..891d115206561
--- /dev/null
+++ b/src/tools/miri/tests/pass/trivial.rs
@@ -0,0 +1,11 @@
+fn empty() {}
+
+fn unit_var() {
+    let x = ();
+    x
+}
+
+fn main() {
+    empty();
+    unit_var();
+}
diff --git a/src/tools/miri/tests/pass/try-operator-custom.rs b/src/tools/miri/tests/pass/try-operator-custom.rs
new file mode 100644
index 0000000000000..ccbf63796af5b
--- /dev/null
+++ b/src/tools/miri/tests/pass/try-operator-custom.rs
@@ -0,0 +1,3 @@
+fn main() {
+    assert!(Ok::<i32, String>(42) == Ok(42));
+}
diff --git a/src/tools/miri/tests/pass/tuple_like_enum_variant_constructor.rs b/src/tools/miri/tests/pass/tuple_like_enum_variant_constructor.rs
new file mode 100644
index 0000000000000..5cf91b3f4d194
--- /dev/null
+++ b/src/tools/miri/tests/pass/tuple_like_enum_variant_constructor.rs
@@ -0,0 +1,3 @@
+fn main() {
+    assert_eq!(Some(42).map(Some), Some(Some(42)));
+}
diff --git a/src/tools/miri/tests/pass/tuple_like_enum_variant_constructor_pointer_opt.rs b/src/tools/miri/tests/pass/tuple_like_enum_variant_constructor_pointer_opt.rs
new file mode 100644
index 0000000000000..fb57d4f4c1652
--- /dev/null
+++ b/src/tools/miri/tests/pass/tuple_like_enum_variant_constructor_pointer_opt.rs
@@ -0,0 +1,4 @@
+fn main() {
+    let x = 5;
+    assert_eq!(Some(&x).map(Some), Some(Some(&x)));
+}
diff --git a/src/tools/miri/tests/pass/tuple_like_enum_variant_constructor_struct_pointer_opt.rs b/src/tools/miri/tests/pass/tuple_like_enum_variant_constructor_struct_pointer_opt.rs
new file mode 100644
index 0000000000000..44441ed1d36c8
--- /dev/null
+++ b/src/tools/miri/tests/pass/tuple_like_enum_variant_constructor_struct_pointer_opt.rs
@@ -0,0 +1,33 @@
+#[derive(Copy, Clone, PartialEq, Debug)]
+struct A<'a> {
+    x: i32,
+    y: &'a i32,
+}
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+struct B<'a>(i32, &'a i32);
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+enum C<'a> {
+    Value(i32, &'a i32),
+    #[allow(dead_code)]
+    NoValue,
+}
+
+fn main() {
+    let x = 5;
+    let a = A { x: 99, y: &x };
+    assert_eq!(Some(a).map(Some), Some(Some(a)));
+    let f = B;
+    assert_eq!(Some(B(42, &x)), Some(f(42, &x)));
+    // the following doesn't compile :(
+    //let f: for<'a> fn(i32, &'a i32) -> B<'a> = B;
+    //assert_eq!(Some(B(42, &x)), Some(f(42, &x)));
+    assert_eq!(B(42, &x), foo(&x, B));
+    let f = C::Value;
+    assert_eq!(C::Value(42, &x), f(42, &x));
+}
+
+fn foo<'a, F: Fn(i32, &'a i32) -> B<'a>>(i: &'a i32, f: F) -> B<'a> {
+    f(42, i)
+}
diff --git a/src/tools/miri/tests/pass/tuple_like_struct_constructor.rs b/src/tools/miri/tests/pass/tuple_like_struct_constructor.rs
new file mode 100644
index 0000000000000..05e8893de1787
--- /dev/null
+++ b/src/tools/miri/tests/pass/tuple_like_struct_constructor.rs
@@ -0,0 +1,5 @@
+fn main() {
+    #[derive(PartialEq, Eq, Debug)]
+    struct A(i32);
+    assert_eq!(Some(42).map(A), Some(A(42)));
+}
diff --git a/src/tools/miri/tests/pass/u128.rs b/src/tools/miri/tests/pass/u128.rs
new file mode 100644
index 0000000000000..0ef7a514cb653
--- /dev/null
+++ b/src/tools/miri/tests/pass/u128.rs
@@ -0,0 +1,70 @@
+#![feature(bench_black_box)]
+use std::hint::black_box as b;
+
+fn main() {
+    let x: u128 = 0xFFFF_FFFF_FFFF_FFFF__FFFF_FFFF_FFFF_FFFF;
+    assert_eq!(0, !x);
+    assert_eq!(0, !x);
+    let y: u128 = 0xFFFF_FFFF_FFFF_FFFF__FFFF_FFFF_FFFF_FFFE;
+    assert_eq!(!1, y);
+    assert_eq!(x, y | 1);
+    assert_eq!(
+        0xFAFF_0000_FF8F_0000__FFFF_0000_FFFF_FFFE,
+        y & 0xFAFF_0000_FF8F_0000__FFFF_0000_FFFF_FFFF,
+    );
+    let z: u128 = 0xABCD_EF;
+    assert_eq!(z * z, 0x734C_C2F2_A521);
+    assert_eq!(z * z * z * z, 0x33EE_0E2A_54E2_59DA_A0E7_8E41);
+    assert_eq!(z + z + z + z, 0x2AF3_7BC);
+    let k: u128 = 0x1234_5678_9ABC_DEFF_EDCB_A987_6543_210;
+    assert_eq!(k + k, 0x2468_ACF1_3579_BDFF_DB97_530E_CA86_420);
+    assert_eq!(0, k - k);
+    assert_eq!(0x1234_5678_9ABC_DEFF_EDCB_A987_5A86_421, k - z);
+    assert_eq!(
+        0x1000_0000_0000_0000_0000_0000_0000_000,
+        k - 0x234_5678_9ABC_DEFF_EDCB_A987_6543_210,
+    );
+    assert_eq!(0x6EF5_DE4C_D3BC_2AAA_3BB4_CC5D_D6EE_8, k / 42);
+    assert_eq!(0, k % 42);
+    assert_eq!(15, z % 42);
+    assert_eq!(0x169D_A8020_CEC18, k % 0x3ACB_FE49_FF24_AC);
+    assert_eq!(0x91A2_B3C4_D5E6_F7, k >> 65);
+    assert_eq!(0xFDB9_7530_ECA8_6420_0000_0000_0000_0000, k << 65);
+    assert!(k > z);
+    assert!(y > k);
+    assert!(y < x);
+    assert_eq!(x as u64, !0);
+    assert_eq!(z as u64, 0xABCD_EF);
+    assert_eq!(k as u64, 0xFEDC_BA98_7654_3210);
+    assert_eq!(k as i128, 0x1234_5678_9ABC_DEFF_EDCB_A987_6543_210);
+    assert_eq!((z as f64) as u128, z);
+    assert_eq!((z as f32) as u128, z);
+    assert_eq!((z as f64 * 16.0) as u128, z * 16);
+    assert_eq!((z as f32 * 16.0) as u128, z * 16);
+    let l: u128 = 432 << 100;
+    assert_eq!((l as f32) as u128, l);
+    assert_eq!((l as f64) as u128, l);
+    // formatting
+    let j: u128 = 1 << 67;
+    assert_eq!("147573952589676412928", format!("{}", j));
+    assert_eq!("80000000000000000", format!("{:x}", j));
+    assert_eq!("20000000000000000000000", format!("{:o}", j));
+    assert_eq!(
+        "10000000000000000000000000000000000000000000000000000000000000000000",
+        format!("{:b}", j),
+    );
+    assert_eq!("340282366920938463463374607431768211455", format!("{}", u128::MAX));
+    assert_eq!("147573952589676412928", format!("{:?}", j));
+    // common traits
+    assert_eq!(x, b(x.clone()));
+    // overflow checks
+    assert_eq!((z).checked_mul(z), Some(0x734C_C2F2_A521));
+    assert_eq!((k).checked_mul(k), None);
+    let l: u128 = b(u128::MAX - 10);
+    let o: u128 = b(17);
+    assert_eq!(l.checked_add(b(11)), None);
+    assert_eq!(l.checked_sub(l), Some(0));
+    assert_eq!(o.checked_sub(b(18)), None);
+    assert_eq!(b(1u128).checked_shl(b(127)), Some(1 << 127));
+    assert_eq!(o.checked_shl(b(128)), None);
+}
diff --git a/src/tools/miri/tests/pass/union-overwrite.rs b/src/tools/miri/tests/pass/union-overwrite.rs
new file mode 100644
index 0000000000000..f1d3d9d48101a
--- /dev/null
+++ b/src/tools/miri/tests/pass/union-overwrite.rs
@@ -0,0 +1,70 @@
+#[repr(C)]
+#[derive(Clone, Copy)]
+struct Pair<T, U>(T, U);
+#[repr(C)]
+#[derive(Clone, Copy)]
+struct Triple<T>(T, T, T);
+
+#[repr(C)]
+union U<A: Copy, B: Copy> {
+    a: Pair<A, A>,
+    b: B,
+}
+
+#[repr(C)]
+union W<A: Copy, B: Copy> {
+    a: A,
+    b: B,
+}
+
+#[cfg(target_endian = "little")]
+unsafe fn check() {
+    let mut u = U::<u8, u16> { b: 0xDE_DE };
+    u.a.0 = 0xBE;
+    assert_eq!(u.b, 0xDE_BE);
+
+    let mut u = U::<u16, u32> { b: 0xDEAD_DEAD };
+    u.a.0 = 0xBEEF;
+    assert_eq!(u.b, 0xDEAD_BEEF);
+
+    let mut u = U::<u32, u64> { b: 0xDEADBEEF_DEADBEEF };
+    u.a.0 = 0xBAADF00D;
+    assert_eq!(u.b, 0xDEADBEEF_BAADF00D);
+
+    let mut w = W::<Pair<Triple<u8>, u8>, u32> { b: 0xDEAD_DEAD };
+    w.a.0 = Triple(0, 0, 0);
+    assert_eq!(w.b, 0xDE00_0000);
+
+    let mut w = W::<Pair<u8, Triple<u8>>, u32> { b: 0xDEAD_DEAD };
+    w.a.1 = Triple(0, 0, 0);
+    assert_eq!(w.b, 0x0000_00AD);
+}
+
+#[cfg(target_endian = "big")]
+unsafe fn check() {
+    let mut u = U::<u8, u16> { b: 0xDE_DE };
+    u.a.0 = 0xBE;
+    assert_eq!(u.b, 0xBE_DE);
+
+    let mut u = U::<u16, u32> { b: 0xDEAD_DEAD };
+    u.a.0 = 0xBEEF;
+    assert_eq!(u.b, 0xBEEF_DEAD);
+
+    let mut u = U::<u32, u64> { b: 0xDEADBEEF_DEADBEEF };
+    u.a.0 = 0xBAADF00D;
+    assert_eq!(u.b, 0xBAADF00D_DEADBEEF);
+
+    let mut w = W::<Pair<Triple<u8>, u8>, u32> { b: 0xDEAD_DEAD };
+    w.a.0 = Triple(0, 0, 0);
+    assert_eq!(w.b, 0x0000_00AD);
+
+    let mut w = W::<Pair<u8, Triple<u8>>, u32> { b: 0xDEAD_DEAD };
+    w.a.1 = Triple(0, 0, 0);
+    assert_eq!(w.b, 0xDE00_0000);
+}
+
+fn main() {
+    unsafe {
+        check();
+    }
+}
diff --git a/src/tools/miri/tests/pass/union.rs b/src/tools/miri/tests/pass/union.rs
new file mode 100644
index 0000000000000..8a6dd49f45038
--- /dev/null
+++ b/src/tools/miri/tests/pass/union.rs
@@ -0,0 +1,91 @@
+fn main() {
+    a();
+    b();
+    c();
+    d();
+}
+
+fn a() {
+    #[allow(dead_code)]
+    union U {
+        f1: u32,
+        f2: f32,
+    }
+    let mut u = U { f1: 1 };
+    unsafe {
+        let b1 = &mut u.f1;
+        *b1 = 5;
+    }
+    assert_eq!(unsafe { u.f1 }, 5);
+}
+
+fn b() {
+    #[derive(Copy, Clone)]
+    struct S {
+        x: u32,
+        y: u32,
+    }
+
+    #[allow(dead_code)]
+    union U {
+        s: S,
+        both: u64,
+    }
+    let mut u = U { s: S { x: 1, y: 2 } };
+    unsafe {
+        let bx = &mut u.s.x;
+        let by = &mut u.s.y;
+        *bx = 5;
+        *by = 10;
+    }
+    assert_eq!(unsafe { u.s.x }, 5);
+    assert_eq!(unsafe { u.s.y }, 10);
+}
+
+fn c() {
+    #[repr(u32)]
+    enum Tag {
+        I,
+        F,
+    }
+
+    #[repr(C)]
+    union U {
+        i: i32,
+        f: f32,
+    }
+
+    #[repr(C)]
+    struct Value {
+        tag: Tag,
+        u: U,
+    }
+
+    fn is_zero(v: Value) -> bool {
+        unsafe {
+            match v {
+                Value { tag: Tag::I, u: U { i: 0 } } => true,
+                Value { tag: Tag::F, u: U { f } } => f == 0.0,
+                _ => false,
+            }
+        }
+    }
+    assert!(is_zero(Value { tag: Tag::I, u: U { i: 0 } }));
+    assert!(is_zero(Value { tag: Tag::F, u: U { f: 0.0 } }));
+    assert!(!is_zero(Value { tag: Tag::I, u: U { i: 1 } }));
+    assert!(!is_zero(Value { tag: Tag::F, u: U { f: 42.0 } }));
+}
+
+fn d() {
+    union MyUnion {
+        f1: u32,
+        f2: f32,
+    }
+    let u = MyUnion { f1: 10 };
+    unsafe {
+        match u {
+            MyUnion { f1: 10 } => {}
+            MyUnion { f2: _f2 } => panic!("foo"),
+        }
+    }
+}
diff --git a/src/tools/miri/tests/pass/unops.rs b/src/tools/miri/tests/pass/unops.rs
new file mode 100644
index 0000000000000..f23f68ccbad00
--- /dev/null
+++ b/src/tools/miri/tests/pass/unops.rs
@@ -0,0 +1,5 @@
+fn main() {
+    assert_eq!(!true, false);
+    assert_eq!(!0xFFu16, 0xFF00);
+    assert_eq!(-{ 1i16 }, -1i16);
+}
diff --git a/src/tools/miri/tests/pass/unsized.rs b/src/tools/miri/tests/pass/unsized.rs
new file mode 100644
index 0000000000000..d9beac4327d7f
--- /dev/null
+++ b/src/tools/miri/tests/pass/unsized.rs
@@ -0,0 +1,36 @@
+#![feature(unsized_tuple_coercion)]
+#![feature(unsized_fn_params)]
+
+use std::mem;
+
+fn unsized_tuple() {
+    let x: &(i32, i32, [i32]) = &(0, 1, [2, 3]);
+    let y: &(i32, i32, [i32]) = &(0, 1, [2, 3, 4]);
+    let mut a = [y, x];
+    a.sort();
+    assert_eq!(a, [x, y]);
+
+    assert_eq!(&format!("{:?}", a), "[(0, 1, [2, 3]), (0, 1, [2, 3, 4])]");
+    assert_eq!(mem::size_of_val(x), 16);
+}
+
+fn unsized_params() {
+    pub fn f0(_f: dyn FnOnce()) {}
+    pub fn f1(_s: str) {}
+    pub fn f2(_x: i32, _y: [i32]) {}
+    pub fn f3(_p: dyn Send) {}
+
+    let c: Box<dyn FnOnce()> = Box::new(|| {});
+    f0(*c);
+    let foo = "foo".to_string().into_boxed_str();
+    f1(*foo);
+    let sl: Box<[i32]> = [0, 1, 2].to_vec().into_boxed_slice();
+    f2(5, *sl);
+    let p: Box<dyn Send> = Box::new((1, 2));
+    f3(*p);
+}
+
+fn main() {
+    unsized_tuple();
+    unsized_params();
+}
diff --git a/src/tools/miri/tests/pass/validation_lifetime_resolution.rs b/src/tools/miri/tests/pass/validation_lifetime_resolution.rs
new file mode 100644
index 0000000000000..f5eef2ad6e753
--- /dev/null
+++ b/src/tools/miri/tests/pass/validation_lifetime_resolution.rs
@@ -0,0 +1,36 @@
+trait Id {
+    type Out;
+
+    fn id(self) -> Self::Out;
+}
+
+impl<'a> Id for &'a mut i32 {
+    type Out = &'a mut i32;
+
+    fn id(self) -> Self {
+        self
+    }
+}
+
+impl<'a> Id for &'a mut u32 {
+    type Out = &'a mut u32;
+
+    fn id(self) -> Self {
+        self
+    }
+}
+
+fn foo<T>(mut x: T)
+where
+    for<'a> &'a mut T: Id,
+{
+    let x = &mut x;
+    let _y = x.id();
+    // Inspecting the trace should show that `_y` has a type involving a local lifetime, when it gets validated.
+    // Unfortunately, there doesn't seem to be a way to actually have a test fail if it does not have the right
+    // type. Currently, this is *not* working correctly; see <https://github.com/rust-lang/miri/issues/298>.
+}
+
+fn main() {
+    foo(3)
+}
diff --git a/src/tools/miri/tests/pass/vec-matching-fold.rs b/src/tools/miri/tests/pass/vec-matching-fold.rs
new file mode 100644
index 0000000000000..3a869703bf96a
--- /dev/null
+++ b/src/tools/miri/tests/pass/vec-matching-fold.rs
@@ -0,0 +1,40 @@
+use std::fmt::Debug;
+
+fn foldl<T, U, F>(values: &[T], initial: U, mut function: F) -> U
+where
+    U: Clone + Debug,
+    T: Debug,
+    F: FnMut(U, &T) -> U,
+{
+    match values {
+        [head, tail @ ..] => foldl(tail, function(initial, head), function),
+        [] => {
+            let res = initial.clone();
+            res
+        }
+    }
+}
+
+fn foldr<T, U, F>(values: &[T], initial: U, mut function: F) -> U
+where
+    U: Clone,
+    F: FnMut(&T, U) -> U,
+{
+    match values {
+        [head @ .., tail] => foldr(head, function(tail, initial), function),
+        [] => {
+            let res = initial.clone();
+            res
+        }
+    }
+}
+
+pub fn main() {
+    let x = &[1, 2, 3, 4, 5];
+
+    let product = foldl(x, 1, |a, b| a * *b);
+    assert_eq!(product, 120);
+
+    let sum = foldr(x, 0, |a, b| *a + b);
+    assert_eq!(sum, 15);
+}
diff --git a/src/tools/miri/tests/pass/vec.rs b/src/tools/miri/tests/pass/vec.rs
new file mode 100644
index 0000000000000..26732cec5eb9a
--- /dev/null
+++ b/src/tools/miri/tests/pass/vec.rs
@@ -0,0 +1,176 @@
+//@compile-flags: -Zmiri-strict-provenance
+// Gather all references from a mutable iterator and make sure Miri notices if
+// using them is dangerous.
+fn test_all_refs<'a, T: 'a>(dummy: &mut T, iter: impl Iterator<Item = &'a mut T>) {
+    // Gather all those references.
+    let mut refs: Vec<&mut T> = iter.collect();
+    // Use them all. Twice, to be sure we got all interleavings.
+    for r in refs.iter_mut() {
+        std::mem::swap(dummy, r);
+    }
+    for r in refs {
+        std::mem::swap(dummy, r);
+    }
+}
+
+fn make_vec() -> Vec<u8> {
+    let mut v = Vec::with_capacity(4);
+    v.push(1);
+    v.push(2);
+    v
+}
+
+fn make_vec_macro() -> Vec<u8> {
+    vec![1, 2]
+}
+
+fn make_vec_macro_repeat() -> Vec<u8> {
+    vec![42; 5]
+}
+
+fn make_vec_macro_repeat_zeroed() -> Vec<u8> {
+    vec![0; 7]
+}
+
+fn vec_into_iter() -> u8 {
+    vec![1, 2, 3, 4].into_iter().map(|x| x * x).fold(0, |x, y| x + y)
+}
+
+fn vec_into_iter_rev() -> u8 {
+    vec![1, 2, 3, 4].into_iter().map(|x| x * x).fold(0, |x, y| x + y)
+}
+
+fn vec_into_iter_zst() -> usize {
+    vec![[0u64; 0], [0u64; 0]].into_iter().rev().map(|x| x.len()).sum()
+}
+
+fn vec_into_iter_rev_zst() -> usize {
+    vec![[0u64; 0], [0u64; 0]].into_iter().rev().map(|x| x.len()).sum()
+}
+
+fn vec_iter_and_mut() {
+    let mut v = vec![1, 2, 3, 4];
+    for i in v.iter_mut() {
+        *i += 1;
+    }
+    assert_eq!(v.iter().sum::<i32>(), 2 + 3 + 4 + 5);
+
+    test_all_refs(&mut 13, v.iter_mut());
+}
+
+fn vec_iter_and_mut_rev() {
+    let mut v = vec![1, 2, 3, 4];
+    for i in v.iter_mut().rev() {
+        *i += 1;
+    }
+    assert_eq!(v.iter().sum::<i32>(), 2 + 3 + 4 + 5);
+}
+
+fn vec_reallocate() -> Vec<u8> {
+    let mut v = vec![1, 2];
+    v.push(3);
+    v.push(4);
+    v.push(5);
+    v
+}
+
+fn vec_push_ptr_stable() {
+    let mut v = Vec::with_capacity(10);
+    v.push(0);
+    let v0 = unsafe { &mut *(&mut v[0] as *mut _) }; // laundering the lifetime -- we take care that `v` does not reallocate, so that's okay.
+    v.push(1);
+    let _val = *v0;
+}
+
+fn vec_extend_ptr_stable() {
+    let mut v = Vec::with_capacity(10);
+    v.push(0);
+    let v0 = unsafe { &mut *(&mut v[0] as *mut _) }; // laundering the lifetime -- we take care that `v` does not reallocate, so that's okay.
+    // `slice::Iter` (with `T: Copy`) specialization
+    v.extend(&[1]);
+    let _val = *v0;
+    // `vec::IntoIter` specialization
+    v.extend(vec![2]);
+    let _val = *v0;
+    // `TrustedLen` specialization
+    v.extend(std::iter::once(3));
+    let _val = *v0;
+    // base case
+    v.extend(std::iter::once(3).filter(|_| true));
+    let _val = *v0;
+}
+
+fn vec_truncate_ptr_stable() {
+    let mut v = vec![0; 10];
+    let v0 = unsafe { &mut *(&mut v[0] as *mut _) }; // laundering the lifetime -- we take care that `v` does not reallocate, so that's okay.
+    v.truncate(5);
+    let _val = *v0;
+}
+
+fn push_str_ptr_stable() {
+    let mut buf = String::with_capacity(11);
+    buf.push_str("hello");
+    let hello: &str = unsafe { &*(buf.as_str() as *const _) }; // laundering the lifetime -- we take care that `buf` does not reallocate, so that's okay.
+    buf.push_str(" world");
+    assert_eq!(format!("{}", hello), "hello");
+}
+
+fn sort() {
+    let mut v = vec![1; 20];
+    v.push(0);
+    v.sort();
+}
+
+fn swap() {
+    let mut v = vec![1, 2, 3, 4];
+    v.swap(2, 2);
+}
+
+fn swap_remove() {
+    let mut a = 0;
+    let mut b = 1;
+    let mut vec = vec![&mut a, &mut b];
+
+    vec.swap_remove(1);
+}
+
+fn reverse() {
+    #[repr(align(2))]
+    #[derive(Debug)]
+    struct Foo(u8);
+
+    let mut v: Vec<_> = (0..50).map(Foo).collect();
+    v.reverse();
+    assert!(v[0].0 == 49);
+}
+
+fn main() {
+    assert_eq!(vec_reallocate().len(), 5);
+
+    assert_eq!(vec_into_iter(), 30);
+    assert_eq!(vec_into_iter_rev(), 30);
+    vec_iter_and_mut();
+    assert_eq!(vec_into_iter_zst(), 0);
+    assert_eq!(vec_into_iter_rev_zst(), 0);
+    vec_iter_and_mut_rev();
+
+    assert_eq!(make_vec().capacity(), 4);
+    assert_eq!(make_vec_macro(), [1, 2]);
+    assert_eq!(make_vec_macro_repeat(), [42; 5]);
+    assert_eq!(make_vec_macro_repeat_zeroed(), [0; 7]);
+
+    // Test interesting empty slice comparison
+    // (one is a real pointer, one an integer pointer).
+    assert_eq!((200..-5).step_by(1).collect::<Vec<isize>>(), []);
+
+    // liballoc has a more extensive test of this, but let's at least do a smoke test here.
+    vec_push_ptr_stable();
+    vec_extend_ptr_stable();
+    vec_truncate_ptr_stable();
+    push_str_ptr_stable();
+
+    sort();
+    swap();
+    swap_remove();
+    reverse();
+}
diff --git a/src/tools/miri/tests/pass/vecdeque.rs b/src/tools/miri/tests/pass/vecdeque.rs
new file mode 100644
index 0000000000000..6f56f9d103e9d
--- /dev/null
+++ b/src/tools/miri/tests/pass/vecdeque.rs
@@ -0,0 +1,51 @@
+//@compile-flags: -Zmiri-strict-provenance
+use std::collections::VecDeque;
+
+fn test_all_refs<'a, T: 'a>(dummy: &mut T, iter: impl Iterator<Item = &'a mut T>) {
+    // Gather all those references.
+    let mut refs: Vec<&mut T> = iter.collect();
+    // Use them all. Twice, to be sure we got all interleavings.
+    for r in refs.iter_mut() {
+        std::mem::swap(dummy, r);
+    }
+    for r in refs {
+        std::mem::swap(dummy, r);
+    }
+}
+
+fn main() {
+    let mut dst = VecDeque::new();
+    dst.push_front(Box::new(1));
+    dst.push_front(Box::new(2));
+    dst.pop_back();
+
+    let mut src = VecDeque::new();
+    src.push_front(Box::new(2));
+    dst.append(&mut src);
+    for a in dst.iter() {
+        assert_eq!(**a, 2);
+    }
+
+    // Regression test for Debug impl's
+    println!("{:?} {:?}", dst, dst.iter());
+    println!("{:?}", VecDeque::<u32>::new().iter());
+
+    for a in dst {
+        assert_eq!(*a, 2);
+    }
+
+    // # Aliasing tests.
+    let mut v = std::collections::VecDeque::new();
+    v.push_back(1);
+    v.push_back(2);
+
+    // Test `fold` bad aliasing.
+    let mut it = v.iter_mut();
+    let ref0 = it.next().unwrap();
+    let sum = it.fold(0, |x, y| x + *y);
+    assert_eq!(*ref0 + sum, 3);
+
+    // Test general iterator aliasing.
+    v.push_front(0);
+    test_all_refs(&mut 0, v.iter_mut());
+}
diff --git a/src/tools/miri/tests/pass/vecdeque.stdout b/src/tools/miri/tests/pass/vecdeque.stdout
new file mode 100644
index 0000000000000..63de960ee2bdf
--- /dev/null
+++ b/src/tools/miri/tests/pass/vecdeque.stdout
@@ -0,0 +1,2 @@
+[2, 2] Iter([2, 2], [])
+Iter([], [])
diff --git a/src/tools/miri/tests/pass/volatile.rs b/src/tools/miri/tests/pass/volatile.rs
new file mode 100644
index 0000000000000..c9799801455c6
--- /dev/null
+++ b/src/tools/miri/tests/pass/volatile.rs
@@ -0,0 +1,12 @@
+// related: #58645
+#![feature(core_intrinsics)]
+use std::intrinsics::{volatile_load, volatile_store};
+
+pub fn main() {
+    unsafe {
+        let i: &mut (isize, isize) = &mut (0, 0);
+        volatile_store(i, (1, 2));
+        assert_eq!(volatile_load(i), (1, 2));
+        assert_eq!(i, &mut (1, 2));
+    }
+}
diff --git a/src/tools/miri/tests/pass/weak_memory/extra_cpp.rs b/src/tools/miri/tests/pass/weak_memory/extra_cpp.rs
new file mode 100644
index 0000000000000..07cbb4a803f1f
--- /dev/null
+++ b/src/tools/miri/tests/pass/weak_memory/extra_cpp.rs
@@ -0,0 +1,80 @@
+//@compile-flags: -Zmiri-ignore-leaks
+
+// Tests operations not perfomable through C++'s atomic API
+// but doable in safe (at least sound) Rust.
+
+#![feature(atomic_from_mut)]
+
+use std::sync::atomic::Ordering::*;
+use std::sync::atomic::{AtomicU16, AtomicU32};
+use std::thread::spawn;
+
+fn static_atomic_mut(val: u32) -> &'static mut AtomicU32 {
+    let ret = Box::leak(Box::new(AtomicU32::new(val)));
+    ret
+}
+
+fn split_u32(dword: &mut u32) -> &mut [u16; 2] {
+    unsafe { std::mem::transmute::<&mut u32, &mut [u16; 2]>(dword) }
+}
+
+fn mem_replace() {
+    let mut x = AtomicU32::new(0);
+
+    let old_x = std::mem::replace(&mut x, AtomicU32::new(42));
+
+    assert_eq!(x.load(Relaxed), 42);
+    assert_eq!(old_x.load(Relaxed), 0);
+}
+
+fn assign_to_mut() {
+    let x = static_atomic_mut(0);
+    x.store(1, Relaxed);
+
+    *x = AtomicU32::new(2);
+
+    assert_eq!(x.load(Relaxed), 2);
+}
+
+fn get_mut_write() {
+    let x = static_atomic_mut(0);
+    x.store(1, Relaxed);
+    {
+        let x_mut = x.get_mut();
+        *x_mut = 2;
+    }
+
+    let j1 = spawn(move || x.load(Relaxed));
+
+    let r1 = j1.join().unwrap();
+    assert_eq!(r1, 2);
+}
+
+// This is technically doable in C++ with atomic_ref
+// but little literature exists atm on its involvement
+// in mixed size/atomicity accesses
+fn from_mut_split() {
+    let mut x: u32 = 0;
+
+    {
+        let x_atomic = AtomicU32::from_mut(&mut x);
+        x_atomic.store(u32::from_be(0xabbafafa), Relaxed);
+    }
+
+    // Split the `AtomicU32` into two `AtomicU16`.
+    // Crucially, there is no non-atomic access to `x`! All accesses are atomic, but of different size.
+    let (x_hi, x_lo) = split_u32(&mut x).split_at_mut(1);
+
+    let x_hi_atomic = AtomicU16::from_mut(&mut x_hi[0]);
+    let x_lo_atomic = AtomicU16::from_mut(&mut x_lo[0]);
+
+    assert_eq!(x_hi_atomic.load(Relaxed), u16::from_be(0xabba));
+    assert_eq!(x_lo_atomic.load(Relaxed), u16::from_be(0xfafa));
+}
+
+pub fn main() {
+    get_mut_write();
+    from_mut_split();
+    assign_to_mut();
+    mem_replace();
+}
diff --git a/src/tools/miri/tests/pass/weak_memory/extra_cpp_unsafe.rs b/src/tools/miri/tests/pass/weak_memory/extra_cpp_unsafe.rs
new file mode 100644
index 0000000000000..f7e2748408ff8
--- /dev/null
+++ b/src/tools/miri/tests/pass/weak_memory/extra_cpp_unsafe.rs
@@ -0,0 +1,40 @@
+//@compile-flags: -Zmiri-ignore-leaks
+
+// Tests operations not perfomable through C++'s atomic API
+// but doable in unsafe Rust which we think *should* be fine.
+// Nonetheless they may be determined as inconsistent with the
+// memory model in the future.
+
+#![feature(atomic_from_mut)]
+
+use std::sync::atomic::AtomicU32;
+use std::sync::atomic::Ordering::*;
+use std::thread::spawn;
+
+fn static_atomic(val: u32) -> &'static AtomicU32 {
+    let ret = Box::leak(Box::new(AtomicU32::new(val)));
+    ret
+}
+
+// We allow perfectly overlapping non-atomic and atomic reads to race
+fn racing_mixed_atomicity_read() {
+    let x = static_atomic(0);
+    x.store(42, Relaxed);
+
+    let j1 = spawn(move || x.load(Relaxed));
+
+    let j2 = spawn(move || {
+        let x_ptr = x as *const AtomicU32 as *const u32;
+        unsafe { x_ptr.read() }
+    });
+
+    let r1 = j1.join().unwrap();
+    let r2 = j2.join().unwrap();
+
+    assert_eq!(r1, 42);
+    assert_eq!(r2, 42);
+}
+
+pub fn main() {
+    racing_mixed_atomicity_read();
+}
diff --git a/src/tools/miri/tests/pass/weak_memory/weak.rs b/src/tools/miri/tests/pass/weak_memory/weak.rs
new file mode 100644
index 0000000000000..4c3be6b3559ae
--- /dev/null
+++ b/src/tools/miri/tests/pass/weak_memory/weak.rs
@@ -0,0 +1,150 @@
+//@compile-flags: -Zmiri-ignore-leaks -Zmiri-preemption-rate=0
+
+// Tests showing weak memory behaviours are exhibited. All tests
+// return true when the desired behaviour is seen.
+// This is scheduler and pseudo-RNG dependent, so each test is
+// run multiple times until one try returns true.
+// Spurious failure is possible, if you are really unlucky with
+// the RNG and always read the latest value from the store buffer.
+
+use std::sync::atomic::Ordering::*;
+use std::sync::atomic::{fence, AtomicUsize};
+use std::thread::spawn;
+
+#[derive(Copy, Clone)]
+struct EvilSend<T>(pub T);
+
+unsafe impl<T> Send for EvilSend<T> {}
+unsafe impl<T> Sync for EvilSend<T> {}
+
+// We can't create static items because we need to run each test
+// multiple times
+fn static_atomic(val: usize) -> &'static AtomicUsize {
+    let ret = Box::leak(Box::new(AtomicUsize::new(val)));
+    ret
+}
+
+// Spins until it reads the given value
+fn reads_value(loc: &AtomicUsize, val: usize) -> usize {
+    while loc.load(Relaxed) != val {
+        std::hint::spin_loop();
+    }
+    val
+}
+
+fn relaxed() -> bool {
+    let x = static_atomic(0);
+    let j1 = spawn(move || {
+        x.store(1, Relaxed);
+        x.store(2, Relaxed);
+    });
+
+    let j2 = spawn(move || x.load(Relaxed));
+
+    j1.join().unwrap();
+    let r2 = j2.join().unwrap();
+
+    r2 == 1
+}
+
+// https://www.doc.ic.ac.uk/~afd/homepages/papers/pdfs/2017/POPL.pdf Figure 8
+fn seq_cst() -> bool {
+    let x = static_atomic(0);
+
+    let j1 = spawn(move || {
+        x.store(1, Relaxed);
+    });
+
+    let j2 = spawn(move || {
+        x.store(2, SeqCst);
+        x.store(3, SeqCst);
+    });
+
+    let j3 = spawn(move || x.load(SeqCst));
+
+    j1.join().unwrap();
+    j2.join().unwrap();
+    let r3 = j3.join().unwrap();
+
+    r3 == 1
+}
+
+fn initialization_write(add_fence: bool) -> bool {
+    let x = static_atomic(11);
+    assert_eq!(x.load(Relaxed), 11); // work around https://github.com/rust-lang/miri/issues/2164
+
+    let wait = static_atomic(0);
+
+    let j1 = spawn(move || {
+        x.store(22, Relaxed);
+        // Relaxed is intentional. We want to test if the thread 2 reads the initialisation write
+        // after a relaxed write
+        wait.store(1, Relaxed);
+    });
+
+    let j2 = spawn(move || {
+        reads_value(wait, 1);
+        if add_fence {
+            fence(AcqRel);
+        }
+        x.load(Relaxed)
+    });
+
+    j1.join().unwrap();
+    let r2 = j2.join().unwrap();
+
+    r2 == 11
+}
+
+fn faa_replaced_by_load() -> bool {
+    // Example from https://github.com/llvm/llvm-project/issues/56450#issuecomment-1183695905
+    #[no_mangle]
+    pub fn rdmw(storing: &AtomicUsize, sync: &AtomicUsize, loading: &AtomicUsize) -> usize {
+        storing.store(1, Relaxed);
+        fence(Release);
+        // sync.fetch_add(0, Relaxed);
+        sync.load(Relaxed);
+        fence(Acquire);
+        loading.load(Relaxed)
+    }
+
+    let x = static_atomic(0);
+    assert_eq!(x.load(Relaxed), 0); // work around https://github.com/rust-lang/miri/issues/2164
+    let y = static_atomic(0);
+    assert_eq!(y.load(Relaxed), 0); // work around https://github.com/rust-lang/miri/issues/2164
+    let z = static_atomic(0);
+    assert_eq!(z.load(Relaxed), 0); // work around https://github.com/rust-lang/miri/issues/2164
+
+    // Since each thread is so short, we need to make sure that they truely run at the same time
+    // Otherwise t1 will finish before t2 even starts
+    let go = static_atomic(0);
+
+    let t1 = spawn(move || {
+        while go.load(Relaxed) == 0 {}
+        rdmw(y, x, z)
+    });
+
+    let t2 = spawn(move || {
+        while go.load(Relaxed) == 0 {}
+        rdmw(z, x, y)
+    });
+
+    go.store(1, Relaxed);
+
+    let a = t1.join().unwrap();
+    let b = t2.join().unwrap();
+    (a, b) == (0, 0)
+}
+
+/// Asserts that the function returns true at least once in 100 runs
+fn assert_once(f: fn() -> bool) {
+    assert!(std::iter::repeat_with(|| f()).take(100).any(|x| x));
+}
+
+pub fn main() {
+    assert_once(relaxed);
+    assert_once(seq_cst);
+    assert_once(|| initialization_write(false));
+    assert_once(|| initialization_write(true));
+    assert_once(faa_replaced_by_load);
+}
diff --git a/src/tools/miri/tests/pass/without-validation.rs b/src/tools/miri/tests/pass/without-validation.rs
new file mode 100644
index 0000000000000..934c44a7deb4d
--- /dev/null
+++ b/src/tools/miri/tests/pass/without-validation.rs
@@ -0,0 +1,24 @@
+// When we notice something breaks only without validation, we add a test here.
+//@compile-flags: -Zmiri-disable-validation
+use std::cell::*;
+
+fn refcell_unsize() {
+    let cell: RefCell<[i32; 3]> = RefCell::new([1, 2, 3]);
+    {
+        let mut cellref: RefMut<'_, [i32; 3]> = cell.borrow_mut();
+        cellref[0] = 4;
+        let mut coerced: RefMut<'_, [i32]> = cellref;
+        coerced[2] = 5;
+    }
+    {
+        let comp: &mut [i32] = &mut [4, 2, 5];
+        let cellref: Ref<'_, [i32; 3]> = cell.borrow();
+        assert_eq!(&*cellref, comp);
+        let coerced: Ref<'_, [i32]> = cellref;
+        assert_eq!(&*coerced, comp);
+    }
+}
+
+fn main() {
+    refcell_unsize();
+}
diff --git a/src/tools/miri/tests/pass/write-bytes.rs b/src/tools/miri/tests/pass/write-bytes.rs
new file mode 100644
index 0000000000000..b2050c5393e22
--- /dev/null
+++ b/src/tools/miri/tests/pass/write-bytes.rs
@@ -0,0 +1,85 @@
+#![feature(core_intrinsics)] // for `volatile_set_memory`
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+struct Foo {
+    a: u64,
+    b: u64,
+    c: u64,
+}
+
+fn main() {
+    const LENGTH: usize = 10;
+    let mut v: [u64; LENGTH] = [0; LENGTH];
+
+    for idx in 0..LENGTH {
+        assert_eq!(v[idx], 0);
+    }
+
+    unsafe {
+        let p = v.as_mut_ptr();
+        ::std::ptr::write_bytes(p, 0xab, LENGTH);
+    }
+
+    for idx in 0..LENGTH {
+        assert_eq!(v[idx], 0xabababababababab);
+    }
+
+    // -----
+
+    let mut w: [Foo; LENGTH] = [Foo { a: 0, b: 0, c: 0 }; LENGTH];
+    for idx in 0..LENGTH {
+        assert_eq!(w[idx].a, 0);
+        assert_eq!(w[idx].b, 0);
+        assert_eq!(w[idx].c, 0);
+    }
+
+    unsafe {
+        let p = w.as_mut_ptr();
+        ::std::ptr::write_bytes(p, 0xcd, LENGTH);
+    }
+
+    for idx in 0..LENGTH {
+        assert_eq!(w[idx].a, 0xcdcdcdcdcdcdcdcd);
+        assert_eq!(w[idx].b, 0xcdcdcdcdcdcdcdcd);
+        assert_eq!(w[idx].c, 0xcdcdcdcdcdcdcdcd);
+    }
+
+    // -----
+    // `std::intrinsics::volatile_set_memory` should behave identically
+
+    let mut v: [u64; LENGTH] = [0; LENGTH];
+
+    for idx in 0..LENGTH {
+        assert_eq!(v[idx], 0);
+    }
+
+    unsafe {
+        let p = v.as_mut_ptr();
+        ::std::intrinsics::volatile_set_memory(p, 0xab, LENGTH);
+    }
+
+    for idx in 0..LENGTH {
+        assert_eq!(v[idx], 0xabababababababab);
+    }
+
+    // -----
+
+    let mut w: [Foo; LENGTH] = [Foo { a: 0, b: 0, c: 0 }; LENGTH];
+    for idx in 0..LENGTH {
+        assert_eq!(w[idx].a, 0);
+        assert_eq!(w[idx].b, 0);
+        assert_eq!(w[idx].c, 0);
+    }
+
+    unsafe {
+        let p = w.as_mut_ptr();
+        ::std::intrinsics::volatile_set_memory(p, 0xcd, LENGTH);
+    }
+
+    for idx in 0..LENGTH {
+        assert_eq!(w[idx].a, 0xcdcdcdcdcdcdcdcd);
+        assert_eq!(w[idx].b, 0xcdcdcdcdcdcdcdcd);
+        assert_eq!(w[idx].c, 0xcdcdcdcdcdcdcdcd);
+    }
+}
diff --git a/src/tools/miri/tests/pass/wtf8.rs b/src/tools/miri/tests/pass/wtf8.rs
new file mode 100644
index 0000000000000..be8348654a325
--- /dev/null
+++ b/src/tools/miri/tests/pass/wtf8.rs
@@ -0,0 +1,22 @@
+//@only-target-windows
+
+use std::ffi::{OsStr, OsString};
+use std::os::windows::ffi::{OsStrExt, OsStringExt};
+
+fn test1() {
+    let base = "a\té \u{7f}💩\r";
+    let mut base: Vec<u16> = OsStr::new(base).encode_wide().collect();
+    base.push(0xD800);
+    let _res = OsString::from_wide(&base);
+}
+
+fn test2() {
+    let mut base: Vec<u16> = OsStr::new("aé ").encode_wide().collect();
+    base.push(0xD83D);
+    let mut _res: Vec<u16> = OsString::from_wide(&base).encode_wide().collect();
+}
+
+fn main() {
+    test1();
+    test2();
+}
diff --git a/src/tools/miri/tests/pass/zst.rs b/src/tools/miri/tests/pass/zst.rs
new file mode 100644
index 0000000000000..a56386a691f8d
--- /dev/null
+++ b/src/tools/miri/tests/pass/zst.rs
@@ -0,0 +1,29 @@
+//@compile-flags: -Zmiri-permissive-provenance
+#[derive(PartialEq, Debug)]
+struct A;
+
+fn zst_ret() -> A {
+    A
+}
+
+fn use_zst() -> A {
+    let a = A;
+    a
+}
+
+fn main() {
+    // Not using the () type here, as writes of that type do not even have MIR generated.
+    // Also not assigning directly as that's array initialization, not assignment.
+    let zst_val = [1u8; 0];
+
+    assert_eq!(zst_ret(), A);
+    assert_eq!(use_zst(), A);
+    let x = 42 as *mut [u8; 0];
+    // Reading and writing is ok.
+    unsafe {
+        *x = zst_val;
+    }
+    unsafe {
+        let _y = *x;
+    }
+}
diff --git a/src/tools/miri/tests/pass/zst_box.rs b/src/tools/miri/tests/pass/zst_box.rs
new file mode 100644
index 0000000000000..12138be5af976
--- /dev/null
+++ b/src/tools/miri/tests/pass/zst_box.rs
@@ -0,0 +1,8 @@
+fn main() {
+    let x = Box::new(());
+    let y = Box::new(());
+    drop(y);
+    let z = Box::new(());
+    drop(x);
+    drop(z);
+}
diff --git a/src/tools/miri/tests/pass/zst_variant_drop.rs b/src/tools/miri/tests/pass/zst_variant_drop.rs
new file mode 100644
index 0000000000000..a76f64ce29df7
--- /dev/null
+++ b/src/tools/miri/tests/pass/zst_variant_drop.rs
@@ -0,0 +1,23 @@
+struct Foo;
+impl Drop for Foo {
+    fn drop(&mut self) {
+        unsafe {
+            FOO = true;
+        }
+    }
+}
+
+static mut FOO: bool = false;
+
+enum Bar {
+    A(Box<i32>),
+    B(Foo),
+}
+
+fn main() {
+    assert!(unsafe { !FOO });
+    drop(Bar::A(Box::new(42)));
+    assert!(unsafe { !FOO });
+    drop(Bar::B(Foo));
+    assert!(unsafe { FOO });
+}
diff --git a/src/tools/miri/triagebot.toml b/src/tools/miri/triagebot.toml
new file mode 100644
index 0000000000000..21a154cafd40d
--- /dev/null
+++ b/src/tools/miri/triagebot.toml
@@ -0,0 +1,11 @@
+[relabel]
+allow-unauthenticated = [
+    "A-*",
+    "C-*",
+    "E-*",
+    "I-*",
+    "S-*",
+ ]
+
+# Gives us the commands 'ready', 'author', 'blocked'
+[shortcut]
diff --git a/src/tools/publish_toolstate.py b/src/tools/publish_toolstate.py
index c0cef8f7b0a01..9c16ef2cbeccc 100755
--- a/src/tools/publish_toolstate.py
+++ b/src/tools/publish_toolstate.py
@@ -30,7 +30,6 @@
 # These should be collaborators of the rust-lang/rust repository (with at least
 # read privileges on it). CI will fail otherwise.
 MAINTAINERS = {
-    'miri': {'oli-obk', 'RalfJung'},
     'book': {'carols10cents'},
     'nomicon': {'frewsxcv', 'Gankra', 'JohnTitor'},
     'reference': {'Havvy', 'matthewjasper', 'ehuss'},
@@ -41,7 +40,6 @@
 }
 
 LABELS = {
-    'miri': ['A-miri', 'C-bug'],
     'book': ['C-bug'],
     'nomicon': ['C-bug'],
     'reference': ['C-bug'],
@@ -52,7 +50,6 @@
 }
 
 REPOS = {
-    'miri': 'https://github.com/rust-lang/miri',
     'book': 'https://github.com/rust-lang/book',
     'nomicon': 'https://github.com/rust-lang/nomicon',
     'reference': 'https://github.com/rust-lang/reference',
@@ -239,16 +236,10 @@ def update_latest(
                     message += '{} (cc {}).\n' \
                         .format(title, maintainers)
                     # See if we need to create an issue.
-                    if tool == 'miri':
-                        # Create issue if tests used to pass before. Don't open a *second*
-                        # issue when we regress from "test-fail" to "build-fail".
-                        if old == 'test-pass':
-                            create_issue_for_status = new
-                    else:
-                        # Create issue if things no longer build.
-                        # (No issue for mere test failures to avoid spurious issues.)
-                        if new == 'build-fail':
-                            create_issue_for_status = new
+                    # Create issue if things no longer build.
+                    # (No issue for mere test failures to avoid spurious issues.)
+                    if new == 'build-fail':
+                        create_issue_for_status = new
 
             if create_issue_for_status is not None:
                 try:
diff --git a/triagebot.toml b/triagebot.toml
index 12a55fda7ef4d..d358e59c24525 100644
--- a/triagebot.toml
+++ b/triagebot.toml
@@ -356,7 +356,7 @@ cc = ["@ehuss"]
 cc = ["@rust-lang/clippy"]
 
 [mentions."src/tools/miri"]
-message = "The Miri submodule was changed"
+message = "The Miri subtree was changed"
 cc = ["@rust-lang/miri"]
 
 [mentions."src/tools/rustfmt"]