Skip to main content

luadec_rust/lua51/
liveness.rs

1use crate::lua51::cfg::ControlFlowGraph;
2use crate::lua51::instruction::{is_k, Instruction};
3use crate::lua51::opcodes::OpCode;
4
5/// Per-instruction register usage info.
6#[derive(Debug, Clone, Default)]
7pub struct RegUsage {
8    /// Registers defined (written) by this instruction.
9    pub defs: Vec<u32>,
10    /// Registers used (read) by this instruction.
11    pub uses: Vec<u32>,
12}
13
14/// Compute register defs/uses for a single instruction.
15pub fn instruction_reg_usage(inst: &Instruction) -> RegUsage {
16    let mut u = RegUsage::default();
17    let a = inst.a;
18
19    match inst.op {
20        OpCode::Move => {
21            u.defs.push(a);
22            u.uses.push(inst.b());
23        }
24        OpCode::LoadK | OpCode::LoadBool => {
25            u.defs.push(a);
26        }
27        OpCode::LoadNil => {
28            for r in a..=inst.b() {
29                u.defs.push(r);
30            }
31        }
32        OpCode::GetUpval => {
33            u.defs.push(a);
34        }
35        OpCode::GetGlobal => {
36            u.defs.push(a);
37        }
38        OpCode::GetTable => {
39            u.defs.push(a);
40            u.uses.push(inst.b());
41            if !is_k(inst.c()) { u.uses.push(inst.c()); }
42        }
43        OpCode::SetGlobal => {
44            u.uses.push(a);
45        }
46        OpCode::SetUpval => {
47            u.uses.push(a);
48        }
49        OpCode::SetTable => {
50            u.uses.push(a);
51            if !is_k(inst.b()) { u.uses.push(inst.b()); }
52            if !is_k(inst.c()) { u.uses.push(inst.c()); }
53        }
54        OpCode::NewTable => {
55            u.defs.push(a);
56        }
57        OpCode::Self_ => {
58            u.defs.push(a);
59            u.defs.push(a + 1);
60            u.uses.push(inst.b());
61            if !is_k(inst.c()) { u.uses.push(inst.c()); }
62        }
63        OpCode::Add | OpCode::Sub | OpCode::Mul | OpCode::Div
64        | OpCode::Mod | OpCode::Pow => {
65            u.defs.push(a);
66            if !is_k(inst.b()) { u.uses.push(inst.b()); }
67            if !is_k(inst.c()) { u.uses.push(inst.c()); }
68        }
69        OpCode::Unm | OpCode::Not | OpCode::Len => {
70            u.defs.push(a);
71            u.uses.push(inst.b());
72        }
73        OpCode::Concat => {
74            u.defs.push(a);
75            for r in inst.b()..=inst.c() {
76                u.uses.push(r);
77            }
78        }
79        OpCode::Jmp => {}
80        OpCode::Eq | OpCode::Lt | OpCode::Le => {
81            if !is_k(inst.b()) { u.uses.push(inst.b()); }
82            if !is_k(inst.c()) { u.uses.push(inst.c()); }
83        }
84        OpCode::Test => {
85            u.uses.push(a);
86        }
87        OpCode::TestSet => {
88            u.defs.push(a);
89            u.uses.push(inst.b());
90        }
91        OpCode::Call => {
92            // Function in R(A), args in R(A+1)..R(A+B-1)
93            let num_args = if inst.b() == 0 { 0 } else { inst.b() - 1 };
94            u.uses.push(a);
95            for i in 1..=num_args {
96                u.uses.push(a + i);
97            }
98            // Results in R(A)..R(A+C-2)
99            let num_results = if inst.c() == 0 { 1 } else { inst.c() - 1 };
100            for i in 0..num_results {
101                u.defs.push(a + i);
102            }
103        }
104        OpCode::TailCall => {
105            let num_args = if inst.b() == 0 { 0 } else { inst.b() - 1 };
106            u.uses.push(a);
107            for i in 1..=num_args {
108                u.uses.push(a + i);
109            }
110        }
111        OpCode::Return => {
112            let num_ret = if inst.b() == 0 { 0 } else { inst.b() - 1 };
113            for i in 0..num_ret {
114                u.uses.push(a + i);
115            }
116        }
117        OpCode::ForLoop => {
118            // Uses and modifies loop control vars
119            u.uses.push(a);
120            u.uses.push(a + 1);
121            u.uses.push(a + 2);
122            u.defs.push(a);
123            u.defs.push(a + 3);
124        }
125        OpCode::ForPrep => {
126            u.uses.push(a);
127            u.uses.push(a + 2);
128            u.defs.push(a);
129        }
130        OpCode::TForLoop => {
131            u.uses.push(a);
132            u.uses.push(a + 1);
133            u.uses.push(a + 2);
134            let num_vars = inst.c();
135            for i in 0..=num_vars {
136                u.defs.push(a + 3 + i);
137            }
138        }
139        OpCode::SetList => {
140            let num = if inst.b() == 0 { 0 } else { inst.b() };
141            u.uses.push(a);
142            for i in 1..=num {
143                u.uses.push(a + i);
144            }
145        }
146        OpCode::Close => {}
147        OpCode::Closure => {
148            u.defs.push(a);
149        }
150        OpCode::VarArg => {
151            let num = if inst.b() == 0 { 1 } else { inst.b() - 1 };
152            for i in 0..num {
153                u.defs.push(a + i);
154            }
155        }
156    }
157    u
158}
159
160/// Liveness information for a CFG.
161#[derive(Debug)]
162pub struct LivenessInfo {
163    /// For each block: set of registers live at entry.
164    pub live_in: Vec<Vec<bool>>,
165    /// For each block: set of registers live at exit.
166    pub live_out: Vec<Vec<bool>>,
167    /// Maximum register index seen.
168    pub max_reg: usize,
169}
170
171/// Compute liveness analysis for the given CFG.
172/// Returns per-block live-in and live-out sets.
173pub fn compute_liveness(cfg: &ControlFlowGraph, max_stack: usize) -> LivenessInfo {
174    let n = cfg.num_blocks();
175    let nregs = max_stack;
176    if n == 0 {
177        return LivenessInfo {
178            live_in: Vec::new(),
179            live_out: Vec::new(),
180            max_reg: nregs,
181        };
182    }
183
184    // Compute per-block use_set (used before def) and kill (defined) sets.
185    let mut use_set = vec![vec![false; nregs]; n];
186    let mut kill = vec![vec![false; nregs]; n];
187
188    for (bid, block) in cfg.blocks.iter().enumerate() {
189        for pc in block.start..=block.end {
190            let inst = &cfg.instructions[pc];
191            let usage = instruction_reg_usage(inst);
192            // use_set: used but not yet killed in this block
193            for &r in &usage.uses {
194                let r = r as usize;
195                if r < nregs && !kill[bid][r] {
196                    use_set[bid][r] = true;
197                }
198            }
199            // kill: defined in this block
200            for &r in &usage.defs {
201                let r = r as usize;
202                if r < nregs {
203                    kill[bid][r] = true;
204                }
205            }
206        }
207    }
208
209    // Iterative dataflow: live_out[B] = union of live_in[S] for all successors S
210    // live_in[B] = use_set[B] | (live_out[B] - kill[B])
211    let mut live_in = vec![vec![false; nregs]; n];
212    let mut live_out = vec![vec![false; nregs]; n];
213
214    let mut changed = true;
215    while changed {
216        changed = false;
217        // Process in reverse order for faster convergence
218        for bid in (0..n).rev() {
219            // live_out = union of live_in of successors
220            for r in 0..nregs {
221                let mut out = false;
222                for &succ in &cfg.blocks[bid].successors {
223                    if succ < n && live_in[succ][r] {
224                        out = true;
225                        break;
226                    }
227                }
228                live_out[bid][r] = out;
229            }
230
231            // live_in = use_set | (live_out - kill)
232            for r in 0..nregs {
233                let new_in = use_set[bid][r] || (live_out[bid][r] && !kill[bid][r]);
234                if new_in != live_in[bid][r] {
235                    live_in[bid][r] = new_in;
236                    changed = true;
237                }
238            }
239        }
240    }
241
242    LivenessInfo { live_in, live_out, max_reg: nregs }
243}
244
245/// Check if a register is live at a given pc within a block.
246/// This does a local scan from end of block backwards.
247pub fn is_reg_live_after(
248    cfg: &ControlFlowGraph,
249    liveness: &LivenessInfo,
250    pc: usize,
251    reg: u32,
252) -> bool {
253    let r = reg as usize;
254    if r >= liveness.max_reg {
255        return false;
256    }
257    let block_id = cfg.block_of(pc);
258    let block = &cfg.blocks[block_id];
259
260    // Start with live_out of the block
261    let mut live = liveness.live_out[block_id][r];
262
263    // Scan backwards from end of block to pc+1
264    for scan_pc in (pc + 1..=block.end).rev() {
265        let inst = &cfg.instructions[scan_pc];
266        let usage = instruction_reg_usage(inst);
267        // If defined here, not live before this point
268        if usage.defs.contains(&reg) {
269            live = false;
270        }
271        // If used here, live before this point
272        if usage.uses.contains(&reg) {
273            live = true;
274        }
275    }
276
277    live
278}