use super::ir::*;
const MIN_BROADCAST_RATIO: usize = 4;
pub(super) fn sink_const_broadcasts(prog: &mut TapeProgram) {
let cont = prog.section_range(Cadence::Continuous);
if cont.is_empty() || cont.start == 0 {
return;
}
let bound = cont.start + prog.n_rhs as usize;
let n = prog.instrs.len();
let tables = prog.tables();
let guarded = prog.guarded_positions();
let chained = super::fuse::chained_tuple_instrs(prog);
let mut readers: Vec<Vec<usize>> = vec![Vec::new(); prog.slots.len()];
for (i, ins) in prog.instrs.iter().enumerate() {
ins.for_each_read(&tables, |s| {
let r = &mut readers[s as usize];
if r.last() != Some(&i) {
r.push(i);
}
});
}
let exported: std::collections::HashSet<SlotId> =
prog.exports.iter().map(|(_, s)| *s).collect();
let mut place: Vec<Vec<(usize, SlotId)>> = vec![Vec::new(); n];
let mut moved = vec![false; n];
let mut renames: Vec<(usize, SlotId, SlotId)> = Vec::new();
for i in 0..cont.start {
let Instr::Gather {
src: SrcRef::Slot(src),
plan,
out,
} = prog.instrs[i]
else {
continue;
};
let p = &prog.plans[plan as usize];
let (o, s) = (out as usize, src as usize);
let rs = &readers[o];
if rs.is_empty()
|| guarded[i]
|| rs.iter().any(|&r| r < cont.start || guarded[r])
|| exported.contains(&out)
|| p.mapped.iter().all(|&m| m)
|| prog.slots[o].elems() < MIN_BROADCAST_RATIO * prog.slots[s].elems()
|| !super::fuse::sunk_gather_fuses(prog, &chained, i, rs)
{
continue;
}
let (head, tail): (Vec<usize>, Vec<usize>) = rs.iter().partition(|&&r| r < bound);
if !head.is_empty() && !tail.iter().all(|&r| renamable(&prog.instrs[r])) {
continue;
}
moved[i] = true;
match (head.first(), tail.first()) {
(Some(&h), Some(&t)) => {
place[h].push((i, out));
let o2 = prog.slots.len() as SlotId;
prog.slots.push(prog.slots[o].clone());
place[t].push((i, o2));
renames.extend(tail.iter().map(|&r| (r, out, o2)));
}
(Some(&h), None) => place[h].push((i, out)),
(None, Some(&t)) => place[t].push((i, out)),
(None, None) => unreachable!("a moved gather has a reader"),
}
}
if !moved.iter().any(|&m| m) {
return;
}
for (r, from, to) in renames {
rename_reads(&mut prog.instrs[r], from, to);
}
let mut instrs = Vec::with_capacity(n);
let mut prov = Vec::with_capacity(n);
let keep_prec = !prog.precision.is_empty();
let mut prec = Vec::with_capacity(if keep_prec { n } else { 0 });
let (mut n_const, mut n_segment, mut n_rhs) = (prog.n_const, prog.n_segment, prog.n_rhs);
for k in 0..n {
for &(i, out) in &place[k] {
let mut g = prog.instrs[i].clone();
if let Instr::Gather { out: o, .. } = &mut g {
*o = out;
}
instrs.push(g);
prov.push(prog.provenance[i]);
if keep_prec {
prec.push(prog.precision[i]);
}
prog.slots[out as usize].cadence = Cadence::Continuous;
if k < bound {
n_rhs += 1;
}
}
if moved[k] {
match prog.section_of(k) {
Cadence::Const => n_const -= 1,
Cadence::Segment => n_segment -= 1,
Cadence::Continuous => {}
}
continue;
}
instrs.push(prog.instrs[k].clone());
prov.push(prog.provenance[k]);
if keep_prec {
prec.push(prog.precision[k]);
}
}
prog.instrs = instrs;
prog.provenance = prov;
if keep_prec {
prog.precision = prec;
}
prog.n_const = n_const;
prog.n_segment = n_segment;
prog.n_rhs = n_rhs;
}
fn renamable(ins: &Instr) -> bool {
matches!(
ins,
Instr::Bin { .. } | Instr::Un { .. } | Instr::Neg { .. } | Instr::Copy { .. }
)
}
fn rename_reads(ins: &mut Instr, from: SlotId, to: SlotId) {
let swap = |op: &mut Operand| {
if *op == Operand::Slot(from) {
*op = Operand::Slot(to);
}
};
match ins {
Instr::Bin { a, b, .. } => {
swap(a);
swap(b);
}
Instr::Un { a, .. } | Instr::Neg { a, .. } | Instr::Copy { a, .. } => swap(a),
_ => unreachable!("only renamable instructions are renamed"),
}
}