use crate::lua51::cfg::{ControlFlowGraph, EdgeKind};
use crate::lua51::opcodes::OpCode;
#[derive(Debug)]
pub struct DominatorTree {
idom: Vec<usize>,
children: Vec<Vec<usize>>,
frontier: Vec<Vec<usize>>,
num_blocks: usize,
}
impl DominatorTree {
pub fn build(cfg: &ControlFlowGraph) -> Self {
let n = cfg.num_blocks();
if n == 0 {
return DominatorTree {
idom: Vec::new(),
children: Vec::new(),
frontier: Vec::new(),
num_blocks: 0,
};
}
let rpo = compute_rpo(cfg, n);
let rpo_order = &rpo.order; let rpo_num = &rpo.number;
const UNDEF: usize = usize::MAX;
let mut idom = vec![UNDEF; n];
idom[0] = 0;
let intersect = |idom: &[usize], rpo_num: &[usize], mut b1: usize, mut b2: usize| -> usize {
while b1 != b2 {
while rpo_num[b1] > rpo_num[b2] {
b1 = idom[b1];
}
while rpo_num[b2] > rpo_num[b1] {
b2 = idom[b2];
}
}
b1
};
let mut changed = true;
while changed {
changed = false;
for &b in &rpo_order[1..] {
let preds = &cfg.blocks[b].predecessors;
let mut new_idom = UNDEF;
for &p in preds {
if idom[p] != UNDEF {
new_idom = p;
break;
}
}
if new_idom == UNDEF {
continue; }
for &p in preds {
if p != new_idom && idom[p] != UNDEF {
new_idom = intersect(&idom, rpo_num, p, new_idom);
}
}
if idom[b] != new_idom {
idom[b] = new_idom;
changed = true;
}
}
}
let mut children = vec![Vec::new(); n];
for b in 0..n {
if idom[b] != b && idom[b] != UNDEF {
children[idom[b]].push(b);
}
}
let frontier = compute_dominance_frontiers(&idom, cfg, n);
DominatorTree {
idom,
children,
frontier,
num_blocks: n,
}
}
pub fn idom(&self, b: usize) -> usize {
self.idom[b]
}
pub fn children(&self, b: usize) -> &[usize] {
&self.children[b]
}
pub fn frontier(&self, b: usize) -> &[usize] {
&self.frontier[b]
}
pub fn dominates(&self, a: usize, b: usize) -> bool {
const UNDEF: usize = usize::MAX;
let mut cur = b;
loop {
if cur == a {
return true;
}
let idom = self.idom[cur];
if idom == UNDEF || idom == cur {
return false; }
cur = idom;
}
}
pub fn num_blocks(&self) -> usize {
self.num_blocks
}
}
impl std::fmt::Display for DominatorTree {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Dominator Tree:")?;
for b in 0..self.num_blocks {
writeln!(
f,
" B{}: idom=B{}, children={:?}, frontier={:?}",
b, self.idom[b], self.children[b], self.frontier[b]
)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct NaturalLoop {
pub header: usize,
pub latch: usize,
pub body: Vec<usize>,
pub kind: LoopKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LoopKind {
NumericFor,
GenericFor,
WhileRepeat,
}
pub fn find_loops(cfg: &ControlFlowGraph, dom: &DominatorTree) -> Vec<NaturalLoop> {
let mut loops = Vec::new();
for edge in &cfg.edges {
let target_block = &cfg.blocks[edge.to];
let target_ends_with_forloop = matches!(
cfg.instructions[target_block.end].op,
OpCode::ForLoop | OpCode::TForLoop
);
let is_explicit_loop_back = matches!(edge.kind, EdgeKind::ForLoopBack | EdgeKind::TForLoopBack);
let is_natural_back_edge = dom.dominates(edge.to, edge.from)
&& !(target_ends_with_forloop && !is_explicit_loop_back);
if is_explicit_loop_back || is_natural_back_edge {
let header = edge.to;
let latch = edge.from;
let mut body = collect_loop_body(cfg, header, latch);
if edge.kind == EdgeKind::ForLoopBack {
body.retain(|&block_id| {
let block = &cfg.blocks[block_id];
cfg.instructions[block.end].op != OpCode::ForPrep
});
}
let kind = classify_loop(cfg, edge.kind, header);
loops.push(NaturalLoop {
header,
latch,
body,
kind,
});
}
}
loops.sort_by_key(|l| (l.header, l.latch));
loops
}
fn collect_loop_body(cfg: &ControlFlowGraph, header: usize, latch: usize) -> Vec<usize> {
let mut body = vec![false; cfg.num_blocks()];
body[header] = true;
if header == latch {
return vec![header];
}
body[latch] = true;
let mut stack = vec![latch];
while let Some(b) = stack.pop() {
for &pred in &cfg.blocks[b].predecessors {
if !body[pred] {
body[pred] = true;
stack.push(pred);
}
}
}
body.iter()
.enumerate()
.filter_map(|(i, &in_loop)| if in_loop { Some(i) } else { None })
.collect()
}
fn classify_loop(cfg: &ControlFlowGraph, edge_kind: EdgeKind, header: usize) -> LoopKind {
match edge_kind {
EdgeKind::ForLoopBack => LoopKind::NumericFor,
EdgeKind::TForLoopBack => LoopKind::GenericFor,
_ => {
let block = &cfg.blocks[header];
use crate::lua51::opcodes::OpCode;
for pc in block.start..=block.end {
match cfg.instructions[pc].op {
OpCode::ForLoop => return LoopKind::NumericFor,
OpCode::TForLoop => return LoopKind::GenericFor,
_ => {}
}
}
LoopKind::WhileRepeat
}
}
}
struct Rpo {
order: Vec<usize>,
number: Vec<usize>,
}
fn compute_rpo(cfg: &ControlFlowGraph, n: usize) -> Rpo {
let mut visited = vec![false; n];
let mut postorder = Vec::with_capacity(n);
fn dfs(
b: usize,
cfg: &ControlFlowGraph,
visited: &mut Vec<bool>,
postorder: &mut Vec<usize>,
) {
visited[b] = true;
for &succ in &cfg.blocks[b].successors {
if !visited[succ] {
dfs(succ, cfg, visited, postorder);
}
}
postorder.push(b);
}
dfs(0, cfg, &mut visited, &mut postorder);
postorder.reverse();
let mut number = vec![0usize; n];
for (i, &b) in postorder.iter().enumerate() {
number[b] = i;
}
Rpo {
order: postorder,
number,
}
}
fn compute_dominance_frontiers(idom: &[usize], cfg: &ControlFlowGraph, n: usize) -> Vec<Vec<usize>> {
let mut frontiers = vec![Vec::new(); n];
for b in 0..n {
let preds = &cfg.blocks[b].predecessors;
if preds.len() >= 2 {
for &p in preds {
let mut runner = p;
while runner != idom[b] && runner != usize::MAX {
if !frontiers[runner].contains(&b) {
frontiers[runner].push(b);
}
if runner == idom[runner] {
break;
}
runner = idom[runner];
}
}
}
}
frontiers
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lua51::cfg::ControlFlowGraph;
use crate::lua51::instruction::{encode_abc, encode_abx, encode_asbx};
use crate::lua51::opcodes::OpCode;
#[test]
fn dominator_linear() {
let code = vec![
encode_abx(OpCode::LoadK, 0, 0),
encode_abc(OpCode::Return, 0, 1, 0),
];
let cfg = ControlFlowGraph::build(&code);
let dom = DominatorTree::build(&cfg);
assert_eq!(dom.idom(0), 0);
assert!(dom.dominates(0, 0));
}
#[test]
fn dominator_diamond() {
let code = vec![
encode_abc(OpCode::Eq, 0, 0, 1),
encode_asbx(OpCode::Jmp, 0, 2),
encode_abx(OpCode::LoadK, 2, 0),
encode_asbx(OpCode::Jmp, 0, 0),
encode_abc(OpCode::Return, 0, 1, 0),
];
let cfg = ControlFlowGraph::build(&code);
let dom = DominatorTree::build(&cfg);
for b in 0..cfg.num_blocks() {
assert!(dom.dominates(0, b), "block 0 should dominate block {}", b);
}
}
#[test]
fn detect_numeric_for_loop() {
let code = vec![
encode_abx(OpCode::LoadK, 0, 0),
encode_abx(OpCode::LoadK, 1, 1),
encode_abx(OpCode::LoadK, 2, 2),
encode_asbx(OpCode::ForPrep, 0, 1),
encode_abx(OpCode::LoadK, 4, 0),
encode_asbx(OpCode::ForLoop, 0, -2),
encode_abc(OpCode::Return, 0, 1, 0),
];
let cfg = ControlFlowGraph::build(&code);
let dom = DominatorTree::build(&cfg);
let loops = find_loops(&cfg, &dom);
assert_eq!(loops.len(), 1);
assert_eq!(loops[0].kind, LoopKind::NumericFor);
assert!(loops[0].body.len() >= 1);
}
#[test]
fn detect_while_loop() {
let code = vec![
encode_abc(OpCode::Eq, 0, 0, 1),
encode_asbx(OpCode::Jmp, 0, 2),
encode_abx(OpCode::LoadK, 2, 0),
encode_asbx(OpCode::Jmp, 0, -4),
encode_abc(OpCode::Return, 0, 1, 0),
];
let cfg = ControlFlowGraph::build(&code);
let dom = DominatorTree::build(&cfg);
let loops = find_loops(&cfg, &dom);
assert_eq!(loops.len(), 1);
assert_eq!(loops[0].kind, LoopKind::WhileRepeat);
}
#[test]
fn empty_cfg_dominator() {
let cfg = ControlFlowGraph::build(&[]);
let dom = DominatorTree::build(&cfg);
assert_eq!(dom.num_blocks(), 0);
let loops = find_loops(&cfg, &dom);
assert!(loops.is_empty());
}
}