use super::body_index::BodyIndex;
use crate::{KernelBody, KernelDescriptor, KernelOpKind};
#[must_use]
pub fn loop_zero_iter(desc: &KernelDescriptor) -> KernelDescriptor {
let mut out = desc.clone();
out.body = loop_zero_iter_body(out.body);
out
}
fn loop_zero_iter_body(mut body: KernelBody) -> KernelBody {
let index = BodyIndex::new(&body);
let mut drop_indices: Vec<usize> = Vec::new();
for (idx, op) in body.ops.iter().enumerate() {
if !matches!(op.kind, KernelOpKind::StructuredForLoop { .. }) {
continue;
}
if op.operands.len() < 2 {
continue;
}
let lo_id = op.operands[0];
let hi_id = op.operands[1];
let lo = match index.u32_lit(&body, lo_id) {
Some(v) => v,
None => continue,
};
let hi = match index.u32_lit(&body, hi_id) {
Some(v) => v,
None => continue,
};
if lo >= hi {
drop_indices.push(idx);
}
}
for idx in drop_indices.into_iter().rev() {
let child_idx = body.ops[idx].operands.get(2).copied().unwrap_or(u32::MAX);
body.ops.remove(idx);
if let Some(child) = body.child_bodies.get_mut(child_idx as usize) {
*child = KernelBody {
ops: Vec::new(),
child_bodies: Vec::new(),
literals: Vec::new(),
};
}
}
body.child_bodies = body
.child_bodies
.into_iter()
.map(loop_zero_iter_body)
.collect();
body
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{BindingLayout, Dispatch, KernelOp, LiteralValue};
fn empty_body() -> KernelBody {
KernelBody {
ops: Vec::new(),
child_bodies: Vec::new(),
literals: Vec::new(),
}
}
fn descriptor_with(body: KernelBody) -> KernelDescriptor {
KernelDescriptor {
id: "loop_zero_iter_test".into(),
bindings: BindingLayout { slots: Vec::new() },
dispatch: Dispatch::new(1, 1, 1),
body,
}
}
fn lit_u32(body: &mut KernelBody, value: u32, result: u32) {
let pool_idx = body.literals.len() as u32;
body.literals.push(LiteralValue::U32(value));
body.ops.push(KernelOp {
kind: KernelOpKind::Literal,
operands: vec![pool_idx],
result: Some(result),
});
}
fn for_loop(body: &mut KernelBody, lo_id: u32, hi_id: u32, child_idx: u32, var: &str) {
body.ops.push(KernelOp {
kind: KernelOpKind::StructuredForLoop {
loop_var: std::sync::Arc::from(var),
},
operands: vec![lo_id, hi_id, child_idx],
result: None,
});
}
#[test]
fn equal_bounds_drop_loop() {
let mut body = empty_body();
lit_u32(&mut body, 5, 0); lit_u32(&mut body, 5, 1); for_loop(&mut body, 0, 1, 0, "i");
body.child_bodies.push(empty_body());
let desc = loop_zero_iter(&descriptor_with(body));
assert!(
!desc
.body
.ops
.iter()
.any(|op| matches!(op.kind, KernelOpKind::StructuredForLoop { .. })),
"Fix: loop with lo == hi must be dropped"
);
}
#[test]
fn lo_greater_than_hi_drops_loop() {
let mut body = empty_body();
lit_u32(&mut body, 10, 0);
lit_u32(&mut body, 5, 1);
for_loop(&mut body, 0, 1, 0, "i");
body.child_bodies.push(empty_body());
let desc = loop_zero_iter(&descriptor_with(body));
assert!(
!desc
.body
.ops
.iter()
.any(|op| matches!(op.kind, KernelOpKind::StructuredForLoop { .. })),
"Fix: loop with lo > hi must be dropped"
);
}
#[test]
fn live_loop_kept() {
let mut body = empty_body();
lit_u32(&mut body, 0, 0);
lit_u32(&mut body, 4, 1);
for_loop(&mut body, 0, 1, 0, "i");
body.child_bodies.push(empty_body());
let desc = loop_zero_iter(&descriptor_with(body));
assert!(
desc.body
.ops
.iter()
.any(|op| matches!(op.kind, KernelOpKind::StructuredForLoop { .. })),
"Fix: live loop (lo < hi) must NOT be dropped"
);
}
#[test]
fn non_literal_bounds_kept() {
let mut body = empty_body();
body.ops.push(KernelOp {
kind: KernelOpKind::GlobalInvocationId,
operands: vec![0], result: Some(0),
});
body.ops.push(KernelOp {
kind: KernelOpKind::LocalInvocationId,
operands: vec![1], result: Some(1),
});
for_loop(&mut body, 0, 1, 0, "i");
body.child_bodies.push(empty_body());
let desc = loop_zero_iter(&descriptor_with(body));
assert!(
desc.body
.ops
.iter()
.any(|op| matches!(op.kind, KernelOpKind::StructuredForLoop { .. })),
"Fix: non-literal-bound loops must be left alone"
);
}
#[test]
fn rewrite_is_idempotent() {
let mut body = empty_body();
lit_u32(&mut body, 3, 0);
lit_u32(&mut body, 3, 1);
for_loop(&mut body, 0, 1, 0, "i");
body.child_bodies.push(empty_body());
let desc = descriptor_with(body);
let once = loop_zero_iter(&desc);
let twice = loop_zero_iter(&once);
assert_eq!(once, twice);
}
#[test]
fn recurses_into_child_bodies() {
let mut child = empty_body();
lit_u32(&mut child, 7, 10);
lit_u32(&mut child, 7, 11);
for_loop(&mut child, 10, 11, 0, "j");
child.child_bodies.push(empty_body());
let mut body = empty_body();
body.child_bodies.push(child);
let desc = loop_zero_iter(&descriptor_with(body));
let child_out = &desc.body.child_bodies[0];
assert!(
!child_out
.ops
.iter()
.any(|op| matches!(op.kind, KernelOpKind::StructuredForLoop { .. })),
"Fix: zero-iter loops in child bodies must drop too"
);
}
}