use crate::ir::{Expr, Node, Program};
use crate::optimizer::{fingerprint_program, vyre_pass, PassAnalysis, PassResult};
use crate::visit::node_map;
#[derive(Debug, Default)]
#[vyre_pass(
name = "noop_assign_eliminate",
requires = [],
invalidates = []
)]
pub struct NoopAssignEliminatePass;
impl NoopAssignEliminatePass {
#[must_use]
pub fn analyze(program: &Program) -> PassAnalysis {
if program
.entry()
.iter()
.any(|n| node_map::any_descendant(n, &mut is_noop_assign))
{
PassAnalysis::RUN
} else {
PassAnalysis::SKIP
}
}
#[must_use]
pub fn transform(program: Program) -> PassResult {
let scaffold = program.with_rewritten_entry(Vec::new());
let mut changed = false;
let entry: Vec<Node> = drop_noop_assigns(
program
.into_entry_vec()
.into_iter()
.map(|n| rewrite_node(n, &mut changed))
.collect(),
&mut changed,
);
PassResult {
program: scaffold.with_rewritten_entry(entry),
changed,
}
}
#[must_use]
pub fn fingerprint(program: &Program) -> u64 {
fingerprint_program(program)
}
}
fn rewrite_node(node: Node, changed: &mut bool) -> Node {
let recursed = node_map::map_children(node, &mut |child| rewrite_node(child, changed));
node_map::map_body(recursed, &mut |body| drop_noop_assigns(body, changed))
}
fn drop_noop_assigns(body: Vec<Node>, changed: &mut bool) -> Vec<Node> {
let mut out = Vec::with_capacity(body.len());
for node in body {
if is_noop_assign(&node) {
*changed = true;
} else {
out.push(node);
}
}
out
}
fn is_noop_assign(node: &Node) -> bool {
matches!(node, Node::Assign { name, value: Expr::Var(v) } if v == name)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::{BufferAccess, BufferDecl, DataType, Expr, Ident, Node};
fn buf() -> BufferDecl {
BufferDecl::storage("buf", 0, BufferAccess::ReadWrite, DataType::U32).with_count(4)
}
fn program_with_entry(entry: Vec<Node>) -> Program {
Program::wrapped(vec![buf()], [1, 1, 1], entry)
}
fn count_assigns(node: &Node) -> usize {
match node {
Node::Assign { .. } => 1,
Node::If {
then, otherwise, ..
} => {
then.iter().map(count_assigns).sum::<usize>()
+ otherwise.iter().map(count_assigns).sum::<usize>()
}
Node::Loop { body, .. } => body.iter().map(count_assigns).sum(),
Node::Block(body) => body.iter().map(count_assigns).sum(),
Node::Region { body, .. } => body.iter().map(count_assigns).sum(),
_ => 0,
}
}
#[test]
fn drops_self_assign() {
let entry = vec![Node::Assign {
name: Ident::from("x"),
value: Expr::var("x"),
}];
let program = program_with_entry(entry);
let result = NoopAssignEliminatePass::transform(program);
assert!(result.changed);
let total: usize = result.program.entry().iter().map(count_assigns).sum();
assert_eq!(total, 0, "self-assign must be dropped");
}
#[test]
fn keeps_real_assign() {
let entry = vec![Node::Assign {
name: Ident::from("x"),
value: Expr::var("y"),
}];
let program = program_with_entry(entry);
let result = NoopAssignEliminatePass::transform(program);
assert!(
!result.changed,
"x = y is a real assign and must be preserved"
);
}
#[test]
fn drops_self_assign_inside_if_branch() {
let entry = vec![Node::if_then(
Expr::var("c"),
vec![Node::Assign {
name: Ident::from("x"),
value: Expr::var("x"),
}],
)];
let program = program_with_entry(entry);
let result = NoopAssignEliminatePass::transform(program);
assert!(result.changed);
let total: usize = result.program.entry().iter().map(count_assigns).sum();
assert_eq!(total, 0);
}
#[test]
fn drops_multiple_self_assigns() {
let entry = vec![
Node::Assign {
name: Ident::from("a"),
value: Expr::var("a"),
},
Node::Assign {
name: Ident::from("b"),
value: Expr::var("b"),
},
Node::Assign {
name: Ident::from("c"),
value: Expr::var("c"),
},
Node::store("buf", Expr::u32(0), Expr::u32(7)),
];
let program = program_with_entry(entry);
let result = NoopAssignEliminatePass::transform(program);
assert!(result.changed);
let total: usize = result.program.entry().iter().map(count_assigns).sum();
assert_eq!(total, 0, "all three self-assigns must be dropped");
}
#[test]
fn analyze_skips_program_with_no_self_assigns() {
let entry = vec![Node::Assign {
name: Ident::from("x"),
value: Expr::var("y"),
}];
let program = program_with_entry(entry);
assert_eq!(
NoopAssignEliminatePass::analyze(&program),
PassAnalysis::SKIP
);
}
#[test]
fn analyze_runs_when_self_assign_present() {
let entry = vec![Node::Assign {
name: Ident::from("x"),
value: Expr::var("x"),
}];
let program = program_with_entry(entry);
assert_eq!(
NoopAssignEliminatePass::analyze(&program),
PassAnalysis::RUN
);
}
}