1use crate::lua51::cfg::{ControlFlowGraph, EdgeKind};
2
3#[derive(Debug)]
8pub struct DominatorTree {
9 idom: Vec<usize>,
11 children: Vec<Vec<usize>>,
13 frontier: Vec<Vec<usize>>,
15 num_blocks: usize,
17}
18
19impl DominatorTree {
20 pub fn build(cfg: &ControlFlowGraph) -> Self {
22 let n = cfg.num_blocks();
23 if n == 0 {
24 return DominatorTree {
25 idom: Vec::new(),
26 children: Vec::new(),
27 frontier: Vec::new(),
28 num_blocks: 0,
29 };
30 }
31
32 let rpo = compute_rpo(cfg, n);
34 let rpo_order = &rpo.order; let rpo_num = &rpo.number; const UNDEF: usize = usize::MAX;
39 let mut idom = vec![UNDEF; n];
40 idom[0] = 0; let intersect = |idom: &[usize], rpo_num: &[usize], mut b1: usize, mut b2: usize| -> usize {
43 while b1 != b2 {
44 while rpo_num[b1] > rpo_num[b2] {
45 b1 = idom[b1];
46 }
47 while rpo_num[b2] > rpo_num[b1] {
48 b2 = idom[b2];
49 }
50 }
51 b1
52 };
53
54 let mut changed = true;
55 while changed {
56 changed = false;
57 for &b in &rpo_order[1..] {
59 let preds = &cfg.blocks[b].predecessors;
60 let mut new_idom = UNDEF;
62 for &p in preds {
63 if idom[p] != UNDEF {
64 new_idom = p;
65 break;
66 }
67 }
68 if new_idom == UNDEF {
69 continue; }
71 for &p in preds {
73 if p != new_idom && idom[p] != UNDEF {
74 new_idom = intersect(&idom, rpo_num, p, new_idom);
75 }
76 }
77 if idom[b] != new_idom {
78 idom[b] = new_idom;
79 changed = true;
80 }
81 }
82 }
83
84 let mut children = vec![Vec::new(); n];
86 for b in 0..n {
87 if idom[b] != b && idom[b] != UNDEF {
88 children[idom[b]].push(b);
89 }
90 }
91
92 let frontier = compute_dominance_frontiers(&idom, cfg, n);
94
95 DominatorTree {
96 idom,
97 children,
98 frontier,
99 num_blocks: n,
100 }
101 }
102
103 pub fn idom(&self, b: usize) -> usize {
105 self.idom[b]
106 }
107
108 pub fn children(&self, b: usize) -> &[usize] {
110 &self.children[b]
111 }
112
113 pub fn frontier(&self, b: usize) -> &[usize] {
115 &self.frontier[b]
116 }
117
118 pub fn dominates(&self, a: usize, b: usize) -> bool {
120 const UNDEF: usize = usize::MAX;
121 let mut cur = b;
122 loop {
123 if cur == a {
124 return true;
125 }
126 let idom = self.idom[cur];
127 if idom == UNDEF || idom == cur {
128 return false; }
130 cur = idom;
131 }
132 }
133
134 pub fn num_blocks(&self) -> usize {
136 self.num_blocks
137 }
138}
139
140impl std::fmt::Display for DominatorTree {
141 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142 writeln!(f, "Dominator Tree:")?;
143 for b in 0..self.num_blocks {
144 writeln!(
145 f,
146 " B{}: idom=B{}, children={:?}, frontier={:?}",
147 b, self.idom[b], self.children[b], self.frontier[b]
148 )?;
149 }
150 Ok(())
151 }
152}
153
154#[derive(Debug, Clone)]
158pub struct NaturalLoop {
159 pub header: usize,
161 pub latch: usize,
163 pub body: Vec<usize>,
165 pub kind: LoopKind,
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171pub enum LoopKind {
172 NumericFor,
174 GenericFor,
176 WhileRepeat,
178}
179
180pub fn find_loops(cfg: &ControlFlowGraph, dom: &DominatorTree) -> Vec<NaturalLoop> {
186 let mut loops = Vec::new();
187
188 for edge in &cfg.edges {
189 if dom.dominates(edge.to, edge.from) {
191 let header = edge.to;
192 let latch = edge.from;
193
194 let body = collect_loop_body(cfg, header, latch);
197
198 let kind = classify_loop(cfg, edge.kind, header);
200
201 loops.push(NaturalLoop {
202 header,
203 latch,
204 body,
205 kind,
206 });
207 }
208 }
209
210 loops.sort_by_key(|l| (l.header, l.latch));
212 loops
213}
214
215fn collect_loop_body(cfg: &ControlFlowGraph, header: usize, latch: usize) -> Vec<usize> {
217 let mut body = vec![false; cfg.num_blocks()];
218 body[header] = true;
219
220 if header == latch {
221 return vec![header];
222 }
223
224 body[latch] = true;
225 let mut stack = vec![latch];
226
227 while let Some(b) = stack.pop() {
229 for &pred in &cfg.blocks[b].predecessors {
230 if !body[pred] {
231 body[pred] = true;
232 stack.push(pred);
233 }
234 }
235 }
236
237 body.iter()
238 .enumerate()
239 .filter_map(|(i, &in_loop)| if in_loop { Some(i) } else { None })
240 .collect()
241}
242
243fn classify_loop(cfg: &ControlFlowGraph, edge_kind: EdgeKind, header: usize) -> LoopKind {
245 match edge_kind {
246 EdgeKind::ForLoopBack => LoopKind::NumericFor,
247 EdgeKind::TForLoopBack => LoopKind::GenericFor,
248 _ => {
249 let block = &cfg.blocks[header];
251 use crate::lua51::opcodes::OpCode;
252 for pc in block.start..=block.end {
253 match cfg.instructions[pc].op {
254 OpCode::ForLoop => return LoopKind::NumericFor,
255 OpCode::TForLoop => return LoopKind::GenericFor,
256 _ => {}
257 }
258 }
259 LoopKind::WhileRepeat
260 }
261 }
262}
263
264struct Rpo {
267 order: Vec<usize>,
269 number: Vec<usize>,
271}
272
273fn compute_rpo(cfg: &ControlFlowGraph, n: usize) -> Rpo {
274 let mut visited = vec![false; n];
275 let mut postorder = Vec::with_capacity(n);
276
277 fn dfs(
278 b: usize,
279 cfg: &ControlFlowGraph,
280 visited: &mut Vec<bool>,
281 postorder: &mut Vec<usize>,
282 ) {
283 visited[b] = true;
284 for &succ in &cfg.blocks[b].successors {
285 if !visited[succ] {
286 dfs(succ, cfg, visited, postorder);
287 }
288 }
289 postorder.push(b);
290 }
291
292 dfs(0, cfg, &mut visited, &mut postorder);
293
294 postorder.reverse();
296 let mut number = vec![0usize; n];
297 for (i, &b) in postorder.iter().enumerate() {
298 number[b] = i;
299 }
300
301 Rpo {
302 order: postorder,
303 number,
304 }
305}
306
307fn compute_dominance_frontiers(idom: &[usize], cfg: &ControlFlowGraph, n: usize) -> Vec<Vec<usize>> {
310 let mut frontiers = vec![Vec::new(); n];
311
312 for b in 0..n {
313 let preds = &cfg.blocks[b].predecessors;
314 if preds.len() >= 2 {
315 for &p in preds {
316 let mut runner = p;
317 while runner != idom[b] && runner != usize::MAX {
318 if !frontiers[runner].contains(&b) {
319 frontiers[runner].push(b);
320 }
321 if runner == idom[runner] {
322 break;
323 }
324 runner = idom[runner];
325 }
326 }
327 }
328 }
329
330 frontiers
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336 use crate::lua51::cfg::ControlFlowGraph;
337 use crate::lua51::instruction::{encode_abc, encode_abx, encode_asbx};
338 use crate::lua51::opcodes::OpCode;
339
340 #[test]
341 fn dominator_linear() {
342 let code = vec![
344 encode_abx(OpCode::LoadK, 0, 0),
345 encode_abc(OpCode::Return, 0, 1, 0),
346 ];
347 let cfg = ControlFlowGraph::build(&code);
348 let dom = DominatorTree::build(&cfg);
349 assert_eq!(dom.idom(0), 0);
350 assert!(dom.dominates(0, 0));
351 }
352
353 #[test]
354 fn dominator_diamond() {
355 let code = vec![
369 encode_abc(OpCode::Eq, 0, 0, 1),
370 encode_asbx(OpCode::Jmp, 0, 2),
371 encode_abx(OpCode::LoadK, 2, 0),
372 encode_asbx(OpCode::Jmp, 0, 0),
373 encode_abc(OpCode::Return, 0, 1, 0),
374 ];
375 let cfg = ControlFlowGraph::build(&code);
376 let dom = DominatorTree::build(&cfg);
377
378 for b in 0..cfg.num_blocks() {
380 assert!(dom.dominates(0, b), "block 0 should dominate block {}", b);
381 }
382 }
383
384 #[test]
385 fn detect_numeric_for_loop() {
386 let code = vec![
394 encode_abx(OpCode::LoadK, 0, 0),
395 encode_abx(OpCode::LoadK, 1, 1),
396 encode_abx(OpCode::LoadK, 2, 2),
397 encode_asbx(OpCode::ForPrep, 0, 1),
398 encode_abx(OpCode::LoadK, 4, 0),
399 encode_asbx(OpCode::ForLoop, 0, -2),
400 encode_abc(OpCode::Return, 0, 1, 0),
401 ];
402 let cfg = ControlFlowGraph::build(&code);
403 let dom = DominatorTree::build(&cfg);
404 let loops = find_loops(&cfg, &dom);
405
406 assert_eq!(loops.len(), 1);
407 assert_eq!(loops[0].kind, LoopKind::NumericFor);
408 assert!(loops[0].body.len() >= 1);
409 }
410
411 #[test]
412 fn detect_while_loop() {
413 let code = vec![
420 encode_abc(OpCode::Eq, 0, 0, 1),
421 encode_asbx(OpCode::Jmp, 0, 2),
422 encode_abx(OpCode::LoadK, 2, 0),
423 encode_asbx(OpCode::Jmp, 0, -4),
424 encode_abc(OpCode::Return, 0, 1, 0),
425 ];
426 let cfg = ControlFlowGraph::build(&code);
427 let dom = DominatorTree::build(&cfg);
428 let loops = find_loops(&cfg, &dom);
429
430 assert_eq!(loops.len(), 1);
431 assert_eq!(loops[0].kind, LoopKind::WhileRepeat);
432 }
433
434 #[test]
435 fn empty_cfg_dominator() {
436 let cfg = ControlFlowGraph::build(&[]);
437 let dom = DominatorTree::build(&cfg);
438 assert_eq!(dom.num_blocks(), 0);
439 let loops = find_loops(&cfg, &dom);
440 assert!(loops.is_empty());
441 }
442}