1use crate::error::{GraphError, GraphResult};
34use crate::graph::ComputeGraph;
35use crate::node::NodeId;
36
37#[derive(Debug, Clone)]
48pub struct DomTree {
49 idom: Vec<Option<NodeId>>,
52 children: Vec<Vec<NodeId>>,
54 n_nodes: usize,
56}
57
58impl DomTree {
59 #[must_use]
61 pub fn idom(&self, node: NodeId) -> Option<NodeId> {
62 self.idom.get(node.0 as usize).copied().flatten()
63 }
64
65 pub fn children(&self, node: NodeId) -> &[NodeId] {
67 self.children
68 .get(node.0 as usize)
69 .map(|v| v.as_slice())
70 .unwrap_or(&[])
71 }
72
73 pub fn roots(&self) -> Vec<NodeId> {
75 (0..self.n_nodes)
76 .filter(|&i| self.idom[i].is_none())
77 .map(|i| NodeId(i as u32))
78 .collect()
79 }
80
81 #[must_use]
85 pub fn dominates(&self, dominator: NodeId, node: NodeId) -> bool {
86 if dominator == node {
87 return true;
88 }
89 let mut cur = node;
90 loop {
91 match self.idom(cur) {
92 None => return false,
93 Some(p) if p == dominator => return true,
94 Some(p) => cur = p,
95 }
96 }
97 }
98
99 pub fn dominated_by(&self, root: NodeId) -> Vec<NodeId> {
101 let mut result = Vec::new();
102 let mut stack = vec![root];
103 while let Some(n) = stack.pop() {
104 result.push(n);
105 for &child in self.children(n) {
106 stack.push(child);
107 }
108 }
109 result
110 }
111
112 #[must_use]
114 pub fn depth(&self, node: NodeId) -> usize {
115 let mut d = 0usize;
116 let mut cur = node;
117 loop {
118 match self.idom(cur) {
119 None => return d,
120 Some(p) => {
121 d += 1;
122 cur = p;
123 }
124 }
125 }
126 }
127
128 #[must_use]
132 pub fn lca(&self, a: NodeId, b: NodeId) -> NodeId {
133 let da = self.depth(a);
134 let db = self.depth(b);
135 let mut x = a;
136 let mut y = b;
137 let (shallow, deep, diff) = if da <= db {
139 (x, y, db - da)
140 } else {
141 (y, x, da - db)
142 };
143 let mut deep = deep;
144 for _ in 0..diff {
145 deep = self.idom(deep).unwrap_or(deep);
146 }
147 x = shallow;
148 y = deep;
149 let mut guard = self.n_nodes + 1;
151 while x != y {
152 x = self.idom(x).unwrap_or(x);
153 y = self.idom(y).unwrap_or(y);
154 if guard == 0 {
155 break; }
157 guard -= 1;
158 }
159 x
160 }
161}
162
163fn intersect(mut b1: usize, mut b2: usize, idom_raw: &[usize], rpo: &[usize]) -> usize {
173 while b1 != b2 {
174 while rpo[b1] > rpo[b2] {
175 b1 = idom_raw[b1];
176 }
177 while rpo[b2] > rpo[b1] {
178 b2 = idom_raw[b2];
179 }
180 }
181 b1
182}
183
184pub fn analyse(graph: &ComputeGraph) -> GraphResult<DomTree> {
198 if graph.is_empty() {
199 return Err(GraphError::EmptyGraph);
200 }
201
202 let n_real = graph.node_count();
203 let vroot = n_real; let total = n_real + 1;
205
206 let mut succ: Vec<Vec<usize>> = vec![Vec::new(); total];
208 let mut pred: Vec<Vec<usize>> = vec![Vec::new(); total];
209
210 for (from, to) in graph.edges() {
211 let f = from.0 as usize;
212 let t = to.0 as usize;
213 succ[f].push(t);
214 pred[t].push(f);
215 }
216 for src in graph.sources() {
217 succ[vroot].push(src.0 as usize);
218 pred[src.0 as usize].push(vroot);
219 }
220
221 let mut rpo_order: Vec<usize> = Vec::with_capacity(total);
226 let mut visited = vec![false; total];
227 let mut dfs_stack: Vec<(usize, usize)> = vec![(vroot, 0)];
229 visited[vroot] = true;
230
231 while let Some((node, idx)) = dfs_stack.last_mut() {
232 if *idx < succ[*node].len() {
233 let child = succ[*node][*idx];
234 *idx += 1;
235 if !visited[child] {
236 visited[child] = true;
237 dfs_stack.push((child, 0));
238 }
239 } else {
240 let n = *node;
242 dfs_stack.pop();
243 rpo_order.push(n);
244 }
245 }
246
247 rpo_order.reverse();
249
250 let mut rpo = vec![usize::MAX; total];
252 for (i, &node) in rpo_order.iter().enumerate() {
253 rpo[node] = i;
254 }
255
256 const UNDEF: usize = usize::MAX;
259 let mut idom_raw = vec![UNDEF; total];
261 idom_raw[vroot] = vroot; let mut changed = true;
264 while changed {
265 changed = false;
266 for &b in &rpo_order[1..] {
268 let mut new_idom = UNDEF;
270 for &p in &pred[b] {
271 if idom_raw[p] == UNDEF {
272 continue; }
274 if new_idom == UNDEF {
275 new_idom = p;
276 } else {
277 new_idom = intersect(p, new_idom, &idom_raw, &rpo);
278 }
279 }
280 if new_idom != UNDEF && idom_raw[b] != new_idom {
281 idom_raw[b] = new_idom;
282 changed = true;
283 }
284 }
285 }
286
287 let mut idom_out: Vec<Option<NodeId>> = vec![None; n_real];
289 for i in 0..n_real {
290 let raw = idom_raw[i];
291 if raw == UNDEF || raw == vroot {
292 idom_out[i] = None; } else {
294 idom_out[i] = Some(NodeId(raw as u32));
295 }
296 }
297
298 let mut children: Vec<Vec<NodeId>> = vec![Vec::new(); n_real];
300 for (i, &idom_opt) in idom_out.iter().enumerate() {
301 if let Some(parent) = idom_opt {
302 children[parent.0 as usize].push(NodeId(i as u32));
303 }
304 }
305
306 Ok(DomTree {
307 idom: idom_out,
308 children,
309 n_nodes: n_real,
310 })
311}
312
313#[cfg(test)]
318mod tests {
319 use super::*;
320 use crate::builder::GraphBuilder;
321
322 fn make_chain(n: usize) -> (ComputeGraph, Vec<NodeId>) {
323 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
324 let ids: Vec<NodeId> = (0..n).map(|_| b.add_barrier("x")).collect();
325 for w in ids.windows(2) {
326 b.dep(w[0], w[1]);
327 }
328 let g = b.build().unwrap();
329 (g, ids)
330 }
331
332 #[test]
333 fn dominance_empty_graph_error() {
334 let g = ComputeGraph::new();
335 assert!(matches!(analyse(&g), Err(GraphError::EmptyGraph)));
336 }
337
338 #[test]
339 fn dominance_single_node_is_root() {
340 let (g, ids) = make_chain(1);
341 let dt = analyse(&g).unwrap();
342 assert!(dt.idom(ids[0]).is_none());
343 assert_eq!(dt.roots(), vec![ids[0]]);
344 }
345
346 #[test]
347 fn dominance_linear_chain() {
348 let (g, ids) = make_chain(4);
350 let dt = analyse(&g).unwrap();
351 assert!(dt.idom(ids[0]).is_none()); assert_eq!(dt.idom(ids[1]), Some(ids[0]));
353 assert_eq!(dt.idom(ids[2]), Some(ids[1]));
354 assert_eq!(dt.idom(ids[3]), Some(ids[2]));
355 }
356
357 #[test]
358 fn dominance_diamond() {
359 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
362 let a = b.add_barrier("a");
363 let bnode = b.add_barrier("b");
364 let c = b.add_barrier("c");
365 let d = b.add_barrier("d");
366 b.dep(a, bnode).dep(a, c).dep(bnode, d).dep(c, d);
367 let g = b.build().unwrap();
368 let dt = analyse(&g).unwrap();
369 assert!(dt.idom(a).is_none());
370 assert_eq!(dt.idom(bnode), Some(a));
371 assert_eq!(dt.idom(c), Some(a));
372 assert_eq!(dt.idom(d), Some(a));
374 }
375
376 #[test]
377 fn dominance_dominates_reflexive() {
378 let (g, ids) = make_chain(3);
379 let dt = analyse(&g).unwrap();
380 assert!(dt.dominates(ids[0], ids[0]));
381 assert!(dt.dominates(ids[1], ids[1]));
382 }
383
384 #[test]
385 fn dominance_dominates_transitive() {
386 let (g, ids) = make_chain(4);
387 let dt = analyse(&g).unwrap();
388 assert!(dt.dominates(ids[0], ids[1]));
390 assert!(dt.dominates(ids[0], ids[2]));
391 assert!(dt.dominates(ids[0], ids[3]));
392 assert!(!dt.dominates(ids[3], ids[0]));
393 }
394
395 #[test]
396 fn dominance_dominated_by_subtree() {
397 let (g, ids) = make_chain(4);
398 let dt = analyse(&g).unwrap();
399 let sub = dt.dominated_by(ids[1]);
401 assert!(sub.contains(&ids[1]));
402 assert!(sub.contains(&ids[2]));
403 assert!(sub.contains(&ids[3]));
404 assert!(!sub.contains(&ids[0]));
405 }
406
407 #[test]
408 fn dominance_depth_linear() {
409 let (g, ids) = make_chain(4);
410 let dt = analyse(&g).unwrap();
411 assert_eq!(dt.depth(ids[0]), 0);
412 assert_eq!(dt.depth(ids[1]), 1);
413 assert_eq!(dt.depth(ids[2]), 2);
414 assert_eq!(dt.depth(ids[3]), 3);
415 }
416
417 #[test]
418 fn dominance_lca_same_node() {
419 let (g, ids) = make_chain(3);
420 let dt = analyse(&g).unwrap();
421 assert_eq!(dt.lca(ids[1], ids[1]), ids[1]);
422 }
423
424 #[test]
425 fn dominance_lca_diamond() {
426 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
429 let a = b.add_barrier("a");
430 let bnode = b.add_barrier("b");
431 let c = b.add_barrier("c");
432 let d = b.add_barrier("d");
433 b.dep(a, bnode).dep(a, c).dep(bnode, d).dep(c, d);
434 let g = b.build().unwrap();
435 let dt = analyse(&g).unwrap();
436 let result = dt.lca(bnode, c);
438 assert_eq!(result, a);
440 assert_eq!(dt.lca(bnode, d), a); }
443
444 #[test]
445 fn dominance_children() {
446 let (g, ids) = make_chain(3);
447 let dt = analyse(&g).unwrap();
448 assert_eq!(dt.children(ids[0]), &[ids[1]]);
450 assert_eq!(dt.children(ids[1]), &[ids[2]]);
451 assert!(dt.children(ids[2]).is_empty());
452 }
453
454 #[test]
455 fn dominance_fork_join_children() {
456 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
458 let a = b.add_barrier("a");
459 let b1 = b.add_barrier("b");
460 let c = b.add_barrier("c");
461 let d = b.add_barrier("d");
462 let e = b.add_barrier("e");
463 b.fan_out(a, &[b1, c, d]);
464 b.fan_in(&[b1, c, d], e);
465 let g = b.build().unwrap();
466 let dt = analyse(&g).unwrap();
467 assert!(dt.dominates(a, e));
469 assert_eq!(dt.idom(b1), Some(a));
471 assert_eq!(dt.idom(c), Some(a));
472 assert_eq!(dt.idom(d), Some(a));
473 }
474
475 #[test]
476 fn dominance_two_independent_nodes() {
477 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
479 let a = b.add_barrier("a");
480 let bnode = b.add_barrier("b");
481 let g = b.build().unwrap();
482 let dt = analyse(&g).unwrap();
483 assert!(dt.idom(a).is_none());
484 assert!(dt.idom(bnode).is_none());
485 let roots = dt.roots();
486 assert_eq!(roots.len(), 2);
487 }
488
489 #[test]
490 fn dominance_longer_chain_all_dominated() {
491 let (g, ids) = make_chain(8);
492 let dt = analyse(&g).unwrap();
493 for &id in &ids[1..] {
495 assert!(dt.dominates(ids[0], id));
496 }
497 for &id in &ids[..7] {
499 assert!(!dt.dominates(ids[7], id));
500 }
501 }
502
503 #[test]
504 fn dominance_dominated_by_includes_self() {
505 let (g, ids) = make_chain(3);
506 let dt = analyse(&g).unwrap();
507 let sub = dt.dominated_by(ids[0]);
508 assert!(sub.contains(&ids[0]));
509 }
510}