use super::alloc_helpers::{push_dominator_vec, resize_dominator_vec};
#[must_use]
#[cfg(any(test, feature = "cpu-parity"))]
pub fn lengauer_tarjan_idoms(
node_count: u32,
entry: u32,
edges: &[(u32, u32)],
) -> Vec<Option<u32>> {
try_lengauer_tarjan_idoms(node_count, entry, edges).unwrap_or_else(|error| panic!("{error}"))
}
#[cfg(any(test, feature = "cpu-parity"))]
pub fn try_lengauer_tarjan_idoms(
node_count: u32,
entry: u32,
edges: &[(u32, u32)],
) -> Result<Vec<Option<u32>>, String> {
let mut idom = Vec::new();
let mut scratch = DominatorTreeCpuScratch::default();
try_lengauer_tarjan_idoms_into(node_count, entry, edges, &mut idom, &mut scratch)?;
Ok(idom)
}
#[cfg(any(test, feature = "cpu-parity"))]
#[derive(Debug, Default)]
pub struct DominatorTreeCpuScratch {
succ: Vec<Vec<usize>>,
pred: Vec<Vec<usize>>,
semi: Vec<usize>,
vertex: Vec<usize>,
parent: Vec<usize>,
dfs_stack: Vec<(usize, usize)>,
ancestor: Vec<usize>,
label: Vec<usize>,
bucket: Vec<Vec<usize>>,
compress_stack: Vec<usize>,
}
#[cfg(any(test, feature = "cpu-parity"))]
impl DominatorTreeCpuScratch {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[cfg(test)]
pub fn reserve_outer_for_test(&mut self, hint: usize, bucket_hint: usize) {
self.succ.reserve(hint);
self.pred.reserve(hint);
self.semi.reserve(hint);
self.vertex.reserve(hint.saturating_add(1));
self.parent.reserve(hint);
self.dfs_stack.reserve(hint);
self.ancestor.reserve(hint);
self.label.reserve(hint);
self.bucket.reserve(bucket_hint);
self.compress_stack.reserve(hint);
}
#[cfg(test)]
#[must_use]
pub fn outer_capacities(&self) -> [usize; 10] {
[
self.succ.capacity(),
self.pred.capacity(),
self.semi.capacity(),
self.vertex.capacity(),
self.parent.capacity(),
self.dfs_stack.capacity(),
self.ancestor.capacity(),
self.label.capacity(),
self.bucket.capacity(),
self.compress_stack.capacity(),
]
}
#[cfg(test)]
#[must_use]
pub fn test_succ_row(&self, node: usize) -> &[usize] {
&self.succ[node]
}
#[cfg(test)]
#[must_use]
pub fn test_pred_row(&self, node: usize) -> &[usize] {
&self.pred[node]
}
#[cfg(test)]
#[must_use]
pub fn test_succ_row_capacity(&self, node: usize) -> usize {
self.succ[node].capacity()
}
#[cfg(test)]
#[must_use]
pub fn test_pred_row_capacity(&self, node: usize) -> usize {
self.pred[node].capacity()
}
}
#[cfg(any(test, feature = "cpu-parity"))]
pub fn try_lengauer_tarjan_idoms_into(
node_count: u32,
entry: u32,
edges: &[(u32, u32)],
idom: &mut Vec<Option<u32>>,
scratch: &mut DominatorTreeCpuScratch,
) -> Result<(), String> {
let n = node_count as usize;
let entry = entry as usize;
if n == 0 {
idom.clear();
return Ok(());
}
if entry >= n {
idom.clear();
resize_dominator_vec(idom, n, None, "dominator_tree entry-out-of-range idoms")?;
return Ok(());
}
resize_dominator_vec(
&mut scratch.succ,
n,
Vec::new(),
"dominator_tree successor rows",
)?;
resize_dominator_vec(
&mut scratch.pred,
n,
Vec::new(),
"dominator_tree predecessor rows",
)?;
for row in scratch.succ.iter_mut().take(n) {
row.clear();
}
for row in scratch.pred.iter_mut().take(n) {
row.clear();
}
for &(u, v) in edges {
let u = u as usize;
let v = v as usize;
if u < n && v < n {
push_dominator_vec(&mut scratch.succ[u], v, "dominator_tree successor row")?;
push_dominator_vec(&mut scratch.pred[v], u, "dominator_tree predecessor row")?;
}
}
scratch.semi.clear();
scratch.vertex.clear();
scratch.parent.clear();
resize_dominator_vec(&mut scratch.semi, n, 0usize, "dominator_tree semi numbers")?;
resize_dominator_vec(
&mut scratch.vertex,
n + 1,
0usize,
"dominator_tree DFS vertices",
)?;
resize_dominator_vec(&mut scratch.parent, n, 0usize, "dominator_tree DFS parents")?;
let mut dfs_num: usize = 0;
scratch.dfs_stack.clear();
push_dominator_vec(
&mut scratch.dfs_stack,
(entry, 0usize),
"dominator_tree DFS stack",
)?;
while let Some((v, next_idx)) = scratch.dfs_stack.last_mut() {
let v = *v;
if *next_idx == 0 {
dfs_num += 1;
scratch.semi[v] = dfs_num;
scratch.vertex[dfs_num] = v;
}
if *next_idx < scratch.succ[v].len() {
let w = scratch.succ[v][*next_idx];
*next_idx += 1;
if scratch.semi[w] == 0 {
scratch.parent[w] = v;
push_dominator_vec(&mut scratch.dfs_stack, (w, 0), "dominator_tree DFS stack")?;
}
} else {
scratch.dfs_stack.pop();
}
}
if dfs_num == 0 {
idom.clear();
resize_dominator_vec(idom, n, None, "dominator_tree unreachable idoms")?;
return Ok(());
}
idom.clear();
scratch.ancestor.clear();
scratch.label.clear();
scratch.bucket.clear();
resize_dominator_vec(idom, n, None, "dominator_tree idoms")?;
resize_dominator_vec(&mut scratch.ancestor, n, 0usize, "dominator_tree ancestors")?;
crate::graph::scratch::reserve_graph_items(
&mut scratch.label,
n,
"dominator tree CPU oracle",
"dominator_tree labels",
)?;
scratch.label.extend(0..n);
resize_dominator_vec(
&mut scratch.bucket,
n + 1,
Vec::new(),
"dominator_tree buckets",
)?;
for row in scratch.bucket.iter_mut().take(n + 1) {
row.clear();
}
scratch.compress_stack.clear();
for i in (1..=dfs_num).rev() {
let w = scratch.vertex[i];
for &v in &scratch.pred[w] {
if scratch.semi[v] > 0 {
let u = try_eval_with_stack(
v,
&mut scratch.ancestor,
&mut scratch.label,
&scratch.semi,
&mut scratch.compress_stack,
)?;
if scratch.semi[u] < scratch.semi[w] {
scratch.semi[w] = scratch.semi[u];
}
}
}
push_dominator_vec(
&mut scratch.bucket[scratch.vertex[scratch.semi[w]]],
w,
"dominator_tree bucket row",
)?;
link(
scratch.parent[w],
w,
&mut scratch.ancestor,
&mut scratch.label,
&scratch.semi,
);
for &v in &scratch.bucket[scratch.parent[w]] {
let u = try_eval_with_stack(
v,
&mut scratch.ancestor,
&mut scratch.label,
&scratch.semi,
&mut scratch.compress_stack,
)?;
if scratch.semi[u] < scratch.semi[v] {
idom[v] = Some(u as u32);
} else {
idom[v] = Some(scratch.parent[w] as u32);
}
}
scratch.bucket[scratch.parent[w]].clear();
}
for i in 2..=dfs_num {
let w = scratch.vertex[i];
if idom[w].map(|x| x as usize) != Some(scratch.vertex[scratch.semi[w]]) {
idom[w] = idom[w]
.and_then(|parent| idom.get(parent as usize))
.copied()
.flatten();
}
}
idom[entry] = Some(entry as u32);
Ok(())
}
#[cfg(any(test, feature = "cpu-parity"))]
fn try_compress(
v: usize,
ancestor: &mut [usize],
label: &mut [usize],
semi: &[usize],
) -> Result<(), String> {
let mut stack = Vec::new();
try_compress_with_stack(v, ancestor, label, semi, &mut stack)
}
#[cfg(any(test, feature = "cpu-parity"))]
fn try_compress_with_stack(
v: usize,
ancestor: &mut [usize],
label: &mut [usize],
semi: &[usize],
stack: &mut Vec<usize>,
) -> Result<(), String> {
if ancestor[v] == 0 {
return Ok(());
}
stack.clear();
let mut u = v;
while ancestor[u] != 0 {
if ancestor[ancestor[u]] != 0 {
push_dominator_vec(stack, u, "dominator_tree compression stack")?;
u = ancestor[u];
} else {
if semi[label[ancestor[u]]] < semi[label[u]] {
label[u] = label[ancestor[u]];
}
break;
}
}
while let Some(w) = stack.pop() {
if semi[label[ancestor[w]]] < semi[label[w]] {
label[w] = label[ancestor[w]];
}
ancestor[w] = ancestor[ancestor[w]];
}
Ok(())
}
#[cfg(any(test, feature = "cpu-parity"))]
fn try_eval(
v: usize,
ancestor: &mut [usize],
label: &mut [usize],
semi: &[usize],
) -> Result<usize, String> {
let mut stack = Vec::new();
try_eval_with_stack(v, ancestor, label, semi, &mut stack)
}
#[cfg(any(test, feature = "cpu-parity"))]
fn try_eval_with_stack(
v: usize,
ancestor: &mut [usize],
label: &mut [usize],
semi: &[usize],
stack: &mut Vec<usize>,
) -> Result<usize, String> {
if ancestor[v] == 0 {
Ok(v)
} else {
try_compress_with_stack(v, ancestor, label, semi, stack)?;
Ok(label[v])
}
}
#[cfg(any(test, feature = "cpu-parity"))]
fn link(v: usize, w: usize, ancestor: &mut [usize], label: &mut [usize], _semi: &[usize]) {
ancestor[w] = v;
label[w] = w;
}