use crate::{KernelBody, KernelDescriptor, KernelOpKind};
#[must_use]
pub fn drop_unused_literals(desc: &KernelDescriptor) -> KernelDescriptor {
let mut out = desc.clone();
out.body = drop_unused_literals_body(out.body);
out
}
fn drop_unused_literals_body(mut body: KernelBody) -> KernelBody {
let mut referenced = vec![false; body.literals.len()];
let mut referenced_count = 0usize;
for op in &body.ops {
if matches!(op.kind, KernelOpKind::Literal) {
if let Some(&pool_idx) = op.operands.first() {
let pool_idx = pool_idx as usize;
if let Some(slot) = referenced.get_mut(pool_idx) {
if !*slot {
*slot = true;
referenced_count += 1;
}
}
}
}
}
if referenced_count == body.literals.len() {
body.child_bodies = body
.child_bodies
.into_iter()
.map(drop_unused_literals_body)
.collect();
return body;
}
let mut remap = vec![u32::MAX; body.literals.len()];
let mut new_literals = Vec::with_capacity(referenced_count);
let old_literals = std::mem::take(&mut body.literals);
for (old_idx, lit) in old_literals.into_iter().enumerate() {
if referenced[old_idx] {
let new_idx = new_literals.len() as u32;
remap[old_idx] = new_idx;
new_literals.push(lit);
}
}
let old_ops = std::mem::take(&mut body.ops);
body.ops = old_ops
.into_iter()
.map(|mut op| {
if matches!(op.kind, KernelOpKind::Literal) {
if let Some(&old) = op.operands.first() {
if let Some(&new) = remap.get(old as usize) {
op.operands[0] = new;
return op;
}
}
}
op
})
.collect();
body.child_bodies = body
.child_bodies
.into_iter()
.map(drop_unused_literals_body)
.collect();
body.literals = new_literals;
body
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
BindingLayout, Dispatch, KernelBody, KernelDescriptor, KernelOp, KernelOpKind, LiteralValue,
};
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 empty_kernel_no_op() {
let desc = empty_desc(vec![], vec![]);
let out = drop_unused_literals(&desc);
assert!(out.body.literals.is_empty());
}
#[test]
fn all_referenced_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),
},
],
vec![LiteralValue::U32(7), LiteralValue::U32(99)],
);
let out = drop_unused_literals(&desc);
assert_eq!(out.body.literals.len(), 2);
assert_eq!(out.body.literals[0], LiteralValue::U32(7));
assert_eq!(out.body.literals[1], LiteralValue::U32(99));
}
#[test]
fn unused_entries_dropped_and_referenced_renumbered() {
let desc = empty_desc(
vec![
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(0),
},
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![2],
result: Some(1),
},
],
vec![
LiteralValue::U32(10), LiteralValue::U32(20), LiteralValue::U32(30), LiteralValue::U32(40), ],
);
let out = drop_unused_literals(&desc);
assert_eq!(out.body.literals.len(), 2);
assert_eq!(out.body.literals[0], LiteralValue::U32(10));
assert_eq!(out.body.literals[1], LiteralValue::U32(30));
assert_eq!(out.body.ops[0].operands, vec![0]);
assert_eq!(out.body.ops[1].operands, vec![1]);
}
#[test]
fn drops_when_no_literal_ops_reference_pool() {
let desc = empty_desc(vec![], vec![LiteralValue::U32(1), LiteralValue::U32(2)]);
let out = drop_unused_literals(&desc);
assert!(out.body.literals.is_empty());
}
#[test]
fn idempotent() {
let desc = empty_desc(
vec![KernelOp {
kind: KernelOpKind::Literal,
operands: vec![1],
result: Some(0),
}],
vec![LiteralValue::U32(1), LiteralValue::U32(99)],
);
let once = drop_unused_literals(&desc);
let twice = drop_unused_literals(&once);
assert_eq!(once.body.literals, twice.body.literals);
assert_eq!(once.body.ops, twice.body.ops);
}
#[test]
fn child_body_pool_processed_independently() {
let desc = KernelDescriptor {
id: "k".into(),
bindings: BindingLayout { slots: vec![] },
dispatch: Dispatch::new(1, 1, 1),
body: KernelBody {
ops: vec![],
child_bodies: vec![KernelBody {
ops: vec![KernelOp {
kind: KernelOpKind::Literal,
operands: vec![1],
result: Some(0),
}],
child_bodies: vec![],
literals: vec![
LiteralValue::U32(5), LiteralValue::U32(8), ],
}],
literals: vec![],
},
};
let out = drop_unused_literals(&desc);
let child = &out.body.child_bodies[0];
assert_eq!(child.literals.len(), 1);
assert_eq!(child.literals[0], LiteralValue::U32(8));
assert_eq!(child.ops[0].operands, vec![0]); }
#[test]
fn out_of_range_pool_index_treated_as_unreferenced() {
let desc = empty_desc(
vec![KernelOp {
kind: KernelOpKind::Literal,
operands: vec![99],
result: Some(0),
}],
vec![LiteralValue::U32(7)],
);
let out = drop_unused_literals(&desc);
assert!(out.body.literals.is_empty());
}
}