use vyre_foundation::ir::BinOp;
use vyre_foundation::optimizer::algebraic_rules::{
binop_identity_replacement, IdentityReplacement, ScalarLiteral,
};
use crate::operand_semantics::operand_is_result_reference;
use crate::{KernelBody, KernelDescriptor, KernelOpKind, LiteralValue};
use rustc_hash::FxHashMap;
#[must_use]
pub fn identity_elim(desc: &KernelDescriptor) -> KernelDescriptor {
let mut out = desc.clone();
out.body = identity_elim_body(out.body);
out
}
fn identity_elim_body(mut body: KernelBody) -> KernelBody {
let mut lit_value: FxHashMap<u32, LiteralValue> = FxHashMap::default();
for op in &body.ops {
if matches!(op.kind, KernelOpKind::Literal) {
if let Some(rid) = op.result {
if let Some(&pool_idx) = op.operands.first() {
if let Some(lv) = body.literals.get(pool_idx as usize) {
lit_value.insert(rid, lv.clone());
}
}
}
}
}
let mut id_remap: FxHashMap<u32, u32> = FxHashMap::default();
for op in &body.ops {
match &op.kind {
KernelOpKind::BinOpKind(bin_op) => {
if op.operands.len() < 2 {
continue;
}
let lhs_raw = op.operands[0];
let rhs_raw = op.operands[1];
let lhs = resolve(lhs_raw, &id_remap);
let rhs = resolve(rhs_raw, &id_remap);
let Some(rid) = op.result else { continue };
if let Some(kept_id) = decide_kept(*bin_op, lhs, rhs, &lit_value) {
id_remap.insert(rid, kept_id);
}
}
KernelOpKind::Select => {
if op.operands.len() < 3 {
continue;
}
let cond_raw = op.operands[0];
let true_raw = op.operands[1];
let false_raw = op.operands[2];
let cond_resolved = resolve(cond_raw, &id_remap);
let Some(rid) = op.result else { continue };
let kept = match lit_value.get(&cond_resolved) {
Some(LiteralValue::Bool(true)) => Some(resolve(true_raw, &id_remap)),
Some(LiteralValue::Bool(false)) => Some(resolve(false_raw, &id_remap)),
_ => None,
};
if let Some(kept_id) = kept {
id_remap.insert(rid, kept_id);
}
}
KernelOpKind::Fma => {
if op.operands.len() < 3 {
continue;
}
let a = resolve(op.operands[0], &id_remap);
let b = resolve(op.operands[1], &id_remap);
let c = resolve(op.operands[2], &id_remap);
let Some(rid) = op.result else { continue };
let a_lit = lit_value.get(&a).map(scalar_literal);
let b_lit = lit_value.get(&b).map(scalar_literal);
let a_zero = a_lit.is_some_and(ScalarLiteral::is_numeric_zero);
let b_zero = b_lit.is_some_and(ScalarLiteral::is_numeric_zero);
let a_finite = a_lit.is_some_and(ScalarLiteral::is_finite_numeric);
let b_finite = b_lit.is_some_and(ScalarLiteral::is_finite_numeric);
if (a_zero && b_finite) || (b_zero && a_finite) {
id_remap.insert(rid, c);
}
}
_ => continue,
}
}
if id_remap.is_empty() {
body.child_bodies = body
.child_bodies
.into_iter()
.map(identity_elim_body)
.collect();
return body;
}
for op in &mut body.ops {
for pos in 0..op.operands.len() {
if operand_is_result_reference(&op.kind, pos) {
op.operands[pos] = resolve(op.operands[pos], &id_remap);
}
}
}
body.child_bodies = body
.child_bodies
.into_iter()
.map(identity_elim_body)
.collect();
body
}
fn resolve(id: u32, remap: &FxHashMap<u32, u32>) -> u32 {
let mut cur = id;
let mut hops = 0usize;
while let Some(&nxt) = remap.get(&cur) {
if nxt == cur {
break;
}
cur = nxt;
hops += 1;
if hops > remap.len() + 1 {
break;
}
}
cur
}
fn decide_kept(
op: BinOp,
lhs_id: u32,
rhs_id: u32,
lit_value: &FxHashMap<u32, LiteralValue>,
) -> Option<u32> {
let lhs_lit = lit_value.get(&lhs_id);
let rhs_lit = lit_value.get(&rhs_id);
match binop_identity_replacement(
op,
lhs_id == rhs_id,
lhs_lit.map(scalar_literal),
rhs_lit.map(scalar_literal),
) {
Some(IdentityReplacement::Left) => Some(lhs_id),
Some(IdentityReplacement::Right) => Some(rhs_id),
None => None,
}
}
fn scalar_literal(v: &LiteralValue) -> ScalarLiteral {
match v {
LiteralValue::U32(value) => ScalarLiteral::U32(*value),
LiteralValue::I32(value) => ScalarLiteral::I32(*value),
LiteralValue::F32(value) => ScalarLiteral::F32(*value),
LiteralValue::Bool(value) => ScalarLiteral::Bool(*value),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
BindingLayout, Dispatch, KernelBody, KernelDescriptor, KernelOp, KernelOpKind, LiteralValue,
};
use vyre_foundation::ir::BinOp;
fn empty_desc(ops: Vec<KernelOp>, literals: Vec<LiteralValue>) -> KernelDescriptor {
KernelDescriptor {
id: "k".into(),
bindings: BindingLayout { slots: vec![] },
dispatch: Dispatch::new(1, 1, 1),
body: KernelBody {
ops,
child_bodies: vec![],
literals,
},
}
}
#[test]
fn add_x_zero_eliminates_to_x() {
let desc = empty_desc(
vec![
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(0),
},
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![1],
result: Some(1),
},
KernelOp {
kind: KernelOpKind::BinOpKind(BinOp::Add),
operands: vec![0, 1],
result: Some(2),
},
KernelOp {
kind: KernelOpKind::StoreGlobal,
operands: vec![0, 0, 2],
result: None,
},
],
vec![LiteralValue::U32(7), LiteralValue::U32(0)],
);
let out = identity_elim(&desc);
assert_eq!(out.body.ops[3].kind, KernelOpKind::StoreGlobal);
assert_eq!(out.body.ops[3].operands, vec![0, 0, 0]);
}
#[test]
fn add_zero_x_eliminates_to_x() {
let desc = empty_desc(
vec![
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(0),
}, KernelOp {
kind: KernelOpKind::Literal,
operands: vec![1],
result: Some(1),
}, KernelOp {
kind: KernelOpKind::BinOpKind(BinOp::Add),
operands: vec![0, 1],
result: Some(2),
},
KernelOp {
kind: KernelOpKind::StoreGlobal,
operands: vec![0, 0, 2],
result: None,
},
],
vec![LiteralValue::U32(0), LiteralValue::U32(7)],
);
let out = identity_elim(&desc);
assert_eq!(out.body.ops[3].operands, vec![0, 0, 1]);
}
#[test]
fn fma_zero_times_infinity_must_not_fold_to_addend() {
let desc = empty_desc(
vec![
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(0),
},
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![1],
result: Some(1),
},
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![2],
result: Some(2),
},
KernelOp {
kind: KernelOpKind::Fma,
operands: vec![0, 1, 2],
result: Some(3),
},
KernelOp {
kind: KernelOpKind::StoreGlobal,
operands: vec![0, 0, 3],
result: None,
},
],
vec![
LiteralValue::F32(0.0),
LiteralValue::F32(f32::INFINITY),
LiteralValue::F32(7.0),
],
);
let out = identity_elim(&desc);
let store = out.body.ops.last().expect("store op present");
assert_eq!(store.kind, KernelOpKind::StoreGlobal);
assert_eq!(
store.operands,
vec![0, 0, 3],
"Fma(0.0, inf, c) was folded to its addend c, a miscompile: \
0.0 * inf = NaN, so the result is NaN, not c"
);
}
#[test]
fn mul_x_one_eliminates_to_x() {
let desc = empty_desc(
vec![
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(0),
}, KernelOp {
kind: KernelOpKind::Literal,
operands: vec![1],
result: Some(1),
}, KernelOp {
kind: KernelOpKind::BinOpKind(BinOp::Mul),
operands: vec![0, 1],
result: Some(2),
},
KernelOp {
kind: KernelOpKind::StoreGlobal,
operands: vec![0, 0, 2],
result: None,
},
],
vec![LiteralValue::U32(42), LiteralValue::U32(1)],
);
let out = identity_elim(&desc);
assert_eq!(out.body.ops[3].operands, vec![0, 0, 0]);
}
#[test]
fn mul_x_zero_absorbs_to_zero() {
let desc = empty_desc(
vec![
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(0),
}, KernelOp {
kind: KernelOpKind::Literal,
operands: vec![1],
result: Some(1),
}, KernelOp {
kind: KernelOpKind::BinOpKind(BinOp::Mul),
operands: vec![0, 1],
result: Some(2),
},
KernelOp {
kind: KernelOpKind::StoreGlobal,
operands: vec![0, 0, 2],
result: None,
},
],
vec![LiteralValue::U32(99), LiteralValue::U32(0)],
);
let out = identity_elim(&desc);
assert_eq!(out.body.ops[3].operands, vec![0, 0, 1]);
}
#[test]
fn bitand_x_zero_absorbs_to_zero() {
let desc = empty_desc(
vec![
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(0),
},
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![1],
result: Some(1),
}, KernelOp {
kind: KernelOpKind::BinOpKind(BinOp::BitAnd),
operands: vec![0, 1],
result: Some(2),
},
KernelOp {
kind: KernelOpKind::StoreGlobal,
operands: vec![0, 0, 2],
result: None,
},
],
vec![LiteralValue::U32(0xFF), LiteralValue::U32(0)],
);
let out = identity_elim(&desc);
assert_eq!(out.body.ops[3].operands, vec![0, 0, 1]);
}
#[test]
fn shl_x_zero_eliminates_to_x() {
let desc = empty_desc(
vec![
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(0),
},
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![1],
result: Some(1),
}, KernelOp {
kind: KernelOpKind::BinOpKind(BinOp::Shl),
operands: vec![0, 1],
result: Some(2),
},
KernelOp {
kind: KernelOpKind::StoreGlobal,
operands: vec![0, 0, 2],
result: None,
},
],
vec![LiteralValue::U32(5), LiteralValue::U32(0)],
);
let out = identity_elim(&desc);
assert_eq!(out.body.ops[3].operands, vec![0, 0, 0]);
}
#[test]
fn sub_zero_x_does_not_eliminate() {
let desc = empty_desc(
vec![
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(0),
}, KernelOp {
kind: KernelOpKind::Literal,
operands: vec![1],
result: Some(1),
}, KernelOp {
kind: KernelOpKind::BinOpKind(BinOp::Sub),
operands: vec![0, 1],
result: Some(2),
},
KernelOp {
kind: KernelOpKind::StoreGlobal,
operands: vec![0, 0, 2],
result: None,
},
],
vec![LiteralValue::U32(0), LiteralValue::U32(5)],
);
let out = identity_elim(&desc);
assert_eq!(out.body.ops[3].operands, vec![0, 0, 2]);
}
#[test]
fn div_one_x_does_not_eliminate() {
let desc = empty_desc(
vec![
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(0),
}, KernelOp {
kind: KernelOpKind::Literal,
operands: vec![1],
result: Some(1),
}, KernelOp {
kind: KernelOpKind::BinOpKind(BinOp::Div),
operands: vec![0, 1],
result: Some(2),
},
KernelOp {
kind: KernelOpKind::StoreGlobal,
operands: vec![0, 0, 2],
result: None,
},
],
vec![LiteralValue::U32(1), LiteralValue::U32(5)],
);
let out = identity_elim(&desc);
assert_eq!(out.body.ops[3].operands, vec![0, 0, 2]);
}
#[test]
fn bitand_x_x_eliminates_to_x() {
let desc = empty_desc(
vec![
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(0),
},
KernelOp {
kind: KernelOpKind::BinOpKind(BinOp::BitAnd),
operands: vec![0, 0],
result: Some(1),
},
KernelOp {
kind: KernelOpKind::StoreGlobal,
operands: vec![0, 0, 1],
result: None,
},
],
vec![LiteralValue::U32(0xCAFE)],
);
let out = identity_elim(&desc);
assert_eq!(out.body.ops[2].operands, vec![0, 0, 0]);
}
#[test]
fn min_x_x_eliminates_to_x() {
let desc = empty_desc(
vec![
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(0),
},
KernelOp {
kind: KernelOpKind::BinOpKind(BinOp::Min),
operands: vec![0, 0],
result: Some(1),
},
KernelOp {
kind: KernelOpKind::StoreGlobal,
operands: vec![0, 0, 1],
result: None,
},
],
vec![LiteralValue::U32(5)],
);
let out = identity_elim(&desc);
assert_eq!(out.body.ops[2].operands, vec![0, 0, 0]);
}
#[test]
fn chained_eliminations_compose() {
let desc = empty_desc(
vec![
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(0),
},
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![1],
result: Some(1),
},
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![2],
result: Some(2),
},
KernelOp {
kind: KernelOpKind::BinOpKind(BinOp::Add),
operands: vec![0, 1],
result: Some(3),
},
KernelOp {
kind: KernelOpKind::BinOpKind(BinOp::Mul),
operands: vec![3, 2],
result: Some(4),
},
KernelOp {
kind: KernelOpKind::StoreGlobal,
operands: vec![0, 0, 4],
result: None,
},
],
vec![
LiteralValue::U32(42),
LiteralValue::U32(0),
LiteralValue::U32(1),
],
);
let out = identity_elim(&desc);
assert_eq!(out.body.ops[5].operands, vec![0, 0, 0]);
}
#[test]
fn non_identity_literals_do_nothing() {
let desc = empty_desc(
vec![
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(0),
},
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![1],
result: Some(1),
}, KernelOp {
kind: KernelOpKind::BinOpKind(BinOp::Add),
operands: vec![0, 1],
result: Some(2),
},
KernelOp {
kind: KernelOpKind::StoreGlobal,
operands: vec![0, 0, 2],
result: None,
},
],
vec![LiteralValue::U32(7), LiteralValue::U32(5)],
);
let out = identity_elim(&desc);
assert_eq!(out.body.ops[3].operands, vec![0, 0, 2]);
}
#[test]
fn float_zero_works_for_add() {
let desc = empty_desc(
vec![
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(0),
},
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![1],
result: Some(1),
}, KernelOp {
kind: KernelOpKind::BinOpKind(BinOp::Add),
operands: vec![0, 1],
result: Some(2),
},
KernelOp {
kind: KernelOpKind::StoreGlobal,
operands: vec![0, 0, 2],
result: None,
},
],
vec![
LiteralValue::F32(std::f32::consts::PI),
LiteralValue::F32(0.0),
],
);
let out = identity_elim(&desc);
assert_eq!(out.body.ops[3].operands, vec![0, 0, 0]);
}
#[test]
fn idempotent_on_already_eliminated() {
let desc = empty_desc(
vec![
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(0),
},
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![1],
result: Some(1),
}, KernelOp {
kind: KernelOpKind::BinOpKind(BinOp::Add),
operands: vec![0, 1],
result: Some(2),
},
KernelOp {
kind: KernelOpKind::StoreGlobal,
operands: vec![0, 0, 2],
result: None,
},
],
vec![LiteralValue::U32(7), LiteralValue::U32(0)],
);
let once = identity_elim(&desc);
let twice = identity_elim(&once);
assert_eq!(once.body.ops.len(), twice.body.ops.len());
assert_eq!(once.body.ops[3].operands, twice.body.ops[3].operands);
}
#[test]
fn empty_kernel_is_noop() {
let desc = empty_desc(vec![], vec![]);
let out = identity_elim(&desc);
assert!(out.body.ops.is_empty());
}
#[test]
fn select_with_true_cond_picks_then_branch() {
let desc = empty_desc(
vec![
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(0),
},
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![1],
result: Some(1),
},
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![2],
result: Some(2),
},
KernelOp {
kind: KernelOpKind::Select,
operands: vec![0, 1, 2],
result: Some(3),
},
KernelOp {
kind: KernelOpKind::StoreGlobal,
operands: vec![0, 0, 3],
result: None,
},
],
vec![
LiteralValue::Bool(true),
LiteralValue::U32(7),
LiteralValue::U32(99),
],
);
let out = identity_elim(&desc);
assert_eq!(out.body.ops[4].operands, vec![0, 0, 1]);
}
#[test]
fn select_with_false_cond_picks_else_branch() {
let desc = empty_desc(
vec![
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(0),
}, KernelOp {
kind: KernelOpKind::Literal,
operands: vec![1],
result: Some(1),
}, KernelOp {
kind: KernelOpKind::Literal,
operands: vec![2],
result: Some(2),
}, KernelOp {
kind: KernelOpKind::Select,
operands: vec![0, 1, 2],
result: Some(3),
},
KernelOp {
kind: KernelOpKind::StoreGlobal,
operands: vec![0, 0, 3],
result: None,
},
],
vec![
LiteralValue::Bool(false),
LiteralValue::U32(7),
LiteralValue::U32(99),
],
);
let out = identity_elim(&desc);
assert_eq!(out.body.ops[4].operands, vec![0, 0, 2]);
}
#[test]
fn select_with_non_literal_cond_unchanged() {
let desc = empty_desc(
vec![
KernelOp {
kind: KernelOpKind::LocalInvocationId,
operands: vec![0],
result: Some(0),
},
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(1),
},
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![1],
result: Some(2),
},
KernelOp {
kind: KernelOpKind::Select,
operands: vec![0, 1, 2],
result: Some(3),
},
KernelOp {
kind: KernelOpKind::StoreGlobal,
operands: vec![0, 0, 3],
result: None,
},
],
vec![LiteralValue::U32(7), LiteralValue::U32(99)],
);
let out = identity_elim(&desc);
assert_eq!(out.body.ops[4].operands, vec![0, 0, 3]);
}
#[test]
fn fma_with_a_zero_picks_c() {
let desc = empty_desc(
vec![
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(0),
}, KernelOp {
kind: KernelOpKind::Literal,
operands: vec![1],
result: Some(1),
}, KernelOp {
kind: KernelOpKind::Literal,
operands: vec![2],
result: Some(2),
}, KernelOp {
kind: KernelOpKind::Fma,
operands: vec![0, 1, 2],
result: Some(3),
},
KernelOp {
kind: KernelOpKind::StoreGlobal,
operands: vec![0, 0, 3],
result: None,
},
],
vec![
LiteralValue::F32(0.0),
LiteralValue::F32(7.0),
LiteralValue::F32(99.0),
],
);
let out = identity_elim(&desc);
assert_eq!(out.body.ops[4].operands, vec![0, 0, 2]);
}
#[test]
fn fma_with_b_zero_picks_c() {
let desc = empty_desc(
vec![
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(0),
}, KernelOp {
kind: KernelOpKind::Literal,
operands: vec![1],
result: Some(1),
}, KernelOp {
kind: KernelOpKind::Literal,
operands: vec![2],
result: Some(2),
}, KernelOp {
kind: KernelOpKind::Fma,
operands: vec![0, 1, 2],
result: Some(3),
},
KernelOp {
kind: KernelOpKind::StoreGlobal,
operands: vec![0, 0, 3],
result: None,
},
],
vec![
LiteralValue::F32(7.0),
LiteralValue::F32(0.0),
LiteralValue::F32(99.0),
],
);
let out = identity_elim(&desc);
assert_eq!(out.body.ops[4].operands, vec![0, 0, 2]);
}
#[test]
fn fma_with_no_zero_unchanged() {
let desc = empty_desc(
vec![
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(0),
},
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![1],
result: Some(1),
},
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![2],
result: Some(2),
},
KernelOp {
kind: KernelOpKind::Fma,
operands: vec![0, 1, 2],
result: Some(3),
},
KernelOp {
kind: KernelOpKind::StoreGlobal,
operands: vec![0, 0, 3],
result: None,
},
],
vec![
LiteralValue::F32(7.0),
LiteralValue::F32(2.0),
LiteralValue::F32(99.0),
],
);
let out = identity_elim(&desc);
assert_eq!(out.body.ops[4].operands, vec![0, 0, 3]);
}
#[test]
fn select_with_non_bool_cond_unchanged() {
let desc = empty_desc(
vec![
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(0),
}, KernelOp {
kind: KernelOpKind::Literal,
operands: vec![1],
result: Some(1),
},
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![2],
result: Some(2),
},
KernelOp {
kind: KernelOpKind::Select,
operands: vec![0, 1, 2],
result: Some(3),
},
KernelOp {
kind: KernelOpKind::StoreGlobal,
operands: vec![0, 0, 3],
result: None,
},
],
vec![
LiteralValue::U32(1),
LiteralValue::U32(7),
LiteralValue::U32(99),
],
);
let out = identity_elim(&desc);
assert_eq!(out.body.ops[4].operands, vec![0, 0, 3]);
}
}