Skip to main content

luadec_rust/lua51/
dominator.rs

1use crate::lua51::cfg::{ControlFlowGraph, EdgeKind};
2use crate::lua51::opcodes::OpCode;
3
4/// Dominator tree for a control flow graph.
5///
6/// Uses the iterative data-flow algorithm (Cooper, Harvey, Kennedy):
7/// simple, correct, and fast enough for the small CFGs produced by Lua functions.
8#[derive(Debug)]
9pub struct DominatorTree {
10    /// Immediate dominator of each block. `idom[entry] == entry`.
11    idom: Vec<usize>,
12    /// Children in the dominator tree (blocks immediately dominated).
13    children: Vec<Vec<usize>>,
14    /// Dominance frontier for each block.
15    frontier: Vec<Vec<usize>>,
16    /// Number of blocks.
17    num_blocks: usize,
18}
19
20impl DominatorTree {
21    /// Build the dominator tree from a CFG. Entry block is assumed to be block 0.
22    pub fn build(cfg: &ControlFlowGraph) -> Self {
23        let n = cfg.num_blocks();
24        if n == 0 {
25            return DominatorTree {
26                idom: Vec::new(),
27                children: Vec::new(),
28                frontier: Vec::new(),
29                num_blocks: 0,
30            };
31        }
32
33        // Compute reverse postorder (RPO) numbering via DFS.
34        let rpo = compute_rpo(cfg, n);
35        let rpo_order = &rpo.order; // block IDs in RPO
36        let rpo_num = &rpo.number; // block ID -> RPO number
37
38        // Iterative dominator computation (Cooper, Harvey, Kennedy 2001).
39        const UNDEF: usize = usize::MAX;
40        let mut idom = vec![UNDEF; n];
41        idom[0] = 0; // entry dominates itself
42
43        let intersect = |idom: &[usize], rpo_num: &[usize], mut b1: usize, mut b2: usize| -> usize {
44            while b1 != b2 {
45                while rpo_num[b1] > rpo_num[b2] {
46                    b1 = idom[b1];
47                }
48                while rpo_num[b2] > rpo_num[b1] {
49                    b2 = idom[b2];
50                }
51            }
52            b1
53        };
54
55        let mut changed = true;
56        while changed {
57            changed = false;
58            // Process in RPO, skipping entry (index 0 in rpo_order).
59            for &b in &rpo_order[1..] {
60                let preds = &cfg.blocks[b].predecessors;
61                // Find first processed predecessor.
62                let mut new_idom = UNDEF;
63                for &p in preds {
64                    if idom[p] != UNDEF {
65                        new_idom = p;
66                        break;
67                    }
68                }
69                if new_idom == UNDEF {
70                    continue; // unreachable block
71                }
72                // Intersect with remaining processed predecessors.
73                for &p in preds {
74                    if p != new_idom && idom[p] != UNDEF {
75                        new_idom = intersect(&idom, rpo_num, p, new_idom);
76                    }
77                }
78                if idom[b] != new_idom {
79                    idom[b] = new_idom;
80                    changed = true;
81                }
82            }
83        }
84
85        // Build children lists.
86        let mut children = vec![Vec::new(); n];
87        for b in 0..n {
88            if idom[b] != b && idom[b] != UNDEF {
89                children[idom[b]].push(b);
90            }
91        }
92
93        // Compute dominance frontiers.
94        let frontier = compute_dominance_frontiers(&idom, cfg, n);
95
96        DominatorTree {
97            idom,
98            children,
99            frontier,
100            num_blocks: n,
101        }
102    }
103
104    /// Immediate dominator of block `b`.
105    pub fn idom(&self, b: usize) -> usize {
106        self.idom[b]
107    }
108
109    /// Children of block `b` in the dominator tree.
110    pub fn children(&self, b: usize) -> &[usize] {
111        &self.children[b]
112    }
113
114    /// Dominance frontier of block `b`.
115    pub fn frontier(&self, b: usize) -> &[usize] {
116        &self.frontier[b]
117    }
118
119    /// Check if block `a` dominates block `b`.
120    pub fn dominates(&self, a: usize, b: usize) -> bool {
121        const UNDEF: usize = usize::MAX;
122        let mut cur = b;
123        loop {
124            if cur == a {
125                return true;
126            }
127            let idom = self.idom[cur];
128            if idom == UNDEF || idom == cur {
129                return false; // unreachable or reached entry without finding a
130            }
131            cur = idom;
132        }
133    }
134
135    /// Number of blocks.
136    pub fn num_blocks(&self) -> usize {
137        self.num_blocks
138    }
139}
140
141impl std::fmt::Display for DominatorTree {
142    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143        writeln!(f, "Dominator Tree:")?;
144        for b in 0..self.num_blocks {
145            writeln!(
146                f,
147                "  B{}: idom=B{}, children={:?}, frontier={:?}",
148                b, self.idom[b], self.children[b], self.frontier[b]
149            )?;
150        }
151        Ok(())
152    }
153}
154
155// --- Natural loop detection ---
156
157/// A natural loop identified from back-edges in the CFG.
158#[derive(Debug, Clone)]
159pub struct NaturalLoop {
160    /// The loop header block (target of back-edge, dominates all loop blocks).
161    pub header: usize,
162    /// The block containing the back-edge to the header.
163    pub latch: usize,
164    /// All blocks in the loop body (including header and latch).
165    pub body: Vec<usize>,
166    /// The kind of loop, if identifiable from Lua patterns.
167    pub kind: LoopKind,
168}
169
170/// Classification of Lua loop constructs.
171#[derive(Debug, Clone, Copy, PartialEq, Eq)]
172pub enum LoopKind {
173    /// `for i = init, limit, step do ... end` (FORPREP/FORLOOP)
174    NumericFor,
175    /// `for k, v in iter do ... end` (TFORLOOP)
176    GenericFor,
177    /// `while cond do ... end` or `repeat ... until cond`
178    WhileRepeat,
179}
180
181/// Find all natural loops in the CFG using dominator information.
182///
183/// A back-edge is an edge N -> H where H dominates N. Each back-edge
184/// defines a natural loop whose body is the set of blocks that can reach N
185/// without going through H.
186pub fn find_loops(cfg: &ControlFlowGraph, dom: &DominatorTree) -> Vec<NaturalLoop> {
187    let mut loops = Vec::new();
188
189    for edge in &cfg.edges {
190        let target_block = &cfg.blocks[edge.to];
191        let target_ends_with_forloop = matches!(
192            cfg.instructions[target_block.end].op,
193            OpCode::ForLoop | OpCode::TForLoop
194        );
195
196        // Numeric/generic for loops have explicit back-edge kinds and do not
197        // satisfy the normal dominance check because FORPREP jumps directly to
198        // the FORLOOP block before entering the body.
199        let is_explicit_loop_back = matches!(edge.kind, EdgeKind::ForLoopBack | EdgeKind::TForLoopBack);
200        let is_natural_back_edge = dom.dominates(edge.to, edge.from)
201            && !(target_ends_with_forloop && !is_explicit_loop_back);
202
203        if is_explicit_loop_back || is_natural_back_edge {
204            let header = edge.to;
205            let latch = edge.from;
206
207            // Collect loop body: all blocks that can reach `latch` without
208            // going through `header`.
209            let mut body = collect_loop_body(cfg, header, latch);
210            if edge.kind == EdgeKind::ForLoopBack {
211                body.retain(|&block_id| {
212                    let block = &cfg.blocks[block_id];
213                    cfg.instructions[block.end].op != OpCode::ForPrep
214                });
215            }
216
217            // Classify the loop kind based on the edge type and instructions.
218            let kind = classify_loop(cfg, edge.kind, header);
219
220            loops.push(NaturalLoop {
221                header,
222                latch,
223                body,
224                kind,
225            });
226        }
227    }
228
229    // Sort loops by header for deterministic ordering.
230    loops.sort_by_key(|l| (l.header, l.latch));
231    loops
232}
233
234/// Collect all blocks in the natural loop defined by the back-edge latch -> header.
235fn collect_loop_body(cfg: &ControlFlowGraph, header: usize, latch: usize) -> Vec<usize> {
236    let mut body = vec![false; cfg.num_blocks()];
237    body[header] = true;
238
239    if header == latch {
240        return vec![header];
241    }
242
243    body[latch] = true;
244    let mut stack = vec![latch];
245
246    // Work backwards from latch, adding predecessors until we reach header.
247    while let Some(b) = stack.pop() {
248        for &pred in &cfg.blocks[b].predecessors {
249            if !body[pred] {
250                body[pred] = true;
251                stack.push(pred);
252            }
253        }
254    }
255
256    body.iter()
257        .enumerate()
258        .filter_map(|(i, &in_loop)| if in_loop { Some(i) } else { None })
259        .collect()
260}
261
262/// Classify a loop based on the back-edge kind and header instructions.
263fn classify_loop(cfg: &ControlFlowGraph, edge_kind: EdgeKind, header: usize) -> LoopKind {
264    match edge_kind {
265        EdgeKind::ForLoopBack => LoopKind::NumericFor,
266        EdgeKind::TForLoopBack => LoopKind::GenericFor,
267        _ => {
268            // Check if header block contains FORLOOP or TFORLOOP
269            let block = &cfg.blocks[header];
270            use crate::lua51::opcodes::OpCode;
271            for pc in block.start..=block.end {
272                match cfg.instructions[pc].op {
273                    OpCode::ForLoop => return LoopKind::NumericFor,
274                    OpCode::TForLoop => return LoopKind::GenericFor,
275                    _ => {}
276                }
277            }
278            LoopKind::WhileRepeat
279        }
280    }
281}
282
283// --- RPO computation ---
284
285struct Rpo {
286    /// Block IDs in reverse postorder.
287    order: Vec<usize>,
288    /// RPO number for each block ID.
289    number: Vec<usize>,
290}
291
292fn compute_rpo(cfg: &ControlFlowGraph, n: usize) -> Rpo {
293    let mut visited = vec![false; n];
294    let mut postorder = Vec::with_capacity(n);
295
296    fn dfs(
297        b: usize,
298        cfg: &ControlFlowGraph,
299        visited: &mut Vec<bool>,
300        postorder: &mut Vec<usize>,
301    ) {
302        visited[b] = true;
303        for &succ in &cfg.blocks[b].successors {
304            if !visited[succ] {
305                dfs(succ, cfg, visited, postorder);
306            }
307        }
308        postorder.push(b);
309    }
310
311    dfs(0, cfg, &mut visited, &mut postorder);
312
313    // Reverse postorder
314    postorder.reverse();
315    let mut number = vec![0usize; n];
316    for (i, &b) in postorder.iter().enumerate() {
317        number[b] = i;
318    }
319
320    Rpo {
321        order: postorder,
322        number,
323    }
324}
325
326/// Compute dominance frontiers using the algorithm from
327/// Cooper, Harvey, Kennedy (2001).
328fn compute_dominance_frontiers(idom: &[usize], cfg: &ControlFlowGraph, n: usize) -> Vec<Vec<usize>> {
329    let mut frontiers = vec![Vec::new(); n];
330
331    for b in 0..n {
332        let preds = &cfg.blocks[b].predecessors;
333        if preds.len() >= 2 {
334            for &p in preds {
335                let mut runner = p;
336                while runner != idom[b] && runner != usize::MAX {
337                    if !frontiers[runner].contains(&b) {
338                        frontiers[runner].push(b);
339                    }
340                    if runner == idom[runner] {
341                        break;
342                    }
343                    runner = idom[runner];
344                }
345            }
346        }
347    }
348
349    frontiers
350}
351
352#[cfg(test)]
353mod tests {
354    use super::*;
355    use crate::lua51::cfg::ControlFlowGraph;
356    use crate::lua51::instruction::{encode_abc, encode_abx, encode_asbx};
357    use crate::lua51::opcodes::OpCode;
358
359    #[test]
360    fn dominator_linear() {
361        // Linear code: single block, entry dominates itself
362        let code = vec![
363            encode_abx(OpCode::LoadK, 0, 0),
364            encode_abc(OpCode::Return, 0, 1, 0),
365        ];
366        let cfg = ControlFlowGraph::build(&code);
367        let dom = DominatorTree::build(&cfg);
368        assert_eq!(dom.idom(0), 0);
369        assert!(dom.dominates(0, 0));
370    }
371
372    #[test]
373    fn dominator_diamond() {
374        // Diamond CFG:
375        // [0] EQ 0 0 1      block 0: [0,1]
376        // [1] JMP +1         -> block 2 (false), block 1 (true)
377        // [2] LOADK 2 0      block 1: [2]
378        // [3] LOADK 3 0      block 2: [3]
379        // [4] RETURN 0 1     block 2 continues: [3,4] -- wait, 3 is a leader
380        //
381        // Actually let me lay this out more carefully:
382        // [0] EQ 0 0 1       -- test
383        // [1] JMP +2         -- if false, jump to [4]
384        // [2] LOADK 2 0      -- true path
385        // [3] JMP +0         -- jump to [4]
386        // [4] RETURN 0 1     -- merge point
387        let code = vec![
388            encode_abc(OpCode::Eq, 0, 0, 1),
389            encode_asbx(OpCode::Jmp, 0, 2),
390            encode_abx(OpCode::LoadK, 2, 0),
391            encode_asbx(OpCode::Jmp, 0, 0),
392            encode_abc(OpCode::Return, 0, 1, 0),
393        ];
394        let cfg = ControlFlowGraph::build(&code);
395        let dom = DominatorTree::build(&cfg);
396
397        // Block 0 dominates all blocks
398        for b in 0..cfg.num_blocks() {
399            assert!(dom.dominates(0, b), "block 0 should dominate block {}", b);
400        }
401    }
402
403    #[test]
404    fn detect_numeric_for_loop() {
405        // [0] LOADK 0 0      -- init
406        // [1] LOADK 1 1      -- limit
407        // [2] LOADK 2 2      -- step
408        // [3] FORPREP 0 +1   -- jump to [5]
409        // [4] LOADK 4 0      -- loop body
410        // [5] FORLOOP 0 -2   -- back to [4]
411        // [6] RETURN 0 1
412        let code = vec![
413            encode_abx(OpCode::LoadK, 0, 0),
414            encode_abx(OpCode::LoadK, 1, 1),
415            encode_abx(OpCode::LoadK, 2, 2),
416            encode_asbx(OpCode::ForPrep, 0, 1),
417            encode_abx(OpCode::LoadK, 4, 0),
418            encode_asbx(OpCode::ForLoop, 0, -2),
419            encode_abc(OpCode::Return, 0, 1, 0),
420        ];
421        let cfg = ControlFlowGraph::build(&code);
422        let dom = DominatorTree::build(&cfg);
423        let loops = find_loops(&cfg, &dom);
424
425        assert_eq!(loops.len(), 1);
426        assert_eq!(loops[0].kind, LoopKind::NumericFor);
427        assert!(loops[0].body.len() >= 1);
428    }
429
430    #[test]
431    fn detect_while_loop() {
432        // Simple while-style loop:
433        // [0] EQ 0 0 1       -- while condition test
434        // [1] JMP +2         -- if false, exit to [4]
435        // [2] LOADK 2 0      -- loop body
436        // [3] JMP -4         -- jump back to [0]
437        // [4] RETURN 0 1
438        let code = vec![
439            encode_abc(OpCode::Eq, 0, 0, 1),
440            encode_asbx(OpCode::Jmp, 0, 2),
441            encode_abx(OpCode::LoadK, 2, 0),
442            encode_asbx(OpCode::Jmp, 0, -4),
443            encode_abc(OpCode::Return, 0, 1, 0),
444        ];
445        let cfg = ControlFlowGraph::build(&code);
446        let dom = DominatorTree::build(&cfg);
447        let loops = find_loops(&cfg, &dom);
448
449        assert_eq!(loops.len(), 1);
450        assert_eq!(loops[0].kind, LoopKind::WhileRepeat);
451    }
452
453    #[test]
454    fn empty_cfg_dominator() {
455        let cfg = ControlFlowGraph::build(&[]);
456        let dom = DominatorTree::build(&cfg);
457        assert_eq!(dom.num_blocks(), 0);
458        let loops = find_loops(&cfg, &dom);
459        assert!(loops.is_empty());
460    }
461}