use super::legalize;
use trident::tir::TIROp;
pub(super) struct Shuffle {
pub consumed: usize,
width: u32,
order: Vec<u32>,
}
impl Shuffle {
pub(super) fn plan(ops: &[TIROp]) -> Option<Self> {
let (mut required, mut balance) = (0i64, 0i64);
let (mut consumed, mut cost, mut deep) = (0, 0u64, false);
for op in ops.iter().take(1024) {
let (need, change, op_cost) = match op {
TIROp::Dup(d) | TIROp::Swap(d) => {
let duplicate = matches!(op, TIROp::Dup(_));
deep |= *d >= 16;
let cost = if *d < 16 {
u64::from(duplicate || *d != 0)
} else {
let cells = u64::from(*d - 13);
16 + 5 * cells + 7 * cells.div_ceil(5)
};
(i64::from(*d) + 1, i64::from(duplicate), cost)
}
TIROp::Pop(n) => (i64::from(*n), -i64::from(*n), u64::from(*n).div_ceil(5)),
_ => break,
};
required = required.max(need - balance);
balance += change;
if required > 4096 || required + balance > 4096 {
return None;
}
consumed += 1;
cost += op_cost;
}
if !deep || consumed < 2 {
return None;
}
let mut order: Vec<u32> = (0..required as u32).collect();
for op in &ops[..consumed] {
match *op {
TIROp::Dup(depth) => order.push(order[order.len() - 1 - depth as usize]),
TIROp::Swap(depth) => {
let top = order.len() - 1;
order.swap(top, top - depth as usize);
}
TIROp::Pop(count) => order.truncate(order.len() - count as usize),
_ => unreachable!("plan contains only pure stack operations"),
}
}
if order.is_empty() {
return None;
}
let mut uses = vec![0usize; required as usize];
for &index in &order {
uses[index as usize] += 1;
}
let prefix = order
.iter()
.enumerate()
.take_while(|(index, value)| **value as usize == *index && uses[*index] == 1)
.count();
let plan = Self {
consumed,
width: required as u32 - prefix as u32,
order: order[prefix..]
.iter()
.map(|index| index - prefix as u32)
.collect(),
};
if plan.width == 0 {
return None;
}
let mut candidate = Vec::new();
plan.emit("__scratch", &mut candidate);
((candidate.len() as u64) < cost).then_some(plan)
}
pub(super) fn emit(&self, search: &str, out: &mut Vec<String>) {
legalize::borrow(self.width, search, out);
legalize::batch("write_mem", self.width, out);
out.extend([format!(" push -{}", self.width), " add".into()]);
for index in &self.order {
out.push(" dup 0".into());
let offset = self.width - 1 - index;
if offset != 0 {
out.extend([format!(" push {offset}"), " add".into()]);
}
out.extend([
" read_mem 1".into(),
" pop 1".into(),
" swap 1".into(),
]);
}
let mut remaining = self.width;
while remaining > 0 {
let count = remaining.min(5);
out.extend((0..count).map(|_| " push 0".into()));
out.extend([
format!(" swap {count}"),
format!(" write_mem {count}"),
]);
remaining -= count;
}
out.push(" pop 1".into());
}
}