use super::TIROp;
pub(crate) mod spill;
#[cfg(test)]
mod tests;
pub(crate) fn optimize(ops: Vec<TIROp>) -> Vec<TIROp> {
let mut ir = ops;
loop {
let before = ir.len();
ir = merge_hints(ir);
ir = merge_pops(ir);
ir = eliminate_nops(ir);
ir = spill::eliminate_dead_spills(ir);
ir = eliminate_dup_pop_nops(ir);
ir = eliminate_double_swaps(ir);
ir = collapse_swap_pop_chains(ir);
ir = collapse_epilogue_cleanup(ir);
ir = optimize_nested(ir);
if ir.len() == before {
break;
}
}
ir
}
fn merge_hints(ops: Vec<TIROp>) -> Vec<TIROp> {
let mut out: Vec<TIROp> = Vec::with_capacity(ops.len());
let mut i = 0;
while i < ops.len() {
if let TIROp::Hint(n) = &ops[i] {
let mut total = *n;
let mut j = i + 1;
while j < ops.len() {
if let TIROp::Hint(m) = &ops[j] {
total += m;
j += 1;
} else {
break;
}
}
while total > 0 {
let batch = total.min(5);
out.push(TIROp::Hint(batch));
total -= batch;
}
i = j;
} else {
out.push(ops[i].clone());
i += 1;
}
}
out
}
fn merge_pops(ops: Vec<TIROp>) -> Vec<TIROp> {
let mut out: Vec<TIROp> = Vec::with_capacity(ops.len());
let mut i = 0;
while i < ops.len() {
if let TIROp::Pop(n) = &ops[i] {
let mut total = *n;
let mut j = i + 1;
while j < ops.len() {
if let TIROp::Pop(m) = &ops[j] {
total += m;
j += 1;
} else {
break;
}
}
while total > 0 {
let batch = total.min(5);
out.push(TIROp::Pop(batch));
total -= batch;
}
i = j;
} else {
out.push(ops[i].clone());
i += 1;
}
}
out
}
fn eliminate_nops(ops: Vec<TIROp>) -> Vec<TIROp> {
ops.into_iter()
.filter(|op| !matches!(op, TIROp::Swap(0) | TIROp::Pop(0)))
.collect()
}
fn eliminate_dup_pop_nops(ops: Vec<TIROp>) -> Vec<TIROp> {
let mut out: Vec<TIROp> = Vec::with_capacity(ops.len());
let mut i = 0;
while i < ops.len() {
if i + 2 < ops.len() {
if let (TIROp::Dup(0), TIROp::Swap(1), TIROp::Pop(1)) =
(&ops[i], &ops[i + 1], &ops[i + 2])
{
i += 3;
continue;
}
}
if i + 1 < ops.len() {
if let (TIROp::Dup(0), TIROp::Pop(1)) = (&ops[i], &ops[i + 1]) {
i += 2;
continue;
}
}
out.push(ops[i].clone());
i += 1;
}
out
}
fn eliminate_double_swaps(ops: Vec<TIROp>) -> Vec<TIROp> {
let mut out: Vec<TIROp> = Vec::with_capacity(ops.len());
let mut i = 0;
while i < ops.len() {
if i + 1 < ops.len() {
if let (TIROp::Swap(a), TIROp::Swap(b)) = (&ops[i], &ops[i + 1]) {
if a == b {
i += 2;
continue;
}
}
}
out.push(ops[i].clone());
i += 1;
}
out
}
fn collapse_swap_pop_chains(ops: Vec<TIROp>) -> Vec<TIROp> {
let mut out: Vec<TIROp> = Vec::with_capacity(ops.len());
let mut i = 0;
while i < ops.len() {
if let TIROp::Dup(d) = &ops[i] {
let d_val = *d;
let mut dup_count = 0u32;
let mut j = i;
while j < ops.len() {
if let TIROp::Dup(dd) = &ops[j] {
if *dd == d_val {
dup_count += 1;
j += 1;
} else {
break;
}
} else {
break;
}
}
if dup_count >= 2 && j < ops.len() {
if let TIROp::Swap(s) = &ops[j] {
if *s == dup_count {
let after_swap = j + 1;
let mut total_popped = 0u32;
let mut k = after_swap;
while k < ops.len() {
if let TIROp::Pop(p) = &ops[k] {
total_popped += p;
k += 1;
if total_popped >= dup_count {
break;
}
} else {
break;
}
}
if total_popped == dup_count && d_val + 1 == dup_count {
i = k;
continue;
}
}
}
}
}
out.push(ops[i].clone());
i += 1;
}
out
}
fn collapse_epilogue_cleanup(ops: Vec<TIROp>) -> Vec<TIROp> {
let mut out: Vec<TIROp> = Vec::with_capacity(ops.len());
let mut i = 0;
while i < ops.len() {
if i + 3 < ops.len() {
if let (TIROp::Swap(d), TIROp::Pop(1)) = (&ops[i], &ops[i + 1]) {
let first_d = *d;
let mut count = 1u32;
let mut is_constant_depth = true;
let mut j = i + 2;
while j + 1 < ops.len() {
if let (TIROp::Swap(dd), TIROp::Pop(1)) = (&ops[j], &ops[j + 1]) {
if *dd == first_d {
count += 1;
j += 2;
} else if first_d == 1 {
break;
} else if *dd + count == first_d || *dd < first_d {
is_constant_depth = false;
count += 1;
j += 2;
} else {
break;
}
} else {
break;
}
}
if count >= 3 {
if first_d == 1 {
let mut remaining = count;
while remaining > 0 {
let chunk = remaining.min(15);
out.push(TIROp::Swap(chunk));
let mut pop_left = chunk;
while pop_left > 0 {
let batch = pop_left.min(5);
out.push(TIROp::Pop(batch));
pop_left -= batch;
}
remaining -= chunk;
}
} else if is_constant_depth {
let total_depth = first_d + count - 1;
if total_depth <= 15 {
for offset in 0..count {
out.push(TIROp::Swap(total_depth - offset));
}
} else {
for _ in 0..count {
out.push(TIROp::Swap(first_d));
}
}
let mut remaining = count;
while remaining > 0 {
let batch = remaining.min(5);
out.push(TIROp::Pop(batch));
remaining -= batch;
}
} else {
out.push(TIROp::Swap(first_d));
let mut remaining = count;
while remaining > 0 {
let batch = remaining.min(5);
out.push(TIROp::Pop(batch));
remaining -= batch;
}
}
i = j;
continue;
}
}
}
out.push(ops[i].clone());
i += 1;
}
out
}
fn optimize_nested(ops: Vec<TIROp>) -> Vec<TIROp> {
ops.into_iter()
.map(|op| match op {
TIROp::IfElse {
then_body,
else_body,
} => TIROp::IfElse {
then_body: optimize(then_body),
else_body: optimize(else_body),
},
TIROp::IfOnly { then_body } => TIROp::IfOnly {
then_body: optimize(then_body),
},
TIROp::Loop { label, body } => TIROp::Loop {
label,
body: optimize(body),
},
TIROp::ProofBlock { program_hash, body } => TIROp::ProofBlock {
program_hash,
body: optimize(body),
},
other => other,
})
.collect()
}