1use std::sync::Arc;
31
32use vyre_foundation::ir::model::expr::{GeneratorRef, Ident};
33use vyre_foundation::ir::{BufferAccess, BufferDecl, DataType, Expr, Node, Program};
34
35pub const OP_ID: &str = "vyre-primitives::graph::dominator_tree";
37const INIT_PHASE_OP_ID: &str = "vyre-primitives::graph::dominator_tree::init_state";
38const DEPTH_PHASE_OP_ID: &str = "vyre-primitives::graph::dominator_tree::recompute_depth";
39const INTERSECT_PHASE_OP_ID: &str =
40 "vyre-primitives::graph::dominator_tree::intersect_predecessors";
41
42pub const IDOM_NONE: u32 = u32::MAX;
44
45#[derive(Debug, Clone, PartialEq, Eq)]
47#[non_exhaustive]
48pub enum DominatorTreeError {
49 EntryOutOfRange {
51 entry: u32,
53 node_count: u32,
55 },
56 BadOffsets {
58 message: String,
60 },
61 TargetOutOfRange {
63 index: usize,
65 target: u32,
67 node_count: u32,
69 },
70 NonMonotonicOffsets {
72 index: usize,
74 },
75}
76
77#[derive(Clone, Copy, Debug, Eq, PartialEq)]
79pub struct DominatorTreeLayout {
80 pub node_count: u32,
82 pub edge_count: u32,
84 pub pred_edge_count: u32,
86 pub idom_words: usize,
88 pub depth_words: usize,
90}
91
92#[must_use]
103pub fn dominator_tree_program(
104 node_count: u32,
105 edge_count: u32,
106 pred_edge_count: u32,
107 idom_out: &str,
108) -> Program {
109 match try_dominator_tree_program(node_count, edge_count, pred_edge_count, idom_out) {
110 Ok(p) => p,
111 Err(_) => inert_dominator_tree_program(idom_out),
112 }
113}
114
115pub fn try_dominator_tree_program(
118 node_count: u32,
119 edge_count: u32,
120 pred_edge_count: u32,
121 idom_out: &str,
122) -> Result<Program, String> {
123 if node_count == u32::MAX {
124 return Err(
125 "dominator_tree node_count == u32::MAX collides with IDOM_NONE sentinel. \
126 Fix: shard the graph before dispatch."
127 .to_string(),
128 );
129 }
130
131 let offset_count = node_count.checked_add(1).ok_or_else(|| {
132 format!(
133 "dominator_tree node_count={node_count} overflows CSR offset buffer count. \
134 Fix: shard the graph before GPU dispatch."
135 )
136 })?;
137
138 let lane0 = Expr::eq(Expr::InvocationId { axis: 0 }, Expr::u32(0));
139
140 let depth_buf = "dt_depth";
146 let init_state = child_phase(
147 INIT_PHASE_OP_ID,
148 vec![
149 Node::loop_for(
151 "i",
152 Expr::u32(0),
153 Expr::u32(node_count),
154 vec![Node::store(idom_out, Expr::var("i"), Expr::u32(IDOM_NONE))],
155 ),
156 Node::store(idom_out, Expr::u32(0), Expr::u32(0)),
158 Node::loop_for(
159 "i",
160 Expr::u32(0),
161 Expr::u32(node_count),
162 vec![Node::store(depth_buf, Expr::var("i"), Expr::u32(0))],
163 ),
164 ],
165 );
166
167 let recompute_depth = child_phase(
170 DEPTH_PHASE_OP_ID,
171 vec![Node::loop_for(
172 "v",
173 Expr::u32(0),
174 Expr::u32(node_count),
175 vec![
176 Node::let_bind("d", Expr::u32(0)),
177 Node::let_bind("cur", Expr::var("v")),
178 Node::loop_for(
179 "depth_step",
180 Expr::u32(0),
181 Expr::u32(node_count),
182 vec![Node::if_then(
183 Expr::ne(Expr::var("cur"), Expr::u32(0)),
184 vec![
185 Node::let_bind("parent", Expr::load(idom_out, Expr::var("cur"))),
186 Node::if_then(
187 Expr::and(
188 Expr::ne(Expr::var("parent"), Expr::var("cur")),
189 Expr::ne(Expr::var("parent"), Expr::u32(IDOM_NONE)),
190 ),
191 vec![
192 Node::assign("d", Expr::add(Expr::var("d"), Expr::u32(1))),
193 Node::assign("cur", Expr::var("parent")),
194 ],
195 ),
196 ],
197 )],
198 ),
199 Node::store(depth_buf, Expr::var("v"), Expr::var("d")),
200 ],
201 )],
202 );
203
204 let body = vec![
205 Node::let_bind("changed", Expr::u32(0)),
207 recompute_depth.clone(),
209 child_phase(
211 INTERSECT_PHASE_OP_ID,
212 vec![Node::loop_for(
213 "v",
214 Expr::u32(0),
215 Expr::u32(node_count),
216 vec![Node::if_then(
217 Expr::ne(Expr::var("v"), Expr::u32(0)),
218 vec![
219 Node::let_bind("new_idom", Expr::u32(IDOM_NONE)),
221 Node::let_bind("p_start", Expr::load("pred_offsets", Expr::var("v"))),
223 Node::let_bind(
224 "p_end",
225 Expr::load("pred_offsets", Expr::add(Expr::var("v"), Expr::u32(1))),
226 ),
227 Node::loop_for(
228 "p_idx",
229 Expr::var("p_start"),
230 Expr::var("p_end"),
231 vec![
232 Node::let_bind("p", Expr::load("pred_targets", Expr::var("p_idx"))),
233 Node::if_then(
235 Expr::ne(
236 Expr::load(idom_out, Expr::var("p")),
237 Expr::u32(IDOM_NONE),
238 ),
239 vec![Node::if_then_else(
240 Expr::eq(Expr::var("new_idom"), Expr::u32(IDOM_NONE)),
241 vec![Node::assign("new_idom", Expr::var("p"))],
243 vec![
245 Node::let_bind("a", Expr::var("new_idom")),
246 Node::let_bind("b", Expr::var("p")),
247 Node::loop_for(
248 "lca_step",
249 Expr::u32(0),
250 Expr::u32(node_count),
251 vec![Node::if_then(
252 Expr::ne(Expr::var("a"), Expr::var("b")),
253 vec![
254 Node::let_bind(
255 "da",
256 Expr::load(depth_buf, Expr::var("a")),
257 ),
258 Node::let_bind(
259 "db",
260 Expr::load(depth_buf, Expr::var("b")),
261 ),
262 Node::if_then_else(
263 Expr::gt(
264 Expr::var("da"),
265 Expr::var("db"),
266 ),
267 vec![Node::assign(
268 "a",
269 Expr::load(
270 idom_out,
271 Expr::var("a"),
272 ),
273 )],
274 vec![Node::assign(
275 "b",
276 Expr::load(
277 idom_out,
278 Expr::var("b"),
279 ),
280 )],
281 ),
282 ],
283 )],
284 ),
285 Node::assign("new_idom", Expr::var("a")),
286 ],
287 )],
288 ),
289 ],
290 ),
291 Node::if_then(
293 Expr::and(
294 Expr::ne(Expr::var("new_idom"), Expr::u32(IDOM_NONE)),
295 Expr::ne(
296 Expr::var("new_idom"),
297 Expr::load(idom_out, Expr::var("v")),
298 ),
299 ),
300 vec![
301 Node::store(idom_out, Expr::var("v"), Expr::var("new_idom")),
302 Node::assign("changed", Expr::u32(1)),
303 ],
304 ),
305 ],
306 )],
307 )],
308 ),
309 ];
310
311 let outer_loop = Node::loop_for("step", Expr::u32(0), Expr::u32(node_count), body);
312
313 let region_body = vec![Node::if_then(lane0, vec![init_state, outer_loop])];
314
315 Ok(Program::wrapped(
316 vec![
317 BufferDecl::storage("pg_edge_offsets", 0, BufferAccess::ReadOnly, DataType::U32)
318 .with_count(offset_count),
319 BufferDecl::storage("pg_edge_targets", 1, BufferAccess::ReadOnly, DataType::U32)
320 .with_count(edge_count.max(1)),
321 BufferDecl::storage("pred_offsets", 2, BufferAccess::ReadOnly, DataType::U32)
322 .with_count(offset_count),
323 BufferDecl::storage("pred_targets", 3, BufferAccess::ReadOnly, DataType::U32)
324 .with_count(pred_edge_count.max(1)),
325 BufferDecl::storage(idom_out, 4, BufferAccess::ReadWrite, DataType::U32)
326 .with_count(node_count.max(1)),
327 BufferDecl::storage(depth_buf, 5, BufferAccess::ReadWrite, DataType::U32)
328 .with_count(node_count.max(1)),
329 ],
330 [1, 1, 1],
331 vec![Node::Region {
332 generator: Ident::from(OP_ID),
333 source_region: None,
334 body: Arc::new(region_body),
335 }],
336 ))
337}
338
339fn child_phase(generator: &'static str, body: Vec<Node>) -> Node {
340 Node::Region {
341 generator: Ident::from(generator),
342 source_region: Some(GeneratorRef {
343 name: OP_ID.to_string(),
344 }),
345 body: Arc::new(body),
346 }
347}
348
349fn inert_dominator_tree_program(idom_out: &str) -> Program {
350 Program::wrapped(
351 vec![
352 BufferDecl::storage("pg_edge_offsets", 0, BufferAccess::ReadOnly, DataType::U32)
353 .with_count(1),
354 BufferDecl::storage("pg_edge_targets", 1, BufferAccess::ReadOnly, DataType::U32)
355 .with_count(1),
356 BufferDecl::storage("pred_offsets", 2, BufferAccess::ReadOnly, DataType::U32)
357 .with_count(1),
358 BufferDecl::storage("pred_targets", 3, BufferAccess::ReadOnly, DataType::U32)
359 .with_count(1),
360 BufferDecl::storage(idom_out, 4, BufferAccess::ReadWrite, DataType::U32).with_count(1),
361 BufferDecl::storage("dt_depth", 5, BufferAccess::ReadWrite, DataType::U32)
362 .with_count(1),
363 ],
364 [1, 1, 1],
365 vec![Node::Region {
366 generator: Ident::from(OP_ID),
367 source_region: None,
368 body: Arc::new(vec![Node::return_()]),
369 }],
370 )
371}
372
373pub fn validate_dominator_tree_inputs(
380 node_count: u32,
381 edge_offsets: &[u32],
382 edge_targets: &[u32],
383 pred_offsets: &[u32],
384 pred_targets: &[u32],
385) -> Result<DominatorTreeLayout, DominatorTreeError> {
386 let expected_offsets =
387 (node_count as usize)
388 .checked_add(1)
389 .ok_or_else(|| DominatorTreeError::BadOffsets {
390 message: format!(
391 "Fix: dominator_tree node_count + 1 overflows usize for node_count={node_count}."
392 ),
393 })?;
394
395 if edge_offsets.len() != expected_offsets {
396 return Err(DominatorTreeError::BadOffsets {
397 message: format!(
398 "Fix: dominator_tree edge_offsets.len() must be {expected_offsets}, got {}.",
399 edge_offsets.len()
400 ),
401 });
402 }
403 if pred_offsets.len() != expected_offsets {
404 return Err(DominatorTreeError::BadOffsets {
405 message: format!(
406 "Fix: dominator_tree pred_offsets.len() must be {expected_offsets}, got {}.",
407 pred_offsets.len()
408 ),
409 });
410 }
411
412 for (idx, pair) in edge_offsets.windows(2).enumerate() {
413 if pair[0] > pair[1] {
414 return Err(DominatorTreeError::NonMonotonicOffsets { index: idx });
415 }
416 }
417 for (idx, pair) in pred_offsets.windows(2).enumerate() {
418 if pair[0] > pair[1] {
419 return Err(DominatorTreeError::NonMonotonicOffsets { index: idx });
420 }
421 }
422
423 let edge_count = edge_offsets.last().copied().unwrap_or(0);
424 let pred_edge_count = pred_offsets.last().copied().unwrap_or(0);
425
426 if edge_targets.len() != edge_count as usize {
427 return Err(DominatorTreeError::BadOffsets {
428 message: format!(
429 "Fix: dominator_tree edge_targets.len()={} != edge_count={edge_count}.",
430 edge_targets.len()
431 ),
432 });
433 }
434 if pred_targets.len() != pred_edge_count as usize {
435 return Err(DominatorTreeError::BadOffsets {
436 message: format!(
437 "Fix: dominator_tree pred_targets.len()={} != pred_edge_count={pred_edge_count}.",
438 pred_targets.len()
439 ),
440 });
441 }
442
443 for (idx, &target) in edge_targets.iter().enumerate() {
444 if target >= node_count {
445 return Err(DominatorTreeError::TargetOutOfRange {
446 index: idx,
447 target,
448 node_count,
449 });
450 }
451 }
452 for (idx, &target) in pred_targets.iter().enumerate() {
453 if target >= node_count {
454 return Err(DominatorTreeError::TargetOutOfRange {
455 index: idx,
456 target,
457 node_count,
458 });
459 }
460 }
461
462 Ok(DominatorTreeLayout {
463 node_count,
464 edge_count,
465 pred_edge_count,
466 idom_words: node_count as usize,
467 depth_words: node_count as usize,
468 })
469}
470
471