1
Fork 0

Auto merge of #134784 - RalfJung:miri-sync, r=RalfJung

Miri subtree update

r? `@ghost`
This commit is contained in:
bors 2024-12-26 13:02:16 +00:00
commit e2848a05a9
25 changed files with 865 additions and 716 deletions

View file

@ -212,6 +212,15 @@ Miri comes with a few benchmarks; you can run `./miri bench` to run them with th
Miri. Note: this will run `./miri install` as a side-effect. Also requires `hyperfine` to be Miri. Note: this will run `./miri install` as a side-effect. Also requires `hyperfine` to be
installed (`cargo install hyperfine`). installed (`cargo install hyperfine`).
To compare the benchmark results with a baseline, do the following:
- Before applying your changes, run `./miri bench --save-baseline=baseline.json`.
- Then do your changes.
- Then run `./miri bench --load-baseline=baseline.json`; the results will include
a comparison with the baseline.
You can run only some of the benchmarks by listing them, e.g. `./miri bench mse`.
The names refer to the folders in `bench-cargo-miri`.
## Configuring `rust-analyzer` ## Configuring `rust-analyzer`
To configure `rust-analyzer` and the IDE for working on Miri, copy one of the provided To configure `rust-analyzer` and the IDE for working on Miri, copy one of the provided

View file

@ -160,14 +160,14 @@ Certain parts of the execution are picked randomly by Miri, such as the exact ba
allocations are stored at and the interleaving of concurrently executing threads. Sometimes, it can allocations are stored at and the interleaving of concurrently executing threads. Sometimes, it can
be useful to explore multiple different execution, e.g. to make sure that your code does not depend be useful to explore multiple different execution, e.g. to make sure that your code does not depend
on incidental "super-alignment" of new allocations and to test different thread interleavings. on incidental "super-alignment" of new allocations and to test different thread interleavings.
This can be done with the `--many-seeds` flag: This can be done with the `-Zmiri-many-seeds` flag:
``` ```
cargo miri test --many-seeds # tries the seeds in 0..64 MIRIFLAGS="-Zmiri-many-seeds" cargo miri test # tries the seeds in 0..64
cargo miri test --many-seeds=0..16 MIRIFLAGS="-Zmiri-many-seeds=0..16" cargo miri test
``` ```
The default of 64 different seeds is quite slow, so you probably want to specify a smaller range. The default of 64 different seeds can be quite slow, so you often want to specify a smaller range.
### Running Miri on CI ### Running Miri on CI
@ -294,9 +294,10 @@ environment variable. We first document the most relevant and most commonly used
will always fail and `0.0` means it will never fail. Note that setting it to will always fail and `0.0` means it will never fail. Note that setting it to
`1.0` will likely cause hangs, since it means programs using `1.0` will likely cause hangs, since it means programs using
`compare_exchange_weak` cannot make progress. `compare_exchange_weak` cannot make progress.
* `-Zmiri-disable-isolation` disables host isolation. As a consequence, * `-Zmiri-disable-isolation` disables host isolation. As a consequence,
the program has access to host resources such as environment variables, file the program has access to host resources such as environment variables, file
systems, and randomness. systems, and randomness.
This overwrites a previous `-Zmiri-isolation-error`.
* `-Zmiri-disable-leak-backtraces` disables backtraces reports for memory leaks. By default, a * `-Zmiri-disable-leak-backtraces` disables backtraces reports for memory leaks. By default, a
backtrace is captured for every allocation when it is created, just in case it leaks. This incurs backtrace is captured for every allocation when it is created, just in case it leaks. This incurs
some memory overhead to store data that is almost never used. This flag is implied by some memory overhead to store data that is almost never used. This flag is implied by
@ -317,6 +318,15 @@ environment variable. We first document the most relevant and most commonly used
execution with a "permission denied" error being returned to the program. execution with a "permission denied" error being returned to the program.
`warn` prints a full backtrace each time that happens; `warn-nobacktrace` is less `warn` prints a full backtrace each time that happens; `warn-nobacktrace` is less
verbose and shown at most once per operation. `hide` hides the warning entirely. verbose and shown at most once per operation. `hide` hides the warning entirely.
This overwrites a previous `-Zmiri-disable-isolation`.
* `-Zmiri-many-seeds=[<from>]..<to>` runs the program multiple times with different seeds for Miri's
RNG. With different seeds, Miri will make different choices to resolve non-determinism such as the
order in which concurrent threads are scheduled, or the exact addresses assigned to allocations.
This is useful to find bugs that only occur under particular interleavings of concurrent threads,
or that otherwise depend on non-determinism. If the `<from>` part is skipped, it defaults to `0`.
Can be used without a value; in that case the range defaults to `0..64`.
* `-Zmiri-many-seeds-keep-going` tells Miri to really try all the seeds in the given range, even if
a failing seed has already been found. This is useful to determine which fraction of seeds fails.
* `-Zmiri-num-cpus` states the number of available CPUs to be reported by miri. By default, the * `-Zmiri-num-cpus` states the number of available CPUs to be reported by miri. By default, the
number of available CPUs is `1`. Note that this flag does not affect how miri handles threads in number of available CPUs is `1`. Note that this flag does not affect how miri handles threads in
any way. any way.
@ -339,8 +349,8 @@ environment variable. We first document the most relevant and most commonly used
can increase test coverage by running Miri multiple times with different seeds. can increase test coverage by running Miri multiple times with different seeds.
* `-Zmiri-strict-provenance` enables [strict * `-Zmiri-strict-provenance` enables [strict
provenance](https://github.com/rust-lang/rust/issues/95228) checking in Miri. This means that 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 casting an integer to a pointer will stop execution because the provenance of the pointer
that cannot be used for any memory access. cannot be determined.
* `-Zmiri-symbolic-alignment-check` makes the alignment check more strict. By default, alignment is * `-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. 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 This can lead to cases where a program passes the alignment check by pure chance, because things
@ -429,6 +439,8 @@ to Miri failing to detect cases of undefined behavior in a program.
of Rust will be stricter than Tree Borrows. In other words, if you use Tree Borrows, of Rust will be stricter than Tree Borrows. In other words, if you use Tree Borrows,
even if your code is accepted today, it might be declared UB in the future. even if your code is accepted today, it might be declared UB in the future.
This is much less likely with Stacked Borrows. This is much less likely with Stacked Borrows.
Using Tree Borrows currently implies `-Zmiri-strict-provenance` because integer-to-pointer
casts are not supported in this mode, but that may change in the future.
* `-Zmiri-force-page-size=<num>` overrides the default page size for an architecture, in multiples of 1k. * `-Zmiri-force-page-size=<num>` overrides the default page size for an architecture, in multiples of 1k.
`4` is default for most targets. This value should always be a power of 2 and nonzero. `4` is default for most targets. This value should always be a power of 2 and nonzero.
* `-Zmiri-unique-is-unique` performs additional aliasing checks for `core::ptr::Unique` to ensure * `-Zmiri-unique-is-unique` performs additional aliasing checks for `core::ptr::Unique` to ensure

View file

@ -1,10 +1,10 @@
//! Implements the various phases of `cargo miri run/test`. //! Implements the various phases of `cargo miri run/test`.
use std::env;
use std::fs::{self, File}; use std::fs::{self, File};
use std::io::{BufReader, Write}; use std::io::BufReader;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Command; use std::process::Command;
use std::{env, thread};
use rustc_version::VersionMeta; use rustc_version::VersionMeta;
@ -24,10 +24,7 @@ Subcommands:
clean Clean the Miri cache & target directory clean Clean the Miri cache & target directory
The cargo options are exactly the same as for `cargo run` and `cargo test`, respectively. The cargo options are exactly the same as for `cargo run` and `cargo test`, respectively.
Furthermore, the following extra flags and environment variables are recognized for `run` and `test`: Furthermore, the following environment variables are recognized for `run` and `test`:
--many-seeds[=from..to] Run the program/tests many times with different seeds in the given range.
The range defaults to `0..64`.
MIRIFLAGS Extra flags to pass to the Miri driver. Use this to pass `-Zmiri-...` flags. MIRIFLAGS Extra flags to pass to the Miri driver. Use this to pass `-Zmiri-...` flags.
@ -41,8 +38,6 @@ Examples:
"; ";
const DEFAULT_MANY_SEEDS: &str = "0..64";
fn show_help() { fn show_help() {
println!("{CARGO_MIRI_HELP}"); println!("{CARGO_MIRI_HELP}");
} }
@ -182,17 +177,15 @@ pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
let target_dir = get_target_dir(&metadata); let target_dir = get_target_dir(&metadata);
cmd.arg("--target-dir").arg(target_dir); cmd.arg("--target-dir").arg(target_dir);
// Store many-seeds argument.
let mut many_seeds = None;
// *After* we set all the flags that need setting, forward everything else. Make sure to skip // *After* we set all the flags that need setting, forward everything else. Make sure to skip
// `--target-dir` (which would otherwise be set twice) and `--many-seeds` (which is our flag, not cargo's). // `--target-dir` (which would otherwise be set twice).
for arg in for arg in
ArgSplitFlagValue::from_string_iter(&mut args, "--target-dir").filter_map(Result::err) ArgSplitFlagValue::from_string_iter(&mut args, "--target-dir").filter_map(Result::err)
{ {
if arg == "--many-seeds" { if arg == "--many-seeds" || arg.starts_with("--many-seeds=") {
many_seeds = Some(DEFAULT_MANY_SEEDS.to_owned()); show_error!(
} else if let Some(val) = arg.strip_prefix("--many-seeds=") { "ERROR: the `--many-seeds` flag has been removed from cargo-miri; use MIRIFLAGS=-Zmiri-many-seeds instead"
many_seeds = Some(val.to_owned()); );
} else { } else {
cmd.arg(arg); cmd.arg(arg);
} }
@ -249,9 +242,6 @@ pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
// Forward some crucial information to our own re-invocations. // Forward some crucial information to our own re-invocations.
cmd.env("MIRI_SYSROOT", miri_sysroot); cmd.env("MIRI_SYSROOT", miri_sysroot);
cmd.env("MIRI_LOCAL_CRATES", local_crates(&metadata)); cmd.env("MIRI_LOCAL_CRATES", local_crates(&metadata));
if let Some(many_seeds) = many_seeds {
cmd.env("MIRI_MANY_SEEDS", many_seeds);
}
if verbose > 0 { if verbose > 0 {
cmd.env("MIRI_VERBOSE", verbose.to_string()); // This makes the other phases verbose. cmd.env("MIRI_VERBOSE", verbose.to_string()); // This makes the other phases verbose.
} }
@ -407,14 +397,11 @@ pub fn phase_rustc(mut args: impl Iterator<Item = String>, phase: RustcPhase) {
// Alter the `-o` parameter so that it does not overwrite the JSON file we stored above. // Alter the `-o` parameter so that it does not overwrite the JSON file we stored above.
let mut args = env.args; let mut args = env.args;
let mut out_filename = None;
for i in 0..args.len() { for i in 0..args.len() {
if args[i] == "-o" { if args[i] == "-o" {
out_filename = Some(args[i + 1].clone());
args[i + 1].push_str(".miri"); args[i + 1].push_str(".miri");
} }
} }
let out_filename = out_filename.expect("rustdoc must pass `-o`");
cmd.args(&args); cmd.args(&args);
cmd.env("MIRI_BE_RUSTC", "target"); cmd.env("MIRI_BE_RUSTC", "target");
@ -427,7 +414,7 @@ pub fn phase_rustc(mut args: impl Iterator<Item = String>, phase: RustcPhase) {
eprintln!("[cargo-miri rustc inside rustdoc] going to run:\n{cmd:?}"); eprintln!("[cargo-miri rustc inside rustdoc] going to run:\n{cmd:?}");
} }
exec_with_pipe(cmd, &env.stdin, format!("{out_filename}.stdin")); exec_with_pipe(cmd, &env.stdin);
} }
return; return;
@ -589,111 +576,81 @@ pub fn phase_runner(mut binary_args: impl Iterator<Item = String>, phase: Runner
} }
}; };
let many_seeds = env::var("MIRI_MANY_SEEDS"); let mut cmd = miri();
run_many_seeds(many_seeds.ok(), |seed| {
let mut cmd = miri();
// Set missing env vars. We prefer build-time env vars over run-time ones; see // 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. // <https://github.com/rust-lang/miri/issues/1661> for the kind of issue that fixes.
for (name, val) in &info.env { for (name, val) in &info.env {
// `CARGO_MAKEFLAGS` contains information about how to reach the jobserver, but by the time // `CARGO_MAKEFLAGS` contains information about how to reach the jobserver, but by the time
// the program is being run, that jobserver no longer exists (cargo only runs the jobserver // the program is being run, that jobserver no longer exists (cargo only runs the jobserver
// for the build portion of `cargo run`/`cargo test`). Hence we shouldn't forward this. // for the build portion of `cargo run`/`cargo test`). Hence we shouldn't forward this.
// Also see <https://github.com/rust-lang/rust/pull/113730>. // Also see <https://github.com/rust-lang/rust/pull/113730>.
if name == "CARGO_MAKEFLAGS" { if name == "CARGO_MAKEFLAGS" {
continue;
}
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; continue;
} } else if verbose > 0 {
if let Some(old_val) = env::var_os(name) { eprintln!(
if *old_val == *val { "[cargo-miri runner] Overwriting run-time env var {name:?}={old_val:?} with build-time value {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 {name:?}={old_val:?} with build-time value {val:?}"
);
}
}
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
// `.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 needs to be dropped.
let mut args = info.args.iter();
while let Some(arg) = args.next() {
if arg == "--extern" {
forward_patched_extern_arg(&mut (&mut args).cloned(), &mut cmd);
} else if let Some(suffix) = arg.strip_prefix("--error-format") {
assert!(suffix.starts_with('='));
// Drop this argument.
} else if let Some(suffix) = arg.strip_prefix("--json") {
assert!(suffix.starts_with('='));
// Drop this argument.
} else {
cmd.arg(arg);
} }
} }
// Respect `MIRIFLAGS`. cmd.env(name, val);
if let Ok(a) = env::var("MIRIFLAGS") { }
let args = flagsplit(&a);
cmd.args(args); if phase != RunnerPhase::Rustdoc {
} // Set the sysroot. Not necessary in rustdoc, where we already set the sysroot in
// Set the current seed. // `phase_rustdoc`. rustdoc will forward that flag when invoking rustc (i.e., us), so the
if let Some(seed) = seed { // flag is present in `info.args`.
eprintln!("Trying seed: {seed}"); cmd.arg("--sysroot").arg(env::var_os("MIRI_SYSROOT").unwrap());
cmd.arg(format!("-Zmiri-seed={seed}")); }
// 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 needs to be dropped.
let mut args = info.args.iter();
while let Some(arg) = args.next() {
if arg == "--extern" {
forward_patched_extern_arg(&mut (&mut args).cloned(), &mut cmd);
} else if let Some(suffix) = arg.strip_prefix("--error-format") {
assert!(suffix.starts_with('='));
// Drop this argument.
} else if let Some(suffix) = arg.strip_prefix("--json") {
assert!(suffix.starts_with('='));
// Drop this argument.
} else {
cmd.arg(arg);
} }
}
// Respect `MIRIFLAGS`.
if let Ok(a) = env::var("MIRIFLAGS") {
let args = flagsplit(&a);
cmd.args(args);
}
// Then pass binary arguments. // Then pass binary arguments.
cmd.arg("--"); cmd.arg("--");
cmd.args(&binary_args); cmd.args(&binary_args);
// Make sure we use the build-time working directory for interpreting Miri/rustc arguments. // 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 to do by setting `MIRI_CWD`. // But then we need to switch to the run-time one, which we instruct Miri to do by setting `MIRI_CWD`.
cmd.current_dir(&info.current_dir); cmd.current_dir(&info.current_dir);
cmd.env("MIRI_CWD", env::current_dir().unwrap()); cmd.env("MIRI_CWD", env::current_dir().unwrap());
// Run it. // Run it.
debug_cmd("[cargo-miri runner]", verbose, &cmd); debug_cmd("[cargo-miri runner]", verbose, &cmd);
match phase { match phase {
RunnerPhase::Rustdoc => { RunnerPhase::Rustdoc => exec_with_pipe(cmd, &info.stdin),
cmd.stdin(std::process::Stdio::piped()); RunnerPhase::Cargo => exec(cmd),
// the warning is wrong, we have a `wait` inside the `scope` closure. }
let mut child = cmd.spawn().expect("failed to spawn process");
let child_stdin = child.stdin.take().unwrap();
// Write stdin in a background thread, as it may block.
let exit_status = thread::scope(|s| {
s.spawn(|| {
let mut child_stdin = child_stdin;
// Ignore failure, it is most likely due to the process having terminated.
let _ = child_stdin.write_all(&info.stdin);
});
child.wait().expect("failed to run command")
});
if !exit_status.success() {
std::process::exit(exit_status.code().unwrap_or(-1));
}
}
RunnerPhase::Cargo => {
let exit_status = cmd.status().expect("failed to run command");
if !exit_status.success() {
std::process::exit(exit_status.code().unwrap_or(-1));
}
}
}
});
} }
pub fn phase_rustdoc(mut args: impl Iterator<Item = String>) { pub fn phase_rustdoc(mut args: impl Iterator<Item = String>) {

View file

@ -143,43 +143,23 @@ pub fn exec(mut cmd: Command) -> ! {
} }
} }
/// Execute the `Command`, where possible by replacing the current process with a new process /// Execute the `Command`, then exit this process with the exit code of the 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.
/// `input` is also piped to the new process's stdin, on cfg(unix) platforms by writing its pub fn exec_with_pipe(mut cmd: Command, input: &[u8]) -> ! {
/// contents to `path` first, then setting stdin to that file. // We can't use `exec` since then the background thread will stop running.
pub fn exec_with_pipe<P>(mut cmd: Command, input: &[u8], path: P) -> ! cmd.stdin(std::process::Stdio::piped());
where let mut child = cmd.spawn().expect("failed to spawn process");
P: AsRef<Path>, let child_stdin = child.stdin.take().unwrap();
{ // Write stdin in a background thread, as it may block.
#[cfg(unix)] let exit_status = std::thread::scope(|s| {
{ s.spawn(|| {
// Write the bytes we want to send to stdin out to a file let mut child_stdin = child_stdin;
std::fs::write(&path, input).unwrap(); // Ignore failure, it is most likely due to the process having terminated.
// Open the file for reading, and set our new stdin to it let _ = child_stdin.write_all(input);
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 child_stdin = child.stdin.take().unwrap();
// Write stdin in a background thread, as it may block.
let exit_status = std::thread::scope(|s| {
s.spawn(|| {
let mut child_stdin = child_stdin;
// Ignore failure, it is most likely due to the process having terminated.
let _ = child_stdin.write_all(input);
});
child.wait().expect("failed to run command")
}); });
std::process::exit(exit_status.code().unwrap_or(-1)) 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) { pub fn ask_to_run(mut cmd: Command, ask: bool, text: &str) {
@ -319,24 +299,3 @@ pub fn clean_target_dir(meta: &Metadata) {
remove_dir_all_idem(&target_dir).unwrap_or_else(|err| show_error!("{}", err)) remove_dir_all_idem(&target_dir).unwrap_or_else(|err| show_error!("{}", err))
} }
/// Run `f` according to the many-seeds argument. In single-seed mode, `f` will only
/// be called once, with `None`.
pub fn run_many_seeds(many_seeds: Option<String>, f: impl Fn(Option<u32>)) {
let Some(many_seeds) = many_seeds else {
return f(None);
};
let (from, to) = many_seeds
.split_once("..")
.unwrap_or_else(|| show_error!("invalid format for `--many-seeds`: expected `from..to`"));
let from: u32 = if from.is_empty() {
0
} else {
from.parse().unwrap_or_else(|_| show_error!("invalid `from` in `--many-seeds=from..to"))
};
let to: u32 =
to.parse().unwrap_or_else(|_| show_error!("invalid `to` in `--many-seeds=from..to"));
for seed in from..to {
f(Some(seed));
}
}

View file

@ -66,9 +66,9 @@ function run_tests {
time MIRIFLAGS="${MIRIFLAGS-} -O -Zmir-opt-level=4 -Cdebug-assertions=yes" MIRI_SKIP_UI_CHECKS=1 ./miri test $TARGET_FLAG tests/{pass,panic} time MIRIFLAGS="${MIRIFLAGS-} -O -Zmir-opt-level=4 -Cdebug-assertions=yes" MIRI_SKIP_UI_CHECKS=1 ./miri test $TARGET_FLAG tests/{pass,panic}
fi fi
if [ -n "${MANY_SEEDS-}" ]; then if [ -n "${MANY_SEEDS-}" ]; then
# Also run some many-seeds tests. (Also tests `./miri run`.) # Run many-seeds tests. (Also tests `./miri run`.)
time for FILE in tests/many-seeds/*.rs; do time for FILE in tests/many-seeds/*.rs; do
./miri run "--many-seeds=0..$MANY_SEEDS" $TARGET_FLAG "$FILE" ./miri run "-Zmiri-many-seeds=0..$MANY_SEEDS" $TARGET_FLAG "$FILE"
done done
fi fi
if [ -n "${TEST_BENCH-}" ]; then if [ -n "${TEST_BENCH-}" ]; then

View file

@ -250,6 +250,8 @@ dependencies = [
"itertools", "itertools",
"path_macro", "path_macro",
"rustc_version", "rustc_version",
"serde",
"serde_derive",
"serde_json", "serde_json",
"shell-words", "shell-words",
"tempfile", "tempfile",

View file

@ -23,6 +23,8 @@ xshell = "0.2.6"
rustc_version = "0.4" rustc_version = "0.4"
dunce = "1.0.4" dunce = "1.0.4"
directories = "5" directories = "5"
serde = "1"
serde_json = "1" serde_json = "1"
serde_derive = "1"
tempfile = "3.13.0" tempfile = "3.13.0"
clap = { version = "4.5.21", features = ["derive"] } clap = { version = "4.5.21", features = ["derive"] }

View file

@ -1,12 +1,16 @@
use std::collections::HashMap;
use std::ffi::{OsStr, OsString}; use std::ffi::{OsStr, OsString};
use std::io::Write; use std::fs::File;
use std::ops::{Not, Range}; use std::io::{BufReader, BufWriter, Write};
use std::ops::Not;
use std::path::PathBuf; use std::path::PathBuf;
use std::time::Duration; use std::time::Duration;
use std::{env, net, process}; use std::{env, net, process};
use anyhow::{Context, Result, anyhow, bail}; use anyhow::{Context, Result, anyhow, bail};
use path_macro::path; use path_macro::path;
use serde_derive::{Deserialize, Serialize};
use tempfile::TempDir;
use walkdir::WalkDir; use walkdir::WalkDir;
use xshell::{Shell, cmd}; use xshell::{Shell, cmd};
@ -174,13 +178,13 @@ impl Command {
Command::Check { flags } => Self::check(flags), Command::Check { flags } => Self::check(flags),
Command::Test { bless, flags, target, coverage } => Command::Test { bless, flags, target, coverage } =>
Self::test(bless, flags, target, coverage), Self::test(bless, flags, target, coverage),
Command::Run { dep, verbose, many_seeds, target, edition, flags } => Command::Run { dep, verbose, target, edition, flags } =>
Self::run(dep, verbose, many_seeds, target, edition, flags), Self::run(dep, verbose, target, edition, flags),
Command::Doc { flags } => Self::doc(flags), Command::Doc { flags } => Self::doc(flags),
Command::Fmt { flags } => Self::fmt(flags), Command::Fmt { flags } => Self::fmt(flags),
Command::Clippy { flags } => Self::clippy(flags), Command::Clippy { flags } => Self::clippy(flags),
Command::Bench { target, no_install, benches } => Command::Bench { target, no_install, save_baseline, load_baseline, benches } =>
Self::bench(target, no_install, benches), Self::bench(target, no_install, save_baseline, load_baseline, benches),
Command::Toolchain { flags } => Self::toolchain(flags), Command::Toolchain { flags } => Self::toolchain(flags),
Command::RustcPull { commit } => Self::rustc_pull(commit.clone()), Command::RustcPull { commit } => Self::rustc_pull(commit.clone()),
Command::RustcPush { github_user, branch } => Self::rustc_push(github_user, branch), Command::RustcPush { github_user, branch } => Self::rustc_push(github_user, branch),
@ -379,7 +383,17 @@ impl Command {
Ok(()) Ok(())
} }
fn bench(target: Option<String>, no_install: bool, benches: Vec<String>) -> Result<()> { fn bench(
target: Option<String>,
no_install: bool,
save_baseline: Option<String>,
load_baseline: Option<String>,
benches: Vec<String>,
) -> Result<()> {
if save_baseline.is_some() && load_baseline.is_some() {
bail!("Only one of `--save-baseline` and `--load-baseline` can be set");
}
// The hyperfine to use // The hyperfine to use
let hyperfine = env::var("HYPERFINE"); let hyperfine = env::var("HYPERFINE");
let hyperfine = hyperfine.as_deref().unwrap_or("hyperfine -w 1 -m 5 --shell=none"); let hyperfine = hyperfine.as_deref().unwrap_or("hyperfine -w 1 -m 5 --shell=none");
@ -387,19 +401,26 @@ impl Command {
let Some((program_name, args)) = hyperfine.split_first() else { let Some((program_name, args)) = hyperfine.split_first() else {
bail!("expected HYPERFINE environment variable to be non-empty"); bail!("expected HYPERFINE environment variable to be non-empty");
}; };
if !no_install { if !no_install {
// Make sure we have an up-to-date Miri installed and selected the right toolchain. // Make sure we have an up-to-date Miri installed and selected the right toolchain.
Self::install(vec![])?; Self::install(vec![])?;
} }
let results_json_dir = if save_baseline.is_some() || load_baseline.is_some() {
Some(TempDir::new()?)
} else {
None
};
let miri_dir = miri_dir()?;
let sh = Shell::new()?; let sh = Shell::new()?;
sh.change_dir(miri_dir()?); sh.change_dir(&miri_dir);
let benches_dir = "bench-cargo-miri"; let benches_dir = "bench-cargo-miri";
let benches: Vec<OsString> = if benches.is_empty() { let benches: Vec<String> = if benches.is_empty() {
sh.read_dir(benches_dir)? sh.read_dir(benches_dir)?
.into_iter() .into_iter()
.filter(|path| path.is_dir()) .filter(|path| path.is_dir())
.map(Into::into) .map(|path| path.into_os_string().into_string().unwrap())
.collect() .collect()
} else { } else {
benches.into_iter().map(Into::into).collect() benches.into_iter().map(Into::into).collect()
@ -414,16 +435,75 @@ impl Command {
let target_flag = &target_flag; let target_flag = &target_flag;
let toolchain = active_toolchain()?; let toolchain = active_toolchain()?;
// Run the requested benchmarks // Run the requested benchmarks
for bench in benches { for bench in &benches {
let current_bench = path!(benches_dir / bench / "Cargo.toml"); let current_bench = path!(benches_dir / bench / "Cargo.toml");
let mut export_json = None;
if let Some(baseline_temp_dir) = &results_json_dir {
export_json = Some(format!(
"--export-json={}",
path!(baseline_temp_dir / format!("{bench}.bench.json")).display()
));
}
// We don't attempt to escape `current_bench`, but we wrap it in quotes. // We don't attempt to escape `current_bench`, but we wrap it in quotes.
// That seems to make Windows CI happy. // That seems to make Windows CI happy.
cmd!( cmd!(
sh, sh,
"{program_name} {args...} 'cargo +'{toolchain}' miri run '{target_flag}' --manifest-path \"'{current_bench}'\"'" "{program_name} {args...} {export_json...} 'cargo +'{toolchain}' miri run '{target_flag}' --manifest-path \"'{current_bench}'\"'"
) )
.run()?; .run()?;
} }
// Gather/load results for baseline saving.
#[derive(Serialize, Deserialize)]
struct BenchResult {
mean: f64,
stddev: f64,
}
let gather_results = || -> Result<HashMap<&str, BenchResult>> {
let baseline_temp_dir = results_json_dir.unwrap();
let mut results = HashMap::new();
for bench in &benches {
let result = File::open(path!(baseline_temp_dir / format!("{bench}.bench.json")))?;
let mut result: serde_json::Value =
serde_json::from_reader(BufReader::new(result))?;
let result: BenchResult = serde_json::from_value(result["results"][0].take())?;
results.insert(bench as &str, result);
}
Ok(results)
};
if let Some(baseline_file) = save_baseline {
let results = gather_results()?;
let baseline = File::create(baseline_file)?;
serde_json::to_writer_pretty(BufWriter::new(baseline), &results)?;
} else if let Some(baseline_file) = load_baseline {
let new_results = gather_results()?;
let baseline_results: HashMap<String, BenchResult> = {
let f = File::open(baseline_file)?;
serde_json::from_reader(BufReader::new(f))?
};
println!(
"Comparison with baseline (relative speed, lower is better for the new results):"
);
for (bench, new_result) in new_results.iter() {
let Some(baseline_result) = baseline_results.get(*bench) else { continue };
// Compare results (inspired by hyperfine)
let ratio = new_result.mean / baseline_result.mean;
// https://en.wikipedia.org/wiki/Propagation_of_uncertainty#Example_formulae
// Covariance asssumed to be 0, i.e. variables are assumed to be independent
let ratio_stddev = ratio
* f64::sqrt(
(new_result.stddev / new_result.mean).powi(2)
+ (baseline_result.stddev / baseline_result.mean).powi(2),
);
println!(" {bench}: {ratio:.2} ± {ratio_stddev:.2}");
}
}
Ok(()) Ok(())
} }
@ -506,7 +586,6 @@ impl Command {
fn run( fn run(
dep: bool, dep: bool,
verbose: bool, verbose: bool,
many_seeds: Option<Range<u32>>,
target: Option<String>, target: Option<String>,
edition: Option<String>, edition: Option<String>,
flags: Vec<String>, flags: Vec<String>,
@ -534,48 +613,34 @@ impl Command {
early_flags.push("--sysroot".into()); early_flags.push("--sysroot".into());
early_flags.push(miri_sysroot.into()); early_flags.push(miri_sysroot.into());
// Compute everything needed to run the actual command. Also add MIRIFLAGS. // Compute flags.
let miri_flags = e.sh.var("MIRIFLAGS").unwrap_or_default(); let miri_flags = e.sh.var("MIRIFLAGS").unwrap_or_default();
let miri_flags = flagsplit(&miri_flags); let miri_flags = flagsplit(&miri_flags);
let quiet_flag = if verbose { None } else { Some("--quiet") }; let quiet_flag = if verbose { None } else { Some("--quiet") };
// This closure runs the command with the given `seed_flag` added between the MIRIFLAGS and
// the `flags` given on the command-line. // Run Miri.
let run_miri = |e: &MiriEnv, seed_flag: Option<String>| -> Result<()> { // The basic command that executes the Miri driver.
// The basic command that executes the Miri driver. let mut cmd = if dep {
let mut cmd = if dep { // We invoke the test suite as that has all the logic for running with dependencies.
// We invoke the test suite as that has all the logic for running with dependencies. e.cargo_cmd(".", "test")
e.cargo_cmd(".", "test") .args(&["--test", "ui"])
.args(&["--test", "ui"]) .args(quiet_flag)
.args(quiet_flag) .arg("--")
.arg("--") .args(&["--miri-run-dep-mode"])
.args(&["--miri-run-dep-mode"])
} else {
cmd!(e.sh, "{miri_bin}")
};
cmd.set_quiet(!verbose);
// Add Miri flags
let mut cmd = cmd.args(&miri_flags).args(&seed_flag).args(&early_flags).args(&flags);
// For `--dep` we also need to set the target in the env var.
if dep {
if let Some(target) = &target {
cmd = cmd.env("MIRI_TEST_TARGET", target);
}
}
// And run the thing.
Ok(cmd.run()?)
};
// Run the closure once or many times.
if let Some(seed_range) = many_seeds {
e.run_many_times(seed_range, |e, seed| {
eprintln!("Trying seed: {seed}");
run_miri(e, Some(format!("-Zmiri-seed={seed}"))).inspect_err(|_| {
eprintln!("FAILING SEED: {seed}");
})
})?;
} else { } else {
run_miri(&e, None)?; cmd!(e.sh, "{miri_bin}")
};
cmd.set_quiet(!verbose);
// Add Miri flags
let mut cmd = cmd.args(&miri_flags).args(&early_flags).args(&flags);
// For `--dep` we also need to set the target in the env var.
if dep {
if let Some(target) = &target {
cmd = cmd.env("MIRI_TEST_TARGET", target);
}
} }
Ok(()) // Finally, run the thing.
Ok(cmd.run()?)
} }
fn fmt(flags: Vec<String>) -> Result<()> { fn fmt(flags: Vec<String>) -> Result<()> {

View file

@ -1,32 +1,12 @@
#![allow(clippy::needless_question_mark)] #![allow(clippy::needless_question_mark, rustc::internal)]
mod commands; mod commands;
mod coverage; mod coverage;
mod util; mod util;
use std::ops::Range; use anyhow::{Result, bail};
use anyhow::{Context, Result, anyhow, bail};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
/// Parses a seed range
///
/// This function is used for the `--many-seeds` flag. It expects the range in the form
/// `<from>..<to>`. `<from>` is inclusive, `<to>` is exclusive. `<from>` can be omitted,
/// in which case it is assumed to be `0`.
fn parse_range(val: &str) -> anyhow::Result<Range<u32>> {
let (from, to) = val
.split_once("..")
.ok_or_else(|| anyhow!("invalid format for `--many-seeds`: expected `from..to`"))?;
let from: u32 = if from.is_empty() {
0
} else {
from.parse().context("invalid `from` in `--many-seeds=from..to")?
};
let to: u32 = to.parse().context("invalid `to` in `--many-seeds=from..to")?;
Ok(from..to)
}
#[derive(Clone, Debug, Subcommand)] #[derive(Clone, Debug, Subcommand)]
pub enum Command { pub enum Command {
/// Installs the miri driver and cargo-miri to the sysroot of the active toolchain. /// Installs the miri driver and cargo-miri to the sysroot of the active toolchain.
@ -81,9 +61,6 @@ pub enum Command {
/// Show build progress. /// Show build progress.
#[arg(long, short)] #[arg(long, short)]
verbose: bool, verbose: bool,
/// Run the driver with the seeds in the given range (`..to` or `from..to`, default: `0..64`).
#[arg(long, value_parser = parse_range)]
many_seeds: Option<Range<u32>>,
/// The cross-interpretation target. /// The cross-interpretation target.
#[arg(long)] #[arg(long)]
target: Option<String>, target: Option<String>,
@ -117,6 +94,14 @@ pub enum Command {
/// When `true`, skip the `./miri install` step. /// When `true`, skip the `./miri install` step.
#[arg(long)] #[arg(long)]
no_install: bool, no_install: bool,
/// Store the benchmark result in the given file, so it can be used
/// as the baseline for a future run.
#[arg(long)]
save_baseline: Option<String>,
/// Load previous stored benchmark results as baseline, and print an analysis of how the
/// current run compares.
#[arg(long)]
load_baseline: Option<String>,
/// List of benchmarks to run (default: run all benchmarks). /// List of benchmarks to run (default: run all benchmarks).
benches: Vec<String>, benches: Vec<String>,
}, },

View file

@ -1,9 +1,7 @@
use std::ffi::{OsStr, OsString}; use std::ffi::{OsStr, OsString};
use std::io::BufRead; use std::io::BufRead;
use std::ops::Range;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use std::{env, iter};
use std::{env, iter, thread};
use anyhow::{Context, Result, anyhow, bail}; use anyhow::{Context, Result, anyhow, bail};
use dunce::canonicalize; use dunce::canonicalize;
@ -29,7 +27,6 @@ pub fn flagsplit(flags: &str) -> Vec<String> {
} }
/// Some extra state we track for building Miri, such as the right RUSTFLAGS. /// Some extra state we track for building Miri, such as the right RUSTFLAGS.
#[derive(Clone)]
pub struct MiriEnv { pub struct MiriEnv {
/// miri_dir is the root of the miri repository checkout we are working in. /// miri_dir is the root of the miri repository checkout we are working in.
pub miri_dir: PathBuf, pub miri_dir: PathBuf,
@ -240,53 +237,4 @@ impl MiriEnv {
Ok(()) Ok(())
} }
/// Run the given closure many times in parallel with access to the shell, once for each value in the `range`.
pub fn run_many_times(
&self,
range: Range<u32>,
run: impl Fn(&Self, u32) -> Result<()> + Sync,
) -> Result<()> {
// `next` is atomic so threads can concurrently fetch their next value to run.
let next = AtomicU32::new(range.start);
let end = range.end; // exclusive!
let failed = AtomicBool::new(false);
thread::scope(|s| {
let mut handles = Vec::new();
// Spawn one worker per core.
for _ in 0..thread::available_parallelism()?.get() {
// Create a copy of the environment for this thread.
let local_miri = self.clone();
let handle = s.spawn(|| -> Result<()> {
let local_miri = local_miri; // move the copy into this thread.
// Each worker thread keeps asking for numbers until we're all done.
loop {
let cur = next.fetch_add(1, Ordering::Relaxed);
if cur >= end {
// We hit the upper limit and are done.
break;
}
// Run the command with this seed.
run(&local_miri, cur).inspect_err(|_| {
// If we failed, tell everyone about this.
failed.store(true, Ordering::Relaxed);
})?;
// Check if some other command failed (in which case we'll stop as well).
if failed.load(Ordering::Relaxed) {
return Ok(());
}
}
Ok(())
});
handles.push(handle);
}
// Wait for all workers to be done.
for handle in handles {
handle.join().unwrap()?;
}
// If all workers succeeded, we can't have failed.
assert!(!failed.load(Ordering::Relaxed));
Ok(())
})
}
} }

View file

@ -9,7 +9,6 @@ use std::cmp::max;
use rand::Rng; use rand::Rng;
use rustc_abi::{Align, Size}; use rustc_abi::{Align, Size};
use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_span::Span;
use self::reuse_pool::ReusePool; use self::reuse_pool::ReusePool;
use crate::concurrency::VClock; use crate::concurrency::VClock;
@ -319,17 +318,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
match global_state.provenance_mode { match global_state.provenance_mode {
ProvenanceMode::Default => { ProvenanceMode::Default => {
// The first time this happens at a particular location, print a warning. // The first time this happens at a particular location, print a warning.
thread_local! { let mut int2ptr_warned = this.machine.int2ptr_warned.borrow_mut();
// `Span` is non-`Send`, so we use a thread-local instead. let first = int2ptr_warned.is_empty();
static PAST_WARNINGS: RefCell<FxHashSet<Span>> = RefCell::default(); if int2ptr_warned.insert(this.cur_span()) {
// Newly inserted, so first time we see this span.
this.emit_diagnostic(NonHaltingDiagnostic::Int2Ptr { details: first });
} }
PAST_WARNINGS.with_borrow_mut(|past_warnings| {
let first = past_warnings.is_empty();
if past_warnings.insert(this.cur_span()) {
// Newly inserted, so first time we see this span.
this.emit_diagnostic(NonHaltingDiagnostic::Int2Ptr { details: first });
}
});
} }
ProvenanceMode::Strict => { ProvenanceMode::Strict => {
throw_machine_stop!(TerminationInfo::Int2PtrWithStrictProvenance); throw_machine_stop!(TerminationInfo::Int2PtrWithStrictProvenance);

View file

@ -26,11 +26,17 @@ extern crate rustc_span;
use std::env::{self, VarError}; use std::env::{self, VarError};
use std::num::NonZero; use std::num::NonZero;
use std::ops::Range;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::{Arc, Once};
use miri::{BacktraceStyle, BorrowTrackerMethod, ProvenanceMode, RetagFields, ValidationMode}; use miri::{
BacktraceStyle, BorrowTrackerMethod, MiriConfig, ProvenanceMode, RetagFields, ValidationMode,
};
use rustc_abi::ExternAbi; use rustc_abi::ExternAbi;
use rustc_data_structures::sync;
use rustc_data_structures::sync::Lrc; use rustc_data_structures::sync::Lrc;
use rustc_driver::Compilation; use rustc_driver::Compilation;
use rustc_hir::def_id::LOCAL_CRATE; use rustc_hir::def_id::LOCAL_CRATE;
@ -52,7 +58,69 @@ use rustc_span::def_id::DefId;
use tracing::debug; use tracing::debug;
struct MiriCompilerCalls { struct MiriCompilerCalls {
miri_config: miri::MiriConfig, miri_config: Option<MiriConfig>,
many_seeds: Option<ManySeedsConfig>,
}
struct ManySeedsConfig {
seeds: Range<u32>,
keep_going: bool,
}
impl MiriCompilerCalls {
fn new(miri_config: MiriConfig, many_seeds: Option<ManySeedsConfig>) -> Self {
Self { miri_config: Some(miri_config), many_seeds }
}
}
fn entry_fn(tcx: TyCtxt<'_>) -> (DefId, EntryFnType) {
if let Some(entry_def) = tcx.entry_fn(()) {
return entry_def;
}
// Look for a symbol in the local crate named `miri_start`, and treat that as the entry point.
let sym = tcx.exported_symbols(LOCAL_CRATE).iter().find_map(|(sym, _)| {
if sym.symbol_name_for_local_instance(tcx).name == "miri_start" { Some(sym) } else { None }
});
if let Some(ExportedSymbol::NonGeneric(id)) = sym {
let start_def_id = id.expect_local();
let start_span = tcx.def_span(start_def_id);
let expected_sig = ty::Binder::dummy(tcx.mk_fn_sig(
[tcx.types.isize, Ty::new_imm_ptr(tcx, Ty::new_imm_ptr(tcx, tcx.types.u8))],
tcx.types.isize,
false,
hir::Safety::Safe,
ExternAbi::Rust,
));
let correct_func_sig = check_function_signature(
tcx,
ObligationCause::new(start_span, start_def_id, ObligationCauseCode::Misc),
*id,
expected_sig,
)
.is_ok();
if correct_func_sig {
(*id, EntryFnType::Start)
} else {
tcx.dcx().fatal(
"`miri_start` must have the following signature:\n\
fn miri_start(argc: isize, argv: *const *const u8) -> isize",
);
}
} else {
tcx.dcx().fatal(
"Miri can only run programs that have a main function.\n\
Alternatively, you can export a `miri_start` function:\n\
\n\
#[cfg(miri)]\n\
#[no_mangle]\n\
fn miri_start(argc: isize, argv: *const *const u8) -> isize {\
\n // Call the actual start function that your project implements, based on your target's conventions.\n\
}"
);
}
} }
impl rustc_driver::Callbacks for MiriCompilerCalls { impl rustc_driver::Callbacks for MiriCompilerCalls {
@ -87,7 +155,7 @@ impl rustc_driver::Callbacks for MiriCompilerCalls {
} }
let (entry_def_id, entry_type) = entry_fn(tcx); let (entry_def_id, entry_type) = entry_fn(tcx);
let mut config = self.miri_config.clone(); let mut config = self.miri_config.take().expect("after_analysis must only be called once");
// Add filename to `miri` arguments. // Add filename to `miri` arguments.
config.args.insert(0, tcx.sess.io.input.filestem().to_string()); config.args.insert(0, tcx.sess.io.input.filestem().to_string());
@ -111,12 +179,36 @@ impl rustc_driver::Callbacks for MiriCompilerCalls {
optimizations is usually marginal at best."); optimizations is usually marginal at best.");
} }
if let Some(return_code) = miri::eval_entry(tcx, entry_def_id, entry_type, config) { if let Some(many_seeds) = self.many_seeds.take() {
std::process::exit(i32::try_from(return_code).expect("Return value was too large!")); assert!(config.seed.is_none());
let exit_code = sync::IntoDynSyncSend(AtomicI32::new(rustc_driver::EXIT_SUCCESS));
sync::par_for_each_in(many_seeds.seeds, |seed| {
let mut config = config.clone();
config.seed = Some(seed.into());
eprintln!("Trying seed: {seed}");
let return_code = miri::eval_entry(tcx, entry_def_id, entry_type, config)
.unwrap_or(rustc_driver::EXIT_FAILURE);
if return_code != rustc_driver::EXIT_SUCCESS {
eprintln!("FAILING SEED: {seed}");
if !many_seeds.keep_going {
// `abort_if_errors` would actually not stop, since `par_for_each` waits for the
// rest of the to finish, so we just exit immediately.
std::process::exit(return_code);
}
exit_code.store(return_code, Ordering::Relaxed);
}
});
std::process::exit(exit_code.0.into_inner());
} else {
let return_code = miri::eval_entry(tcx, entry_def_id, entry_type, config)
.unwrap_or_else(|| {
tcx.dcx().abort_if_errors();
rustc_driver::EXIT_FAILURE
});
std::process::exit(return_code);
} }
tcx.dcx().abort_if_errors();
Compilation::Stop // Unreachable.
} }
} }
@ -241,21 +333,26 @@ fn rustc_logger_config() -> rustc_log::LoggerConfig {
cfg cfg
} }
/// The global logger can only be set once per process, so track
/// whether that already happened.
static LOGGER_INITED: Once = Once::new();
fn init_early_loggers(early_dcx: &EarlyDiagCtxt) { fn init_early_loggers(early_dcx: &EarlyDiagCtxt) {
// Now for rustc. We only initialize `rustc` if the env var is set (so the user asked for it). // 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 // 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. // settings, and *not* log anything for what happens before `miri` starts interpreting.
if env::var_os("RUSTC_LOG").is_some() { if env::var_os("RUSTC_LOG").is_some() {
rustc_driver::init_logger(early_dcx, rustc_logger_config()); LOGGER_INITED.call_once(|| {
rustc_driver::init_logger(early_dcx, rustc_logger_config());
});
} }
} }
fn init_late_loggers(early_dcx: &EarlyDiagCtxt, tcx: TyCtxt<'_>) { fn init_late_loggers(early_dcx: &EarlyDiagCtxt, tcx: TyCtxt<'_>) {
// If `RUSTC_LOG` is not set, then `init_early_loggers` did not call // If the logger is not yet initialized, initialize it.
// `rustc_driver::init_logger`, so we have to do this now. LOGGER_INITED.call_once(|| {
if env::var_os("RUSTC_LOG").is_none() {
rustc_driver::init_logger(early_dcx, rustc_logger_config()); rustc_driver::init_logger(early_dcx, rustc_logger_config());
} });
// If `MIRI_BACKTRACE` is set and `RUSTC_CTFE_BACKTRACE` is not, set `RUSTC_CTFE_BACKTRACE`. // 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. // Do this late, so we ideally only apply this to Miri's errors.
@ -270,25 +367,14 @@ fn init_late_loggers(early_dcx: &EarlyDiagCtxt, tcx: TyCtxt<'_>) {
} }
/// Execute a compiler with the given CLI arguments and callbacks. /// Execute a compiler with the given CLI arguments and callbacks.
fn run_compiler( fn run_compiler_and_exit(
mut args: Vec<String>, args: &[String],
target_crate: bool,
callbacks: &mut (dyn rustc_driver::Callbacks + Send), callbacks: &mut (dyn rustc_driver::Callbacks + Send),
using_internal_features: std::sync::Arc<std::sync::atomic::AtomicBool>, using_internal_features: Arc<std::sync::atomic::AtomicBool>,
) -> ! { ) -> ! {
// 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. // Invoke compiler, and handle return code.
let exit_code = rustc_driver::catch_with_exit_code(move || { let exit_code = rustc_driver::catch_with_exit_code(move || {
rustc_driver::RunCompiler::new(&args, callbacks) rustc_driver::RunCompiler::new(args, callbacks)
.set_using_internal_features(using_internal_features) .set_using_internal_features(using_internal_features)
.run(); .run();
Ok(()) Ok(())
@ -311,6 +397,18 @@ fn parse_rate(input: &str) -> Result<f64, &'static str> {
} }
} }
/// Parses a seed range
///
/// This function is used for the `-Zmiri-many-seeds` flag. It expects the range in the form
/// `<from>..<to>`. `<from>` is inclusive, `<to>` is exclusive. `<from>` can be omitted,
/// in which case it is assumed to be `0`.
fn parse_range(val: &str) -> Result<Range<u32>, &'static str> {
let (from, to) = val.split_once("..").ok_or("expected `from..to`")?;
let from: u32 = if from.is_empty() { 0 } else { from.parse().map_err(|_| "invalid `from`")? };
let to: u32 = to.parse().map_err(|_| "invalid `to`")?;
Ok(from..to)
}
#[cfg(any(target_os = "linux", target_os = "macos"))] #[cfg(any(target_os = "linux", target_os = "macos"))]
fn jemalloc_magic() { fn jemalloc_magic() {
// These magic runes are copied from // These magic runes are copied from
@ -349,56 +447,6 @@ fn jemalloc_magic() {
} }
} }
fn entry_fn(tcx: TyCtxt<'_>) -> (DefId, EntryFnType) {
if let Some(entry_def) = tcx.entry_fn(()) {
return entry_def;
}
// Look for a symbol in the local crate named `miri_start`, and treat that as the entry point.
let sym = tcx.exported_symbols(LOCAL_CRATE).iter().find_map(|(sym, _)| {
if sym.symbol_name_for_local_instance(tcx).name == "miri_start" { Some(sym) } else { None }
});
if let Some(ExportedSymbol::NonGeneric(id)) = sym {
let start_def_id = id.expect_local();
let start_span = tcx.def_span(start_def_id);
let expected_sig = ty::Binder::dummy(tcx.mk_fn_sig(
[tcx.types.isize, Ty::new_imm_ptr(tcx, Ty::new_imm_ptr(tcx, tcx.types.u8))],
tcx.types.isize,
false,
hir::Safety::Safe,
ExternAbi::Rust,
));
let correct_func_sig = check_function_signature(
tcx,
ObligationCause::new(start_span, start_def_id, ObligationCauseCode::Misc),
*id,
expected_sig,
)
.is_ok();
if correct_func_sig {
(*id, EntryFnType::Start)
} else {
tcx.dcx().fatal(
"`miri_start` must have the following signature:\n\
fn miri_start(argc: isize, argv: *const *const u8) -> isize",
);
}
} else {
tcx.dcx().fatal(
"Miri can only run programs that have a main function.\n\
Alternatively, you can export a `miri_start` function:\n\
\n\
#[cfg(miri)]\n\
#[no_mangle]\n\
fn miri_start(argc: isize, argv: *const *const u8) -> isize {\
\n // Call the actual start function that your project implements, based on your target's conventions.\n\
}"
);
}
}
fn main() { fn main() {
#[cfg(any(target_os = "linux", target_os = "macos"))] #[cfg(any(target_os = "linux", target_os = "macos"))]
jemalloc_magic(); jemalloc_magic();
@ -431,10 +479,21 @@ fn main() {
panic!("invalid `MIRI_BE_RUSTC` value: {crate_kind:?}") panic!("invalid `MIRI_BE_RUSTC` value: {crate_kind:?}")
}; };
// We cannot use `rustc_driver::main` as we need to adjust the CLI arguments. let mut args = args;
run_compiler( // Don't insert `MIRI_DEFAULT_ARGS`, in particular, `--cfg=miri`, if we are building
args, // a "host" crate. That may cause procedural macros (and probably build scripts) to
target_crate, // depend on Miri-only symbols, such as `miri_resolve_frame`:
// https://github.com/rust-lang/miri/issues/1760
if target_crate {
// Splice in the default arguments after the program name.
// 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));
}
// We cannot use `rustc_driver::main` as we want it to use `args` as the CLI arguments.
run_compiler_and_exit(
&args,
&mut MiriBeRustCompilerCalls { target_crate }, &mut MiriBeRustCompilerCalls { target_crate },
using_internal_features, using_internal_features,
) )
@ -448,13 +507,13 @@ fn main() {
init_early_loggers(&early_dcx); init_early_loggers(&early_dcx);
// Parse our arguments and split them across `rustc` and `miri`. // Parse our arguments and split them across `rustc` and `miri`.
let mut miri_config = miri::MiriConfig::default(); let mut many_seeds: Option<Range<u32>> = None;
let mut many_seeds_keep_going = false;
let mut miri_config = MiriConfig::default();
miri_config.env = env_snapshot; miri_config.env = env_snapshot;
let mut rustc_args = vec![]; let mut rustc_args = vec![];
let mut after_dashdash = false; let mut after_dashdash = false;
// If user has explicitly enabled/disabled isolation
let mut isolation_enabled: Option<bool> = None;
// Note that we require values to be given with `=`, not with a space. // Note that we require values to be given with `=`, not with a space.
// This matches how rustc parses `-Z`. // This matches how rustc parses `-Z`.
@ -463,6 +522,8 @@ fn main() {
if rustc_args.is_empty() { if rustc_args.is_empty() {
// Very first arg: binary name. // Very first arg: binary name.
rustc_args.push(arg); rustc_args.push(arg);
// Also add the default arguments.
rustc_args.extend(miri::MIRI_DEFAULT_ARGS.iter().map(ToString::to_string));
} else if after_dashdash { } else if after_dashdash {
// Everything that comes after `--` is forwarded to the interpreted crate. // Everything that comes after `--` is forwarded to the interpreted crate.
miri_config.args.push(arg); miri_config.args.push(arg);
@ -476,6 +537,7 @@ fn main() {
miri_config.borrow_tracker = None; miri_config.borrow_tracker = None;
} else if arg == "-Zmiri-tree-borrows" { } else if arg == "-Zmiri-tree-borrows" {
miri_config.borrow_tracker = Some(BorrowTrackerMethod::TreeBorrows); miri_config.borrow_tracker = Some(BorrowTrackerMethod::TreeBorrows);
miri_config.provenance_mode = ProvenanceMode::Strict;
} else if arg == "-Zmiri-unique-is-unique" { } else if arg == "-Zmiri-unique-is-unique" {
miri_config.unique_is_unique = true; miri_config.unique_is_unique = true;
} else if arg == "-Zmiri-disable-data-race-detector" { } else if arg == "-Zmiri-disable-data-race-detector" {
@ -485,19 +547,7 @@ fn main() {
miri_config.check_alignment = miri::AlignmentCheck::None; miri_config.check_alignment = miri::AlignmentCheck::None;
} else if arg == "-Zmiri-symbolic-alignment-check" { } else if arg == "-Zmiri-symbolic-alignment-check" {
miri_config.check_alignment = miri::AlignmentCheck::Symbolic; miri_config.check_alignment = miri::AlignmentCheck::Symbolic;
} else if arg == "-Zmiri-disable-abi-check" {
eprintln!(
"WARNING: the flag `-Zmiri-disable-abi-check` no longer has any effect; \
ABI checks cannot be disabled any more"
);
} else if arg == "-Zmiri-disable-isolation" { } 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; miri_config.isolated_op = miri::IsolatedOp::Allow;
} else if arg == "-Zmiri-disable-leak-backtraces" { } else if arg == "-Zmiri-disable-leak-backtraces" {
miri_config.collect_leak_backtraces = false; miri_config.collect_leak_backtraces = false;
@ -506,14 +556,6 @@ fn main() {
} else if arg == "-Zmiri-track-weak-memory-loads" { } else if arg == "-Zmiri-track-weak-memory-loads" {
miri_config.track_outdated_loads = true; miri_config.track_outdated_loads = true;
} else if let Some(param) = arg.strip_prefix("-Zmiri-isolation-error=") { } 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 { miri_config.isolated_op = match param {
"abort" => miri::IsolatedOp::Reject(miri::RejectOpWith::Abort), "abort" => miri::IsolatedOp::Reject(miri::RejectOpWith::Abort),
"hide" => miri::IsolatedOp::Reject(miri::RejectOpWith::NoWarning), "hide" => miri::IsolatedOp::Reject(miri::RejectOpWith::NoWarning),
@ -544,17 +586,21 @@ fn main() {
_ => show_error!("`-Zmiri-retag-fields` can only be `all`, `none`, or `scalar`"), _ => show_error!("`-Zmiri-retag-fields` can only be `all`, `none`, or `scalar`"),
}; };
} else if let Some(param) = arg.strip_prefix("-Zmiri-seed=") { } 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 = param.parse::<u64>().unwrap_or_else(|_| { let seed = param.parse::<u64>().unwrap_or_else(|_| {
show_error!("-Zmiri-seed must be an integer that fits into u64") show_error!("-Zmiri-seed must be an integer that fits into u64")
}); });
miri_config.seed = Some(seed); miri_config.seed = Some(seed);
} else if let Some(_param) = arg.strip_prefix("-Zmiri-env-exclude=") { } else if let Some(param) = arg.strip_prefix("-Zmiri-many-seeds=") {
show_error!( let range = parse_range(param).unwrap_or_else(|err| {
"`-Zmiri-env-exclude` has been removed; unset env vars before starting Miri instead" show_error!(
); "-Zmiri-many-seeds requires a range in the form `from..to` or `..to`: {err}"
)
});
many_seeds = Some(range);
} else if arg == "-Zmiri-many-seeds" {
many_seeds = Some(0..64);
} else if arg == "-Zmiri-many-seeds-keep-going" {
many_seeds_keep_going = true;
} else if let Some(param) = arg.strip_prefix("-Zmiri-env-forward=") { } else if let Some(param) = arg.strip_prefix("-Zmiri-env-forward=") {
miri_config.forwarded_env_vars.push(param.to_owned()); miri_config.forwarded_env_vars.push(param.to_owned());
} else if let Some(param) = arg.strip_prefix("-Zmiri-env-set=") { } else if let Some(param) = arg.strip_prefix("-Zmiri-env-set=") {
@ -657,21 +703,41 @@ fn main() {
"-Zmiri-unique-is-unique only has an effect when -Zmiri-tree-borrows is also used" "-Zmiri-unique-is-unique only has an effect when -Zmiri-tree-borrows is also used"
); );
} }
// Tree Borrows + permissive provenance does not work. // Tree Borrows implies strict provenance, and is not compatible with native calls.
if miri_config.provenance_mode == ProvenanceMode::Permissive if matches!(miri_config.borrow_tracker, Some(BorrowTrackerMethod::TreeBorrows)) {
&& matches!(miri_config.borrow_tracker, Some(BorrowTrackerMethod::TreeBorrows)) if miri_config.provenance_mode != ProvenanceMode::Strict {
{ show_error!(
show_error!( "Tree Borrows does not support integer-to-pointer casts, and hence requires strict provenance"
"Tree Borrows does not support integer-to-pointer casts, and is hence not compatible with permissive provenance" );
); }
if miri_config.native_lib.is_some() {
show_error!("Tree Borrows is not compatible with calling native functions");
}
} }
// Native calls and strict provenance are not compatible.
if miri_config.native_lib.is_some() && miri_config.provenance_mode == ProvenanceMode::Strict {
show_error!("strict provenance is not compatible with calling native functions");
}
// You can set either one seed or many.
if many_seeds.is_some() && miri_config.seed.is_some() {
show_error!("Only one of `-Zmiri-seed` and `-Zmiri-many-seeds can be set");
}
// Ensure we have parallelism for many-seeds mode.
if many_seeds.is_some() && !rustc_args.iter().any(|arg| arg.starts_with("-Zthreads=")) {
rustc_args.push(format!(
"-Zthreads={}",
std::thread::available_parallelism().map_or(1, |n| n.get())
));
}
let many_seeds =
many_seeds.map(|seeds| ManySeedsConfig { seeds, keep_going: many_seeds_keep_going });
debug!("rustc arguments: {:?}", rustc_args); debug!("rustc arguments: {:?}", rustc_args);
debug!("crate arguments: {:?}", miri_config.args); debug!("crate arguments: {:?}", miri_config.args);
run_compiler( run_compiler_and_exit(
rustc_args, &rustc_args,
/* target_crate: */ true, &mut MiriCompilerCalls::new(miri_config, many_seeds),
&mut MiriCompilerCalls { miri_config },
using_internal_features, using_internal_features,
) )
} }

View file

@ -5,7 +5,6 @@ pub mod diagnostics;
mod item; mod item;
mod stack; mod stack;
use std::cell::RefCell;
use std::fmt::Write; use std::fmt::Write;
use std::{cmp, mem}; use std::{cmp, mem};
@ -822,16 +821,9 @@ trait EvalContextPrivExt<'tcx, 'ecx>: crate::MiriInterpCxExt<'tcx> {
let size = match size { let size = match size {
Some(size) => size, Some(size) => size,
None => { None => {
// The first time this happens, show a warning. if !this.machine.sb_extern_type_warned.replace(true) {
thread_local! { static WARNING_SHOWN: RefCell<bool> = const { RefCell::new(false) }; }
WARNING_SHOWN.with_borrow_mut(|shown| {
if *shown {
return;
}
// Not yet shown. Show it!
*shown = true;
this.emit_diagnostic(NonHaltingDiagnostic::ExternTypeReborrow); this.emit_diagnostic(NonHaltingDiagnostic::ExternTypeReborrow);
}); }
return interp_ok(place.clone()); return interp_ok(place.clone());
} }
}; };

View file

@ -12,7 +12,7 @@ use crate::*;
/// Details of premature program termination. /// Details of premature program termination.
pub enum TerminationInfo { pub enum TerminationInfo {
Exit { Exit {
code: i64, code: i32,
leak_check: bool, leak_check: bool,
}, },
Abort(String), Abort(String),
@ -214,7 +214,7 @@ pub fn prune_stacktrace<'tcx>(
pub fn report_error<'tcx>( pub fn report_error<'tcx>(
ecx: &InterpCx<'tcx, MiriMachine<'tcx>>, ecx: &InterpCx<'tcx, MiriMachine<'tcx>>,
e: InterpErrorInfo<'tcx>, e: InterpErrorInfo<'tcx>,
) -> Option<(i64, bool)> { ) -> Option<(i32, bool)> {
use InterpErrorKind::*; use InterpErrorKind::*;
use UndefinedBehaviorInfo::*; use UndefinedBehaviorInfo::*;

View file

@ -249,6 +249,13 @@ impl<'tcx> MainThreadState<'tcx> {
// Figure out exit code. // Figure out exit code.
let ret_place = this.machine.main_fn_ret_place.clone().unwrap(); let ret_place = this.machine.main_fn_ret_place.clone().unwrap();
let exit_code = this.read_target_isize(&ret_place)?; let exit_code = this.read_target_isize(&ret_place)?;
// Rust uses `isize` but the underlying type of an exit code is `i32`.
// Do a saturating cast.
let exit_code = i32::try_from(exit_code).unwrap_or(if exit_code >= 0 {
i32::MAX
} else {
i32::MIN
});
// Deal with our thread-local memory. We do *not* want to actually free it, instead we consider TLS // Deal with our thread-local memory. We do *not* want to actually free it, instead we consider TLS
// to be like a global `static`, so that all memory reached by it is considered to "not leak". // to be like a global `static`, so that all memory reached by it is considered to "not leak".
this.terminate_active_thread(TlsAllocAction::Leak)?; this.terminate_active_thread(TlsAllocAction::Leak)?;
@ -421,7 +428,7 @@ pub fn create_ecx<'tcx>(
} }
/// Evaluates the entry function specified by `entry_id`. /// Evaluates the entry function specified by `entry_id`.
/// Returns `Some(return_code)` if program executed completed. /// Returns `Some(return_code)` if program execution completed.
/// Returns `None` if an evaluation error occurred. /// Returns `None` if an evaluation error occurred.
#[expect(clippy::needless_lifetimes)] #[expect(clippy::needless_lifetimes)]
pub fn eval_entry<'tcx>( pub fn eval_entry<'tcx>(
@ -429,7 +436,7 @@ pub fn eval_entry<'tcx>(
entry_id: DefId, entry_id: DefId,
entry_type: EntryFnType, entry_type: EntryFnType,
config: MiriConfig, config: MiriConfig,
) -> Option<i64> { ) -> Option<i32> {
// Copy setting before we move `config`. // Copy setting before we move `config`.
let ignore_leaks = config.ignore_leaks; let ignore_leaks = config.ignore_leaks;

View file

@ -1,6 +1,4 @@
use std::collections::BTreeSet;
use std::num::NonZero; use std::num::NonZero;
use std::sync::Mutex;
use std::time::Duration; use std::time::Duration;
use std::{cmp, iter}; use std::{cmp, iter};
@ -332,19 +330,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
base: &P, base: &P,
name: &str, name: &str,
) -> InterpResult<'tcx, P> { ) -> InterpResult<'tcx, P> {
if let Some(field) = self.try_project_field_named(base, name)? { interp_ok(
return interp_ok(field); self.try_project_field_named(base, name)?
} .unwrap_or_else(|| bug!("no field named {} in type {}", name, base.layout().ty)),
bug!("No field named {} in type {}", name, base.layout().ty); )
}
/// Search if `base` (which must be a struct or union type) contains the `name` field.
fn projectable_has_field<P: Projectable<'tcx, Provenance>>(
&self,
base: &P,
name: &str,
) -> bool {
self.try_project_field_named(base, name).unwrap().is_some()
} }
/// Write an int of the appropriate size to `dest`. The target type may be signed or unsigned, /// Write an int of the appropriate size to `dest`. The target type may be signed or unsigned,
@ -650,11 +639,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
match reject_with { match reject_with {
RejectOpWith::Abort => isolation_abort_error(op_name), RejectOpWith::Abort => isolation_abort_error(op_name),
RejectOpWith::WarningWithoutBacktrace => { RejectOpWith::WarningWithoutBacktrace => {
// This exists to reduce verbosity; make sure we emit the warning at most once per let mut emitted_warnings = this.machine.reject_in_isolation_warned.borrow_mut();
// operation.
static EMITTED_WARNINGS: Mutex<BTreeSet<String>> = Mutex::new(BTreeSet::new());
let mut emitted_warnings = EMITTED_WARNINGS.lock().unwrap();
if !emitted_warnings.contains(op_name) { if !emitted_warnings.contains(op_name) {
// First time we are seeing this. // First time we are seeing this.
emitted_warnings.insert(op_name.to_owned()); emitted_warnings.insert(op_name.to_owned());
@ -662,6 +647,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
.dcx() .dcx()
.warn(format!("{op_name} was made to return an error due to isolation")); .warn(format!("{op_name} was made to return an error due to isolation"));
} }
interp_ok(()) interp_ok(())
} }
RejectOpWith::Warning => { RejectOpWith::Warning => {

View file

@ -3,7 +3,7 @@
use std::any::Any; use std::any::Any;
use std::borrow::Cow; use std::borrow::Cow;
use std::cell::RefCell; use std::cell::{Cell, RefCell};
use std::collections::hash_map::Entry; use std::collections::hash_map::Entry;
use std::path::Path; use std::path::Path;
use std::{fmt, process}; use std::{fmt, process};
@ -595,6 +595,21 @@ pub struct MiriMachine<'tcx> {
/// A cache of "data range" computations for unions (i.e., the offsets of non-padding bytes). /// A cache of "data range" computations for unions (i.e., the offsets of non-padding bytes).
union_data_ranges: FxHashMap<Ty<'tcx>, RangeSet>, union_data_ranges: FxHashMap<Ty<'tcx>, RangeSet>,
/// Caches the sanity-checks for various pthread primitives.
pub(crate) pthread_mutex_sanity: Cell<bool>,
pub(crate) pthread_rwlock_sanity: Cell<bool>,
pub(crate) pthread_condvar_sanity: Cell<bool>,
/// Remembers whether we already warned about an extern type with Stacked Borrows.
pub(crate) sb_extern_type_warned: Cell<bool>,
/// Remember whether we already warned about sharing memory with a native call.
#[cfg(unix)]
pub(crate) native_call_mem_warned: Cell<bool>,
/// Remembers which shims have already shown the warning about erroring in isolation.
pub(crate) reject_in_isolation_warned: RefCell<FxHashSet<String>>,
/// Remembers which int2ptr casts we have already warned about.
pub(crate) int2ptr_warned: RefCell<FxHashSet<Span>>,
} }
impl<'tcx> MiriMachine<'tcx> { impl<'tcx> MiriMachine<'tcx> {
@ -732,6 +747,14 @@ impl<'tcx> MiriMachine<'tcx> {
const_cache: RefCell::new(FxHashMap::default()), const_cache: RefCell::new(FxHashMap::default()),
symbolic_alignment: RefCell::new(FxHashMap::default()), symbolic_alignment: RefCell::new(FxHashMap::default()),
union_data_ranges: FxHashMap::default(), union_data_ranges: FxHashMap::default(),
pthread_mutex_sanity: Cell::new(false),
pthread_rwlock_sanity: Cell::new(false),
pthread_condvar_sanity: Cell::new(false),
sb_extern_type_warned: Cell::new(false),
#[cfg(unix)]
native_call_mem_warned: Cell::new(false),
reject_in_isolation_warned: Default::default(),
int2ptr_warned: Default::default(),
} }
} }
@ -844,6 +867,14 @@ impl VisitProvenance for MiriMachine<'_> {
const_cache: _, const_cache: _,
symbolic_alignment: _, symbolic_alignment: _,
union_data_ranges: _, union_data_ranges: _,
pthread_mutex_sanity: _,
pthread_rwlock_sanity: _,
pthread_condvar_sanity: _,
sb_extern_type_warned: _,
#[cfg(unix)]
native_call_mem_warned: _,
reject_in_isolation_warned: _,
int2ptr_warned: _,
} = self; } = self;
threads.visit_provenance(visit); threads.visit_provenance(visit);

View file

@ -428,7 +428,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
"exit" => { "exit" => {
let [code] = this.check_shim(abi, Conv::C, link_name, args)?; let [code] = this.check_shim(abi, Conv::C, link_name, args)?;
let code = this.read_scalar(code)?.to_i32()?; let code = this.read_scalar(code)?.to_i32()?;
throw_machine_stop!(TerminationInfo::Exit { code: code.into(), leak_check: false }); throw_machine_stop!(TerminationInfo::Exit { code, leak_check: false });
} }
"abort" => { "abort" => {
let [] = this.check_shim(abi, Conv::C, link_name, args)?; let [] = this.check_shim(abi, Conv::C, link_name, args)?;

View file

@ -1,5 +1,4 @@
//! Implements calling functions from a native library. //! Implements calling functions from a native library.
use std::cell::RefCell;
use std::ops::Deref; use std::ops::Deref;
use libffi::high::call as ffi; use libffi::high::call as ffi;
@ -173,17 +172,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// Wildcard pointer, whatever it points to must be already exposed. // Wildcard pointer, whatever it points to must be already exposed.
continue; continue;
}; };
// The first time this happens at a particular location, print a warning. // The first time this happens, print a warning.
thread_local! { if !this.machine.native_call_mem_warned.replace(true) {
static HAVE_WARNED: RefCell<bool> = const { RefCell::new(false) }; // Newly set, so first time we get here.
this.emit_diagnostic(NonHaltingDiagnostic::NativeCallSharedMem);
} }
HAVE_WARNED.with_borrow_mut(|have_warned| {
if !*have_warned {
// Newly inserted, so first time we see this span.
this.emit_diagnostic(NonHaltingDiagnostic::NativeCallSharedMem);
*have_warned = true;
}
});
this.prepare_for_native_call(alloc_id, prov)?; this.prepare_for_native_call(alloc_id, prov)?;
} }

View file

@ -1,5 +1,3 @@
use std::sync::atomic::{AtomicBool, Ordering};
use rustc_abi::Size; use rustc_abi::Size;
use crate::concurrency::sync::LAZY_INIT_COOKIE; use crate::concurrency::sync::LAZY_INIT_COOKIE;
@ -136,8 +134,7 @@ fn mutex_init_offset<'tcx>(ecx: &MiriInterpCx<'tcx>) -> InterpResult<'tcx, Size>
// Sanity-check this against PTHREAD_MUTEX_INITIALIZER (but only once): // Sanity-check this against PTHREAD_MUTEX_INITIALIZER (but only once):
// the `init` field must start out not equal to INIT_COOKIE. // the `init` field must start out not equal to INIT_COOKIE.
static SANITY: AtomicBool = AtomicBool::new(false); if !ecx.machine.pthread_mutex_sanity.replace(true) {
if !SANITY.swap(true, Ordering::Relaxed) {
let check_static_initializer = |name| { let check_static_initializer = |name| {
let static_initializer = ecx.eval_path(&["libc", name]); let static_initializer = ecx.eval_path(&["libc", name]);
let init_field = let init_field =
@ -248,8 +245,7 @@ fn rwlock_init_offset<'tcx>(ecx: &MiriInterpCx<'tcx>) -> InterpResult<'tcx, Size
// Sanity-check this against PTHREAD_RWLOCK_INITIALIZER (but only once): // Sanity-check this against PTHREAD_RWLOCK_INITIALIZER (but only once):
// the `init` field must start out not equal to LAZY_INIT_COOKIE. // the `init` field must start out not equal to LAZY_INIT_COOKIE.
static SANITY: AtomicBool = AtomicBool::new(false); if !ecx.machine.pthread_rwlock_sanity.replace(true) {
if !SANITY.swap(true, Ordering::Relaxed) {
let static_initializer = ecx.eval_path(&["libc", "PTHREAD_RWLOCK_INITIALIZER"]); let static_initializer = ecx.eval_path(&["libc", "PTHREAD_RWLOCK_INITIALIZER"]);
let init_field = static_initializer.offset(offset, ecx.machine.layouts.u32, ecx).unwrap(); let init_field = static_initializer.offset(offset, ecx.machine.layouts.u32, ecx).unwrap();
let init = ecx.read_scalar(&init_field).unwrap().to_u32().unwrap(); let init = ecx.read_scalar(&init_field).unwrap().to_u32().unwrap();
@ -357,8 +353,7 @@ fn cond_init_offset<'tcx>(ecx: &MiriInterpCx<'tcx>) -> InterpResult<'tcx, Size>
// Sanity-check this against PTHREAD_COND_INITIALIZER (but only once): // Sanity-check this against PTHREAD_COND_INITIALIZER (but only once):
// the `init` field must start out not equal to LAZY_INIT_COOKIE. // the `init` field must start out not equal to LAZY_INIT_COOKIE.
static SANITY: AtomicBool = AtomicBool::new(false); if !ecx.machine.pthread_condvar_sanity.replace(true) {
if !SANITY.swap(true, Ordering::Relaxed) {
let static_initializer = ecx.eval_path(&["libc", "PTHREAD_COND_INITIALIZER"]); let static_initializer = ecx.eval_path(&["libc", "PTHREAD_COND_INITIALIZER"]);
let init_field = static_initializer.offset(offset, ecx.machine.layouts.u32, ecx).unwrap(); let init_field = static_initializer.offset(offset, ecx.machine.layouts.u32, ecx).unwrap();
let init = ecx.read_scalar(&init_field).unwrap().to_u32().unwrap(); let init = ecx.read_scalar(&init_field).unwrap().to_u32().unwrap();

View file

@ -552,8 +552,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// Miscellaneous // Miscellaneous
"ExitProcess" => { "ExitProcess" => {
let [code] = this.check_shim(abi, sys_conv, link_name, args)?; let [code] = this.check_shim(abi, sys_conv, link_name, args)?;
let code = this.read_scalar(code)?.to_u32()?; // Windows technically uses u32, but we unify everything to a Unix-style i32.
throw_machine_stop!(TerminationInfo::Exit { code: code.into(), leak_check: false }); let code = this.read_scalar(code)?.to_i32()?;
throw_machine_stop!(TerminationInfo::Exit { code, leak_check: false });
} }
"SystemFunction036" => { "SystemFunction036" => {
// used by getrandom 0.1 // used by getrandom 0.1

View file

@ -40,7 +40,7 @@ fn static_atomic_bool(val: bool) -> &'static AtomicBool {
Box::leak(Box::new(AtomicBool::new(val))) Box::leak(Box::new(AtomicBool::new(val)))
} }
// Spins until it acquires a pre-determined value. /// Spins until it acquires a pre-determined value.
fn loads_value(loc: &AtomicI32, ord: Ordering, val: i32) -> i32 { fn loads_value(loc: &AtomicI32, ord: Ordering, val: i32) -> i32 {
while loc.load(ord) != val { while loc.load(ord) != val {
std::hint::spin_loop(); std::hint::spin_loop();
@ -186,31 +186,6 @@ fn test_mixed_access() {
assert_eq!(r2, 2); 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() { fn test_single_thread() {
let x = AtomicI32::new(42); let x = AtomicI32::new(42);
@ -257,178 +232,6 @@ fn test_sync_through_rmw_and_fences() {
assert_ne!((a, b), (0, 0)); 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);
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);
}
// Similar to `test_iriw_sc_rlx` but with fences instead of SC accesses.
fn test_cpp20_sc_fence_fix() {
let x = static_atomic_bool(false);
let y = static_atomic_bool(false);
let thread1 = spawn(|| {
let a = x.load(Relaxed);
fence(SeqCst);
let b = y.load(Relaxed);
(a, b)
});
let thread2 = spawn(|| {
x.store(true, Relaxed);
});
let thread3 = spawn(|| {
y.store(true, Relaxed);
});
let thread4 = spawn(|| {
let c = y.load(Relaxed);
fence(SeqCst);
let d = x.load(Relaxed);
(c, d)
});
let (a, b) = thread1.join().unwrap();
thread2.join().unwrap();
thread3.join().unwrap();
let (c, d) = thread4.join().unwrap();
let bad = a == true && b == false && c == true && d == false;
assert!(!bad);
}
// https://plv.mpi-sws.org/scfix/paper.pdf
// 2.2 Second Problem: SC Fences are Too Weak
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 || {
loads_value(&x, Relaxed, 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();
assert!((b, c) != (0, 0));
}
/// This checks that the *last* thing the SC fence does is act like a release fence.
/// See <https://github.com/rust-lang/miri/pull/4057#issuecomment-2522296601>.
fn test_sc_fence_release() {
let x = static_atomic(0);
let y = static_atomic(0);
let z = static_atomic(0);
let k = static_atomic(0);
let j1 = spawn(move || {
x.store(1, Relaxed);
fence(SeqCst);
k.store(1, Relaxed);
});
let j2 = spawn(move || {
y.store(1, Relaxed);
fence(SeqCst);
z.store(1, Relaxed);
});
let j3 = spawn(move || {
let kval = k.load(Acquire);
let yval = y.load(Relaxed);
(kval, yval)
});
let j4 = spawn(move || {
let zval = z.load(Acquire);
let xval = x.load(Relaxed);
(zval, xval)
});
j1.join().unwrap();
j2.join().unwrap();
let (kval, yval) = j3.join().unwrap();
let (zval, xval) = j4.join().unwrap();
let bad = kval == 1 && yval == 0 && zval == 1 && xval == 0;
assert!(!bad);
}
/// Test that SC fences and accesses sync correctly with each other.
fn test_sc_fence_access() {
/*
Wx1 sc
Ry0 sc
||
Wy1 rlx
SC-fence
Rx0 rlx
*/
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, Relaxed);
fence(SeqCst);
x.load(Relaxed)
});
let v1 = j1.join().unwrap();
let v2 = j2.join().unwrap();
let bad = v1 == 0 && v2 == 0;
assert!(!bad);
}
pub fn main() { pub fn main() {
for _ in 0..50 { for _ in 0..50 {
test_single_thread(); test_single_thread();
@ -437,12 +240,6 @@ pub fn main() {
test_message_passing(); test_message_passing();
test_wrc(); test_wrc();
test_corr(); test_corr();
test_sc_store_buffering();
test_sync_through_rmw_and_fences(); test_sync_through_rmw_and_fences();
test_iriw_sc_rlx();
test_cpp20_sc_fence_fix();
test_cpp20_rwc_syncs();
test_sc_fence_release();
test_sc_fence_access();
} }
} }

View file

@ -0,0 +1,354 @@
//@compile-flags: -Zmiri-ignore-leaks -Zmiri-disable-stacked-borrows -Zmiri-disable-validation -Zmiri-provenance-gc=10000
// This test's runtime explodes if the GC interval is set to 1 (which we do in CI), so we
// override it internally back to the default frequency.
// The following tests check whether our weak memory emulation produces
// any inconsistent execution outcomes
// This file here focuses on SC accesses and fences.
use std::sync::atomic::Ordering::*;
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering, fence};
use std::thread::spawn;
// We can't create static items because we need to run each test
// multiple times
fn static_atomic(val: i32) -> &'static AtomicI32 {
Box::leak(Box::new(AtomicI32::new(val)))
}
fn static_atomic_bool(val: bool) -> &'static AtomicBool {
Box::leak(Box::new(AtomicBool::new(val)))
}
/// Spins until it acquires a pre-determined value.
fn loads_value(loc: &AtomicI32, ord: Ordering, val: i32) -> i32 {
while loc.load(ord) != val {
std::hint::spin_loop();
}
val
}
// Test case SB taken from Repairing Sequential Consistency in C/C++11
// by Lahav et al.
// https://plv.mpi-sws.org/scfix/paper.pdf
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));
}
// 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);
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);
}
// Similar to `test_iriw_sc_rlx` but with fences instead of SC accesses.
fn test_cpp20_sc_fence_fix() {
let x = static_atomic_bool(false);
let y = static_atomic_bool(false);
let thread1 = spawn(|| {
let a = x.load(Relaxed);
fence(SeqCst);
let b = y.load(Relaxed);
(a, b)
});
let thread2 = spawn(|| {
x.store(true, Relaxed);
});
let thread3 = spawn(|| {
y.store(true, Relaxed);
});
let thread4 = spawn(|| {
let c = y.load(Relaxed);
fence(SeqCst);
let d = x.load(Relaxed);
(c, d)
});
let (a, b) = thread1.join().unwrap();
thread2.join().unwrap();
thread3.join().unwrap();
let (c, d) = thread4.join().unwrap();
let bad = a == true && b == false && c == true && d == false;
assert!(!bad);
}
// https://plv.mpi-sws.org/scfix/paper.pdf
// 2.2 Second Problem: SC Fences are Too Weak
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 || {
loads_value(&x, Relaxed, 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();
assert!((b, c) != (0, 0));
}
/// This checks that the *last* thing the SC fence does is act like a release fence.
/// See <https://github.com/rust-lang/miri/pull/4057#issuecomment-2522296601>.
/// Test by Ori Lahav.
fn test_sc_fence_release() {
let x = static_atomic(0);
let y = static_atomic(0);
let z = static_atomic(0);
let k = static_atomic(0);
let j1 = spawn(move || {
x.store(1, Relaxed);
fence(SeqCst);
k.store(1, Relaxed);
});
let j2 = spawn(move || {
y.store(1, Relaxed);
fence(SeqCst);
z.store(1, Relaxed);
});
let j3 = spawn(move || {
let kval = k.load(Acquire); // bad case: loads 1
let yval = y.load(Relaxed); // bad case: loads 0
(kval, yval)
});
let j4 = spawn(move || {
let zval = z.load(Acquire); // bad case: loads 1
let xval = x.load(Relaxed); // bad case: loads 0
(zval, xval)
});
j1.join().unwrap();
j2.join().unwrap();
let (kval, yval) = j3.join().unwrap();
let (zval, xval) = j4.join().unwrap();
let bad = kval == 1 && yval == 0 && zval == 1 && xval == 0;
assert!(!bad);
}
/// Test that SC fences and accesses sync correctly with each other.
/// Test by Ori Lahav.
fn test_sc_fence_access() {
/*
Wx1 sc
Ry0 sc
||
Wy1 rlx
SC-fence
Rx0 rlx
*/
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, Relaxed);
fence(SeqCst);
// If this sees a 0, the fence must have been *before* the x.store(1).
x.load(Relaxed)
});
let yval = j1.join().unwrap();
let xval = j2.join().unwrap();
let bad = yval == 0 && xval == 0;
assert!(!bad);
}
/// Test that SC fences and accesses sync correctly with each other
/// when mediated by a release-acquire pair.
/// Test by Ori Lahav (https://github.com/rust-lang/miri/pull/4057#issuecomment-2525268730).
fn test_sc_fence_access_relacq() {
let x = static_atomic(0);
let y = static_atomic(0);
let z = static_atomic(0);
let j1 = spawn(move || {
x.store(1, SeqCst);
y.load(SeqCst) // bad case: loads 0
});
let j2 = spawn(move || {
y.store(1, Relaxed);
z.store(1, Release)
});
let j3 = spawn(move || {
let zval = z.load(Acquire); // bad case: loads 1
// If we see 1 here, the rel-acq pair makes the fence happen after the z.store(1).
fence(SeqCst);
// If this sees a 0, the fence must have been *before* the x.store(1).
let xval = x.load(Relaxed); // bad case: loads 0
(zval, xval)
});
let yval = j1.join().unwrap();
j2.join().unwrap();
let (zval, xval) = j3.join().unwrap();
let bad = yval == 0 && zval == 1 && xval == 0;
assert!(!bad);
}
/// A test that involves multiple SC fences and accesses.
/// Test by Ori Lahav (https://github.com/rust-lang/miri/pull/4057#issuecomment-2525268730).
fn test_sc_multi_fence() {
let x = static_atomic(0);
let y = static_atomic(0);
let z = static_atomic(0);
let j1 = spawn(move || {
x.store(1, SeqCst);
y.load(SeqCst) // bad case: loads 0
});
let j2 = spawn(move || {
y.store(1, Relaxed);
// In the bad case this fence is *after* the j1 y.load, since
// otherwise that load would pick up the 1 we just stored.
fence(SeqCst);
z.load(Relaxed) // bad case: loads 0
});
let j3 = spawn(move || {
z.store(1, Relaxed);
});
let j4 = spawn(move || {
let zval = z.load(Relaxed); // bad case: loads 1
// In the bad case this fence is *after* the one above since
// otherwise, the j2 load of z would load 1.
fence(SeqCst);
// Since that fence is in turn after the j1 y.load, our fence is
// after the j1 x.store, which means we must pick up that store.
let xval = x.load(Relaxed); // bad case: loads 0
(zval, xval)
});
let yval = j1.join().unwrap();
let zval1 = j2.join().unwrap();
j3.join().unwrap();
let (zval2, xval) = j4.join().unwrap();
let bad = yval == 0 && zval1 == 0 && zval2 == 1 && xval == 0;
assert!(!bad);
}
fn test_sc_relaxed() {
/*
y:=1 rlx
Fence sc
a:=x rlx
Fence acq
b:=z rlx // 0
||
z:=1 rlx
x:=1 sc
c:=y sc // 0
*/
let x = static_atomic(0);
let y = static_atomic(0);
let z = static_atomic(0);
let j1 = spawn(move || {
y.store(1, Relaxed);
fence(SeqCst);
// If the relaxed load here is removed, then the "bad" behavior becomes allowed
// by C++20 (and by RC11 / scfix as well).
let _a = x.load(Relaxed);
fence(Acquire);
// If we see 0 here this means in some sense we are "before" the store to z below.
let b = z.load(Relaxed);
b
});
let j2 = spawn(move || {
z.store(1, Relaxed);
x.store(1, SeqCst);
// If we see 0 here, this means in some sense we are "before" the store to y above.
let c = y.load(SeqCst);
c
});
let b = j1.join().unwrap();
let c = j2.join().unwrap();
let bad = b == 0 && c == 0;
assert!(!bad);
}
pub fn main() {
for _ in 0..50 {
test_sc_store_buffering();
test_iriw_sc_rlx();
test_cpp20_sc_fence_fix();
test_cpp20_rwc_syncs();
test_sc_fence_release();
test_sc_fence_access();
test_sc_fence_access_relacq();
test_sc_multi_fence();
test_sc_relaxed();
}
}

View file

@ -1,7 +1,4 @@
//@revisions: stack tree //@compile-flags: -Zmiri-permissive-provenance
// Tree Borrows doesn't support int2ptr casts, but let's make sure we don't immediately crash either.
//@[tree]compile-flags: -Zmiri-tree-borrows
//@[stack]compile-flags: -Zmiri-permissive-provenance
use std::{mem, ptr}; use std::{mem, ptr};
fn eq_ref<T>(x: &T, y: &T) -> bool { fn eq_ref<T>(x: &T, y: &T) -> bool {

View file

@ -1,7 +1,4 @@
//@revisions: stack tree //@compile-flags: -Zmiri-permissive-provenance
// Tree Borrows doesn't support int2ptr casts, but let's make sure we don't immediately crash either.
//@[tree]compile-flags: -Zmiri-tree-borrows
//@[stack]compile-flags: -Zmiri-permissive-provenance
use std::ptr; use std::ptr;