use super::types::{
ByteReducerExecution, FingerprintProbe, ProbeOutcome, ReductionCensus, ReductionEvidence,
ReductionHalt, ReductionOutcome, ReductionPlan, ReductionProbeBinding, ReductionRefusal,
SemanticReducerExecution, ShrinkVerdict,
};
use crate::report::{Fingerprint, ReplayCapsule, ReplayPosture};
enum Step {
Admitted,
Refused,
Spent,
}
struct Reduction {
best: Vec<u8>,
census: ReductionCensus,
probes_left: u32,
preserved: Fingerprint,
probe: FingerprintProbe,
}
impl Reduction {
fn offer(&mut self, candidate: Vec<u8>) -> Step {
if self.probes_left == 0 {
return Step::Spent;
}
self.probes_left = self.probes_left.saturating_sub(1);
let verdict = shrink_verdict(self.preserved, &candidate, self.probe);
self.census.count(verdict);
match verdict {
ShrinkVerdict::Accepted => {
self.best = candidate;
Step::Admitted
}
ShrinkVerdict::RejectedFingerprintMoved { found: _ }
| ShrinkVerdict::RejectedNoFailure => Step::Refused,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Budget {
Standing,
Spent,
}
struct SemanticPhase {
path: Vec<SemanticReducerExecution>,
replay: ReplayPosture,
budget: Budget,
}
struct Offered {
probes: usize,
budget: Budget,
}
#[must_use]
pub fn shrink_verdict(
preserved: Fingerprint,
candidate: &[u8],
probe: FingerprintProbe,
) -> ShrinkVerdict {
match probe(candidate) {
ProbeOutcome::NoFailure => ShrinkVerdict::RejectedNoFailure,
ProbeOutcome::Reproduced(found) if found == preserved => ShrinkVerdict::Accepted,
ProbeOutcome::Reproduced(found) => ShrinkVerdict::RejectedFingerprintMoved { found },
}
}
pub fn reduce(
plan: &ReductionPlan,
input: &[u8],
binding: &ReductionProbeBinding,
) -> Result<ReductionEvidence, ReductionRefusal> {
let probe = binding.probe();
let preserved = binding.preserved();
let ProbeOutcome::Reproduced(baseline) = probe(input) else {
return Err(ReductionRefusal::BaselineDidNotFail);
};
if baseline != preserved {
return Err(ReductionRefusal::BaselineFingerprintDiffers { found: baseline });
}
let mut state = Reduction {
best: input.to_vec(),
census: ReductionCensus::opening(),
probes_left: plan.budget().probes(),
preserved,
probe,
};
let semantic = semantic_phase(plan, &mut state, binding.replay_posture())?;
let (halt, byte_reducer) = match semantic.budget {
Budget::Spent => (
ReductionHalt::BudgetExhausted,
ByteReducerExecution::NotReachedBecauseBudgetSpent,
),
Budget::Standing => (
byte_passes(&mut state),
ByteReducerExecution::Executed(plan.byte_reducer()),
),
};
let outcome = ReductionOutcome::reduced(state.best, preserved, state.census, halt);
Ok(ReductionEvidence::recorded(
binding,
plan.profile(),
semantic.path,
byte_reducer,
outcome,
semantic.replay,
))
}
#[must_use]
pub fn capture_replay(evidence: &ReductionEvidence) -> ReplayCapsule {
ReplayCapsule::captured(
evidence.standing(),
evidence.outcome().input(),
evidence.outcome().fingerprint(),
evidence.generation(),
evidence.minimization(),
evidence.schema(),
evidence.replay_posture(),
)
}
fn semantic_phase(
plan: &ReductionPlan,
state: &mut Reduction,
opening: ReplayPosture,
) -> Result<SemanticPhase, ReductionRefusal> {
let mut phase = SemanticPhase {
path: Vec::new(),
replay: opening,
budget: Budget::Standing,
};
for binding in plan.semantic_reducers() {
if state.probes_left == 0 {
phase.budget = Budget::Spent;
break;
}
let candidates = binding.call(&state.best).map_err(|cause| {
ReductionRefusal::SemanticReducerRefused {
reducer: binding.reducer(),
cause,
}
})?;
phase.replay = phase.replay.meet_revision(binding.revision().posture());
let authored = candidates.candidates().len();
let offered = offer_each(state, candidates.into_candidates());
phase.path.push(SemanticReducerExecution::recorded(
*binding,
authored,
offered.probes,
));
if state.probes_left == 0 || offered.budget == Budget::Spent {
phase.budget = Budget::Spent;
break;
}
}
Ok(phase)
}
fn offer_each(state: &mut Reduction, candidates: Vec<Vec<u8>>) -> Offered {
let mut offered = Offered {
probes: 0usize,
budget: Budget::Standing,
};
for candidate in candidates {
match state.offer(candidate) {
Step::Admitted | Step::Refused => offered.probes = offered.probes.saturating_add(1),
Step::Spent => {
offered.budget = Budget::Spent;
break;
}
}
}
offered
}
fn byte_passes(state: &mut Reduction) -> ReductionHalt {
loop {
match round(state) {
Step::Spent => return ReductionHalt::BudgetExhausted,
Step::Refused => return ReductionHalt::FixedPointReached,
Step::Admitted => {}
}
}
}
fn round(state: &mut Reduction) -> Step {
let mut progress = Step::Refused;
let mut window = state.best.len();
while window > 0 {
match removal_pass(state, window) {
Step::Spent => return Step::Spent,
Step::Admitted => progress = Step::Admitted,
Step::Refused => {}
}
match zeroing_pass(state, window) {
Step::Spent => return Step::Spent,
Step::Admitted => progress = Step::Admitted,
Step::Refused => {}
}
window = halved(window);
}
progress
}
fn removal_pass(state: &mut Reduction, window: usize) -> Step {
let mut progress = Step::Refused;
let mut offset = 0usize;
while offset < state.best.len() {
let candidate = without(&state.best, offset, window);
match state.offer(candidate) {
Step::Spent => return Step::Spent,
Step::Admitted => progress = Step::Admitted,
Step::Refused => offset = offset.saturating_add(window),
}
}
progress
}
fn zeroing_pass(state: &mut Reduction, window: usize) -> Step {
let mut progress = Step::Refused;
let mut offset = 0usize;
while offset < state.best.len() {
if all_zero(&state.best, offset, window) {
offset = offset.saturating_add(window);
continue;
}
let candidate = zeroed(&state.best, offset, window);
match state.offer(candidate) {
Step::Spent => return Step::Spent,
Step::Admitted => progress = Step::Admitted,
Step::Refused => {}
}
offset = offset.saturating_add(window);
}
progress
}
fn halved(window: usize) -> usize {
window.checked_div(2usize).unwrap_or(0usize)
}
fn without(bytes: &[u8], offset: usize, window: usize) -> Vec<u8> {
let end = offset.saturating_add(window);
bytes
.iter()
.copied()
.take(offset)
.chain(bytes.iter().copied().skip(end))
.collect()
}
fn zeroed(bytes: &[u8], offset: usize, window: usize) -> Vec<u8> {
let mut candidate = bytes.to_vec();
for byte in candidate.iter_mut().skip(offset).take(window) {
*byte = 0u8;
}
candidate
}
fn all_zero(bytes: &[u8], offset: usize, window: usize) -> bool {
bytes
.iter()
.skip(offset)
.take(window)
.all(|byte| *byte == 0u8)
}