use super::body_index::BodyIndex;
use crate::{KernelBody, KernelDescriptor, KernelOpKind};
use vyre_foundation::ir::UnOp;
#[must_use]
pub fn unary_idemp(desc: &KernelDescriptor) -> KernelDescriptor {
let mut out = desc.clone();
out.body = unary_idemp_body(out.body);
out
}
fn unary_idemp_body(mut body: KernelBody) -> KernelBody {
let index = BodyIndex::new(&body);
let mut rewrites: Vec<(usize, u32)> = Vec::new();
for (idx, op) in body.ops.iter().enumerate() {
let outer = match &op.kind {
KernelOpKind::UnOpKind(u) => u.clone(),
_ => continue,
};
if !is_idempotent_unop(&outer) {
continue;
}
if op.operands.len() != 1 {
continue;
}
let inner_id = op.operands[0];
let Some(producer) = index.producer(&body, inner_id) else {
continue;
};
if let KernelOpKind::UnOpKind(inner) = &producer.kind {
if same_unop(&outer, inner) {
rewrites.push((idx, inner_id));
}
}
}
for (op_idx, replace_id) in rewrites {
body.ops[op_idx].kind = KernelOpKind::Copy;
body.ops[op_idx].operands = vec![replace_id];
}
body.child_bodies = body
.child_bodies
.into_iter()
.map(unary_idemp_body)
.collect();
body
}
fn is_idempotent_unop(op: &UnOp) -> bool {
matches!(
op,
UnOp::Floor | UnOp::Ceil | UnOp::Round | UnOp::Trunc | UnOp::Abs | UnOp::Sign
)
}
fn same_unop(a: &UnOp, b: &UnOp) -> bool {
std::mem::discriminant(a) == std::mem::discriminant(b)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{BindingLayout, Dispatch, KernelOp, LiteralValue};
use vyre_foundation::ir::UnOp;
fn empty_body() -> KernelBody {
KernelBody {
ops: Vec::new(),
child_bodies: Vec::new(),
literals: Vec::new(),
}
}
fn descriptor_with(body: KernelBody) -> KernelDescriptor {
KernelDescriptor {
id: "unary_idemp_test".into(),
bindings: BindingLayout { slots: Vec::new() },
dispatch: Dispatch::new(1, 1, 1),
body,
}
}
fn lit_f32(body: &mut KernelBody, value: f32, result: u32) {
let pool_idx = body.literals.len() as u32;
body.literals.push(LiteralValue::F32(value));
body.ops.push(KernelOp {
kind: KernelOpKind::Literal,
operands: vec![pool_idx],
result: Some(result),
});
}
fn unop(body: &mut KernelBody, op: UnOp, operand: u32, result: u32) {
body.ops.push(KernelOp {
kind: KernelOpKind::UnOpKind(op),
operands: vec![operand],
result: Some(result),
});
}
fn copied_source(desc: &KernelDescriptor, result: u32) -> u32 {
let op = desc
.body
.ops
.iter()
.find(|op| op.result == Some(result))
.unwrap();
assert!(matches!(op.kind, KernelOpKind::Copy));
op.operands[0]
}
fn assert_collapses(idemp_op: UnOp) {
let mut body = empty_body();
lit_f32(&mut body, 3.7, 0);
unop(&mut body, idemp_op.clone(), 0, 1);
unop(&mut body, idemp_op, 1, 2);
let desc = unary_idemp(&descriptor_with(body));
assert_eq!(copied_source(&desc, 2), 1);
}
#[test]
fn floor_idemp() {
assert_collapses(UnOp::Floor);
}
#[test]
fn ceil_idemp() {
assert_collapses(UnOp::Ceil);
}
#[test]
fn round_idemp() {
assert_collapses(UnOp::Round);
}
#[test]
fn trunc_idemp() {
assert_collapses(UnOp::Trunc);
}
#[test]
fn abs_idemp() {
assert_collapses(UnOp::Abs);
}
#[test]
fn sign_idemp() {
assert_collapses(UnOp::Sign);
}
#[test]
fn mismatched_unops_do_not_fold() {
let mut body = empty_body();
lit_f32(&mut body, 3.7, 0);
unop(&mut body, UnOp::Ceil, 0, 1);
unop(&mut body, UnOp::Floor, 1, 2);
let desc = unary_idemp(&descriptor_with(body));
let op = desc
.body
.ops
.iter()
.find(|op| op.result == Some(2))
.unwrap();
assert!(matches!(op.kind, KernelOpKind::UnOpKind(UnOp::Floor)));
}
#[test]
fn non_idempotent_unop_unchanged() {
let mut body = empty_body();
lit_f32(&mut body, 1.0, 0);
unop(&mut body, UnOp::Sin, 0, 1);
unop(&mut body, UnOp::Sin, 1, 2);
let desc = unary_idemp(&descriptor_with(body));
let op = desc
.body
.ops
.iter()
.find(|op| op.result == Some(2))
.unwrap();
assert!(matches!(op.kind, KernelOpKind::UnOpKind(UnOp::Sin)));
}
#[test]
fn rewrite_is_idempotent() {
let mut body = empty_body();
lit_f32(&mut body, 2.5, 0);
unop(&mut body, UnOp::Floor, 0, 1);
unop(&mut body, UnOp::Floor, 1, 2);
let desc = descriptor_with(body);
let once = unary_idemp(&desc);
let twice = unary_idemp(&once);
assert_eq!(once, twice);
}
#[test]
fn recurses_into_child_bodies() {
let mut child = empty_body();
lit_f32(&mut child, 3.7, 10);
unop(&mut child, UnOp::Abs, 10, 11);
unop(&mut child, UnOp::Abs, 11, 12);
let mut body = empty_body();
body.child_bodies.push(child);
let desc = unary_idemp(&descriptor_with(body));
let op = desc.body.child_bodies[0]
.ops
.iter()
.find(|op| op.result == Some(12))
.unwrap();
assert!(matches!(op.kind, KernelOpKind::Copy));
assert_eq!(op.operands[0], 11);
}
}