Auto merge of #134784 - RalfJung:miri-sync, r=RalfJung
Miri subtree update r? `@ghost`
This commit is contained in:
commit
e2848a05a9
25 changed files with 865 additions and 716 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>) {
|
||||||
|
|
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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"] }
|
||||||
|
|
|
@ -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<()> {
|
||||||
|
|
|
@ -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>,
|
||||||
},
|
},
|
||||||
|
|
|
@ -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(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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::*;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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 => {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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)?;
|
||||||
|
|
|
@ -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)?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
354
src/tools/miri/tests/pass/0weak_memory_consistency_sc.rs
Normal file
354
src/tools/miri/tests/pass/0weak_memory_consistency_sc.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue