use std::collections::{HashMap, HashSet};
use crate::bytecode::{InstructionIR, Label, MatchIR};
use crate::compile::CompileResult;
pub fn collapse_prefix(result: &mut CompileResult) {
let label_to_idx: HashMap<Label, usize> = result
.instructions
.iter()
.enumerate()
.map(|(i, instr)| (instr.label(), i))
.collect();
let mut merge_targets: HashSet<Label> = HashSet::new();
for instr in &result.instructions {
let InstructionIR::Match(m) = instr else {
continue;
};
if m.successors.len() < 2 {
continue;
}
let groups = group_by_structure(&m.successors, &label_to_idx, &result.instructions);
for group in &groups {
if group.len() > 1 {
merge_targets.insert(group[0]);
}
}
}
let mut updates: HashMap<Label, Vec<Label>> = HashMap::new();
let mut removed: HashSet<Label> = HashSet::new();
for instr in &result.instructions {
let InstructionIR::Match(m) = instr else {
continue;
};
if merge_targets.contains(&m.label) {
continue;
}
if m.successors.len() < 2 {
continue;
}
let groups = group_by_structure(&m.successors, &label_to_idx, &result.instructions);
if groups.iter().all(|g| g.len() == 1) {
continue;
}
let mut new_successors = Vec::new();
for group in groups {
if group.len() == 1 {
new_successors.push(group[0]);
} else {
let first = group[0];
let merged_succs: Vec<Label> = group
.iter()
.flat_map(|&label| {
let idx = label_to_idx[&label];
result.instructions[idx].successors()
})
.collect();
updates.insert(first, merged_succs);
new_successors.push(first);
removed.extend(group[1..].iter().copied());
}
}
updates.insert(m.label, new_successors);
}
for instr in &mut result.instructions {
if let InstructionIR::Match(m) = instr
&& let Some(new_succs) = updates.remove(&m.label)
{
m.successors = new_succs;
}
}
result
.instructions
.retain(|instr| !removed.contains(&instr.label()));
}
fn group_by_structure(
successors: &[Label],
label_to_idx: &HashMap<Label, usize>,
instructions: &[InstructionIR],
) -> Vec<Vec<Label>> {
let mut groups: Vec<Vec<Label>> = Vec::new();
for &label in successors {
let Some(&idx) = label_to_idx.get(&label) else {
groups.push(vec![label]);
continue;
};
let instr = &instructions[idx];
let found = groups.iter_mut().find(|group| {
let Some(&first_idx) = label_to_idx.get(&group[0]) else {
return false;
};
structure_eq(&instructions[first_idx], instr)
});
if let Some(group) = found {
group.push(label);
} else {
groups.push(vec![label]);
}
}
groups
}
fn structure_eq(a: &InstructionIR, b: &InstructionIR) -> bool {
match (a, b) {
(InstructionIR::Match(a), InstructionIR::Match(b)) => structure_eq_match(a, b),
(InstructionIR::Call(a), InstructionIR::Call(b)) => {
a.nav == b.nav && a.node_field == b.node_field && a.target == b.target
}
(InstructionIR::Return(_), InstructionIR::Return(_)) => true,
(InstructionIR::Trampoline(_), InstructionIR::Trampoline(_)) => true,
_ => false,
}
}
fn structure_eq_match(a: &MatchIR, b: &MatchIR) -> bool {
a.nav == b.nav
&& a.node_type == b.node_type
&& a.node_field == b.node_field
&& a.pre_effects == b.pre_effects
&& a.neg_fields == b.neg_fields
&& a.post_effects == b.post_effects
&& a.predicate == b.predicate
}