use super::{ShrinkResult, Shrinker};
pub type ShrinkPassFn<'a> = Box<dyn FnMut(&mut Shrinker<'a>) -> ShrinkResult<()> + 'a>;
fn next_rand(mut x: u64) -> u64 {
x = x.wrapping_add(0x9E3779B97F4A7C15);
let mut z = x;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D049BB133111EB);
z ^ (z >> 31)
}
pub struct ShrinkPass<'a> {
pub name: &'static str,
pub run: ShrinkPassFn<'a>,
pub calls: usize,
pub shrinks: usize,
pub deletions: usize,
}
impl<'a> ShrinkPass<'a> {
pub fn new(name: &'static str, run: ShrinkPassFn<'a>) -> Self {
ShrinkPass {
name,
run,
calls: 0,
shrinks: 0,
deletions: 0,
}
}
}
impl<'a> Shrinker<'a> {
pub fn fixate_shrink_passes(&mut self, passes: &mut [ShrinkPass<'a>]) -> ShrinkResult<()> {
const MAX_FAILURES: usize = 20;
let mut any_ran = true;
let mut shuffle_state: u64 = 0x9E3779B97F4A7C15;
while any_ran {
any_ran = false;
let mut can_discard = self.remove_discarded()?;
let calls_at_loop_start = self.calls;
let mut max_calls_per_failing_step: usize = 1;
let mut reorder_keys: Vec<i32> = vec![0; passes.len()];
let mut shuffle_requested = false;
for idx in 0..passes.len() {
if can_discard {
can_discard = self.remove_discarded()?;
}
let before_nodes_len = self.current_nodes.len();
let epoch_before_pass = self.improvements;
let mut failures: usize = 0;
while failures < MAX_FAILURES {
let span = self.calls.saturating_sub(calls_at_loop_start);
let target = max_calls_per_failing_step
.saturating_mul(2)
.saturating_add(span);
if target > self.max_stall {
self.max_stall = target;
}
if failures >= MAX_FAILURES / 2 {
shuffle_requested = true;
}
if self.debug.is_some() {
let name = passes[idx].name;
self.debug_msg(&format!("Trying shrink pass: {name}"));
}
passes[idx].calls += 1;
let epoch_before_iter = self.improvements;
let initial_calls = self.calls;
(passes[idx].run)(self)?;
if self.improvements > epoch_before_iter {
passes[idx].shrinks += 1;
if self.current_nodes.len() < before_nodes_len {
passes[idx].deletions += 1;
}
any_ran = true;
failures = 0;
} else if initial_calls != self.calls {
max_calls_per_failing_step = max_calls_per_failing_step
.max(self.calls.saturating_sub(initial_calls));
failures += 1;
} else {
break;
}
}
reorder_keys[idx] = if self.current_nodes.len() < before_nodes_len {
-1
} else if self.improvements > epoch_before_pass {
0
} else {
1
};
}
let mut indexed: Vec<(i32, usize, usize)> = reorder_keys
.iter()
.enumerate()
.map(|(i, &k)| {
let tiebreaker = if shuffle_requested {
shuffle_state = next_rand(shuffle_state);
shuffle_state as usize
} else {
i
};
(k, tiebreaker, i)
})
.collect();
indexed.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
let permutation: Vec<usize> = indexed.iter().map(|t| t.2).collect();
let mut new_order: Vec<Option<ShrinkPass<'a>>> =
(0..passes.len()).map(|_| None).collect();
for (dest, &src) in permutation.iter().enumerate() {
new_order[dest] = Some(std::mem::replace(
&mut passes[src],
ShrinkPass::new("__placeholder__", Box::new(|_| Ok(()))),
));
}
for (dest, slot) in new_order.into_iter().enumerate() {
passes[dest] = slot.expect("permutation fills every slot");
}
}
Ok(())
}
pub fn pass_stats(
&self,
passes: &[ShrinkPass<'a>],
) -> Vec<(&'static str, usize, usize, usize)> {
passes
.iter()
.map(|sp| (sp.name, sp.calls, sp.shrinks, sp.deletions))
.collect()
}
}
#[cfg(test)]
#[path = "../../../tests/embedded/native/shrinker_scheduling_tests.rs"]
mod tests;