use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use crate::error::TldrError;
use crate::types::CfgInfo;
use crate::TldrResult;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DominatorNode {
pub block_id: usize,
pub idom: Option<usize>,
pub children: Vec<usize>,
pub depth: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DominatorTree {
pub function: String,
pub nodes: HashMap<usize, DominatorNode>,
pub entry: usize,
pub preorder: Vec<usize>,
pub postorder: Vec<usize>,
}
impl DominatorTree {
pub fn dominates(&self, a: usize, b: usize) -> bool {
if a == b {
return true;
}
let mut current = b;
while let Some(node) = self.nodes.get(¤t) {
if let Some(idom) = node.idom {
if idom == a {
return true;
}
current = idom;
} else {
break;
}
}
false
}
pub fn strictly_dominates(&self, a: usize, b: usize) -> bool {
a != b && self.dominates(a, b)
}
pub fn dominated_by(&self, block: usize) -> Vec<usize> {
let mut result = Vec::new();
let mut stack = vec![block];
while let Some(current) = stack.pop() {
result.push(current);
if let Some(node) = self.nodes.get(¤t) {
stack.extend(node.children.iter());
}
}
result
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DominanceFrontier {
pub frontier: HashMap<usize, HashSet<usize>>,
}
impl DominanceFrontier {
pub fn get(&self, block: usize) -> HashSet<usize> {
self.frontier.get(&block).cloned().unwrap_or_default()
}
pub fn iterated(&self, blocks: &HashSet<usize>) -> HashSet<usize> {
compute_iterated_df(self, blocks)
}
}
pub fn build_dominator_tree(cfg: &CfgInfo) -> TldrResult<DominatorTree> {
if cfg.blocks.is_empty() {
return Err(TldrError::InvalidArgs {
arg: "cfg".to_string(),
message: "Empty CFG has no blocks".to_string(),
suggestion: None,
});
}
let entry = cfg.entry_block;
let mut successors: HashMap<usize, Vec<usize>> = HashMap::new();
let mut predecessors: HashMap<usize, Vec<usize>> = HashMap::new();
for block in &cfg.blocks {
successors.entry(block.id).or_default();
predecessors.entry(block.id).or_default();
}
for edge in &cfg.edges {
successors.entry(edge.from).or_default().push(edge.to);
predecessors.entry(edge.to).or_default().push(edge.from);
}
let mut lt = LengauerTarjan::new(entry, &successors, &predecessors);
lt.compute();
let mut nodes: HashMap<usize, DominatorNode> = HashMap::new();
for &block in <.vertex {
let idom = if block == entry {
None
} else {
lt.idom.get(&block).copied()
};
nodes.insert(
block,
DominatorNode {
block_id: block,
idom,
children: Vec::new(),
depth: 0,
},
);
}
for (&block, node) in &nodes.clone() {
if let Some(idom) = node.idom {
if let Some(parent) = nodes.get_mut(&idom) {
parent.children.push(block);
}
}
}
for node in nodes.values_mut() {
node.children.sort();
}
compute_depths(&mut nodes, entry);
let (preorder, postorder) = compute_traversals(&nodes, entry);
Ok(DominatorTree {
function: cfg.function.clone(),
nodes,
entry,
preorder,
postorder,
})
}
fn compute_depths(nodes: &mut HashMap<usize, DominatorNode>, entry: usize) {
let mut queue = std::collections::VecDeque::new();
queue.push_back((entry, 0u32));
while let Some((block, depth)) = queue.pop_front() {
if let Some(node) = nodes.get_mut(&block) {
node.depth = depth;
for &child in &node.children.clone() {
queue.push_back((child, depth + 1));
}
}
}
}
fn compute_traversals(
nodes: &HashMap<usize, DominatorNode>,
entry: usize,
) -> (Vec<usize>, Vec<usize>) {
let mut preorder = Vec::new();
let mut postorder = Vec::new();
let mut stack = vec![(entry, false)];
while let Some((block, visited)) = stack.pop() {
if visited {
postorder.push(block);
} else {
preorder.push(block);
stack.push((block, true));
if let Some(node) = nodes.get(&block) {
for &child in node.children.iter().rev() {
stack.push((child, false));
}
}
}
}
(preorder, postorder)
}
struct LengauerTarjan<'a> {
entry: usize,
successors: &'a HashMap<usize, Vec<usize>>,
predecessors: &'a HashMap<usize, Vec<usize>>,
dfnum: HashMap<usize, usize>,
vertex: Vec<usize>,
parent: HashMap<usize, usize>,
semi: HashMap<usize, usize>,
ancestor: HashMap<usize, Option<usize>>,
label: HashMap<usize, usize>,
idom: HashMap<usize, usize>,
bucket: HashMap<usize, Vec<usize>>,
}
impl<'a> LengauerTarjan<'a> {
fn new(
entry: usize,
successors: &'a HashMap<usize, Vec<usize>>,
predecessors: &'a HashMap<usize, Vec<usize>>,
) -> Self {
LengauerTarjan {
entry,
successors,
predecessors,
dfnum: HashMap::new(),
vertex: Vec::new(),
parent: HashMap::new(),
semi: HashMap::new(),
ancestor: HashMap::new(),
label: HashMap::new(),
idom: HashMap::new(),
bucket: HashMap::new(),
}
}
fn compute(&mut self) {
self.dfs(self.entry);
if self.vertex.is_empty() {
return;
}
for &v in &self.vertex {
self.ancestor.insert(v, None);
self.label.insert(v, v);
self.bucket.entry(v).or_default();
}
let n = self.vertex.len();
for i in (1..n).rev() {
let w = self.vertex[i];
if let Some(preds) = self.predecessors.get(&w) {
for &v in preds {
if self.dfnum.contains_key(&v) {
let u = self.eval(v);
let semi_u = self.semi.get(&u).copied().unwrap_or(n);
let semi_w = self.semi.get(&w).copied().unwrap_or(n);
if semi_u < semi_w {
self.semi.insert(w, semi_u);
}
}
}
}
let semi_w = self.semi.get(&w).copied().unwrap_or(self.dfnum[&w]);
let semi_vertex = self.vertex[semi_w];
self.bucket.entry(semi_vertex).or_default().push(w);
let parent_w = self.parent.get(&w).copied();
if let Some(p) = parent_w {
self.link(p, w);
if let Some(bucket) = self.bucket.get_mut(&p) {
let to_process = std::mem::take(bucket);
for v in to_process {
let u = self.eval(v);
let semi_u = self.semi.get(&u).copied().unwrap_or(n);
let semi_v = self.semi.get(&v).copied().unwrap_or(n);
if semi_u < semi_v {
self.idom.insert(v, u);
} else {
self.idom.insert(v, p);
}
}
}
}
}
for i in 1..n {
let w = self.vertex[i];
let semi_w = self.semi.get(&w).copied().unwrap_or(self.dfnum[&w]);
let semi_vertex = self.vertex[semi_w];
if let Some(&idom_w) = self.idom.get(&w) {
if idom_w != semi_vertex {
if let Some(&idom_idom_w) = self.idom.get(&idom_w) {
self.idom.insert(w, idom_idom_w);
}
}
}
}
}
fn dfs(&mut self, start: usize) {
let mut stack = vec![(start, None::<usize>, false)];
let mut visited = HashSet::new();
while let Some((v, parent, processed)) = stack.pop() {
if processed {
continue;
}
if visited.contains(&v) {
continue;
}
visited.insert(v);
let num = self.vertex.len();
self.dfnum.insert(v, num);
self.vertex.push(v);
self.semi.insert(v, num);
if let Some(p) = parent {
self.parent.insert(v, p);
}
if let Some(succs) = self.successors.get(&v) {
for &w in succs.iter().rev() {
if !visited.contains(&w) {
stack.push((w, Some(v), false));
}
}
}
}
}
fn eval(&mut self, v: usize) -> usize {
if self.ancestor.get(&v).copied().flatten().is_none() {
return v;
}
self.compress(v);
self.label.get(&v).copied().unwrap_or(v)
}
fn compress(&mut self, v: usize) {
let mut path = Vec::new();
let mut current = v;
while let Some(Some(anc)) = self.ancestor.get(¤t) {
if self.ancestor.get(anc).copied().flatten().is_some() {
path.push(current);
current = *anc;
} else {
break;
}
}
for node in path.into_iter().rev() {
if let Some(Some(anc)) = self.ancestor.get(&node).copied() {
let label_anc = self.label.get(&anc).copied().unwrap_or(anc);
let semi_label_anc = self.semi.get(&label_anc).copied().unwrap_or(usize::MAX);
let label_node = self.label.get(&node).copied().unwrap_or(node);
let semi_label_node = self.semi.get(&label_node).copied().unwrap_or(usize::MAX);
if semi_label_anc < semi_label_node {
self.label.insert(node, label_anc);
}
if let Some(Some(anc_anc)) = self.ancestor.get(&anc).copied() {
self.ancestor.insert(node, Some(anc_anc));
}
}
}
}
fn link(&mut self, v: usize, w: usize) {
self.ancestor.insert(w, Some(v));
}
}
pub fn compute_dominance_frontier(
cfg: &CfgInfo,
dom_tree: &DominatorTree,
) -> TldrResult<DominanceFrontier> {
let mut frontier: HashMap<usize, HashSet<usize>> = HashMap::with_capacity(dom_tree.nodes.len());
for &block_id in dom_tree.nodes.keys() {
frontier.insert(block_id, HashSet::new());
}
let mut predecessors: HashMap<usize, Vec<usize>> = HashMap::new();
for block in &cfg.blocks {
predecessors.entry(block.id).or_default();
}
for edge in &cfg.edges {
predecessors.entry(edge.to).or_default().push(edge.from);
}
let max_iterations = dom_tree.nodes.len() * dom_tree.nodes.len();
let mut total_iterations = 0;
for block in &cfg.blocks {
let preds = predecessors
.get(&block.id)
.map(|v| v.as_slice())
.unwrap_or(&[]);
if preds.len() < 2 {
continue;
}
let join_point = block.id;
let join_idom = dom_tree.nodes.get(&join_point).and_then(|n| n.idom);
for &pred in preds {
if !dom_tree.nodes.contains_key(&pred) {
continue;
}
let mut runner = pred;
loop {
total_iterations += 1;
if total_iterations > max_iterations {
return Err(TldrError::InvalidArgs {
arg: "dom_tree".to_string(),
message: "Iteration limit exceeded computing dominance frontier - possible corrupted dominator tree".to_string(),
suggestion: Some("Check that the dominator tree was computed correctly".to_string()),
});
}
if Some(runner) == join_idom {
break;
}
if runner == join_point {
break;
}
if let Some(df_set) = frontier.get_mut(&runner) {
df_set.insert(join_point);
}
match dom_tree.nodes.get(&runner).and_then(|n| n.idom) {
Some(idom) => runner = idom,
None => break, }
}
}
}
Ok(DominanceFrontier { frontier })
}
pub fn compute_iterated_df(df: &DominanceFrontier, blocks: &HashSet<usize>) -> HashSet<usize> {
let mut idf = HashSet::new();
let mut worklist: Vec<usize> = blocks.iter().copied().collect();
let mut processed: HashSet<usize> = HashSet::new();
while let Some(block) = worklist.pop() {
if !processed.insert(block) {
continue;
}
if let Some(frontier) = df.frontier.get(&block) {
for &y in frontier {
if idf.insert(y) {
if !processed.contains(&y) {
worklist.push(y);
}
}
}
}
}
idf
}