1use super::alloc_helpers::{push_dominator_vec, resize_dominator_vec};
5
6#[must_use]
18#[cfg(any(test, feature = "cpu-parity"))]
19pub fn lengauer_tarjan_idoms(
20 node_count: u32,
21 entry: u32,
22 edges: &[(u32, u32)],
23) -> Vec<Option<u32>> {
24 try_lengauer_tarjan_idoms(node_count, entry, edges).unwrap_or_else(|error| panic!("{error}"))
25}
26
27#[cfg(any(test, feature = "cpu-parity"))]
29pub fn try_lengauer_tarjan_idoms(
30 node_count: u32,
31 entry: u32,
32 edges: &[(u32, u32)],
33) -> Result<Vec<Option<u32>>, String> {
34 let mut idom = Vec::new();
35 let mut scratch = DominatorTreeCpuScratch::default();
36 try_lengauer_tarjan_idoms_into(node_count, entry, edges, &mut idom, &mut scratch)?;
37 Ok(idom)
38}
39
40#[cfg(any(test, feature = "cpu-parity"))]
42#[derive(Debug, Default)]
43pub struct DominatorTreeCpuScratch {
44 succ: Vec<Vec<usize>>,
45 pred: Vec<Vec<usize>>,
46 semi: Vec<usize>,
47 vertex: Vec<usize>,
48 parent: Vec<usize>,
49 dfs_stack: Vec<(usize, usize)>,
50 ancestor: Vec<usize>,
51 label: Vec<usize>,
52 bucket: Vec<Vec<usize>>,
53 compress_stack: Vec<usize>,
54}
55
56#[cfg(any(test, feature = "cpu-parity"))]
57impl DominatorTreeCpuScratch {
58 #[must_use]
60 pub fn new() -> Self {
61 Self::default()
62 }
63
64 #[cfg(test)]
66 pub fn reserve_outer_for_test(&mut self, hint: usize, bucket_hint: usize) {
67 self.succ.reserve(hint);
68 self.pred.reserve(hint);
69 self.semi.reserve(hint);
70 self.vertex.reserve(hint.saturating_add(1));
71 self.parent.reserve(hint);
72 self.dfs_stack.reserve(hint);
73 self.ancestor.reserve(hint);
74 self.label.reserve(hint);
75 self.bucket.reserve(bucket_hint);
76 self.compress_stack.reserve(hint);
77 }
78
79 #[cfg(test)]
81 #[must_use]
82 pub fn outer_capacities(&self) -> [usize; 10] {
83 [
84 self.succ.capacity(),
85 self.pred.capacity(),
86 self.semi.capacity(),
87 self.vertex.capacity(),
88 self.parent.capacity(),
89 self.dfs_stack.capacity(),
90 self.ancestor.capacity(),
91 self.label.capacity(),
92 self.bucket.capacity(),
93 self.compress_stack.capacity(),
94 ]
95 }
96
97 #[cfg(test)]
99 #[must_use]
100 pub fn test_succ_row(&self, node: usize) -> &[usize] {
101 &self.succ[node]
102 }
103
104 #[cfg(test)]
106 #[must_use]
107 pub fn test_pred_row(&self, node: usize) -> &[usize] {
108 &self.pred[node]
109 }
110
111 #[cfg(test)]
113 #[must_use]
114 pub fn test_succ_row_capacity(&self, node: usize) -> usize {
115 self.succ[node].capacity()
116 }
117
118 #[cfg(test)]
120 #[must_use]
121 pub fn test_pred_row_capacity(&self, node: usize) -> usize {
122 self.pred[node].capacity()
123 }
124}
125
126#[cfg(any(test, feature = "cpu-parity"))]
128pub fn try_lengauer_tarjan_idoms_into(
129 node_count: u32,
130 entry: u32,
131 edges: &[(u32, u32)],
132 idom: &mut Vec<Option<u32>>,
133 scratch: &mut DominatorTreeCpuScratch,
134) -> Result<(), String> {
135 let n = node_count as usize;
136 let entry = entry as usize;
137 if n == 0 {
138 idom.clear();
139 return Ok(());
140 }
141 if entry >= n {
142 idom.clear();
143 resize_dominator_vec(idom, n, None, "dominator_tree entry-out-of-range idoms")?;
144 return Ok(());
145 }
146
147 resize_dominator_vec(
149 &mut scratch.succ,
150 n,
151 Vec::new(),
152 "dominator_tree successor rows",
153 )?;
154 resize_dominator_vec(
155 &mut scratch.pred,
156 n,
157 Vec::new(),
158 "dominator_tree predecessor rows",
159 )?;
160 for row in scratch.succ.iter_mut().take(n) {
161 row.clear();
162 }
163 for row in scratch.pred.iter_mut().take(n) {
164 row.clear();
165 }
166 for &(u, v) in edges {
167 let u = u as usize;
168 let v = v as usize;
169 if u < n && v < n {
170 push_dominator_vec(&mut scratch.succ[u], v, "dominator_tree successor row")?;
171 push_dominator_vec(&mut scratch.pred[v], u, "dominator_tree predecessor row")?;
172 }
173 }
174
175 scratch.semi.clear();
177 scratch.vertex.clear();
178 scratch.parent.clear();
179 resize_dominator_vec(&mut scratch.semi, n, 0usize, "dominator_tree semi numbers")?;
180 resize_dominator_vec(
181 &mut scratch.vertex,
182 n + 1,
183 0usize,
184 "dominator_tree DFS vertices",
185 )?;
186 resize_dominator_vec(&mut scratch.parent, n, 0usize, "dominator_tree DFS parents")?;
187 let mut dfs_num: usize = 0;
188
189 scratch.dfs_stack.clear();
191 push_dominator_vec(
192 &mut scratch.dfs_stack,
193 (entry, 0usize),
194 "dominator_tree DFS stack",
195 )?;
196 while let Some((v, next_idx)) = scratch.dfs_stack.last_mut() {
197 let v = *v;
198 if *next_idx == 0 {
199 dfs_num += 1;
200 scratch.semi[v] = dfs_num;
201 scratch.vertex[dfs_num] = v;
202 }
203 if *next_idx < scratch.succ[v].len() {
204 let w = scratch.succ[v][*next_idx];
205 *next_idx += 1;
206 if scratch.semi[w] == 0 {
207 scratch.parent[w] = v;
208 push_dominator_vec(&mut scratch.dfs_stack, (w, 0), "dominator_tree DFS stack")?;
209 }
210 } else {
211 scratch.dfs_stack.pop();
212 }
213 }
214
215 if dfs_num == 0 {
216 idom.clear();
217 resize_dominator_vec(idom, n, None, "dominator_tree unreachable idoms")?;
218 return Ok(());
219 }
220
221 idom.clear();
222 scratch.ancestor.clear();
223 scratch.label.clear();
224 scratch.bucket.clear();
225 resize_dominator_vec(idom, n, None, "dominator_tree idoms")?;
226 resize_dominator_vec(&mut scratch.ancestor, n, 0usize, "dominator_tree ancestors")?;
227 crate::graph::scratch::reserve_graph_items(
228 &mut scratch.label,
229 n,
230 "dominator tree CPU oracle",
231 "dominator_tree labels",
232 )?;
233 scratch.label.extend(0..n);
234 resize_dominator_vec(
235 &mut scratch.bucket,
236 n + 1,
237 Vec::new(),
238 "dominator_tree buckets",
239 )?;
240 for row in scratch.bucket.iter_mut().take(n + 1) {
241 row.clear();
242 }
243 scratch.compress_stack.clear();
244
245 for i in (1..=dfs_num).rev() {
246 let w = scratch.vertex[i];
247
248 for &v in &scratch.pred[w] {
249 if scratch.semi[v] > 0 {
250 let u = try_eval_with_stack(
251 v,
252 &mut scratch.ancestor,
253 &mut scratch.label,
254 &scratch.semi,
255 &mut scratch.compress_stack,
256 )?;
257 if scratch.semi[u] < scratch.semi[w] {
258 scratch.semi[w] = scratch.semi[u];
259 }
260 }
261 }
262
263 push_dominator_vec(
264 &mut scratch.bucket[scratch.vertex[scratch.semi[w]]],
265 w,
266 "dominator_tree bucket row",
267 )?;
268
269 link(
270 scratch.parent[w],
271 w,
272 &mut scratch.ancestor,
273 &mut scratch.label,
274 &scratch.semi,
275 );
276
277 for &v in &scratch.bucket[scratch.parent[w]] {
278 let u = try_eval_with_stack(
279 v,
280 &mut scratch.ancestor,
281 &mut scratch.label,
282 &scratch.semi,
283 &mut scratch.compress_stack,
284 )?;
285 if scratch.semi[u] < scratch.semi[v] {
286 idom[v] = Some(u as u32);
287 } else {
288 idom[v] = Some(scratch.parent[w] as u32);
289 }
290 }
291 scratch.bucket[scratch.parent[w]].clear();
292 }
293
294 for i in 2..=dfs_num {
295 let w = scratch.vertex[i];
296 if idom[w].map(|x| x as usize) != Some(scratch.vertex[scratch.semi[w]]) {
297 idom[w] = idom[w]
298 .and_then(|parent| idom.get(parent as usize))
299 .copied()
300 .flatten();
301 }
302 }
303
304 idom[entry] = Some(entry as u32);
305
306 Ok(())
308}
309
310#[cfg(any(test, feature = "cpu-parity"))]
311fn try_compress(
312 v: usize,
313 ancestor: &mut [usize],
314 label: &mut [usize],
315 semi: &[usize],
316) -> Result<(), String> {
317 let mut stack = Vec::new();
318 try_compress_with_stack(v, ancestor, label, semi, &mut stack)
319}
320
321#[cfg(any(test, feature = "cpu-parity"))]
322fn try_compress_with_stack(
323 v: usize,
324 ancestor: &mut [usize],
325 label: &mut [usize],
326 semi: &[usize],
327 stack: &mut Vec<usize>,
328) -> Result<(), String> {
329 if ancestor[v] == 0 {
330 return Ok(());
331 }
332
333 stack.clear();
339 let mut u = v;
340 while ancestor[u] != 0 {
341 if ancestor[ancestor[u]] != 0 {
342 push_dominator_vec(stack, u, "dominator_tree compression stack")?;
343 u = ancestor[u];
344 } else {
345 if semi[label[ancestor[u]]] < semi[label[u]] {
348 label[u] = label[ancestor[u]];
349 }
350 break;
351 }
352 }
353
354 while let Some(w) = stack.pop() {
356 if semi[label[ancestor[w]]] < semi[label[w]] {
357 label[w] = label[ancestor[w]];
358 }
359 ancestor[w] = ancestor[ancestor[w]];
360 }
361 Ok(())
362}
363
364#[cfg(any(test, feature = "cpu-parity"))]
365fn try_eval(
366 v: usize,
367 ancestor: &mut [usize],
368 label: &mut [usize],
369 semi: &[usize],
370) -> Result<usize, String> {
371 let mut stack = Vec::new();
372 try_eval_with_stack(v, ancestor, label, semi, &mut stack)
373}
374
375#[cfg(any(test, feature = "cpu-parity"))]
376fn try_eval_with_stack(
377 v: usize,
378 ancestor: &mut [usize],
379 label: &mut [usize],
380 semi: &[usize],
381 stack: &mut Vec<usize>,
382) -> Result<usize, String> {
383 if ancestor[v] == 0 {
384 Ok(v)
385 } else {
386 try_compress_with_stack(v, ancestor, label, semi, stack)?;
387 Ok(label[v])
388 }
389}
390
391#[cfg(any(test, feature = "cpu-parity"))]
392fn link(v: usize, w: usize, ancestor: &mut [usize], label: &mut [usize], _semi: &[usize]) {
393 ancestor[w] = v;
394 label[w] = w;
395}