use crate::ops::AlgebraicLaw;
use crate::ir::transform::compiler::{U32X4_INPUTS, U32_OUTPUTS};
use crate::lower::wgsl::compiler::wgsl_backend;
use crate::ops::{IntrinsicDescriptor, OpSpec};
use thiserror::Error;
#[must_use]
pub const fn source() -> &'static str {
include_str!("../../../lower/wgsl/compiler/dominator_tree.wgsl")
}
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum DominatorTreeError {
#[error("DominatorEmptyOffsets: successor_offsets must include node_count + 1 entries. Fix: emit a valid CSR offset table.")]
EmptyOffsets,
#[error("DominatorInvalidEntry: entry {entry} outside node_count {node_count}. Fix: pass a valid CFG entry node.")]
InvalidEntry {
entry: u32,
node_count: usize,
},
#[error("DominatorInvalidOffset: CSR offsets must be monotone and within successors. Fix: rebuild successor_offsets.")]
InvalidOffset,
#[error(
"DominatorNodeIndexOverflow: node id cannot fit usize. Fix: split the CFG before dispatch."
)]
NodeIndexOverflow,
#[error("DominatorInvalidSuccessor: successor {successor} outside node_count {node_count}. Fix: validate CFG edge endpoints.")]
InvalidSuccessor {
successor: u32,
node_count: usize,
},
}
#[derive(Debug, Default, Clone, Copy)]
pub struct DominatorTreeOp;
pub fn immediate_dominators(
entry: u32,
successor_offsets: &[u32],
successors: &[u32],
) -> Result<Vec<u32>, DominatorTreeError> {
let node_count = successor_offsets
.len()
.checked_sub(1)
.ok_or(DominatorTreeError::EmptyOffsets)?;
let entry_index = usize::try_from(entry).map_err(|_| DominatorTreeError::NodeIndexOverflow)?;
if entry_index >= node_count {
return Err(DominatorTreeError::InvalidEntry { entry, node_count });
}
validate_graph(node_count, successor_offsets, successors)?;
let rpo = reverse_postorder(entry_index, successor_offsets, successors)?;
let mut order = vec![UNREACHABLE; node_count];
for (rank, &node) in rpo.iter().enumerate() {
order[node] = u32::try_from(rank).map_err(|_| DominatorTreeError::NodeIndexOverflow)?;
}
let preds = predecessors(node_count, successor_offsets, successors)?;
let mut idom = vec![UNREACHABLE; node_count];
idom[entry_index] = entry;
let mut changed = true;
while changed {
changed = false;
for &node in rpo.iter().skip(1) {
let mut new_idom = UNREACHABLE;
for &pred in &preds[node] {
let pred_index =
usize::try_from(pred).map_err(|_| DominatorTreeError::NodeIndexOverflow)?;
if idom[pred_index] != UNREACHABLE {
new_idom = if new_idom == UNREACHABLE {
pred
} else {
intersect(pred, new_idom, &idom, &order)?
};
}
}
if idom[node] != new_idom {
idom[node] = new_idom;
changed = true;
}
}
}
Ok(idom)
}
impl DominatorTreeOp {
pub const SPEC: OpSpec = OpSpec::intrinsic(
"compiler_primitives.dominator_tree",
U32X4_INPUTS,
U32_OUTPUTS,
LAWS,
wgsl_backend,
IntrinsicDescriptor::new(
"compiler_primitives_dominator_tree",
"workgroup_cfg_analysis",
crate::ops::cpu_op::structured_intrinsic_cpu,
),
);
}
pub fn index(value: u32) -> Result<usize, DominatorTreeError> {
usize::try_from(value).map_err(|_| DominatorTreeError::NodeIndexOverflow)
}
pub fn intersect(
mut left: u32,
mut right: u32,
idom: &[u32],
order: &[u32],
) -> Result<u32, DominatorTreeError> {
while left != right {
while order[index(left)?] > order[index(right)?] {
left = idom[index(left)?];
}
while order[index(right)?] > order[index(left)?] {
right = idom[index(right)?];
}
}
Ok(left)
}
pub const LAWS: &[AlgebraicLaw] = &[AlgebraicLaw::Bounded {
lo: 0,
hi: u32::MAX,
}];
pub fn predecessors(
node_count: usize,
offsets: &[u32],
successors: &[u32],
) -> Result<Vec<Vec<u32>>, DominatorTreeError> {
let mut preds = vec![Vec::new(); node_count];
for node in 0..node_count {
for &succ in &successors[index(offsets[node])?..index(offsets[node + 1])?] {
preds[index(succ)?]
.push(u32::try_from(node).map_err(|_| DominatorTreeError::NodeIndexOverflow)?);
}
}
Ok(preds)
}
pub fn reverse_postorder(
entry: usize,
offsets: &[u32],
successors: &[u32],
) -> Result<Vec<usize>, DominatorTreeError> {
let mut seen = vec![false; offsets.len() - 1];
let mut stack = vec![(entry, false)];
let mut postorder = Vec::new();
while let Some((node, expanded)) = stack.pop() {
if expanded {
postorder.push(node);
continue;
}
if seen[node] {
continue;
}
seen[node] = true;
stack.push((node, true));
let start = index(offsets[node])?;
let end = index(offsets[node + 1])?;
for &succ in successors[start..end].iter().rev() {
let succ_index = index(succ)?;
if !seen[succ_index] {
stack.push((succ_index, false));
}
}
}
postorder.reverse();
Ok(postorder)
}
pub const UNREACHABLE: u32 = u32::MAX;
pub fn validate_graph(
node_count: usize,
offsets: &[u32],
successors: &[u32],
) -> Result<(), DominatorTreeError> {
let mut previous = 0usize;
for &offset in offsets {
let current = index(offset)?;
if current < previous || current > successors.len() {
return Err(DominatorTreeError::InvalidOffset);
}
previous = current;
}
for &successor in successors {
if index(successor)? >= node_count {
return Err(DominatorTreeError::InvalidSuccessor {
successor,
node_count,
});
}
}
Ok(())
}
pub const WORKGROUP_SIZE: [u32; 3] = [64, 1, 1];