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
|
||||
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`
|
||||
|
||||
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
|
||||
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.
|
||||
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
|
||||
cargo miri test --many-seeds=0..16
|
||||
MIRIFLAGS="-Zmiri-many-seeds" cargo miri test # tries the seeds in 0..64
|
||||
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
|
||||
|
||||
|
@ -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
|
||||
`1.0` will likely cause hangs, since it means programs using
|
||||
`compare_exchange_weak` cannot make progress.
|
||||
* `-Zmiri-disable-isolation` disables host isolation. As a consequence,
|
||||
* `-Zmiri-disable-isolation` disables host isolation. As a consequence,
|
||||
the program has access to host resources such as environment variables, file
|
||||
systems, and randomness.
|
||||
This overwrites a previous `-Zmiri-isolation-error`.
|
||||
* `-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
|
||||
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.
|
||||
`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.
|
||||
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
|
||||
number of available CPUs is `1`. Note that this flag does not affect how miri handles threads in
|
||||
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.
|
||||
* `-Zmiri-strict-provenance` enables [strict
|
||||
provenance](https://github.com/rust-lang/rust/issues/95228) checking in Miri. This means that
|
||||
casting an integer to a pointer yields a result with 'invalid' provenance, i.e., with provenance
|
||||
that cannot be used for any memory access.
|
||||
casting an integer to a pointer will stop execution because the provenance of the pointer
|
||||
cannot be determined.
|
||||
* `-Zmiri-symbolic-alignment-check` makes the alignment check more strict. By default, alignment is
|
||||
checked by casting the pointer to an integer, and making sure that is a multiple of the alignment.
|
||||
This can lead to cases where a program passes the alignment check by pure chance, because things
|
||||
|
@ -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,
|
||||
even if your code is accepted today, it might be declared UB in the future.
|
||||
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.
|
||||
`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
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
//! Implements the various phases of `cargo miri run/test`.
|
||||
|
||||
use std::env;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{BufReader, Write};
|
||||
use std::io::BufReader;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::{env, thread};
|
||||
|
||||
use rustc_version::VersionMeta;
|
||||
|
||||
|
@ -24,10 +24,7 @@ Subcommands:
|
|||
clean Clean the Miri cache & target directory
|
||||
|
||||
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`:
|
||||
|
||||
--many-seeds[=from..to] Run the program/tests many times with different seeds in the given range.
|
||||
The range defaults to `0..64`.
|
||||
Furthermore, the following environment variables are recognized for `run` and `test`:
|
||||
|
||||
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() {
|
||||
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);
|
||||
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
|
||||
// `--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
|
||||
ArgSplitFlagValue::from_string_iter(&mut args, "--target-dir").filter_map(Result::err)
|
||||
{
|
||||
if arg == "--many-seeds" {
|
||||
many_seeds = Some(DEFAULT_MANY_SEEDS.to_owned());
|
||||
} else if let Some(val) = arg.strip_prefix("--many-seeds=") {
|
||||
many_seeds = Some(val.to_owned());
|
||||
if arg == "--many-seeds" || arg.starts_with("--many-seeds=") {
|
||||
show_error!(
|
||||
"ERROR: the `--many-seeds` flag has been removed from cargo-miri; use MIRIFLAGS=-Zmiri-many-seeds instead"
|
||||
);
|
||||
} else {
|
||||
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.
|
||||
cmd.env("MIRI_SYSROOT", miri_sysroot);
|
||||
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 {
|
||||
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.
|
||||
let mut args = env.args;
|
||||
let mut out_filename = None;
|
||||
for i in 0..args.len() {
|
||||
if args[i] == "-o" {
|
||||
out_filename = Some(args[i + 1].clone());
|
||||
args[i + 1].push_str(".miri");
|
||||
}
|
||||
}
|
||||
let out_filename = out_filename.expect("rustdoc must pass `-o`");
|
||||
|
||||
cmd.args(&args);
|
||||
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:?}");
|
||||
}
|
||||
|
||||
exec_with_pipe(cmd, &env.stdin, format!("{out_filename}.stdin"));
|
||||
exec_with_pipe(cmd, &env.stdin);
|
||||
}
|
||||
|
||||
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");
|
||||
run_many_seeds(many_seeds.ok(), |seed| {
|
||||
let mut cmd = miri();
|
||||
let mut cmd = miri();
|
||||
|
||||
// Set missing env vars. We prefer build-time env vars over run-time ones; see
|
||||
// <https://github.com/rust-lang/miri/issues/1661> for the kind of issue that fixes.
|
||||
for (name, val) in &info.env {
|
||||
// `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
|
||||
// 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>.
|
||||
if name == "CARGO_MAKEFLAGS" {
|
||||
// Set missing env vars. We prefer build-time env vars over run-time ones; see
|
||||
// <https://github.com/rust-lang/miri/issues/1661> for the kind of issue that fixes.
|
||||
for (name, val) in &info.env {
|
||||
// `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
|
||||
// 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>.
|
||||
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;
|
||||
}
|
||||
if let Some(old_val) = env::var_os(name) {
|
||||
if *old_val == *val {
|
||||
// This one did not actually change, no need to re-set it.
|
||||
// (This keeps the `debug_cmd` below more manageable.)
|
||||
continue;
|
||||
} else if verbose > 0 {
|
||||
eprintln!(
|
||||
"[cargo-miri runner] Overwriting run-time env var {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);
|
||||
} else if verbose > 0 {
|
||||
eprintln!(
|
||||
"[cargo-miri runner] Overwriting run-time env var {name:?}={old_val:?} with build-time value {val:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
// Respect `MIRIFLAGS`.
|
||||
if let Ok(a) = env::var("MIRIFLAGS") {
|
||||
let args = flagsplit(&a);
|
||||
cmd.args(args);
|
||||
}
|
||||
// Set the current seed.
|
||||
if let Some(seed) = seed {
|
||||
eprintln!("Trying seed: {seed}");
|
||||
cmd.arg(format!("-Zmiri-seed={seed}"));
|
||||
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`.
|
||||
if let Ok(a) = env::var("MIRIFLAGS") {
|
||||
let args = flagsplit(&a);
|
||||
cmd.args(args);
|
||||
}
|
||||
|
||||
// Then pass binary arguments.
|
||||
cmd.arg("--");
|
||||
cmd.args(&binary_args);
|
||||
// Then pass binary arguments.
|
||||
cmd.arg("--");
|
||||
cmd.args(&binary_args);
|
||||
|
||||
// Make sure we use the build-time working directory for interpreting Miri/rustc arguments.
|
||||
// But then we need to switch to the run-time one, which we instruct Miri to do by setting `MIRI_CWD`.
|
||||
cmd.current_dir(&info.current_dir);
|
||||
cmd.env("MIRI_CWD", env::current_dir().unwrap());
|
||||
// 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`.
|
||||
cmd.current_dir(&info.current_dir);
|
||||
cmd.env("MIRI_CWD", env::current_dir().unwrap());
|
||||
|
||||
// Run it.
|
||||
debug_cmd("[cargo-miri runner]", verbose, &cmd);
|
||||
// Run it.
|
||||
debug_cmd("[cargo-miri runner]", verbose, &cmd);
|
||||
|
||||
match phase {
|
||||
RunnerPhase::Rustdoc => {
|
||||
cmd.stdin(std::process::Stdio::piped());
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
match phase {
|
||||
RunnerPhase::Rustdoc => exec_with_pipe(cmd, &info.stdin),
|
||||
RunnerPhase::Cargo => exec(cmd),
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
/// described by the `Command`. Then exit this process with the exit code of the new process.
|
||||
/// `input` is also piped to the new process's stdin, on cfg(unix) platforms by writing its
|
||||
/// contents to `path` first, then setting stdin to that file.
|
||||
pub fn exec_with_pipe<P>(mut cmd: Command, input: &[u8], path: P) -> !
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
#[cfg(unix)]
|
||||
{
|
||||
// Write the bytes we want to send to stdin out to a file
|
||||
std::fs::write(&path, input).unwrap();
|
||||
// Open the file for reading, and set our new stdin to it
|
||||
let stdin = File::open(&path).unwrap();
|
||||
cmd.stdin(stdin);
|
||||
// Unlink the file so that it is fully cleaned up as soon as the new process exits
|
||||
std::fs::remove_file(&path).unwrap();
|
||||
// Finally, we can hand off control.
|
||||
exec(cmd)
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
drop(path); // We don't need the path, we can pipe the bytes directly
|
||||
cmd.stdin(std::process::Stdio::piped());
|
||||
let mut child = cmd.spawn().expect("failed to spawn process");
|
||||
let 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")
|
||||
/// Execute the `Command`, then exit this process with the exit code of the new process.
|
||||
/// `input` is also piped to the new process's stdin.
|
||||
pub fn exec_with_pipe(mut cmd: Command, input: &[u8]) -> ! {
|
||||
// We can't use `exec` since then the background thread will stop running.
|
||||
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);
|
||||
});
|
||||
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) {
|
||||
|
@ -319,24 +299,3 @@ pub fn clean_target_dir(meta: &Metadata) {
|
|||
|
||||
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}
|
||||
fi
|
||||
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
|
||||
./miri run "--many-seeds=0..$MANY_SEEDS" $TARGET_FLAG "$FILE"
|
||||
./miri run "-Zmiri-many-seeds=0..$MANY_SEEDS" $TARGET_FLAG "$FILE"
|
||||
done
|
||||
fi
|
||||
if [ -n "${TEST_BENCH-}" ]; then
|
||||
|
|
|
@ -250,6 +250,8 @@ dependencies = [
|
|||
"itertools",
|
||||
"path_macro",
|
||||
"rustc_version",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"shell-words",
|
||||
"tempfile",
|
||||
|
|
|
@ -23,6 +23,8 @@ xshell = "0.2.6"
|
|||
rustc_version = "0.4"
|
||||
dunce = "1.0.4"
|
||||
directories = "5"
|
||||
serde = "1"
|
||||
serde_json = "1"
|
||||
serde_derive = "1"
|
||||
tempfile = "3.13.0"
|
||||
clap = { version = "4.5.21", features = ["derive"] }
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
use std::collections::HashMap;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::io::Write;
|
||||
use std::ops::{Not, Range};
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, BufWriter, Write};
|
||||
use std::ops::Not;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use std::{env, net, process};
|
||||
|
||||
use anyhow::{Context, Result, anyhow, bail};
|
||||
use path_macro::path;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tempfile::TempDir;
|
||||
use walkdir::WalkDir;
|
||||
use xshell::{Shell, cmd};
|
||||
|
||||
|
@ -174,13 +178,13 @@ impl Command {
|
|||
Command::Check { flags } => Self::check(flags),
|
||||
Command::Test { bless, flags, target, coverage } =>
|
||||
Self::test(bless, flags, target, coverage),
|
||||
Command::Run { dep, verbose, many_seeds, target, edition, flags } =>
|
||||
Self::run(dep, verbose, many_seeds, target, edition, flags),
|
||||
Command::Run { dep, verbose, target, edition, flags } =>
|
||||
Self::run(dep, verbose, target, edition, flags),
|
||||
Command::Doc { flags } => Self::doc(flags),
|
||||
Command::Fmt { flags } => Self::fmt(flags),
|
||||
Command::Clippy { flags } => Self::clippy(flags),
|
||||
Command::Bench { target, no_install, benches } =>
|
||||
Self::bench(target, no_install, benches),
|
||||
Command::Bench { target, no_install, save_baseline, load_baseline, benches } =>
|
||||
Self::bench(target, no_install, save_baseline, load_baseline, benches),
|
||||
Command::Toolchain { flags } => Self::toolchain(flags),
|
||||
Command::RustcPull { commit } => Self::rustc_pull(commit.clone()),
|
||||
Command::RustcPush { github_user, branch } => Self::rustc_push(github_user, branch),
|
||||
|
@ -379,7 +383,17 @@ impl Command {
|
|||
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
|
||||
let hyperfine = env::var("HYPERFINE");
|
||||
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 {
|
||||
bail!("expected HYPERFINE environment variable to be non-empty");
|
||||
};
|
||||
|
||||
if !no_install {
|
||||
// Make sure we have an up-to-date Miri installed and selected the right toolchain.
|
||||
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()?;
|
||||
sh.change_dir(miri_dir()?);
|
||||
sh.change_dir(&miri_dir);
|
||||
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)?
|
||||
.into_iter()
|
||||
.filter(|path| path.is_dir())
|
||||
.map(Into::into)
|
||||
.map(|path| path.into_os_string().into_string().unwrap())
|
||||
.collect()
|
||||
} else {
|
||||
benches.into_iter().map(Into::into).collect()
|
||||
|
@ -414,16 +435,75 @@ impl Command {
|
|||
let target_flag = &target_flag;
|
||||
let toolchain = active_toolchain()?;
|
||||
// Run the requested benchmarks
|
||||
for bench in benches {
|
||||
for bench in &benches {
|
||||
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.
|
||||
// That seems to make Windows CI happy.
|
||||
cmd!(
|
||||
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()?;
|
||||
}
|
||||
|
||||
// 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(())
|
||||
}
|
||||
|
||||
|
@ -506,7 +586,6 @@ impl Command {
|
|||
fn run(
|
||||
dep: bool,
|
||||
verbose: bool,
|
||||
many_seeds: Option<Range<u32>>,
|
||||
target: Option<String>,
|
||||
edition: Option<String>,
|
||||
flags: Vec<String>,
|
||||
|
@ -534,48 +613,34 @@ impl Command {
|
|||
early_flags.push("--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 = flagsplit(&miri_flags);
|
||||
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.
|
||||
let run_miri = |e: &MiriEnv, seed_flag: Option<String>| -> Result<()> {
|
||||
// The basic command that executes the Miri driver.
|
||||
let mut cmd = if dep {
|
||||
// We invoke the test suite as that has all the logic for running with dependencies.
|
||||
e.cargo_cmd(".", "test")
|
||||
.args(&["--test", "ui"])
|
||||
.args(quiet_flag)
|
||||
.arg("--")
|
||||
.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}");
|
||||
})
|
||||
})?;
|
||||
|
||||
// Run Miri.
|
||||
// The basic command that executes the Miri driver.
|
||||
let mut cmd = if dep {
|
||||
// We invoke the test suite as that has all the logic for running with dependencies.
|
||||
e.cargo_cmd(".", "test")
|
||||
.args(&["--test", "ui"])
|
||||
.args(quiet_flag)
|
||||
.arg("--")
|
||||
.args(&["--miri-run-dep-mode"])
|
||||
} 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<()> {
|
||||
|
|
|
@ -1,32 +1,12 @@
|
|||
#![allow(clippy::needless_question_mark)]
|
||||
#![allow(clippy::needless_question_mark, rustc::internal)]
|
||||
|
||||
mod commands;
|
||||
mod coverage;
|
||||
mod util;
|
||||
|
||||
use std::ops::Range;
|
||||
|
||||
use anyhow::{Context, Result, anyhow, bail};
|
||||
use anyhow::{Result, bail};
|
||||
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)]
|
||||
pub enum Command {
|
||||
/// Installs the miri driver and cargo-miri to the sysroot of the active toolchain.
|
||||
|
@ -81,9 +61,6 @@ pub enum Command {
|
|||
/// Show build progress.
|
||||
#[arg(long, short)]
|
||||
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.
|
||||
#[arg(long)]
|
||||
target: Option<String>,
|
||||
|
@ -117,6 +94,14 @@ pub enum Command {
|
|||
/// When `true`, skip the `./miri install` step.
|
||||
#[arg(long)]
|
||||
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).
|
||||
benches: Vec<String>,
|
||||
},
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
use std::ffi::{OsStr, OsString};
|
||||
use std::io::BufRead;
|
||||
use std::ops::Range;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
||||
use std::{env, iter, thread};
|
||||
use std::{env, iter};
|
||||
|
||||
use anyhow::{Context, Result, anyhow, bail};
|
||||
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.
|
||||
#[derive(Clone)]
|
||||
pub struct MiriEnv {
|
||||
/// miri_dir is the root of the miri repository checkout we are working in.
|
||||
pub miri_dir: PathBuf,
|
||||
|
@ -240,53 +237,4 @@ impl MiriEnv {
|
|||
|
||||
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 rustc_abi::{Align, Size};
|
||||
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
|
||||
use rustc_span::Span;
|
||||
|
||||
use self::reuse_pool::ReusePool;
|
||||
use crate::concurrency::VClock;
|
||||
|
@ -319,17 +318,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
match global_state.provenance_mode {
|
||||
ProvenanceMode::Default => {
|
||||
// The first time this happens at a particular location, print a warning.
|
||||
thread_local! {
|
||||
// `Span` is non-`Send`, so we use a thread-local instead.
|
||||
static PAST_WARNINGS: RefCell<FxHashSet<Span>> = RefCell::default();
|
||||
let mut int2ptr_warned = this.machine.int2ptr_warned.borrow_mut();
|
||||
let first = int2ptr_warned.is_empty();
|
||||
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 => {
|
||||
throw_machine_stop!(TerminationInfo::Int2PtrWithStrictProvenance);
|
||||
|
|
|
@ -26,11 +26,17 @@ extern crate rustc_span;
|
|||
|
||||
use std::env::{self, VarError};
|
||||
use std::num::NonZero;
|
||||
use std::ops::Range;
|
||||
use std::path::PathBuf;
|
||||
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_data_structures::sync;
|
||||
use rustc_data_structures::sync::Lrc;
|
||||
use rustc_driver::Compilation;
|
||||
use rustc_hir::def_id::LOCAL_CRATE;
|
||||
|
@ -52,7 +58,69 @@ use rustc_span::def_id::DefId;
|
|||
use tracing::debug;
|
||||
|
||||
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 {
|
||||
|
@ -87,7 +155,7 @@ impl rustc_driver::Callbacks for MiriCompilerCalls {
|
|||
}
|
||||
|
||||
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.
|
||||
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.");
|
||||
}
|
||||
|
||||
if let Some(return_code) = miri::eval_entry(tcx, entry_def_id, entry_type, config) {
|
||||
std::process::exit(i32::try_from(return_code).expect("Return value was too large!"));
|
||||
if let Some(many_seeds) = self.many_seeds.take() {
|
||||
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
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
// 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
|
||||
// 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() {
|
||||
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<'_>) {
|
||||
// If `RUSTC_LOG` is not set, then `init_early_loggers` did not call
|
||||
// `rustc_driver::init_logger`, so we have to do this now.
|
||||
if env::var_os("RUSTC_LOG").is_none() {
|
||||
// If the logger is not yet initialized, initialize it.
|
||||
LOGGER_INITED.call_once(|| {
|
||||
rustc_driver::init_logger(early_dcx, rustc_logger_config());
|
||||
}
|
||||
});
|
||||
|
||||
// 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.
|
||||
|
@ -270,25 +367,14 @@ fn init_late_loggers(early_dcx: &EarlyDiagCtxt, tcx: TyCtxt<'_>) {
|
|||
}
|
||||
|
||||
/// Execute a compiler with the given CLI arguments and callbacks.
|
||||
fn run_compiler(
|
||||
mut args: Vec<String>,
|
||||
target_crate: bool,
|
||||
fn run_compiler_and_exit(
|
||||
args: &[String],
|
||||
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.
|
||||
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)
|
||||
.run();
|
||||
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"))]
|
||||
fn jemalloc_magic() {
|
||||
// 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() {
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
jemalloc_magic();
|
||||
|
@ -431,10 +479,21 @@ fn main() {
|
|||
panic!("invalid `MIRI_BE_RUSTC` value: {crate_kind:?}")
|
||||
};
|
||||
|
||||
// We cannot use `rustc_driver::main` as we need to adjust the CLI arguments.
|
||||
run_compiler(
|
||||
args,
|
||||
target_crate,
|
||||
let mut args = args;
|
||||
// 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 {
|
||||
// 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 },
|
||||
using_internal_features,
|
||||
)
|
||||
|
@ -448,13 +507,13 @@ fn main() {
|
|||
init_early_loggers(&early_dcx);
|
||||
|
||||
// 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;
|
||||
|
||||
let mut rustc_args = vec![];
|
||||
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.
|
||||
// This matches how rustc parses `-Z`.
|
||||
|
@ -463,6 +522,8 @@ fn main() {
|
|||
if rustc_args.is_empty() {
|
||||
// Very first arg: binary name.
|
||||
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 {
|
||||
// Everything that comes after `--` is forwarded to the interpreted crate.
|
||||
miri_config.args.push(arg);
|
||||
|
@ -476,6 +537,7 @@ fn main() {
|
|||
miri_config.borrow_tracker = None;
|
||||
} else if arg == "-Zmiri-tree-borrows" {
|
||||
miri_config.borrow_tracker = Some(BorrowTrackerMethod::TreeBorrows);
|
||||
miri_config.provenance_mode = ProvenanceMode::Strict;
|
||||
} else if arg == "-Zmiri-unique-is-unique" {
|
||||
miri_config.unique_is_unique = true;
|
||||
} else if arg == "-Zmiri-disable-data-race-detector" {
|
||||
|
@ -485,19 +547,7 @@ fn main() {
|
|||
miri_config.check_alignment = miri::AlignmentCheck::None;
|
||||
} else if arg == "-Zmiri-symbolic-alignment-check" {
|
||||
miri_config.check_alignment = miri::AlignmentCheck::Symbolic;
|
||||
} else if arg == "-Zmiri-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" {
|
||||
if matches!(isolation_enabled, Some(true)) {
|
||||
show_error!(
|
||||
"-Zmiri-disable-isolation cannot be used along with -Zmiri-isolation-error"
|
||||
);
|
||||
} else {
|
||||
isolation_enabled = Some(false);
|
||||
}
|
||||
miri_config.isolated_op = miri::IsolatedOp::Allow;
|
||||
} else if arg == "-Zmiri-disable-leak-backtraces" {
|
||||
miri_config.collect_leak_backtraces = false;
|
||||
|
@ -506,14 +556,6 @@ fn main() {
|
|||
} else if arg == "-Zmiri-track-weak-memory-loads" {
|
||||
miri_config.track_outdated_loads = true;
|
||||
} else if let Some(param) = arg.strip_prefix("-Zmiri-isolation-error=") {
|
||||
if matches!(isolation_enabled, Some(false)) {
|
||||
show_error!(
|
||||
"-Zmiri-isolation-error cannot be used along with -Zmiri-disable-isolation"
|
||||
);
|
||||
} else {
|
||||
isolation_enabled = Some(true);
|
||||
}
|
||||
|
||||
miri_config.isolated_op = match param {
|
||||
"abort" => miri::IsolatedOp::Reject(miri::RejectOpWith::Abort),
|
||||
"hide" => miri::IsolatedOp::Reject(miri::RejectOpWith::NoWarning),
|
||||
|
@ -544,17 +586,21 @@ fn main() {
|
|||
_ => show_error!("`-Zmiri-retag-fields` can only be `all`, `none`, or `scalar`"),
|
||||
};
|
||||
} 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(|_| {
|
||||
show_error!("-Zmiri-seed must be an integer that fits into u64")
|
||||
});
|
||||
miri_config.seed = Some(seed);
|
||||
} else if let Some(_param) = arg.strip_prefix("-Zmiri-env-exclude=") {
|
||||
show_error!(
|
||||
"`-Zmiri-env-exclude` has been removed; unset env vars before starting Miri instead"
|
||||
);
|
||||
} else if let Some(param) = arg.strip_prefix("-Zmiri-many-seeds=") {
|
||||
let range = parse_range(param).unwrap_or_else(|err| {
|
||||
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=") {
|
||||
miri_config.forwarded_env_vars.push(param.to_owned());
|
||||
} 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"
|
||||
);
|
||||
}
|
||||
// Tree Borrows + permissive provenance does not work.
|
||||
if miri_config.provenance_mode == ProvenanceMode::Permissive
|
||||
&& matches!(miri_config.borrow_tracker, Some(BorrowTrackerMethod::TreeBorrows))
|
||||
{
|
||||
show_error!(
|
||||
"Tree Borrows does not support integer-to-pointer casts, and is hence not compatible with permissive provenance"
|
||||
);
|
||||
// Tree Borrows implies strict provenance, and is not compatible with native calls.
|
||||
if matches!(miri_config.borrow_tracker, Some(BorrowTrackerMethod::TreeBorrows)) {
|
||||
if miri_config.provenance_mode != ProvenanceMode::Strict {
|
||||
show_error!(
|
||||
"Tree Borrows does not support integer-to-pointer casts, and hence requires strict 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!("crate arguments: {:?}", miri_config.args);
|
||||
run_compiler(
|
||||
rustc_args,
|
||||
/* target_crate: */ true,
|
||||
&mut MiriCompilerCalls { miri_config },
|
||||
run_compiler_and_exit(
|
||||
&rustc_args,
|
||||
&mut MiriCompilerCalls::new(miri_config, many_seeds),
|
||||
using_internal_features,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ pub mod diagnostics;
|
|||
mod item;
|
||||
mod stack;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::fmt::Write;
|
||||
use std::{cmp, mem};
|
||||
|
||||
|
@ -822,16 +821,9 @@ trait EvalContextPrivExt<'tcx, 'ecx>: crate::MiriInterpCxExt<'tcx> {
|
|||
let size = match size {
|
||||
Some(size) => size,
|
||||
None => {
|
||||
// The first time this happens, show a warning.
|
||||
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;
|
||||
if !this.machine.sb_extern_type_warned.replace(true) {
|
||||
this.emit_diagnostic(NonHaltingDiagnostic::ExternTypeReborrow);
|
||||
});
|
||||
}
|
||||
return interp_ok(place.clone());
|
||||
}
|
||||
};
|
||||
|
|
|
@ -12,7 +12,7 @@ use crate::*;
|
|||
/// Details of premature program termination.
|
||||
pub enum TerminationInfo {
|
||||
Exit {
|
||||
code: i64,
|
||||
code: i32,
|
||||
leak_check: bool,
|
||||
},
|
||||
Abort(String),
|
||||
|
@ -214,7 +214,7 @@ pub fn prune_stacktrace<'tcx>(
|
|||
pub fn report_error<'tcx>(
|
||||
ecx: &InterpCx<'tcx, MiriMachine<'tcx>>,
|
||||
e: InterpErrorInfo<'tcx>,
|
||||
) -> Option<(i64, bool)> {
|
||||
) -> Option<(i32, bool)> {
|
||||
use InterpErrorKind::*;
|
||||
use UndefinedBehaviorInfo::*;
|
||||
|
||||
|
|
|
@ -249,6 +249,13 @@ impl<'tcx> MainThreadState<'tcx> {
|
|||
// Figure out exit code.
|
||||
let ret_place = this.machine.main_fn_ret_place.clone().unwrap();
|
||||
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
|
||||
// to be like a global `static`, so that all memory reached by it is considered to "not leak".
|
||||
this.terminate_active_thread(TlsAllocAction::Leak)?;
|
||||
|
@ -421,7 +428,7 @@ pub fn create_ecx<'tcx>(
|
|||
}
|
||||
|
||||
/// 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.
|
||||
#[expect(clippy::needless_lifetimes)]
|
||||
pub fn eval_entry<'tcx>(
|
||||
|
@ -429,7 +436,7 @@ pub fn eval_entry<'tcx>(
|
|||
entry_id: DefId,
|
||||
entry_type: EntryFnType,
|
||||
config: MiriConfig,
|
||||
) -> Option<i64> {
|
||||
) -> Option<i32> {
|
||||
// Copy setting before we move `config`.
|
||||
let ignore_leaks = config.ignore_leaks;
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
use std::collections::BTreeSet;
|
||||
use std::num::NonZero;
|
||||
use std::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
use std::{cmp, iter};
|
||||
|
||||
|
@ -332,19 +330,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
base: &P,
|
||||
name: &str,
|
||||
) -> InterpResult<'tcx, P> {
|
||||
if let Some(field) = self.try_project_field_named(base, name)? {
|
||||
return interp_ok(field);
|
||||
}
|
||||
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()
|
||||
interp_ok(
|
||||
self.try_project_field_named(base, name)?
|
||||
.unwrap_or_else(|| bug!("no field named {} in type {}", name, base.layout().ty)),
|
||||
)
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
RejectOpWith::Abort => isolation_abort_error(op_name),
|
||||
RejectOpWith::WarningWithoutBacktrace => {
|
||||
// This exists to reduce verbosity; make sure we emit the warning at most once per
|
||||
// operation.
|
||||
static EMITTED_WARNINGS: Mutex<BTreeSet<String>> = Mutex::new(BTreeSet::new());
|
||||
|
||||
let mut emitted_warnings = EMITTED_WARNINGS.lock().unwrap();
|
||||
let mut emitted_warnings = this.machine.reject_in_isolation_warned.borrow_mut();
|
||||
if !emitted_warnings.contains(op_name) {
|
||||
// First time we are seeing this.
|
||||
emitted_warnings.insert(op_name.to_owned());
|
||||
|
@ -662,6 +647,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
.dcx()
|
||||
.warn(format!("{op_name} was made to return an error due to isolation"));
|
||||
}
|
||||
|
||||
interp_ok(())
|
||||
}
|
||||
RejectOpWith::Warning => {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
use std::any::Any;
|
||||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::path::Path;
|
||||
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).
|
||||
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> {
|
||||
|
@ -732,6 +747,14 @@ impl<'tcx> MiriMachine<'tcx> {
|
|||
const_cache: RefCell::new(FxHashMap::default()),
|
||||
symbolic_alignment: RefCell::new(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: _,
|
||||
symbolic_alignment: _,
|
||||
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;
|
||||
|
||||
threads.visit_provenance(visit);
|
||||
|
|
|
@ -428,7 +428,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
"exit" => {
|
||||
let [code] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
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" => {
|
||||
let [] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//! Implements calling functions from a native library.
|
||||
use std::cell::RefCell;
|
||||
use std::ops::Deref;
|
||||
|
||||
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.
|
||||
continue;
|
||||
};
|
||||
// The first time this happens at a particular location, print a warning.
|
||||
thread_local! {
|
||||
static HAVE_WARNED: RefCell<bool> = const { RefCell::new(false) };
|
||||
// The first time this happens, print a warning.
|
||||
if !this.machine.native_call_mem_warned.replace(true) {
|
||||
// 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)?;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use rustc_abi::Size;
|
||||
|
||||
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):
|
||||
// the `init` field must start out not equal to INIT_COOKIE.
|
||||
static SANITY: AtomicBool = AtomicBool::new(false);
|
||||
if !SANITY.swap(true, Ordering::Relaxed) {
|
||||
if !ecx.machine.pthread_mutex_sanity.replace(true) {
|
||||
let check_static_initializer = |name| {
|
||||
let static_initializer = ecx.eval_path(&["libc", name]);
|
||||
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):
|
||||
// the `init` field must start out not equal to LAZY_INIT_COOKIE.
|
||||
static SANITY: AtomicBool = AtomicBool::new(false);
|
||||
if !SANITY.swap(true, Ordering::Relaxed) {
|
||||
if !ecx.machine.pthread_rwlock_sanity.replace(true) {
|
||||
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 = 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):
|
||||
// the `init` field must start out not equal to LAZY_INIT_COOKIE.
|
||||
static SANITY: AtomicBool = AtomicBool::new(false);
|
||||
if !SANITY.swap(true, Ordering::Relaxed) {
|
||||
if !ecx.machine.pthread_condvar_sanity.replace(true) {
|
||||
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 = ecx.read_scalar(&init_field).unwrap().to_u32().unwrap();
|
||||
|
|
|
@ -552,8 +552,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
// Miscellaneous
|
||||
"ExitProcess" => {
|
||||
let [code] = this.check_shim(abi, sys_conv, link_name, args)?;
|
||||
let code = this.read_scalar(code)?.to_u32()?;
|
||||
throw_machine_stop!(TerminationInfo::Exit { code: code.into(), leak_check: false });
|
||||
// Windows technically uses u32, but we unify everything to a Unix-style i32.
|
||||
let code = this.read_scalar(code)?.to_i32()?;
|
||||
throw_machine_stop!(TerminationInfo::Exit { code, leak_check: false });
|
||||
}
|
||||
"SystemFunction036" => {
|
||||
// used by getrandom 0.1
|
||||
|
|
|
@ -40,7 +40,7 @@ fn static_atomic_bool(val: bool) -> &'static AtomicBool {
|
|||
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 {
|
||||
while loc.load(ord) != val {
|
||||
std::hint::spin_loop();
|
||||
|
@ -186,31 +186,6 @@ fn test_mixed_access() {
|
|||
assert_eq!(r2, 2);
|
||||
}
|
||||
|
||||
// The following two tests are taken from Repairing Sequential Consistency in C/C++11
|
||||
// by Lahav et al.
|
||||
// https://plv.mpi-sws.org/scfix/paper.pdf
|
||||
|
||||
// Test case SB
|
||||
fn test_sc_store_buffering() {
|
||||
let x = static_atomic(0);
|
||||
let y = static_atomic(0);
|
||||
|
||||
let j1 = spawn(move || {
|
||||
x.store(1, SeqCst);
|
||||
y.load(SeqCst)
|
||||
});
|
||||
|
||||
let j2 = spawn(move || {
|
||||
y.store(1, SeqCst);
|
||||
x.load(SeqCst)
|
||||
});
|
||||
|
||||
let a = j1.join().unwrap();
|
||||
let b = j2.join().unwrap();
|
||||
|
||||
assert_ne!((a, b), (0, 0));
|
||||
}
|
||||
|
||||
fn test_single_thread() {
|
||||
let x = AtomicI32::new(42);
|
||||
|
||||
|
@ -257,178 +232,6 @@ fn test_sync_through_rmw_and_fences() {
|
|||
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() {
|
||||
for _ in 0..50 {
|
||||
test_single_thread();
|
||||
|
@ -437,12 +240,6 @@ pub fn main() {
|
|||
test_message_passing();
|
||||
test_wrc();
|
||||
test_corr();
|
||||
test_sc_store_buffering();
|
||||
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
|
||||
// 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
|
||||
//@compile-flags: -Zmiri-permissive-provenance
|
||||
use std::{mem, ptr};
|
||||
|
||||
fn eq_ref<T>(x: &T, y: &T) -> bool {
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
//@revisions: stack tree
|
||||
// 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
|
||||
//@compile-flags: -Zmiri-permissive-provenance
|
||||
|
||||
use std::ptr;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue