This commit is contained in:
nora 2022-12-20 17:59:17 +01:00
parent 79b69fafb9
commit 64da92ab9e
7 changed files with 113 additions and 31 deletions

7
Cargo.lock generated
View file

@ -32,6 +32,7 @@ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
"libc", "libc",
"owo-colors",
"prettyplease", "prettyplease",
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -250,6 +251,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "owo-colors"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.9" version = "0.2.9"

View file

@ -12,13 +12,14 @@ description = "A tool for minimizing rustc ICEs"
[dependencies] [dependencies]
anyhow = "1.0.65" anyhow = "1.0.65"
clap = { version = "4.0.29", features = ["derive"] } clap = { version = "4.0.29", features = ["derive"] }
owo-colors = "3.5.0"
prettyplease = "0.1.19" prettyplease = "0.1.19"
proc-macro2 = { version = "1.0.48", features = ["span-locations"] } proc-macro2 = { version = "1.0.48", features = ["span-locations"] }
quote = "1.0.23" quote = "1.0.23"
rustfix = "0.6.1" rustfix = "0.6.1"
serde = { version = "1.0.151", features = ["derive"] } serde = { version = "1.0.151", features = ["derive"] }
serde_json = "1.0.90" serde_json = "1.0.90"
syn = { version = "1.0.101", features = ["full", "visit", "visit-mut"] } syn = { version = "1.0.101", features = ["full", "visit-mut"] }
tempfile = "3.3.0" tempfile = "3.3.0"
tracing = "0.1.37" tracing = "0.1.37"
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }

View file

@ -38,6 +38,7 @@ struct BuildInner {
input_path: PathBuf, input_path: PathBuf,
verify: Verify, verify: Verify,
env: Vec<EnvVar>, env: Vec<EnvVar>,
allow_color: bool,
} }
#[derive(Debug)] #[derive(Debug)]
@ -76,20 +77,24 @@ impl Build {
input_path: options.path.clone(), input_path: options.path.clone(),
verify, verify,
env: options.env.clone(), env: options.env.clone(),
allow_color: !options.no_color,
}), }),
} }
} }
pub fn build(&self) -> Result<BuildResult> { pub fn build(&self) -> Result<BuildResult> {
if let Verify::None = self.inner.verify { let inner = &self.inner;
if let Verify::None = inner.verify {
return Ok(BuildResult { return Ok(BuildResult {
reproduces_issue: false, reproduces_issue: false,
no_verify: true, no_verify: true,
output: String::new(), output: String::new(),
allow_color: inner.allow_color,
}); });
} }
let (is_ice, output) = match &self.inner.mode { let (is_ice, output) = match &inner.mode {
BuildMode::Cargo { args } => { BuildMode::Cargo { args } => {
let mut cmd = Command::new("cargo"); let mut cmd = Command::new("cargo");
cmd.args(["build", "--color=always"]); cmd.args(["build", "--color=always"]);
@ -98,7 +103,7 @@ impl Build {
cmd.arg(arg); cmd.arg(arg);
} }
for env in &self.inner.env { for env in &inner.env {
cmd.env(&env.key, &env.value); cmd.env(&env.key, &env.value);
} }
@ -115,7 +120,7 @@ impl Build {
BuildMode::Script(script_path) => { BuildMode::Script(script_path) => {
let mut cmd = Command::new(script_path); let mut cmd = Command::new(script_path);
for env in &self.inner.env { for env in &inner.env {
cmd.env(&env.key, &env.value); cmd.env(&env.key, &env.value);
} }
@ -128,9 +133,9 @@ impl Build {
BuildMode::Rustc => { BuildMode::Rustc => {
let mut cmd = Command::new("rustc"); let mut cmd = Command::new("rustc");
cmd.args(["--edition", "2021"]); cmd.args(["--edition", "2021"]);
cmd.arg(&self.inner.input_path); cmd.arg(&inner.input_path);
for env in &self.inner.env { for env in &inner.env {
cmd.env(&env.key, &env.value); cmd.env(&env.key, &env.value);
} }
@ -146,7 +151,7 @@ impl Build {
} }
}; };
let reproduces_issue = match self.inner.verify { let reproduces_issue = match inner.verify {
Verify::None => unreachable!("handled ealier"), Verify::None => unreachable!("handled ealier"),
Verify::Ice => is_ice, Verify::Ice => is_ice,
Verify::Custom(func) => func.call(&output), Verify::Custom(func) => func.call(&output),
@ -156,11 +161,14 @@ impl Build {
reproduces_issue, reproduces_issue,
no_verify: false, no_verify: false,
output, output,
allow_color: inner.allow_color,
}) })
} }
pub fn get_diags(&self) -> Result<(Vec<Diagnostic>, Vec<rustfix::Suggestion>)> { pub fn get_diags(&self) -> Result<(Vec<Diagnostic>, Vec<rustfix::Suggestion>)> {
let diags = match &self.inner.mode { let inner = &self.inner;
let diags = match &inner.mode {
BuildMode::Cargo { args } => { BuildMode::Cargo { args } => {
let mut cmd = Command::new("cargo"); let mut cmd = Command::new("cargo");
cmd.args(["build", "--message-format=json"]); cmd.args(["build", "--message-format=json"]);
@ -169,7 +177,7 @@ impl Build {
cmd.arg(arg); cmd.arg(arg);
} }
for env in &self.inner.env { for env in &inner.env {
cmd.env(&env.key, &env.value); cmd.env(&env.key, &env.value);
} }
@ -189,9 +197,9 @@ impl Build {
BuildMode::Rustc => { BuildMode::Rustc => {
let mut cmd = std::process::Command::new("rustc"); let mut cmd = std::process::Command::new("rustc");
cmd.args(["--edition", "2021", "--error-format=json"]); cmd.args(["--edition", "2021", "--error-format=json"]);
cmd.arg(&self.inner.input_path); cmd.arg(&inner.input_path);
for env in &self.inner.env { for env in &inner.env {
cmd.env(&env.key, &env.value); cmd.env(&env.key, &env.value);
} }
@ -225,6 +233,7 @@ pub struct BuildResult {
reproduces_issue: bool, reproduces_issue: bool,
no_verify: bool, no_verify: bool,
output: String, output: String,
allow_color: bool,
} }
impl BuildResult { impl BuildResult {
@ -245,10 +254,19 @@ impl BuildResult {
impl Display for BuildResult { impl Display for BuildResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match (self.reproduces_issue, self.no_verify) { use owo_colors::OwoColorize;
(true, _) => f.write_str("yes"),
(false, true) => f.write_str("yes (no-verify)"), match self.allow_color {
(false, false) => f.write_str("no"), false => match (self.reproduces_issue, self.no_verify) {
(true, _) => f.write_str("yes"),
(false, true) => f.write_str("yes (no-verify)"),
(false, false) => f.write_str("no"),
},
true => match (self.reproduces_issue, self.no_verify) {
(true, _) => write!(f, "{}", "yes".green()),
(false, true) => write!(f, "{}", "yes (no-verify)".green()),
(false, false) => write!(f, "{}", "no".red()),
},
} }
} }
} }

View file

@ -33,6 +33,9 @@ pub struct Options {
#[arg(long)] #[arg(long)]
cargo_args: Option<String>, cargo_args: Option<String>,
#[arg(long)]
no_color: bool,
#[arg(long)] #[arg(long)]
rustc: bool, rustc: bool,
#[arg(long)] #[arg(long)]
@ -74,7 +77,7 @@ pub fn minimize() -> Result<()> {
let build = build::Build::new(&options); let build = build::Build::new(&options);
let mut minimizer = Minimizer::new_glob_dir(&options.path, build); let mut minimizer = Minimizer::new_glob_dir(options, build);
minimizer.run_passes([ minimizer.run_passes([
Box::<privatize::Privatize>::default() as Box<dyn Processor>, Box::<privatize::Privatize>::default() as Box<dyn Processor>,

View file

@ -1,15 +1,16 @@
mod files; mod files;
mod reaper; mod reaper;
pub(crate) use self::files::SourceFile; pub(crate) use self::files::SourceFile;
use crate::{build::Build, processor::files::Changes}; use self::worklist::Worklist;
use crate::{build::Build, processor::files::Changes, Options};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use owo_colors::OwoColorize;
use std::{ use std::{
borrow::Borrow, borrow::Borrow,
collections::{BTreeSet, HashSet}, collections::{BTreeSet, HashSet},
ffi::OsStr, ffi::OsStr,
fmt::Debug, fmt::Debug,
mem, mem,
path::Path,
}; };
pub(crate) trait Processor { pub(crate) trait Processor {
@ -47,11 +48,14 @@ pub(crate) enum ProcessState {
pub(crate) struct Minimizer { pub(crate) struct Minimizer {
files: Vec<SourceFile>, files: Vec<SourceFile>,
build: Build, build: Build,
options: Options,
} }
impl Minimizer { impl Minimizer {
pub(crate) fn new_glob_dir(path: &Path, build: Build) -> Self { pub(crate) fn new_glob_dir(options: Options, build: Build) -> Self {
let path = &options.path;
let walk = walkdir::WalkDir::new(path); let walk = walkdir::WalkDir::new(path);
let files = walk let files = walk
.into_iter() .into_iter()
.filter_map(|entry| match entry { .filter_map(|entry| match entry {
@ -70,7 +74,11 @@ impl Minimizer {
}) })
.collect(); .collect();
Self { files, build } Self {
files,
build,
options,
}
} }
pub(crate) fn run_passes<'a>( pub(crate) fn run_passes<'a>(
@ -130,7 +138,6 @@ impl Minimizer {
) -> Result<()> { ) -> Result<()> {
let mut checker = PassController::new(); let mut checker = PassController::new();
loop { loop {
debug!(?checker);
let file_display = file.path.display(); let file_display = file.path.display();
let mut change = file.try_change(changes)?; let mut change = file.try_change(changes)?;
let mut krate = syn::parse_file(change.before_content()) let mut krate = syn::parse_file(change.before_content())
@ -155,7 +162,15 @@ impl Minimizer {
} }
} }
ProcessState::NoChange => { ProcessState::NoChange => {
info!("{file_display}: After {}: no changes", pass.name()); if self.options.no_color {
info!("{file_display}: After {}: no changes", pass.name());
} else {
info!(
"{file_display}: After {}: {}",
pass.name(),
"no changes".yellow()
);
}
checker.no_change(); checker.no_change();
} }
} }
@ -197,16 +212,51 @@ enum PassControllerState {
committed: BTreeSet<AstPath>, committed: BTreeSet<AstPath>,
failed: BTreeSet<AstPath>, failed: BTreeSet<AstPath>,
current: BTreeSet<AstPath>, current: BTreeSet<AstPath>,
worklist: Vec<Vec<AstPath>>, worklist: Worklist,
}, },
Success, Success,
} }
mod worklist {
use super::AstPath;
/// A worklist that ensures that the inner list is never empty.
#[derive(Debug)]
pub(super) struct Worklist(Vec<Vec<AstPath>>);
impl Worklist {
pub(super) fn new() -> Self {
Self(Vec::new())
}
pub(super) fn push(&mut self, next: Vec<AstPath>) {
if !next.is_empty() {
self.0.push(next);
}
}
pub(super) fn pop(&mut self) -> Option<Vec<AstPath>> {
self.0.pop()
}
}
}
// copied from `core` because who needs stable features anyways
pub const fn div_ceil(lhs: usize, rhs: usize) -> usize {
let d = lhs / rhs;
let r = lhs % rhs;
if r > 0 && rhs > 0 {
d + 1
} else {
d
}
}
fn split_owned<T, From: IntoIterator<Item = T>, A: FromIterator<T>, B: FromIterator<T>>( fn split_owned<T, From: IntoIterator<Item = T>, A: FromIterator<T>, B: FromIterator<T>>(
vec: From, vec: From,
) -> (A, B) { ) -> (A, B) {
let candidates = vec.into_iter().collect::<Vec<_>>(); let candidates = vec.into_iter().collect::<Vec<_>>();
let half = candidates.len() / 2; let half = div_ceil(candidates.len(), 2);
let mut candidates = candidates.into_iter(); let mut candidates = candidates.into_iter();
@ -265,21 +315,22 @@ impl PassController {
PassControllerState::InitialCollection { candidates } => { PassControllerState::InitialCollection { candidates } => {
let (current, first_worklist_item) = split_owned(mem::take(candidates)); let (current, first_worklist_item) = split_owned(mem::take(candidates));
let mut worklist = Worklist::new();
worklist.push(first_worklist_item);
self.state = PassControllerState::Bisecting { self.state = PassControllerState::Bisecting {
committed: BTreeSet::new(), committed: BTreeSet::new(),
failed: BTreeSet::new(), failed: BTreeSet::new(),
current, current,
worklist: vec![first_worklist_item], worklist,
}; };
} }
PassControllerState::Bisecting { PassControllerState::Bisecting {
committed, committed: _,
failed, failed,
current, current,
worklist, worklist,
} => { } => {
debug!(?committed, ?current, ?worklist);
if current.len() == 1 { if current.len() == 1 {
// We are at a leaf. This is a failure. // We are at a leaf. This is a failure.
// FIXME: We should retry the failed ones until a fixpoint is reached. // FIXME: We should retry the failed ones until a fixpoint is reached.

View file

@ -2,6 +2,6 @@ fn unused() {
this_is_required_to_error_haha(); this_is_required_to_error_haha();
} }
fn main() { fn main() {
other::unused(); loop {}
} }
mod other; mod other;

View file

@ -1 +1,3 @@
pub(crate) fn unused() {} pub(crate) fn unused() {
loop {}
}