write some docs challenge

This commit is contained in:
nora 2023-04-01 16:58:09 +02:00
parent 8b76e561cb
commit 1b5d2f6228
2 changed files with 68 additions and 42 deletions

View file

@ -19,23 +19,38 @@ impl Debug for AstPath {
} }
} }
/// `PassController` is the interface between the passes and the core logic.
/// Its job is to bisect down the minimization sites so that all the ones that can be applied
/// are applied while trying to apply as many as possible in batches.
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct PassController { pub(crate) struct PassController {
state: PassControllerState, state: PassControllerState,
pub(crate) options: Options, pub(crate) options: Options,
} }
/// The current state of the bisection.
#[derive(Debug)] #[derive(Debug)]
enum PassControllerState { enum PassControllerState {
InitialCollection { /// Initially, we have a bunch of candidates (minimization sites) that could be applied.
candidates: Vec<AstPath>, /// We collect them in the initial application of the pass where we try to apply all candiates.
}, /// If that works, great! We're done. But often it doesn't and we enter the next stage.
InitialCollection { candidates: Vec<AstPath> },
/// After applying all candidates fails, we know that we have a few bad candidates.
/// Now our job is to apply all the good candidates as efficiently as possible.
Bisecting { Bisecting {
/// These candidates could be applied successfully while still reproducing the issue.
/// They are now on disk and will be included in all subsequent runs.
/// This is only used for debugging, we could also just throw them away.
committed: BTreeSet<AstPath>, committed: BTreeSet<AstPath>,
/// These candidates failed in isolation and are therefore bad.
/// This is only used for debugging, we could also just throw them away.
failed: BTreeSet<AstPath>, failed: BTreeSet<AstPath>,
/// The set of candidates that we want to apply in this iteration.
current: BTreeSet<AstPath>, current: BTreeSet<AstPath>,
/// The list of `current`s that we want to try in the future.
worklist: Worklist, worklist: Worklist,
}, },
/// Bisection is over and all candidates were able to be committed or thrown away.
Success, Success,
} }
@ -63,31 +78,6 @@ mod worklist {
} }
} }
// 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>>(
vec: From,
) -> (A, B) {
let candidates = vec.into_iter().collect::<Vec<_>>();
let half = div_ceil(candidates.len(), 2);
let mut candidates = candidates.into_iter();
let first_half = candidates.by_ref().take(half).collect();
let second_half = candidates.collect();
(first_half, second_half)
}
impl PassController { impl PassController {
pub fn new(options: Options) -> Self { pub fn new(options: Options) -> Self {
Self { Self {
@ -117,9 +107,11 @@ impl PassController {
} }
} }
/// The changes did not reproduce the regression. Bisect further.
pub fn does_not_reproduce(&mut self) { pub fn does_not_reproduce(&mut self) {
match &mut self.state { match &mut self.state {
PassControllerState::InitialCollection { candidates } => { PassControllerState::InitialCollection { candidates } => {
// Applying them all was too much, let's bisect!
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(); let mut worklist = Worklist::new();
@ -148,7 +140,6 @@ impl PassController {
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.
failed.extend(mem::take(current)); failed.extend(mem::take(current));
} else { } else {
// Split it further and add it to the worklist. // Split it further and add it to the worklist.
@ -164,12 +155,13 @@ impl PassController {
} }
} }
/// The pass did not apply any changes. We're done.
pub fn no_change(&mut self) { pub fn no_change(&mut self) {
match &self.state { match &self.state {
PassControllerState::InitialCollection { candidates } => { PassControllerState::InitialCollection { candidates } => {
assert!( assert!(
candidates.is_empty(), candidates.is_empty(),
"No change but received candidates: {candidates:?}" "No change but received candidates. The responsible pass does not seem to track the ProcessState correctly: {candidates:?}"
); );
self.state = PassControllerState::Success; self.state = PassControllerState::Success;
} }
@ -188,9 +180,11 @@ impl PassController {
} }
} }
/// Checks whether a pass may apply the changes for a minimization site.
pub fn can_process(&mut self, path: &[String]) -> bool { pub fn can_process(&mut self, path: &[String]) -> bool {
match &mut self.state { match &mut self.state {
PassControllerState::InitialCollection { candidates } => { PassControllerState::InitialCollection { candidates } => {
// For the initial collection, we collect the candidate and apply them all.
candidates.push(AstPath(path.to_owned())); candidates.push(AstPath(path.to_owned()));
true true
} }
@ -202,18 +196,45 @@ impl PassController {
} }
fn next_in_worklist(&mut self) { fn next_in_worklist(&mut self) {
match &mut self.state { let PassControllerState::Bisecting {
PassControllerState::Bisecting {
current, worklist, .. current, worklist, ..
} => match worklist.pop() { } = &mut self.state else {
unreachable!("next_in_worklist called on non-bisecting state");
};
match worklist.pop() {
Some(next) => { Some(next) => {
*current = next.into_iter().collect(); *current = next.into_iter().collect();
} }
None => { None => {
self.state = PassControllerState::Success; self.state = PassControllerState::Success;
} }
},
_ => unreachable!("next_in_worklist called on non-bisecting state"),
} }
} }
} }
// copied from `core` because who needs stable features anyways
// update: still not stabilized because of bikeshedding for div_floor.
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
}
}
/// Splits an owned container in half.
fn split_owned<T, From: IntoIterator<Item = T>, A: FromIterator<T>, B: FromIterator<T>>(
vec: From,
) -> (A, B) {
let candidates = vec.into_iter().collect::<Vec<_>>();
let half = div_ceil(candidates.len(), 2);
let mut candidates = candidates.into_iter();
let first_half = candidates.by_ref().take(half).collect();
let second_half = candidates.collect();
(first_half, second_half)
}

View file

@ -181,6 +181,11 @@ impl Minimizer {
invalidated_files: &mut HashSet<&'file SourceFile>, invalidated_files: &mut HashSet<&'file SourceFile>,
changes: &mut Changes, changes: &mut Changes,
) -> Result<()> { ) -> Result<()> {
// The core logic of minimization.
// Here we process a single file (a unit of work) for a single pass.
// For this, we repeatedly try to apply a pass to a subset of a file until we've exhausted all options.
// The logic for bisecting down lives in PassController.
let mut checker = PassController::new(self.options.clone()); let mut checker = PassController::new(self.options.clone());
loop { loop {
let file_display = file.path.display(); let file_display = file.path.display();