use super::*;
use crate::span::Spanned;
impl TIRBuilder {
pub(crate) fn build_tuple_assign(&mut self, names: &[Spanned<String>], value: &Expr) {
let widths: Vec<_> = names
.iter()
.map(|name| {
let info = self.stack.find_var_depth_and_width(&name.node);
self.flush_stack_effects();
info.map_or(0, |(_, width)| width)
})
.collect();
self.build_expr(value);
let rhs_width = self.stack.last().map_or(0, |v| v.width);
if widths.contains(&0) || widths.iter().sum::<u32>() != rhs_width {
self.ops.push(TIROp::Comment(
"ERROR: tuple assignment has unresolved or incompatible target widths".into(),
));
self.emit_pop(rhs_width);
self.stack.pop();
return;
}
for (name, &width) in names.iter().zip(&widths).rev() {
let depth = self.stack.access_var(&name.node);
self.flush_stack_effects();
self.store_top_words_into(depth, width);
if let Some(rhs) = self.stack.last_mut() {
rhs.width -= width;
}
}
self.stack.pop();
}
pub(crate) fn build_assign(&mut self, place: &Place, value: &Expr) {
match place {
Place::Var(name) if !name.contains('.') => {
self.stack.access_var(name);
self.flush_stack_effects();
self.build_expr(value);
let depth = self.stack.access_var(name);
let width = self.stack.last().map_or(0, |value| value.width);
self.flush_stack_effects();
self.store_top_words_into(depth, width);
self.stack.pop();
}
_ => self.build_projected_store(place, value),
}
}
fn store_top_words_into(&mut self, depth: u32, width: u32) {
for _ in 0..width {
self.ops.extend([TIROp::Swap(depth), TIROp::Pop(1)]);
}
}
pub(super) fn append_branch_cleanup(
body: &mut Vec<TIROp>,
post_depth: u32,
pre_depth: u32,
keep: u32,
) {
let leftover = post_depth.saturating_sub(pre_depth + keep);
if leftover > 0 {
for _ in 0..leftover {
for depth in 1..=keep {
body.push(TIROp::Swap(depth));
}
body.push(TIROp::Pop(1));
}
}
}
}
#[cfg(test)]
mod cleanup_tests {
use super::*;
#[test]
fn aggregate_reassignment_replaces_all_words_without_disturbing_neighbors() {
for width in 1..=20u32 {
for above in 0..=20u32 {
let mut builder = TIRBuilder::new(crate::target::TerrainConfig::nox());
builder.store_top_words_into(width + above, width);
let mut stack: Vec<_> = (0..3 + width + above + width).collect();
let mut expected = stack[..(3 + width + above) as usize].to_vec();
expected[3..(3 + width) as usize]
.copy_from_slice(&stack[(3 + width + above) as usize..]);
for op in builder.ops {
match op {
TIROp::Swap(depth) => {
let top = stack.len() - 1;
stack.swap(top, top - depth as usize);
}
TIROp::Pop(count) => stack.truncate(stack.len() - count as usize),
_ => unreachable!(),
}
}
assert_eq!(stack, expected, "width={width} above={above}");
}
}
}
#[test]
fn branch_cleanup_preserves_outer_stack_and_result_order() {
for outer in 0..5 {
for dead in 0..40 {
for keep in 0..20 {
let mut ops = Vec::new();
TIRBuilder::append_branch_cleanup(&mut ops, outer + dead + keep, outer, keep);
let mut stack: Vec<_> = (0..outer + dead + keep).collect();
let expected: Vec<_> = stack[..outer as usize]
.iter()
.chain(&stack[(outer + dead) as usize..])
.copied()
.collect();
for op in ops {
match op {
TIROp::Swap(d) => {
let top = stack.len() - 1;
stack.swap(top, top - d as usize);
}
TIROp::Pop(n) => stack.truncate(stack.len() - n as usize),
_ => unreachable!(),
}
}
assert_eq!(stack, expected, "outer={outer} dead={dead} keep={keep}");
}
}
}
}
}