use crate::{KernelBody, KernelDescriptor, KernelOpKind};
#[must_use]
pub fn drop_unused_child_bodies(desc: &KernelDescriptor) -> KernelDescriptor {
let mut out = desc.clone();
out.body = drop_unused_child_bodies_body(out.body);
out
}
fn drop_unused_child_bodies_body(mut body: KernelBody) -> KernelBody {
let recursed_children: Vec<KernelBody> = body
.child_bodies
.into_iter()
.map(drop_unused_child_bodies_body)
.collect();
body.child_bodies = Vec::new();
let mut referenced = vec![false; recursed_children.len()];
let mut referenced_count = 0usize;
for op in &body.ops {
for (pos, &val) in op.operands.iter().enumerate() {
if is_child_body_idx(&op.kind, pos) {
if let Some(slot) = referenced.get_mut(val as usize) {
if !*slot {
*slot = true;
referenced_count += 1;
}
}
}
}
}
if referenced_count == recursed_children.len() {
body.child_bodies = recursed_children;
return body;
}
let mut remap = vec![u32::MAX; recursed_children.len()];
let mut new_children = Vec::with_capacity(referenced_count);
for (old_idx, child) in recursed_children.into_iter().enumerate() {
if referenced[old_idx] {
let new_idx = new_children.len() as u32;
remap[old_idx] = new_idx;
new_children.push(child);
}
}
let old_ops = std::mem::take(&mut body.ops);
body.ops = old_ops
.into_iter()
.map(|mut op| {
for pos in 0..op.operands.len() {
if is_child_body_idx(&op.kind, pos) {
let val = &mut op.operands[pos];
if let Some(&new) = remap.get(*val as usize) {
*val = new;
}
}
}
op
})
.collect();
body.child_bodies = new_children;
body
}
fn is_child_body_idx(kind: &KernelOpKind, pos: usize) -> bool {
use KernelOpKind::*;
match kind {
StructuredIfThen => pos == 1,
StructuredIfThenElse => pos == 1 || pos == 2,
StructuredForLoop { .. } => pos == 2,
StructuredBlock | Region { .. } => pos == 0,
_ => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
BindingLayout, Dispatch, KernelBody, KernelDescriptor, KernelOp, KernelOpKind, LiteralValue,
};
fn empty_desc(ops: Vec<KernelOp>, child_bodies: Vec<KernelBody>) -> KernelDescriptor {
KernelDescriptor {
id: "k".into(),
bindings: BindingLayout { slots: vec![] },
dispatch: Dispatch::new(1, 1, 1),
body: KernelBody {
ops,
child_bodies,
literals: vec![LiteralValue::U32(1)],
},
}
}
fn tiny_body() -> KernelBody {
KernelBody {
ops: vec![KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(0),
}],
child_bodies: vec![],
literals: vec![LiteralValue::U32(7)],
}
}
#[test]
fn no_children_no_op() {
let desc = empty_desc(vec![], vec![]);
let out = drop_unused_child_bodies(&desc);
assert!(out.body.child_bodies.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::StructuredIfThen,
operands: vec![0, 0], result: None,
},
KernelOp {
kind: KernelOpKind::StructuredIfThen,
operands: vec![0, 1], result: None,
},
],
vec![tiny_body(), tiny_body()],
);
let out = drop_unused_child_bodies(&desc);
assert_eq!(out.body.child_bodies.len(), 2);
}
#[test]
fn unused_child_body_dropped() {
let desc = empty_desc(
vec![
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(0),
},
KernelOp {
kind: KernelOpKind::StructuredIfThen,
operands: vec![0, 1],
result: None,
},
],
vec![tiny_body(), tiny_body()],
);
let out = drop_unused_child_bodies(&desc);
assert_eq!(out.body.child_bodies.len(), 1);
assert_eq!(out.body.ops[1].operands, vec![0, 0]);
}
#[test]
fn middle_child_dropped_with_renumber() {
let desc = empty_desc(
vec![
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(0),
},
KernelOp {
kind: KernelOpKind::StructuredIfThen,
operands: vec![0, 0], result: None,
},
KernelOp {
kind: KernelOpKind::StructuredIfThen,
operands: vec![0, 2], result: None,
},
],
vec![tiny_body(), tiny_body(), tiny_body()],
);
let out = drop_unused_child_bodies(&desc);
assert_eq!(out.body.child_bodies.len(), 2);
assert_eq!(out.body.ops[1].operands, vec![0, 0]);
assert_eq!(out.body.ops[2].operands, vec![0, 1]);
}
#[test]
fn structured_if_then_else_keeps_both_arms() {
let desc = empty_desc(
vec![
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(0),
},
KernelOp {
kind: KernelOpKind::StructuredIfThenElse,
operands: vec![0, 0, 1], result: None,
},
],
vec![tiny_body(), tiny_body()],
);
let out = drop_unused_child_bodies(&desc);
assert_eq!(out.body.child_bodies.len(), 2);
}
#[test]
fn idempotent() {
let desc = empty_desc(
vec![
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(0),
},
KernelOp {
kind: KernelOpKind::StructuredIfThen,
operands: vec![0, 0],
result: None,
},
],
vec![tiny_body(), tiny_body()],
);
let once = drop_unused_child_bodies(&desc);
let twice = drop_unused_child_bodies(&once);
assert_eq!(once.body.child_bodies.len(), twice.body.child_bodies.len());
assert_eq!(once.body.ops, twice.body.ops);
}
#[test]
fn structured_for_loop_pos_2_recognized() {
let desc = empty_desc(
vec![
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(0),
},
KernelOp {
kind: KernelOpKind::Literal,
operands: vec![0],
result: Some(1),
},
KernelOp {
kind: KernelOpKind::StructuredForLoop {
loop_var: std::sync::Arc::from("i"),
},
operands: vec![0, 1, 1], result: None,
},
],
vec![tiny_body(), tiny_body()],
);
let out = drop_unused_child_bodies(&desc);
assert_eq!(out.body.child_bodies.len(), 1);
assert_eq!(out.body.ops[2].operands, vec![0, 1, 0]);
}
}