From 6ce00aa99234c4771abb84ef9a1b46fc818a0d5f Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 4 May 2024 00:03:23 +0200 Subject: [PATCH 1/2] make many-seeds a mode of ./miri run rather than a separate command --- src/tools/miri/ci/ci.sh | 13 ++- src/tools/miri/miri-script/Cargo.lock | 8 +- src/tools/miri/miri-script/Cargo.toml | 2 +- src/tools/miri/miri-script/src/commands.rs | 96 ++++++++++------------ src/tools/miri/miri-script/src/main.rs | 49 ++++++----- src/tools/miri/miri-script/src/util.rs | 53 ++++++++++++ 6 files changed, 134 insertions(+), 87 deletions(-) diff --git a/src/tools/miri/ci/ci.sh b/src/tools/miri/ci/ci.sh index c1ffb80783ef..0db267650dbf 100755 --- a/src/tools/miri/ci/ci.sh +++ b/src/tools/miri/ci/ci.sh @@ -71,10 +71,9 @@ function run_tests { time MIRIFLAGS="${MIRIFLAGS-} -O -Zmir-opt-level=4 -Cdebug-assertions=yes" MIRI_SKIP_UI_CHECKS=1 ./miri test -- tests/{pass,panic} fi if [ -n "${MANY_SEEDS-}" ]; then - # Also run some many-seeds tests. 64 seeds means this takes around a minute per test. - # (Need to invoke via explicit `bash -c` for Windows.) + # Also run some many-seeds tests. time for FILE in tests/many-seeds/*.rs; do - MIRI_SEEDS=$MANY_SEEDS ./miri many-seeds "$BASH" -c "./miri run '$FILE'" + ./miri run "--many-seeds=0..$MANY_SEEDS" "$FILE" done fi if [ -n "${TEST_BENCH-}" ]; then @@ -135,7 +134,7 @@ case $HOST_TARGET in GC_STRESS=1 MIR_OPT=1 MANY_SEEDS=64 TEST_BENCH=1 CARGO_MIRI_ENV=1 run_tests # Extra tier 1 # With reduced many-seed count to avoid spending too much time on that. - # (All OSes are run with 64 seeds at least once though via the macOS runner.) + # (All OSes and ABIs are run with 64 seeds at least once though via the macOS runner.) MANY_SEEDS=16 MIRI_TEST_TARGET=i686-unknown-linux-gnu run_tests MANY_SEEDS=16 MIRI_TEST_TARGET=aarch64-unknown-linux-gnu run_tests MANY_SEEDS=16 MIRI_TEST_TARGET=x86_64-apple-darwin run_tests @@ -164,9 +163,9 @@ case $HOST_TARGET in ;; i686-pc-windows-msvc) # Host - # Only smoke-test `many-seeds`; 64 runs of just the scoped-thread-leak test take 15min here! - # See . - GC_STRESS=1 MIR_OPT=1 MANY_SEEDS=1 TEST_BENCH=1 run_tests + # With reduced many-seeds count as this is the slowest runner already. + # (The macOS runner checks windows-msvc with full many-seeds count.) + GC_STRESS=1 MIR_OPT=1 MANY_SEEDS=16 TEST_BENCH=1 run_tests # Extra tier 1 # We really want to ensure a Linux target works on a Windows host, # and a 64bit target works on a 32bit host. diff --git a/src/tools/miri/miri-script/Cargo.lock b/src/tools/miri/miri-script/Cargo.lock index a6f7467f0a2f..5e792abac175 100644 --- a/src/tools/miri/miri-script/Cargo.lock +++ b/src/tools/miri/miri-script/Cargo.lock @@ -466,15 +466,15 @@ checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" [[package]] name = "xshell" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce2107fe03e558353b4c71ad7626d58ed82efaf56c54134228608893c77023ad" +checksum = "6db0ab86eae739efd1b054a8d3d16041914030ac4e01cd1dca0cf252fd8b6437" dependencies = [ "xshell-macros", ] [[package]] name = "xshell-macros" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e2c411759b501fb9501aac2b1b2d287a6e93e5bdcf13c25306b23e1b716dd0e" +checksum = "9d422e8e38ec76e2f06ee439ccc765e9c6a9638b9e7c9f2e8255e4d41e8bd852" diff --git a/src/tools/miri/miri-script/Cargo.toml b/src/tools/miri/miri-script/Cargo.toml index 79d0b13600d4..631d3a82102b 100644 --- a/src/tools/miri/miri-script/Cargo.toml +++ b/src/tools/miri/miri-script/Cargo.toml @@ -19,7 +19,7 @@ itertools = "0.11" path_macro = "1.0" shell-words = "1.1" anyhow = "1.0" -xshell = "0.2" +xshell = "0.2.6" rustc_version = "0.4" dunce = "1.0.4" directories = "5" diff --git a/src/tools/miri/miri-script/src/commands.rs b/src/tools/miri/miri-script/src/commands.rs index b460c7eba560..7e34ad050b31 100644 --- a/src/tools/miri/miri-script/src/commands.rs +++ b/src/tools/miri/miri-script/src/commands.rs @@ -2,6 +2,7 @@ use std::env; use std::ffi::OsString; use std::io::Write; use std::ops::Not; +use std::ops::Range; use std::path::PathBuf; use std::process; use std::thread; @@ -150,7 +151,6 @@ impl Command { | Command::Fmt { .. } | Command::Clippy { .. } | Command::Cargo { .. } => Self::auto_actions()?, - | Command::ManySeeds { .. } | Command::Toolchain { .. } | Command::Bench { .. } | Command::RustcPull { .. } @@ -162,11 +162,11 @@ impl Command { Command::Build { flags } => Self::build(flags), Command::Check { flags } => Self::check(flags), Command::Test { bless, flags } => Self::test(bless, flags), - Command::Run { dep, verbose, flags } => Self::run(dep, verbose, flags), + Command::Run { dep, verbose, many_seeds, flags } => + Self::run(dep, verbose, many_seeds, flags), Command::Fmt { flags } => Self::fmt(flags), Command::Clippy { flags } => Self::clippy(flags), Command::Cargo { flags } => Self::cargo(flags), - Command::ManySeeds { command } => Self::many_seeds(command), Command::Bench { benches } => Self::bench(benches), Command::Toolchain { flags } => Self::toolchain(flags), Command::RustcPull { commit } => Self::rustc_pull(commit.clone()), @@ -367,43 +367,6 @@ impl Command { Ok(()) } - fn many_seeds(command: Vec) -> Result<()> { - let seed_start: u64 = env::var("MIRI_SEED_START") - .unwrap_or_else(|_| "0".into()) - .parse() - .context("failed to parse MIRI_SEED_START")?; - let seed_end: u64 = match (env::var("MIRI_SEEDS"), env::var("MIRI_SEED_END")) { - (Ok(_), Ok(_)) => bail!("Only one of MIRI_SEEDS and MIRI_SEED_END may be set"), - (Ok(seeds), Err(_)) => - seed_start + seeds.parse::().context("failed to parse MIRI_SEEDS")?, - (Err(_), Ok(seed_end)) => seed_end.parse().context("failed to parse MIRI_SEED_END")?, - (Err(_), Err(_)) => seed_start + 256, - }; - if seed_end <= seed_start { - bail!("the end of the seed range must be larger than the start."); - } - - let Some((command_name, trailing_args)) = command.split_first() else { - bail!("expected many-seeds command to be non-empty"); - }; - let sh = Shell::new()?; - sh.set_var("MIRI_AUTO_OPS", "no"); // just in case we get recursively invoked - for seed in seed_start..seed_end { - println!("Trying seed: {seed}"); - let mut miriflags = env::var_os("MIRIFLAGS").unwrap_or_default(); - miriflags.push(format!(" -Zlayout-seed={seed} -Zmiri-seed={seed}")); - let status = cmd!(sh, "{command_name} {trailing_args...}") - .env("MIRIFLAGS", miriflags) - .quiet() - .run(); - if let Err(err) = status { - println!("Failing seed: {seed}"); - return Err(err.into()); - } - } - Ok(()) - } - fn bench(benches: Vec) -> Result<()> { // The hyperfine to use let hyperfine = env::var("HYPERFINE"); @@ -495,7 +458,12 @@ impl Command { Ok(()) } - fn run(dep: bool, verbose: bool, mut flags: Vec) -> Result<()> { + fn run( + dep: bool, + verbose: bool, + many_seeds: Option>, + mut flags: Vec, + ) -> Result<()> { let mut e = MiriEnv::new()?; // Scan for "--target" to overwrite the "MIRI_TEST_TARGET" env var so // that we set the MIRI_SYSROOT up the right way. We must make sure that @@ -526,26 +494,46 @@ impl Command { flags.push("--sysroot".into()); flags.push(miri_sysroot.into()); - // Then run the actual command. Also add MIRIFLAGS. + // Compute everything needed to run the actual command. Also add MIRIFLAGS. let miri_manifest = path!(e.miri_dir / "Cargo.toml"); let miri_flags = e.sh.var("MIRIFLAGS").unwrap_or_default(); let miri_flags = flagsplit(&miri_flags); let toolchain = &e.toolchain; let extra_flags = &e.cargo_extra_flags; let quiet_flag = if verbose { None } else { Some("--quiet") }; - let mut cmd = if dep { - cmd!( - e.sh, - "cargo +{toolchain} {quiet_flag...} test {extra_flags...} --manifest-path {miri_manifest} --test ui -- --miri-run-dep-mode {miri_flags...} {flags...}" - ) - } else { - cmd!( - e.sh, - "cargo +{toolchain} {quiet_flag...} run {extra_flags...} --manifest-path {miri_manifest} -- {miri_flags...} {flags...}" - ) + // 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 = |sh: &Shell, seed_flag: Option| -> Result<()> { + // The basic command that executes the Miri driver. + let mut cmd = if dep { + cmd!( + sh, + "cargo +{toolchain} {quiet_flag...} test {extra_flags...} --manifest-path {miri_manifest} --test ui -- --miri-run-dep-mode" + ) + } else { + cmd!( + sh, + "cargo +{toolchain} {quiet_flag...} run {extra_flags...} --manifest-path {miri_manifest} --" + ) + }; + cmd.set_quiet(!verbose); + // Add Miri flags + let cmd = cmd.args(&miri_flags).args(seed_flag).args(&flags); + // And run the thing. + Ok(cmd.run()?) }; - cmd.set_quiet(!verbose); - cmd.run()?; + // Run the closure once or many times. + if let Some(seed_range) = many_seeds { + e.run_many_times(seed_range, |sh, seed| { + eprintln!("Trying seed: {seed}"); + run_miri(sh, Some(format!("-Zmiri-seed={seed}"))).map_err(|err| { + eprintln!("FAILING SEED: {seed}"); + err + }) + })?; + } else { + run_miri(&e.sh, None)?; + } Ok(()) } diff --git a/src/tools/miri/miri-script/src/main.rs b/src/tools/miri/miri-script/src/main.rs index 4904744cb9f6..f0ebbc846906 100644 --- a/src/tools/miri/miri-script/src/main.rs +++ b/src/tools/miri/miri-script/src/main.rs @@ -3,10 +3,10 @@ mod commands; mod util; -use std::env; use std::ffi::OsString; +use std::{env, ops::Range}; -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, bail, Context, Result}; #[derive(Clone, Debug)] pub enum Command { @@ -39,6 +39,7 @@ pub enum Command { Run { dep: bool, verbose: bool, + many_seeds: Option>, /// Flags that are passed through to `miri`. flags: Vec, }, @@ -55,10 +56,6 @@ pub enum Command { /// Runs just `cargo ` with the Miri-specific environment variables. /// Mainly meant to be invoked by rust-analyzer. Cargo { flags: Vec }, - /// Runs over and over again with different seeds for Miri. The MIRIFLAGS - /// variable is set to its original value appended with ` -Zmiri-seed=$SEED` for - /// many different seeds. - ManySeeds { command: Vec }, /// Runs the benchmarks from bench-cargo-miri in hyperfine. hyperfine needs to be installed. Bench { /// List of benchmarks to run. By default all benchmarks are run. @@ -91,9 +88,11 @@ Just check miri. are passed to `cargo check`. Build miri, set up a sysroot and then run the test suite. are passed to the final `cargo test` invocation. -./miri run [--dep] [-v|--verbose] : +./miri run [--dep] [-v|--verbose] [--many-seeds|--many-seeds=..to|--many-seeds=from..to] : Build miri, set up a sysroot and then run the driver with the given . (Also respects MIRIFLAGS environment variable.) +If `--many-seeds` is present, Miri is run many times in parallel with different seeds. +The range defaults to `0..256`. ./miri fmt : Format all sources and tests. are passed to `rustfmt`. @@ -111,13 +110,6 @@ install`. Sets up the rpath such that the installed binary should work in any working directory. Note that the binaries are placed in the `miri` toolchain sysroot, to prevent conflicts with other toolchains. -./miri many-seeds : -Runs over and over again with different seeds for Miri. The MIRIFLAGS -variable is set to its original value appended with ` -Zmiri-seed=$SEED` for -many different seeds. MIRI_SEED_START controls the first seed to try (default: 0). -MIRI_SEEDS controls how many seeds are being tried (default: 256); -alternatively, MIRI_SEED_END controls the end of the (exclusive) seed range to try. - ./miri bench : Runs the benchmarks from bench-cargo-miri in hyperfine. hyperfine needs to be installed. can explicitly list the benchmarks to run; by default, all of them are run. @@ -165,22 +157,37 @@ fn main() -> Result<()> { Some("run") => { let mut dep = false; let mut verbose = false; - while let Some(arg) = args.peek().and_then(|a| a.to_str()) { - match arg { - "--dep" => dep = true, - "-v" | "--verbose" => verbose = true, - _ => break, // not for us + let mut many_seeds = None; + while let Some(arg) = args.peek().and_then(|s| s.to_str()) { + if arg == "--dep" { + dep = true; + } else if arg == "-v" || arg == "--verbose" { + verbose = true; + } else if arg == "--many-seeds" { + many_seeds = Some(0..256); + } else if let Some(val) = arg.strip_prefix("--many-seeds=") { + 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")?; + many_seeds = Some(from..to); + } else { + break; // not for us } // Consume the flag, look at the next one. args.next().unwrap(); } - Command::Run { dep, verbose, flags: args.collect() } + Command::Run { dep, verbose, many_seeds, flags: args.collect() } } Some("fmt") => Command::Fmt { flags: args.collect() }, Some("clippy") => Command::Clippy { flags: args.collect() }, Some("cargo") => Command::Cargo { flags: args.collect() }, Some("install") => Command::Install { flags: args.collect() }, - Some("many-seeds") => Command::ManySeeds { command: args.collect() }, Some("bench") => Command::Bench { benches: args.collect() }, Some("toolchain") => Command::Toolchain { flags: args.collect() }, Some("rustc-pull") => { diff --git a/src/tools/miri/miri-script/src/util.rs b/src/tools/miri/miri-script/src/util.rs index 361a4ca0cf76..23b5e936edd8 100644 --- a/src/tools/miri/miri-script/src/util.rs +++ b/src/tools/miri/miri-script/src/util.rs @@ -1,5 +1,8 @@ use std::ffi::{OsStr, OsString}; +use std::ops::Range; use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; +use std::thread; use anyhow::{anyhow, Context, Result}; use dunce::canonicalize; @@ -189,4 +192,54 @@ 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, + run: impl Fn(&Shell, 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 shell for this thread. + let local_shell = self.sh.clone(); + let handle = s.spawn(|| -> Result<()> { + let local_shell = local_shell; // 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_shell, cur).map_err(|err| { + // If we failed, tell everyone about this. + failed.store(true, Ordering::Relaxed); + err + })?; + // 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(()) + }) + } } From f7a3aa9811aaadbd59b6d399b10f323de7f9647d Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 4 May 2024 09:02:51 +0200 Subject: [PATCH 2/2] remove a hack that is no longer needed --- src/tools/miri/ci/ci.sh | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/tools/miri/ci/ci.sh b/src/tools/miri/ci/ci.sh index 0db267650dbf..c8c24ba5da61 100755 --- a/src/tools/miri/ci/ci.sh +++ b/src/tools/miri/ci/ci.sh @@ -13,15 +13,6 @@ function endgroup { begingroup "Building Miri" -# Special Windows hacks -if [ "$HOST_TARGET" = i686-pc-windows-msvc ]; then - # The $BASH variable is `/bin/bash` here, but that path does not actually work. There are some - # hacks in place somewhere to try to paper over this, but the hacks dont work either (see - # ). So we hard-code the correct location for Github - # CI instead. - BASH="C:/Program Files/Git/usr/bin/bash" -fi - # Global configuration export RUSTFLAGS="-D warnings" export CARGO_INCREMENTAL=0