use vyre_foundation::ir::BinOp;
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_raw = op.operands[0];
let b_raw = op.operands[1];
let c_raw = op.operands[2];
let a = resolve(a_raw, &id_remap);
let b = resolve(b_raw, &id_remap);
let c = resolve(c_raw, &id_remap);
let Some(rid) = op.result else { continue };
let a_zero = lit_value.get(&a).map(is_numeric_zero).unwrap_or(false);
let b_zero = lit_value.get(&b).map(is_numeric_zero).unwrap_or(false);
if a_zero || b_zero {
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);
let lhs_is_zero = lhs_lit.map(is_numeric_zero).unwrap_or(false);
let rhs_is_zero = rhs_lit.map(is_numeric_zero).unwrap_or(false);
let lhs_is_one = lhs_lit.map(is_numeric_one).unwrap_or(false);
let rhs_is_one = rhs_lit.map(is_numeric_one).unwrap_or(false);
if lhs_id == rhs_id {
match op {
BinOp::BitAnd | BinOp::BitOr | BinOp::Min | BinOp::Max => return Some(lhs_id),
_ => {}
}
}
let right_identity_when_zero = matches!(
op,
BinOp::Add
| BinOp::Sub
| BinOp::WrappingAdd
| BinOp::WrappingSub
| BinOp::BitOr
| BinOp::BitXor
| BinOp::Shl
| BinOp::Shr
);
let right_identity_when_one = matches!(op, BinOp::Mul | BinOp::Div);
if (right_identity_when_zero && rhs_is_zero) || (right_identity_when_one && rhs_is_one) {
return Some(lhs_id);
}
let left_identity_when_zero = matches!(
op,
BinOp::Add | BinOp::WrappingAdd | BinOp::BitOr | BinOp::BitXor
);
let left_identity_when_one = matches!(op, BinOp::Mul);
if (left_identity_when_zero && lhs_is_zero) || (left_identity_when_one && lhs_is_one) {
return Some(rhs_id);
}
let absorbs_to_zero = matches!(op, BinOp::Mul | BinOp::BitAnd);
if absorbs_to_zero {
if rhs_is_zero {
return Some(rhs_id);
}
if lhs_is_zero {
return Some(lhs_id);
}
}
None
}
fn is_numeric_zero(v: &LiteralValue) -> bool {
match v {
LiteralValue::U32(0) => true,
LiteralValue::I32(0) => true,
LiteralValue::F32(f) => *f == 0.0,
_ => false,
}
}
fn is_numeric_one(v: &LiteralValue) -> bool {
match v {
LiteralValue::U32(1) => true,
LiteralValue::I32(1) => true,
LiteralValue::F32(f) => *f == 1.0,
_ => false,
}
}
fn operand_is_result_reference(kind: &KernelOpKind, pos: usize) -> bool {
use KernelOpKind::*;
match kind {
Literal => false,
LocalInvocationId | GlobalInvocationId | WorkgroupId => false,
SubgroupLocalId | SubgroupSize | LoopIndex { .. } => false,
LoopCarrier { .. } | LoopCarrierEnd { .. } => pos == 0,
LoopCarrierFinal { .. } => false,
LoadGlobal | LoadShared | LoadConstant => pos != 0,
BufferLength => false,
StoreGlobal | StoreShared => pos != 0,
BinOpKind(_) | UnOpKind(_) | Fma | MatrixMma { .. } | Select | Cast { .. } => true,
Atomic { .. } => pos != 0,
SubgroupBallot | SubgroupShuffle | SubgroupAdd => true,
StructuredIfThen | StructuredIfThenElse => pos == 0,
StructuredForLoop { .. } => pos != 2,
StructuredBlock | Region { .. } => false,
Return | Barrier { .. } => false,
AsyncLoad { .. } | AsyncStore { .. } => pos >= 2,
AsyncWait { .. } => false,
Trap { .. } => pos == 0,
Resume { .. } => false,
IndirectDispatch { .. } => false,
Call { .. } => true,
OpaqueExpr { .. } | OpaqueNode { .. } => true,
}
}
#[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 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]);
}
}