about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthias Krüger <matthias.krueger@famsik.de>2024-04-16 17:54:44 +0200
committerGitHub <noreply@github.com>2024-04-16 17:54:44 +0200
commitcf7900476efc3a91b477b0780fba80d62d3b7a97 (patch)
tree6b912a9211ff6dd0047a8d01505245785c56f1cd
parentcad7d94e0f56e2e272742227363e4e12e0135d46 (diff)
parent876ac7b1c3fcd99b06a3170bd4abdc7e3317a005 (diff)
downloadrust-cf7900476efc3a91b477b0780fba80d62d3b7a97.tar.gz
rust-cf7900476efc3a91b477b0780fba80d62d3b7a97.zip
Rollup merge of #124007 - RalfJung:miri, r=RalfJung
Miri subtree update

r? ``@ghost``
-rw-r--r--Cargo.lock4
-rw-r--r--src/tools/miri/README.md38
-rw-r--r--src/tools/miri/cargo-miri/Cargo.lock4
-rw-r--r--src/tools/miri/cargo-miri/Cargo.toml2
-rw-r--r--src/tools/miri/cargo-miri/src/phases.rs26
-rw-r--r--src/tools/miri/miri-script/src/commands.rs18
-rw-r--r--src/tools/miri/src/bin/miri.rs19
-rw-r--r--src/tools/miri/src/helpers.rs56
-rw-r--r--src/tools/miri/src/lib.rs1
-rw-r--r--src/tools/miri/src/shims/env.rs25
-rw-r--r--src/tools/miri/src/shims/os_str.rs25
-rw-r--r--src/tools/miri/src/shims/windows/foreign_items.rs131
-rw-r--r--src/tools/miri/src/shims/x86/mod.rs77
-rw-r--r--src/tools/miri/tests/pass/shims/fs.rs2
-rw-r--r--src/tools/miri/tests/pass/shims/io.rs16
-rw-r--r--src/tools/miri/tests/pass/shims/path.rs38
-rw-r--r--src/tools/miri/tests/ui.rs10
17 files changed, 347 insertions, 145 deletions
diff --git a/Cargo.lock b/Cargo.lock
index c37afbe408f..4099b1a2832 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3364,9 +3364,9 @@ dependencies = [
 
 [[package]]
 name = "rustc-build-sysroot"
-version = "0.4.5"
+version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a26170e1d79ea32f7ccec3188dd13cfc1f18c82764a9cbc1071667c0f865a4ea"
+checksum = "a9bf37423495cd3a6a9ef8c75fc4566de0d26de0ab75f36f67a426e58df5768c"
 dependencies = [
  "anyhow",
  "rustc_version",
diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md
index 60a1c1fa1dd..948f1ee6c63 100644
--- a/src/tools/miri/README.md
+++ b/src/tools/miri/README.md
@@ -451,36 +451,32 @@ Some native rustc `-Z` flags are also very relevant for Miri:
 * `-Zmir-emit-retag` controls whether `Retag` statements are emitted. Miri
   enables this per default because it is needed for [Stacked Borrows] and [Tree Borrows].
 
-Moreover, Miri recognizes some environment variables:
+Moreover, Miri recognizes some environment variables (unless noted otherwise, these are supported
+by all intended entry points, i.e. `cargo miri` and `./miri {test,run}`):
 
 * `MIRI_AUTO_OPS` indicates whether the automatic execution of rustfmt, clippy and toolchain setup
   should be skipped. If it is set to `no`, they are skipped. This is used 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.
-* `MIRI_SYSROOT` (recognized by `cargo miri` and the Miri driver) indicates the sysroot to use. When
-  using `cargo miri`, this skips the automatic setup -- 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. When invoking `cargo miri setup`, this indicates where the sysroot
-  will be put.
-* `MIRI_TEST_TARGET` (recognized by the test suite and the `./miri` script) indicates which target
+* `MIRIFLAGS` 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.
+* `MIRI_SYSROOT` indicates the sysroot to use. When using `cargo miri`, this skips the automatic
+  setup -- only set this if you do not want to use the automatically created sysroot. When invoking
+  `cargo miri setup`, this indicates where the sysroot will be put.
+* `MIRI_TEST_TARGET` (recognized by `./miri {test,run}`) indicates which target
   architecture to test against.  `miri` and `cargo miri` accept the `--target` flag for the same
   purpose.
-* `MIRI_TEST_THREADS` (recognized by the test suite): set the number of threads to use for running tests.
-  By default the number of cores is used.
-* `MIRI_NO_STD` (recognized by `cargo miri`) makes sure that the target's sysroot is built without
-  libstd. This allows testing and running no_std programs.
-  (Miri has a heuristic to detect no-std targets based on the target name; this environment variable
-  is only needed when that heuristic fails.)
-* `RUSTC_BLESS` (recognized by the test suite and `cargo-miri-test/run-test.py`): overwrite all
+* `MIRI_TEST_THREADS` (recognized by `./miri test`): set the number of threads to use for running tests.
+  By default, the number of cores is used.
+* `MIRI_NO_STD` makes sure that the target's sysroot is built without libstd. This allows testing
+  and running no_std programs. (Miri has a heuristic to detect no-std targets based on the target
+  name; this environment variable is only needed when that heuristic fails.)
+* `RUSTC_BLESS` (recognized by `./miri test` and `cargo-miri-test/run-test.py`): overwrite all
   `stderr` and `stdout` files instead of checking whether the output matches.
-* `MIRI_SKIP_UI_CHECKS` (recognized by the test suite): don't check whether the
+* `MIRI_SKIP_UI_CHECKS` (recognized by `./miri test`): don't check whether the
   `stderr` or `stdout` files match the actual output.
 
 The following environment variables are *internal* and must not be used by
diff --git a/src/tools/miri/cargo-miri/Cargo.lock b/src/tools/miri/cargo-miri/Cargo.lock
index 6841a345ce1..a1dbc85605f 100644
--- a/src/tools/miri/cargo-miri/Cargo.lock
+++ b/src/tools/miri/cargo-miri/Cargo.lock
@@ -194,9 +194,9 @@ dependencies = [
 
 [[package]]
 name = "rustc-build-sysroot"
-version = "0.4.5"
+version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a26170e1d79ea32f7ccec3188dd13cfc1f18c82764a9cbc1071667c0f865a4ea"
+checksum = "a9bf37423495cd3a6a9ef8c75fc4566de0d26de0ab75f36f67a426e58df5768c"
 dependencies = [
  "anyhow",
  "rustc_version",
diff --git a/src/tools/miri/cargo-miri/Cargo.toml b/src/tools/miri/cargo-miri/Cargo.toml
index 55f6b5ac7ef..b16068b6d19 100644
--- a/src/tools/miri/cargo-miri/Cargo.toml
+++ b/src/tools/miri/cargo-miri/Cargo.toml
@@ -18,7 +18,7 @@ directories = "5"
 rustc_version = "0.4"
 serde_json = "1.0.40"
 cargo_metadata = "0.18.0"
-rustc-build-sysroot = "0.4.1"
+rustc-build-sysroot = "0.4.6"
 
 # Enable some feature flags that dev-dependencies need but dependencies
 # do not.  This makes `./miri install` after `./miri build` faster.
diff --git a/src/tools/miri/cargo-miri/src/phases.rs b/src/tools/miri/cargo-miri/src/phases.rs
index 04f3b2918b5..b774ca8fa72 100644
--- a/src/tools/miri/cargo-miri/src/phases.rs
+++ b/src/tools/miri/cargo-miri/src/phases.rs
@@ -172,8 +172,6 @@ pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
     // Forward all further arguments (not consumed by `ArgSplitFlagValue`) to cargo.
     cmd.args(args);
 
-    // Let it know where the Miri sysroot lives.
-    cmd.env("MIRI_SYSROOT", miri_sysroot);
     // 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`.)
@@ -200,10 +198,7 @@ pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
     // always applied. However, buggy build scripts (https://github.com/eyre-rs/eyre/issues/84) and
     // also cargo (https://github.com/rust-lang/cargo/issues/10885) will invoke `rustc` even when
     // `RUSTC_WRAPPER` is set, bypassing the wrapper. 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.
+    // that to be the Miri driver, but acting as rustc, in host mode.
     //
     // In `main`, we need the value of `RUSTC` to distinguish RUSTC_WRAPPER invocations from rustdoc
     // or TARGET_RUNNER invocations, so we canonicalize it here to make it exceedingly unlikely that
@@ -212,7 +207,10 @@ pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
     // 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!
+    // In case we get invoked as RUSTC without the wrapper, let's be a host rustc. This makes no
+    // sense for cross-interpretation situations, but without the wrapper, this will use the host
+    // sysroot, so asking it to behave like a target build makes even less sense.
+    cmd.env("MIRI_BE_RUSTC", "host"); // we better remember to *unset* this in the other phases!
 
     // Set rustdoc to us as well, so we can run doctests.
     if let Some(orig_rustdoc) = env::var_os("RUSTDOC") {
@@ -220,6 +218,8 @@ pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
     }
     cmd.env("RUSTDOC", &cargo_miri_path);
 
+    // Forward some crucial information to our own re-invocations.
+    cmd.env("MIRI_SYSROOT", miri_sysroot);
     cmd.env("MIRI_LOCAL_CRATES", local_crates(&metadata));
     if verbose > 0 {
         cmd.env("MIRI_VERBOSE", verbose.to_string()); // This makes the other phases verbose.
@@ -412,6 +412,12 @@ pub fn phase_rustc(mut args: impl Iterator<Item = String>, phase: RustcPhase) {
     // 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 target_crate {
+        if phase != RustcPhase::Setup {
+            // Set the sysroot -- except during setup, where we don't have an existing sysroot yet
+            // and where the bootstrap wrapper adds its own `--sysroot` flag so we can't set ours.
+            cmd.arg("--sysroot").arg(env::var_os("MIRI_SYSROOT").unwrap());
+        }
+
         // Forward arguments, but patched.
         let emit_flag = "--emit";
         // This hack helps bootstrap run standard library tests in Miri. The issue is as follows:
@@ -574,6 +580,12 @@ pub fn phase_runner(mut binary_args: impl Iterator<Item = String>, phase: Runner
         cmd.env(name, val);
     }
 
+    if phase != RunnerPhase::Rustdoc {
+        // Set the sysroot. Not necessary in rustdoc, where we already set the sysroot in
+        // `phase_rustdoc`. rustdoc will forward that flag when invoking rustc (i.e., us), so the
+        // flag is present in `info.args`.
+        cmd.arg("--sysroot").arg(env::var_os("MIRI_SYSROOT").unwrap());
+    }
     // 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
diff --git a/src/tools/miri/miri-script/src/commands.rs b/src/tools/miri/miri-script/src/commands.rs
index 55b3b62819f..66707dee5e7 100644
--- a/src/tools/miri/miri-script/src/commands.rs
+++ b/src/tools/miri/miri-script/src/commands.rs
@@ -2,6 +2,7 @@ use std::env;
 use std::ffi::OsString;
 use std::io::Write;
 use std::ops::Not;
+use std::path::PathBuf;
 use std::process;
 use std::thread;
 use std::time;
@@ -20,10 +21,11 @@ const JOSH_FILTER: &str =
 const JOSH_PORT: &str = "42042";
 
 impl MiriEnv {
-    fn build_miri_sysroot(&mut self, quiet: bool) -> Result<()> {
-        if self.sh.var("MIRI_SYSROOT").is_ok() {
+    /// Returns the location of the sysroot.
+    fn build_miri_sysroot(&mut self, quiet: bool) -> Result<PathBuf> {
+        if let Some(miri_sysroot) = self.sh.var_os("MIRI_SYSROOT") {
             // Sysroot already set, use that.
-            return Ok(());
+            return Ok(miri_sysroot.into());
         }
         let manifest_path = path!(self.miri_dir / "cargo-miri" / "Cargo.toml");
         let Self { toolchain, cargo_extra_flags, .. } = &self;
@@ -57,8 +59,8 @@ impl MiriEnv {
             .with_context(|| "`cargo miri setup` failed")?;
             panic!("`cargo miri setup` didn't fail again the 2nd time?");
         };
-        self.sh.set_var("MIRI_SYSROOT", output);
-        Ok(())
+        self.sh.set_var("MIRI_SYSROOT", &output);
+        Ok(output.into())
     }
 }
 
@@ -505,8 +507,10 @@ impl Command {
             flags.push("--edition=2021".into()); // keep in sync with `tests/ui.rs`.`
         }
 
-        // Prepare a sysroot.
-        e.build_miri_sysroot(/* quiet */ true)?;
+        // Prepare a sysroot, and add it to the flags.
+        let miri_sysroot = e.build_miri_sysroot(/* quiet */ true)?;
+        flags.push("--sysroot".into());
+        flags.push(miri_sysroot.into());
 
         // Then run the actual command. Also add MIRIFLAGS.
         let miri_manifest = path!(e.miri_dir / "Cargo.toml");
diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs
index 64e2c13a451..3f7a965e9df 100644
--- a/src/tools/miri/src/bin/miri.rs
+++ b/src/tools/miri/src/bin/miri.rs
@@ -282,25 +282,6 @@ fn run_compiler(
     callbacks: &mut (dyn rustc_driver::Callbacks + Send),
     using_internal_features: std::sync::Arc<std::sync::atomic::AtomicBool>,
 ) -> ! {
-    if target_crate {
-        // Miri needs a custom sysroot for target crates.
-        // If no `--sysroot` is given, the `MIRI_SYSROOT` env var is consulted to find where
-        // that sysroot lives, and that is passed to rustc.
-        let sysroot_flag = "--sysroot";
-        if !args.iter().any(|e| e.starts_with(sysroot_flag)) {
-            // Using the built-in default here would be plain wrong, so we *require*
-            // the env var to make sure things make sense.
-            let miri_sysroot = env::var("MIRI_SYSROOT").unwrap_or_else(|_| {
-                show_error!(
-                    "Miri was invoked in 'target' mode without `MIRI_SYSROOT` or `--sysroot` being set"
-                    )
-            });
-
-            args.push(sysroot_flag.to_owned());
-            args.push(miri_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`:
diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs
index 55be4a43792..e2c6769ccb5 100644
--- a/src/tools/miri/src/helpers.rs
+++ b/src/tools/miri/src/helpers.rs
@@ -83,6 +83,17 @@ const UNIX_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = {
         ("EAGAIN", WouldBlock),
     ]
 };
+// This mapping should match `decode_error_kind` in
+// <https://github.com/rust-lang/rust/blob/master/library/std/src/sys/pal/windows/mod.rs>.
+const WINDOWS_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = {
+    use std::io::ErrorKind::*;
+    // FIXME: this is still incomplete.
+    &[
+        ("ERROR_ACCESS_DENIED", PermissionDenied),
+        ("ERROR_FILE_NOT_FOUND", NotFound),
+        ("ERROR_INVALID_PARAMETER", InvalidInput),
+    ]
+};
 
 /// Gets an instance for a path.
 ///
@@ -749,20 +760,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
             }
             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::*;
-            Ok(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
-                        ),
-                },
-            ))
+            for &(name, kind) in WINDOWS_IO_ERROR_TABLE {
+                if err_kind == kind {
+                    return Ok(this.eval_windows("c", name));
+                }
+            }
+            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 {}",
@@ -786,8 +789,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                     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 if target.families.iter().any(|f| f == "windows") {
+            let errnum = errnum.to_u32()?;
+            for &(name, kind) in WINDOWS_IO_ERROR_TABLE {
+                if errnum == this.eval_windows("c", name).to_u32()? {
+                    return Ok(Some(kind));
+                }
+            }
             return Ok(None);
         } else {
             throw_unsup_format!(
@@ -1344,3 +1353,18 @@ pub(crate) fn simd_element_to_bool(elem: ImmTy<'_, Provenance>) -> InterpResult<
         _ => throw_ub_format!("each element of a SIMD mask must be all-0-bits or all-1-bits"),
     })
 }
+
+/// 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.
+pub(crate) 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()
+    }
+}
diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs
index 7821aa9efd4..484908086ac 100644
--- a/src/tools/miri/src/lib.rs
+++ b/src/tools/miri/src/lib.rs
@@ -13,6 +13,7 @@
 #![feature(let_chains)]
 #![feature(lint_reasons)]
 #![feature(trait_upcasting)]
+#![feature(absolute_path)]
 // Configure clippy and other lints
 #![allow(
     clippy::collapsible_else_if,
diff --git a/src/tools/miri/src/shims/env.rs b/src/tools/miri/src/shims/env.rs
index 9e8239cdbac..1779189c9ce 100644
--- a/src/tools/miri/src/shims/env.rs
+++ b/src/tools/miri/src/shims/env.rs
@@ -9,21 +9,7 @@ use rustc_middle::ty::Ty;
 use rustc_target::abi::Size;
 
 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()
-    }
-}
+use helpers::windows_check_buffer_size;
 
 #[derive(Default)]
 pub struct EnvVars<'tcx> {
@@ -164,7 +150,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
         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) => {
+            Some(&var_ptr) => {
+                this.set_last_error(Scalar::from_u32(0))?; // make sure this is unambiguously not an error
                 // The offset is used to strip the "{name}=" part of the string.
                 #[rustfmt::skip]
                 let name_offset_bytes = u64::try_from(name.len()).unwrap()
@@ -375,10 +362,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
 
         // If we cannot get the current directory, we return 0
         match env::current_dir() {
-            Ok(cwd) =>
+            Ok(cwd) => {
+                this.set_last_error(Scalar::from_u32(0))?; // make sure this is unambiguously not an error
                 return Ok(Scalar::from_u32(windows_check_buffer_size(
                     this.write_path_to_wide_str(&cwd, buf, size, /*truncate*/ false)?,
-                ))),
+                )));
+            }
             Err(e) => this.set_last_error_from_io_error(e.kind())?,
         }
         Ok(Scalar::from_u32(0))
diff --git a/src/tools/miri/src/shims/os_str.rs b/src/tools/miri/src/shims/os_str.rs
index 62ce2ee58ae..0157c4845c5 100644
--- a/src/tools/miri/src/shims/os_str.rs
+++ b/src/tools/miri/src/shims/os_str.rs
@@ -98,6 +98,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
     ///
     /// If `truncate == true`, then in case `size` is not large enough it *will* write the first
     /// `size.saturating_sub(1)` many items, followed by a null terminator (if `size > 0`).
+    /// The return value is still `(false, length)` in that case.
     fn write_os_str_to_wide_str(
         &mut self,
         os_str: &OsStr,
@@ -315,9 +316,21 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
             // We also have to ensure that absolute paths remain absolute.
             match direction {
                 PathConversion::HostToTarget => {
-                    // If this start withs a `\`, we add `\\?` so it starts with `\\?\` which is
-                    // some magic path on Windows that *is* considered absolute.
-                    if converted.get(0).copied() == Some(b'\\') {
+                    // If the path is `/C:/`, the leading backslash was probably added by the below
+                    // driver letter handling and we should get rid of it again.
+                    if converted.get(0).copied() == Some(b'\\')
+                        && converted.get(2).copied() == Some(b':')
+                        && converted.get(3).copied() == Some(b'\\')
+                    {
+                        converted.remove(0);
+                    }
+                    // If this start withs a `\` but not a `\\`, then for Windows this is a relative
+                    // path. But the host path is absolute as it started with `/`. We add `\\?` so
+                    // it starts with `\\?\` which is some magic path on Windows that *is*
+                    // considered absolute.
+                    else if converted.get(0).copied() == Some(b'\\')
+                        && converted.get(1).copied() != Some(b'\\')
+                    {
                         converted.splice(0..0, b"\\\\?".iter().copied());
                     }
                 }
@@ -332,6 +345,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                         // Remove first 3 characters
                         converted.splice(0..3, std::iter::empty());
                     }
+                    // If it starts with a drive letter, convert it to an absolute Unix path.
+                    else if converted.get(1).copied() == Some(b':')
+                        && converted.get(2).copied() == Some(b'/')
+                    {
+                        converted.insert(0, b'/');
+                    }
                 }
             }
             Cow::Owned(OsString::from_vec(converted))
diff --git a/src/tools/miri/src/shims/windows/foreign_items.rs b/src/tools/miri/src/shims/windows/foreign_items.rs
index 9c6994cec70..de80df3c80d 100644
--- a/src/tools/miri/src/shims/windows/foreign_items.rs
+++ b/src/tools/miri/src/shims/windows/foreign_items.rs
@@ -1,4 +1,7 @@
+use std::ffi::OsStr;
+use std::io;
 use std::iter;
+use std::path::{self, Path, PathBuf};
 use std::str;
 
 use rustc_span::Symbol;
@@ -20,6 +23,61 @@ fn is_dyn_sym(name: &str) -> bool {
     )
 }
 
+#[cfg(windows)]
+fn win_absolute<'tcx>(path: &Path) -> InterpResult<'tcx, io::Result<PathBuf>> {
+    // We are on Windows so we can simply lte the host do this.
+    return Ok(path::absolute(path));
+}
+
+#[cfg(unix)]
+#[allow(clippy::get_first, clippy::arithmetic_side_effects)]
+fn win_absolute<'tcx>(path: &Path) -> InterpResult<'tcx, io::Result<PathBuf>> {
+    // We are on Unix, so we need to implement parts of the logic ourselves.
+    let bytes = path.as_os_str().as_encoded_bytes();
+    // If it starts with `//` (these were backslashes but are already converted)
+    // then this is a magic special path, we just leave it unchanged.
+    if bytes.get(0).copied() == Some(b'/') && bytes.get(1).copied() == Some(b'/') {
+        return Ok(Ok(path.into()));
+    };
+    // Special treatment for Windows' magic filenames: they are treated as being relative to `\\.\`.
+    let magic_filenames = &[
+        "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8",
+        "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
+    ];
+    if magic_filenames.iter().any(|m| m.as_bytes() == bytes) {
+        let mut result: Vec<u8> = br"//./".into();
+        result.extend(bytes);
+        return Ok(Ok(bytes_to_os_str(&result)?.into()));
+    }
+    // Otherwise we try to do something kind of close to what Windows does, but this is probably not
+    // right in all cases. We iterate over the components between `/`, and remove trailing `.`,
+    // except that trailing `..` remain unchanged.
+    let mut result = vec![];
+    let mut bytes = bytes; // the remaining bytes to process
+    loop {
+        let len = bytes.iter().position(|&b| b == b'/').unwrap_or(bytes.len());
+        let mut component = &bytes[..len];
+        if len >= 2 && component[len - 1] == b'.' && component[len - 2] != b'.' {
+            // Strip trailing `.`
+            component = &component[..len - 1];
+        }
+        // Add this component to output.
+        result.extend(component);
+        // Prepare next iteration.
+        if len < bytes.len() {
+            // There's a component after this; add `/` and process remaining bytes.
+            result.push(b'/');
+            bytes = &bytes[len + 1..];
+            continue;
+        } else {
+            // This was the last component and it did not have a trailing `/`.
+            break;
+        }
+    }
+    // Let the host `absolute` function do working-dir handling
+    Ok(path::absolute(bytes_to_os_str(&result)?))
+}
+
 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_inner(
@@ -111,7 +169,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
 
                 let written = if handle == -11 || handle == -12 {
                     // stdout/stderr
-                    use std::io::{self, Write};
+                    use io::Write;
 
                     let buf_cont =
                         this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(u64::from(n)))?;
@@ -145,6 +203,40 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                     dest,
                 )?;
             }
+            "GetFullPathNameW" => {
+                let [filename, size, buffer, filepart] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                this.check_no_isolation("`GetFullPathNameW`")?;
+
+                let filename = this.read_pointer(filename)?;
+                let size = this.read_scalar(size)?.to_u32()?;
+                let buffer = this.read_pointer(buffer)?;
+                let filepart = this.read_pointer(filepart)?;
+
+                if !this.ptr_is_null(filepart)? {
+                    throw_unsup_format!("GetFullPathNameW: non-null `lpFilePart` is not supported");
+                }
+
+                let filename = this.read_path_from_wide_str(filename)?;
+                let result = match win_absolute(&filename)? {
+                    Err(err) => {
+                        this.set_last_error_from_io_error(err.kind())?;
+                        Scalar::from_u32(0) // return zero upon failure
+                    }
+                    Ok(abs_filename) => {
+                        this.set_last_error(Scalar::from_u32(0))?; // make sure this is unambiguously not an error
+                        Scalar::from_u32(helpers::windows_check_buffer_size(
+                            this.write_path_to_wide_str(
+                                &abs_filename,
+                                buffer,
+                                size.into(),
+                                /*truncate*/ false,
+                            )?,
+                        ))
+                    }
+                };
+                this.write_scalar(result, dest)?;
+            }
 
             // Allocation
             "HeapAlloc" => {
@@ -533,6 +625,43 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                     this.set_last_error(insufficient_buffer)?;
                 }
             }
+            "FormatMessageW" => {
+                let [flags, module, message_id, language_id, buffer, size, arguments] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+
+                let flags = this.read_scalar(flags)?.to_u32()?;
+                let _module = this.read_pointer(module)?; // seems to contain a module name
+                let message_id = this.read_scalar(message_id)?;
+                let _language_id = this.read_scalar(language_id)?.to_u32()?;
+                let buffer = this.read_pointer(buffer)?;
+                let size = this.read_scalar(size)?.to_u32()?;
+                let _arguments = this.read_pointer(arguments)?;
+
+                // We only support `FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS`
+                // This also means `arguments` can be ignored.
+                if flags != 4096u32 | 512u32 {
+                    throw_unsup_format!("FormatMessageW: unsupported flags {flags:#x}");
+                }
+
+                let error = this.try_errnum_to_io_error(message_id)?;
+                let formatted = match error {
+                    Some(err) => format!("{err}"),
+                    None => format!("<unknown error in FormatMessageW: {message_id}>"),
+                };
+                let (complete, length) = this.write_os_str_to_wide_str(
+                    OsStr::new(&formatted),
+                    buffer,
+                    size.into(),
+                    /*trunacte*/ false,
+                )?;
+                if !complete {
+                    // The API docs don't say what happens when the buffer is not big enough...
+                    // Let's just bail.
+                    throw_unsup_format!("FormatMessageW: buffer not big enough");
+                }
+                // The return value is the number of characters stored *excluding* the null terminator.
+                this.write_int(length.checked_sub(1).unwrap(), 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.
diff --git a/src/tools/miri/src/shims/x86/mod.rs b/src/tools/miri/src/shims/x86/mod.rs
index 2a663d300a7..615821b2e37 100644
--- a/src/tools/miri/src/shims/x86/mod.rs
+++ b/src/tools/miri/src/shims/x86/mod.rs
@@ -664,54 +664,35 @@ fn convert_float_to_int<'tcx>(
     Ok(())
 }
 
-/// Splits `left`, `right` and `dest` (which must be SIMD vectors)
-/// into 128-bit chuncks.
-///
-/// `left`, `right` and `dest` cannot have different types.
+/// Splits `op` (which must be a SIMD vector) into 128-bit chuncks.
 ///
 /// Returns a tuple where:
 /// * The first element is the number of 128-bit chunks (let's call it `N`).
 /// * The second element is the number of elements per chunk (let's call it `M`).
-/// * The third element is the `left` vector split into chunks, i.e, it's
-///   type is `[[T; M]; N]`.
-/// * The fourth element is the `right` vector split into chunks.
-/// * The fifth element is the `dest` vector split into chunks.
-fn split_simd_to_128bit_chunks<'tcx>(
+/// * The third element is the `op` vector split into chunks, i.e, it's
+///   type is `[[T; M]; N]` where `T` is the element type of `op`.
+fn split_simd_to_128bit_chunks<'tcx, P: Projectable<'tcx, Provenance>>(
     this: &mut crate::MiriInterpCx<'_, 'tcx>,
-    left: &OpTy<'tcx, Provenance>,
-    right: &OpTy<'tcx, Provenance>,
-    dest: &MPlaceTy<'tcx, Provenance>,
-) -> InterpResult<
-    'tcx,
-    (u64, u64, MPlaceTy<'tcx, Provenance>, MPlaceTy<'tcx, Provenance>, MPlaceTy<'tcx, Provenance>),
-> {
-    assert_eq!(dest.layout, left.layout);
-    assert_eq!(dest.layout, right.layout);
+    op: &P,
+) -> InterpResult<'tcx, (u64, u64, P)> {
+    let simd_layout = op.layout();
+    let (simd_len, element_ty) = simd_layout.ty.simd_size_and_type(this.tcx.tcx);
 
-    let (left, left_len) = this.operand_to_simd(left)?;
-    let (right, right_len) = this.operand_to_simd(right)?;
-    let (dest, dest_len) = this.mplace_to_simd(dest)?;
-
-    assert_eq!(dest_len, left_len);
-    assert_eq!(dest_len, right_len);
-
-    assert_eq!(dest.layout.size.bits() % 128, 0);
-    let num_chunks = dest.layout.size.bits() / 128;
-    assert_eq!(dest_len.checked_rem(num_chunks), Some(0));
-    let items_per_chunk = dest_len.checked_div(num_chunks).unwrap();
+    assert_eq!(simd_layout.size.bits() % 128, 0);
+    let num_chunks = simd_layout.size.bits() / 128;
+    let items_per_chunk = simd_len.checked_div(num_chunks).unwrap();
 
     // Transmute to `[[T; items_per_chunk]; num_chunks]`
-    let element_layout = left.layout.field(this, 0);
-    let chunked_layout = this.layout_of(Ty::new_array(
-        this.tcx.tcx,
-        Ty::new_array(this.tcx.tcx, element_layout.ty, items_per_chunk),
-        num_chunks,
-    ))?;
-    let left = left.transmute(chunked_layout, this)?;
-    let right = right.transmute(chunked_layout, this)?;
-    let dest = dest.transmute(chunked_layout, this)?;
-
-    Ok((num_chunks, items_per_chunk, left, right, dest))
+    let chunked_layout = this
+        .layout_of(Ty::new_array(
+            this.tcx.tcx,
+            Ty::new_array(this.tcx.tcx, element_ty, items_per_chunk),
+            num_chunks,
+        ))
+        .unwrap();
+    let chunked_op = op.transmute(chunked_layout, this)?;
+
+    Ok((num_chunks, items_per_chunk, chunked_op))
 }
 
 /// Horizontaly performs `which` operation on adjacent values of
@@ -731,8 +712,12 @@ fn horizontal_bin_op<'tcx>(
     right: &OpTy<'tcx, Provenance>,
     dest: &MPlaceTy<'tcx, Provenance>,
 ) -> InterpResult<'tcx, ()> {
-    let (num_chunks, items_per_chunk, left, right, dest) =
-        split_simd_to_128bit_chunks(this, left, right, dest)?;
+    assert_eq!(left.layout, dest.layout);
+    assert_eq!(right.layout, dest.layout);
+
+    let (num_chunks, items_per_chunk, left) = split_simd_to_128bit_chunks(this, left)?;
+    let (_, _, right) = split_simd_to_128bit_chunks(this, right)?;
+    let (_, _, dest) = split_simd_to_128bit_chunks(this, dest)?;
 
     let middle = items_per_chunk / 2;
     for i in 0..num_chunks {
@@ -779,8 +764,12 @@ fn conditional_dot_product<'tcx>(
     imm: &OpTy<'tcx, Provenance>,
     dest: &MPlaceTy<'tcx, Provenance>,
 ) -> InterpResult<'tcx, ()> {
-    let (num_chunks, items_per_chunk, left, right, dest) =
-        split_simd_to_128bit_chunks(this, left, right, dest)?;
+    assert_eq!(left.layout, dest.layout);
+    assert_eq!(right.layout, dest.layout);
+
+    let (num_chunks, items_per_chunk, left) = split_simd_to_128bit_chunks(this, left)?;
+    let (_, _, right) = split_simd_to_128bit_chunks(this, right)?;
+    let (_, _, dest) = split_simd_to_128bit_chunks(this, dest)?;
 
     let element_layout = left.layout.field(this, 0).field(this, 0);
     assert!(items_per_chunk <= 4);
diff --git a/src/tools/miri/tests/pass/shims/fs.rs b/src/tools/miri/tests/pass/shims/fs.rs
index d10faebac7d..8a500b857bc 100644
--- a/src/tools/miri/tests/pass/shims/fs.rs
+++ b/src/tools/miri/tests/pass/shims/fs.rs
@@ -260,7 +260,7 @@ fn test_errors() {
     // 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());
+    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.
diff --git a/src/tools/miri/tests/pass/shims/io.rs b/src/tools/miri/tests/pass/shims/io.rs
index 295723957a4..d20fc75b793 100644
--- a/src/tools/miri/tests/pass/shims/io.rs
+++ b/src/tools/miri/tests/pass/shims/io.rs
@@ -1,7 +1,19 @@
-use std::io::IsTerminal;
+use std::io::{self, IsTerminal};
 
 fn main() {
     // We can't really assume that this is truly a terminal, and anyway on Windows Miri will always
     // return `false` here, but we can check that the call succeeds.
-    std::io::stdout().is_terminal();
+    io::stdout().is_terminal();
+
+    // Ensure we can format `io::Error` created from OS errors
+    // (calls OS-specific error formatting functions).
+    let raw_os_error = if cfg!(unix) {
+        22 // EINVAL (on most Unixes, anyway)
+    } else if cfg!(windows) {
+        87 // ERROR_INVALID_PARAMETER
+    } else {
+        panic!("unsupported OS")
+    };
+    let err = io::Error::from_raw_os_error(raw_os_error);
+    format!("{err}: {err:?}");
 }
diff --git a/src/tools/miri/tests/pass/shims/path.rs b/src/tools/miri/tests/pass/shims/path.rs
new file mode 100644
index 00000000000..9fc6e7faefb
--- /dev/null
+++ b/src/tools/miri/tests/pass/shims/path.rs
@@ -0,0 +1,38 @@
+//@compile-flags: -Zmiri-disable-isolation
+#![feature(absolute_path)]
+use std::path::{absolute, Path};
+
+#[track_caller]
+fn test_absolute(in_: &str, out: &str) {
+    assert_eq!(absolute(in_).unwrap().as_os_str(), Path::new(out).as_os_str());
+}
+
+fn main() {
+    if cfg!(unix) {
+        test_absolute("/a/b/c", "/a/b/c");
+        test_absolute("/a/b/c", "/a/b/c");
+        test_absolute("/a//b/c", "/a/b/c");
+        test_absolute("//a/b/c", "//a/b/c");
+        test_absolute("///a/b/c", "/a/b/c");
+        test_absolute("/a/b/c/", "/a/b/c/");
+        test_absolute("/a/./b/../c/.././..", "/a/b/../c/../..");
+    } else if cfg!(windows) {
+        // Test that all these are unchanged
+        test_absolute(r"C:\path\to\file", r"C:\path\to\file");
+        test_absolute(r"C:\path\to\file\", r"C:\path\to\file\");
+        test_absolute(r"\\server\share\to\file", r"\\server\share\to\file");
+        test_absolute(r"\\server.\share.\to\file", r"\\server.\share.\to\file");
+        test_absolute(r"\\.\PIPE\name", r"\\.\PIPE\name");
+        test_absolute(r"\\.\C:\path\to\COM1", r"\\.\C:\path\to\COM1");
+        test_absolute(r"\\?\C:\path\to\file", r"\\?\C:\path\to\file");
+        test_absolute(r"\\?\UNC\server\share\to\file", r"\\?\UNC\server\share\to\file");
+        test_absolute(r"\\?\PIPE\name", r"\\?\PIPE\name");
+        // Verbatim paths are always unchanged, no matter what.
+        test_absolute(r"\\?\path.\to/file..", r"\\?\path.\to/file..");
+
+        test_absolute(r"C:\path..\to.\file.", r"C:\path..\to\file");
+        test_absolute(r"COM1", r"\\.\COM1");
+    } else {
+        panic!("unsupported OS");
+    }
+}
diff --git a/src/tools/miri/tests/ui.rs b/src/tools/miri/tests/ui.rs
index 7e8d1401183..ace0da01253 100644
--- a/src/tools/miri/tests/ui.rs
+++ b/src/tools/miri/tests/ui.rs
@@ -112,6 +112,13 @@ fn run_tests(
     config.program.envs.push(("RUST_BACKTRACE".into(), Some("1".into())));
 
     // Add some flags we always want.
+    config.program.args.push(
+        format!(
+            "--sysroot={}",
+            env::var("MIRI_SYSROOT").expect("MIRI_SYSROOT must be set to run the ui test suite")
+        )
+        .into(),
+    );
     config.program.args.push("-Dwarnings".into());
     config.program.args.push("-Dunused".into());
     config.program.args.push("-Ainternal_features".into());
@@ -296,12 +303,13 @@ fn main() -> Result<()> {
 
 fn run_dep_mode(target: String, mut args: impl Iterator<Item = OsString>) -> Result<()> {
     let path = args.next().expect("./miri run-dep must be followed by a file name");
-    let config = miri_config(
+    let mut config = miri_config(
         &target,
         "",
         Mode::Yolo { rustfix: RustfixMode::Disabled },
         /* with dependencies */ true,
     );
+    config.program.args.clear(); // remove the `--error-format` that ui_test adds by default
     let dep_args = config.build_dependencies()?;
 
     let mut cmd = config.program.build(&config.out_dir);