1use std::sync::Arc;
46
47use vyre_foundation::ir::model::expr::Ident;
48use vyre_foundation::ir::{
49 BufferAccess, BufferDecl, DataType, Expr, MemoryOrdering, Node, Program,
50};
51
52pub const OP_ID: &str = "vyre-primitives::graph::level_wave";
54pub const LEVEL_WAVE_WORKGROUP_SIZE: [u32; 3] = [256, 1, 1];
56
57#[must_use]
59pub const fn level_wave_dispatch_grid(lane_count: u32) -> [u32; 3] {
60 let blocks = lane_count.div_ceil(LEVEL_WAVE_WORKGROUP_SIZE[0]);
61 [if blocks == 0 { 1 } else { blocks }, 1, 1]
62}
63
64fn depth_wave_body(
65 step_body: Vec<Node>,
66 depth_buf: &str,
67 depth: Expr,
68 lane_count: u32,
69) -> Vec<Node> {
70 let lane = Expr::InvocationId { axis: 0 };
71 vec![Node::if_then(
79 Expr::lt(lane.clone(), Expr::u32(lane_count)),
80 vec![Node::if_then(
81 Expr::eq(Expr::load(depth_buf, lane), depth),
82 step_body,
83 )],
84 )]
85}
86
87#[must_use]
105pub fn level_wave_program(
106 step_body: Vec<Node>,
107 depth_buf: &str,
108 max_depth: u32,
109 lane_count: u32,
110) -> Program {
111 level_wave_program_with_buffers(step_body, depth_buf, Vec::new(), max_depth, lane_count)
112}
113
114#[must_use]
124pub fn level_wave_program_with_buffers(
125 step_body: Vec<Node>,
126 depth_buf: &str,
127 extra_buffers: Vec<BufferDecl>,
128 max_depth: u32,
129 lane_count: u32,
130) -> Program {
131 let body = if lane_count <= LEVEL_WAVE_WORKGROUP_SIZE[0] {
132 vec![Node::loop_for(
133 "__lw_depth__",
134 Expr::u32(0),
135 Expr::u32(max_depth),
136 {
137 let mut loop_body = depth_wave_body(
138 step_body.clone(),
139 depth_buf,
140 Expr::var("__lw_depth__"),
141 lane_count,
142 );
143 loop_body.push(Node::Barrier {
144 ordering: MemoryOrdering::SeqCst,
145 });
146 loop_body
147 },
148 )]
149 } else {
150 let mut waves = Vec::with_capacity(max_depth.saturating_mul(2) as usize);
151 for depth in 0..max_depth {
152 waves.extend(depth_wave_body(
153 step_body.clone(),
154 depth_buf,
155 Expr::u32(depth),
156 lane_count,
157 ));
158 if depth + 1 < max_depth {
159 waves.push(Node::Barrier {
160 ordering: MemoryOrdering::GridSync,
161 });
162 }
163 }
164 waves
165 };
166
167 let mut buffers =
168 vec![
169 BufferDecl::storage(depth_buf, 0, BufferAccess::ReadOnly, DataType::U32)
170 .with_count(lane_count),
171 ];
172 buffers.extend(extra_buffers);
173
174 Program::wrapped(
175 buffers,
176 LEVEL_WAVE_WORKGROUP_SIZE,
177 vec![Node::Region {
178 generator: Ident::from(OP_ID),
179 source_region: None,
180 body: Arc::new(body),
181 }],
182 )
183}
184
185#[cfg(any(test, feature = "cpu-parity"))]
190pub fn cpu_ref<F>(depths: &[u32], max_depth: u32, mut step_for_lane: F)
191where
192 F: FnMut(u32, u32),
193{
194 for current_depth in 0..max_depth {
195 for (lane_idx, lane_depth) in depths.iter().enumerate() {
196 if *lane_depth == current_depth {
197 step_for_lane(lane_idx as u32, current_depth);
198 }
199 }
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 fn entry_region_body(program: &Program) -> &[Node] {
208 match &program.entry()[0] {
209 Node::Region { body, .. } => body.as_slice(),
210 other => panic!("expected wrapped level-wave region, got {other:?}"),
211 }
212 }
213
214 fn contains_grid_sync(nodes: &[Node]) -> bool {
215 nodes.iter().any(|node| match node {
216 Node::Barrier {
217 ordering: MemoryOrdering::GridSync,
218 } => true,
219 Node::Block(children) | Node::Loop { body: children, .. } => {
220 contains_grid_sync(children)
221 }
222 Node::If {
223 then, otherwise, ..
224 } => contains_grid_sync(then) || contains_grid_sync(otherwise),
225 Node::Region { body, .. } => contains_grid_sync(body),
226 _ => false,
227 })
228 }
229
230 fn contains_loop(nodes: &[Node]) -> bool {
231 nodes.iter().any(|node| match node {
232 Node::Loop { .. } => true,
233 Node::Block(children) => contains_loop(children),
234 Node::If {
235 then, otherwise, ..
236 } => contains_loop(then) || contains_loop(otherwise),
237 Node::Region { body, .. } => contains_loop(body),
238 _ => false,
239 })
240 }
241
242 #[test]
243 fn cpu_ref_visits_each_lane_at_its_depth() {
244 let depths = vec![0u32, 1, 2, 1, 0];
245 let mut visits: Vec<(u32, u32)> = Vec::new();
246 cpu_ref(&depths, 3, |lane, depth| visits.push((lane, depth)));
247 assert_eq!(visits.len(), depths.len());
249 for (idx, &(lane, depth)) in visits.iter().enumerate() {
250 assert_eq!(depth, depths[lane as usize]);
251 if idx > 0 {
253 assert!(depth >= visits[idx - 1].1);
254 }
255 }
256 }
257
258 #[test]
259 fn dispatch_grid_packs_lane_count_into_workgroups() {
260 assert_eq!(level_wave_dispatch_grid(0), [1, 1, 1]);
261 assert_eq!(level_wave_dispatch_grid(1), [1, 1, 1]);
262 assert_eq!(level_wave_dispatch_grid(256), [1, 1, 1]);
263 assert_eq!(level_wave_dispatch_grid(257), [2, 1, 1]);
264 assert_eq!(level_wave_dispatch_grid(1029), [5, 1, 1]);
265 }
266
267 #[test]
268 fn program_shape_matches_contract() {
269 let step = vec![Node::store("out", Expr::u32(0), Expr::u32(1))];
270 let program = level_wave_program(step, "depths", 8, 64);
271 assert_eq!(program.workgroup_size(), LEVEL_WAVE_WORKGROUP_SIZE);
272 assert!(
273 program.buffers.iter().any(|b| b.name() == "depths"),
274 "depth buffer must be declared"
275 );
276 assert!(!contains_grid_sync(entry_region_body(&program)));
277 }
278
279 #[test]
280 fn program_with_buffers_declares_depth_then_caller_buffers() {
281 let step = vec![Node::store("out", Expr::u32(0), Expr::u32(1))];
282 let extra = vec![
283 BufferDecl::storage("kinds", 1, BufferAccess::ReadOnly, DataType::U32).with_count(4),
284 BufferDecl::storage("out", 2, BufferAccess::ReadWrite, DataType::U32).with_count(4),
285 ];
286 let program = level_wave_program_with_buffers(step, "depths", extra, 8, 4);
287 let names: Vec<&str> = program.buffers.iter().map(|b| b.name()).collect();
288 assert_eq!(
289 names,
290 vec!["depths", "kinds", "out"],
291 "depth buffer is bound first (index 0), then the caller's extra buffers in order"
292 );
293 let plain = level_wave_program(
295 vec![Node::store("out", Expr::u32(0), Expr::u32(1))],
296 "depths",
297 8,
298 4,
299 );
300 assert_eq!(
301 plain.buffers.len(),
302 1,
303 "plain level-wave declares only depths"
304 );
305 }
306
307 #[test]
308 fn multi_block_program_uses_top_level_grid_sync_waves() {
309 let step = vec![Node::store(
310 "out",
311 Expr::InvocationId { axis: 0 },
312 Expr::u32(1),
313 )];
314 let program = level_wave_program(step, "depths", 4, LEVEL_WAVE_WORKGROUP_SIZE[0] + 1);
315 let body = entry_region_body(&program);
316 assert!(contains_grid_sync(body));
317 assert!(
318 !contains_loop(body),
319 "multi-block level-wave must expose GridSync at split-visible depth-wave boundaries"
320 );
321 assert_eq!(
322 body.iter()
323 .filter(|node| matches!(
324 node,
325 Node::Barrier {
326 ordering: MemoryOrdering::GridSync,
327 }
328 ))
329 .count(),
330 3
331 );
332 }
333}