Skip to main content

luadec_rust/lua51/
lifter.rs

1use std::collections::{HashMap, HashSet};
2
3use luac_parser::{LuaChunk, LuaConstant, LuaNumber};
4
5use crate::lua51::ast::*;
6use crate::lua51::cfg::{BasicBlock, ControlFlowGraph, EdgeKind};
7use crate::lua51::dominator::{find_loops, DominatorTree, LoopKind, NaturalLoop};
8use crate::lua51::instruction::{is_k, index_k};
9use crate::lua51::liveness::{compute_liveness, is_reg_live_after, LivenessInfo};
10use crate::lua51::opcodes::OpCode;
11
12/// Context for decompiling a single Lua function.
13pub struct Lifter<'a> {
14    chunk: &'a LuaChunk,
15    cfg: ControlFlowGraph,
16    dom: DominatorTree,
17    loops: Vec<NaturalLoop>,
18    liveness: LivenessInfo,
19    /// Register expressions: tracks what expression is currently held in each register.
20    regs: Vec<Option<Expr>>,
21    /// Pending tables being constructed (register -> accumulated fields).
22    pending_tables: HashMap<u32, Vec<TableField>>,
23    /// Blocks already visited to prevent infinite recursion.
24    visited_blocks: HashSet<usize>,
25    /// Local variable names assigned to registers (reg -> name).
26    local_names: HashMap<u32, String>,
27    /// Registers that have been declared as `local`.
28    declared_locals: HashSet<u32>,
29    /// Number of parameters (these registers are implicitly declared).
30    num_params: u32,
31    /// Whether this chunk has debug info (locals/upvalue names).
32    has_debug_info: bool,
33    /// Loop exit block IDs for break detection.
34    loop_exits: HashSet<usize>,
35}
36
37impl<'a> Lifter<'a> {
38    pub fn decompile(chunk: &'a LuaChunk) -> Function {
39        let cfg = ControlFlowGraph::build(&chunk.instructions);
40        let dom = DominatorTree::build(&cfg);
41        let loops = find_loops(&cfg, &dom);
42        let liveness = compute_liveness(&cfg, chunk.max_stack as usize);
43        let has_debug_info = !chunk.locals.is_empty();
44
45        // Collect loop exit blocks for break detection
46        let mut loop_exits = HashSet::new();
47        for lp in &loops {
48            let max_body = lp.body.iter().copied().max().unwrap_or(lp.header);
49            if max_body + 1 < cfg.num_blocks() {
50                loop_exits.insert(max_body + 1);
51            }
52        }
53
54        let max_stack = chunk.max_stack as usize;
55        let mut lifter = Lifter {
56            chunk,
57            cfg,
58            dom,
59            loops,
60            liveness,
61            regs: vec![None; max_stack.max(256)],
62            pending_tables: HashMap::new(),
63            visited_blocks: HashSet::new(),
64            local_names: HashMap::new(),
65            declared_locals: HashSet::new(),
66            num_params: chunk.num_params as u32,
67            has_debug_info,
68            loop_exits,
69        };
70
71        let params: Vec<String> = (0..chunk.num_params as u32)
72            .map(|i| {
73                let name = lifter.local_name(i, 0);
74                lifter.local_names.insert(i, name.clone());
75                lifter.declared_locals.insert(i);
76                lifter.set_reg(i, Expr::Name(name.clone()));
77                name
78            })
79            .collect();
80        let is_vararg = chunk.is_vararg.is_some();
81
82        let body = if lifter.cfg.num_blocks() > 0 {
83            lifter.lift_block_range(0, lifter.cfg.num_blocks())
84        } else {
85            Vec::new()
86        };
87
88        Function {
89            params,
90            is_vararg,
91            body,
92        }
93    }
94
95    /// Lift a range of blocks into a statement list, handling control structures.
96    fn lift_block_range(&mut self, start_block: usize, end_block: usize) -> Block {
97        let mut stmts = Vec::new();
98        let mut block_idx = start_block;
99
100        while block_idx < end_block && block_idx < self.cfg.num_blocks() {
101            // Prevent revisiting blocks
102            if !self.visited_blocks.insert(block_idx) {
103                block_idx += 1;
104                continue;
105            }
106
107            // Check if this block is a loop header
108            if let Some(lp) = self.find_loop_at(block_idx) {
109                let lp = lp.clone();
110                let next = self.lift_loop(&lp, &mut stmts);
111                if next <= block_idx {
112                    // Safety: avoid infinite loop
113                    block_idx += 1;
114                } else {
115                    block_idx = next;
116                }
117                continue;
118            }
119
120            let block = self.cfg.blocks[block_idx].clone();
121            let _last_pc = block.end;
122
123            // Check for conditional (if/elseif/else)
124            if self.is_conditional_block(&block) {
125                let next = self.lift_conditional(block_idx, &mut stmts);
126                if next <= block_idx {
127                    // Safety: avoid infinite loop, lift as normal instructions
128                    self.lift_instructions(block.start, block.end, &mut stmts);
129                    block_idx += 1;
130                } else {
131                    block_idx = next;
132                }
133                continue;
134            }
135
136            // Normal block: lift instructions sequentially
137            self.lift_instructions(block.start, block.end, &mut stmts);
138
139            // Check if this block ends with a JMP to a loop exit (break)
140            let last_inst = self.cfg.instructions[block.end];
141            if last_inst.op == OpCode::Jmp && block.successors.len() == 1 {
142                let target = block.successors[0];
143                if self.loop_exits.contains(&target) {
144                    stmts.push(Stat::Break);
145                }
146            }
147
148            block_idx += 1;
149        }
150
151        stmts
152    }
153
154    /// Lift a single loop structure.
155    fn lift_loop(&mut self, lp: &NaturalLoop, stmts: &mut Block) -> usize {
156        match lp.kind {
157            LoopKind::NumericFor => self.lift_numeric_for(lp, stmts),
158            LoopKind::GenericFor => self.lift_generic_for(lp, stmts),
159            LoopKind::WhileRepeat => self.lift_while(lp, stmts),
160        }
161    }
162
163    fn lift_numeric_for(&mut self, lp: &NaturalLoop, stmts: &mut Block) -> usize {
164        let header = &self.cfg.blocks[lp.header].clone();
165
166        // Find the FORPREP instruction: it's in the block preceding the header
167        // or in the header itself.  The FORPREP's A register tells us the for-loop slots.
168        let forprep_block = self.find_forprep_block(lp.header);
169        let forprep_inst = if let Some(fb) = forprep_block {
170            let b = &self.cfg.blocks[fb];
171            self.cfg.instructions[b.end]
172        } else {
173            self.cfg.instructions[header.start]
174        };
175
176        let base = forprep_inst.a;
177        let var_name = self.local_name(base + 3, header.start);
178
179        // Lift the pre-loop setup to get init/limit/step
180        if let Some(fb) = forprep_block {
181            let b = &self.cfg.blocks[fb].clone();
182            // Lift instructions before FORPREP to set up init/limit/step
183            if b.end > b.start {
184                self.lift_instructions(b.start, b.end - 1, stmts);
185            }
186        }
187
188        let start_expr = self.reg_expr(base);
189        let limit_expr = self.reg_expr(base + 1);
190        let step_expr = self.reg_expr(base + 2);
191        let step = if matches!(&step_expr, Expr::Number(NumLit::Int(1))) {
192            None
193        } else {
194            Some(step_expr)
195        };
196
197        // Body: blocks between header and latch (exclusive of FORLOOP block)
198        let body_start = lp.header + 1;
199        let body_end = lp.latch;
200        let body = if body_start < body_end {
201            self.lift_block_range(body_start, body_end)
202        } else {
203            // Single-block body: the header IS the body (header contains FORLOOP at end)
204            let hdr = self.cfg.blocks[lp.header].clone();
205            if hdr.end > hdr.start {
206                let mut body = Vec::new();
207                self.lift_instructions(hdr.start, hdr.end - 1, &mut body);
208                body
209            } else {
210                Vec::new()
211            }
212        };
213
214        stmts.push(Stat::NumericFor {
215            name: var_name,
216            start: start_expr,
217            limit: limit_expr,
218            step,
219            body,
220        });
221
222        // Return the block after the loop exit
223        self.max_loop_block(lp) + 1
224    }
225
226    fn lift_generic_for(&mut self, lp: &NaturalLoop, stmts: &mut Block) -> usize {
227        let header = &self.cfg.blocks[lp.header].clone();
228
229        // Find TFORLOOP instruction in the latch block
230        let latch_block = &self.cfg.blocks[lp.latch].clone();
231        let mut tforloop_inst = None;
232        for pc in latch_block.start..=latch_block.end {
233            if self.cfg.instructions[pc].op == OpCode::TForLoop {
234                tforloop_inst = Some(self.cfg.instructions[pc]);
235                break;
236            }
237        }
238        let tfl = tforloop_inst.unwrap_or(self.cfg.instructions[latch_block.end]);
239
240        let base = tfl.a;
241        let num_vars = tfl.c();
242
243        let names: Vec<String> = (0..num_vars)
244            .map(|i| self.local_name(base + 3 + i, header.start))
245            .collect();
246        let iter_expr = self.reg_expr(base);
247
248        let body_start = lp.header;
249        let body_end = lp.latch;
250        let body = if body_start < body_end {
251            self.lift_block_range(body_start, body_end)
252        } else {
253            Vec::new()
254        };
255
256        stmts.push(Stat::GenericFor {
257            names,
258            iterators: vec![iter_expr],
259            body,
260        });
261
262        self.max_loop_block(lp) + 1
263    }
264
265    fn lift_while(&mut self, lp: &NaturalLoop, stmts: &mut Block) -> usize {
266        let _header = &self.cfg.blocks[lp.header].clone();
267
268        // Try to extract condition from header block
269        let cond = self.extract_condition(lp.header).unwrap_or(Expr::Bool(true));
270
271        // Body: blocks in the loop excluding header
272        let body_start = lp.header + 1;
273        let body_end = lp.latch + 1;
274        let body = self.lift_block_range(body_start, body_end);
275
276        stmts.push(Stat::While { cond, body });
277
278        self.max_loop_block(lp) + 1
279    }
280
281    /// Lift an if/elseif/else chain.
282    fn lift_conditional(&mut self, block_idx: usize, stmts: &mut Block) -> usize {
283        let block = self.cfg.blocks[block_idx].clone();
284
285        // Lift any instructions before the test/JMP at the end of this block.
286        // The test is typically the second-to-last instruction (before JMP).
287        let test_pc = self.find_test_pc(&block);
288        if let Some(tp) = test_pc {
289            if tp > block.start {
290                self.lift_instructions(block.start, tp - 1, stmts);
291            }
292        }
293
294        let cond = self.extract_condition(block_idx).unwrap_or(Expr::Bool(true));
295
296        // Find the two branches
297        let succs = block.successors.clone();
298        if succs.len() != 2 {
299            // Not a proper conditional; just lift as normal
300            self.lift_instructions(block.start, block.end, stmts);
301            return block_idx + 1;
302        }
303
304        // The edge order: ConditionalFalse is added first, ConditionalTrue second
305        // ConditionalFalse = the JMP target (condition NOT met)
306        // ConditionalTrue = fallthrough after JMP (condition met, test passed -> skip JMP)
307        let false_target = succs[0]; // Where JMP goes (condition false)
308        let true_target = succs[1];  // Fallthrough past JMP (condition true)
309
310        // Detect guard clause pattern: `if not cond then return end`
311        // In Lua bytecode this is: TEST/EQ -> JMP past return -> RETURN -> continuation
312        // The true_target block (condition true = skip JMP) is a small block ending in RETURN
313        // and false_target is the continuation.
314        if self.is_return_block(true_target) && false_target > true_target {
315            // Guard clause: the "true" path is just a return
316            let guard_body = self.lift_block_range(true_target, true_target + 1);
317            stmts.push(Stat::If {
318                cond,
319                then_block: guard_body,
320                elseif_clauses: Vec::new(),
321                else_block: None,
322            });
323            return false_target;
324        }
325
326        // Detect inverted guard: `if cond then <continue> else return end`
327        // Here false_target is a return block and true_target is the continuation
328        if self.is_return_block(false_target) && true_target < false_target {
329            let guard_body = self.lift_block_range(false_target, false_target + 1);
330            let inv_cond = negate_expr(cond);
331            stmts.push(Stat::If {
332                cond: inv_cond,
333                then_block: guard_body,
334                elseif_clauses: Vec::new(),
335                else_block: None,
336            });
337            return true_target;
338        }
339
340        // Find the merge point
341        let merge = self.find_merge_point(block_idx, true_target, false_target);
342
343        let then_end = merge.unwrap_or(false_target);
344        let then_block = self.lift_block_range(true_target, then_end);
345
346        let else_block = if let Some(merge) = merge {
347            if false_target < merge {
348                let eb = self.lift_block_range(false_target, merge);
349                if eb.is_empty() { None } else { Some(eb) }
350            } else {
351                None
352            }
353        } else {
354            None
355        };
356
357        stmts.push(Stat::If {
358            cond,
359            then_block,
360            elseif_clauses: Vec::new(),
361            else_block,
362        });
363
364        merge.unwrap_or(false_target.max(true_target) + 1)
365    }
366
367    /// Lift a range of instructions into statements.
368    fn lift_instructions(&mut self, start_pc: usize, end_pc: usize, stmts: &mut Block) {
369        let mut pc = start_pc;
370        while pc <= end_pc {
371            let inst = self.cfg.instructions[pc];
372            match inst.op {
373                OpCode::Move => {
374                    let src = self.reg_expr(inst.b());
375                    self.set_reg(inst.a, src);
376                }
377                OpCode::LoadK => {
378                    let expr = self.const_expr(inst.bx());
379                    self.set_reg(inst.a, expr);
380                }
381                OpCode::LoadBool => {
382                    self.set_reg(inst.a, Expr::Bool(inst.b() != 0));
383                    if inst.c() != 0 {
384                        pc += 1; // skip next instruction
385                    }
386                }
387                OpCode::LoadNil => {
388                    for r in inst.a..=inst.b() {
389                        self.set_reg(r, Expr::Nil);
390                    }
391                }
392                OpCode::GetUpval => {
393                    let expr = self.upvalue_expr(inst.b());
394                    self.set_reg(inst.a, expr);
395                }
396                OpCode::GetGlobal => {
397                    let name = self.const_string(inst.bx());
398                    self.set_reg(inst.a, Expr::Global(name));
399                }
400                OpCode::GetTable => {
401                    let table = self.reg_expr(inst.b());
402                    let key = self.rk_expr(inst.c());
403                    let expr = make_index(table, key);
404                    self.set_reg(inst.a, expr);
405                }
406                OpCode::SetGlobal => {
407                    self.flush_pending_table(inst.a);
408                    let name = self.const_string(inst.bx());
409                    let val = self.reg_expr(inst.a);
410                    stmts.push(Stat::Assign {
411                        targets: vec![Expr::Global(name)],
412                        values: vec![val],
413                    });
414                }
415                OpCode::SetUpval => {
416                    let val = self.reg_expr(inst.a);
417                    let uv = self.upvalue_expr(inst.b());
418                    stmts.push(Stat::Assign {
419                        targets: vec![uv],
420                        values: vec![val],
421                    });
422                }
423                OpCode::SetTable => {
424                    // Check if this is part of table construction
425                    let is_pending = self.pending_tables.contains_key(&inst.a);
426                    if is_pending {
427                        let key = self.rk_expr(inst.b());
428                        let val = self.rk_expr(inst.c());
429                        let fields = self.pending_tables.get_mut(&inst.a).unwrap();
430                        // If key is a string identifier, use NameField
431                        if let Expr::StringLit(ref s) = key {
432                            if let Ok(name) = std::str::from_utf8(s) {
433                                if is_identifier(name) {
434                                    fields.push(TableField::NameField(
435                                        name.to_string(),
436                                        val,
437                                    ));
438                                    pc += 1;
439                                    continue;
440                                }
441                            }
442                        }
443                        fields.push(TableField::IndexField(key, val));
444                        pc += 1;
445                        continue;
446                    }
447                    // Flush any pending table first
448                    self.flush_pending_table(inst.a);
449                    let table = self.reg_expr(inst.a);
450                    let key = self.rk_expr(inst.b());
451                    let val = self.rk_expr(inst.c());
452                    let target = make_index(table, key);
453                    stmts.push(Stat::Assign {
454                        targets: vec![target],
455                        values: vec![val],
456                    });
457                }
458                OpCode::NewTable => {
459                    self.set_reg(inst.a, Expr::Table(Vec::new()));
460                    self.pending_tables.insert(inst.a, Vec::new());
461                }
462                OpCode::Self_ => {
463                    let table = self.reg_expr(inst.b());
464                    let method = self.rk_expr(inst.c());
465                    let method_ref = make_index(table.clone(), method);
466                    self.set_reg(inst.a + 1, table);
467                    self.set_reg(inst.a, method_ref);
468                }
469                OpCode::Add => {
470                    let expr = Expr::BinOp(
471                        BinOp::Add,
472                        Box::new(self.rk_expr(inst.b())),
473                        Box::new(self.rk_expr(inst.c())),
474                    );
475                    self.set_reg(inst.a, expr);
476                }
477                OpCode::Sub => {
478                    let expr = Expr::BinOp(
479                        BinOp::Sub,
480                        Box::new(self.rk_expr(inst.b())),
481                        Box::new(self.rk_expr(inst.c())),
482                    );
483                    self.set_reg(inst.a, expr);
484                }
485                OpCode::Mul => {
486                    let expr = Expr::BinOp(
487                        BinOp::Mul,
488                        Box::new(self.rk_expr(inst.b())),
489                        Box::new(self.rk_expr(inst.c())),
490                    );
491                    self.set_reg(inst.a, expr);
492                }
493                OpCode::Div => {
494                    let expr = Expr::BinOp(
495                        BinOp::Div,
496                        Box::new(self.rk_expr(inst.b())),
497                        Box::new(self.rk_expr(inst.c())),
498                    );
499                    self.set_reg(inst.a, expr);
500                }
501                OpCode::Mod => {
502                    let expr = Expr::BinOp(
503                        BinOp::Mod,
504                        Box::new(self.rk_expr(inst.b())),
505                        Box::new(self.rk_expr(inst.c())),
506                    );
507                    self.set_reg(inst.a, expr);
508                }
509                OpCode::Pow => {
510                    let expr = Expr::BinOp(
511                        BinOp::Pow,
512                        Box::new(self.rk_expr(inst.b())),
513                        Box::new(self.rk_expr(inst.c())),
514                    );
515                    self.set_reg(inst.a, expr);
516                }
517                OpCode::Unm => {
518                    let expr = Expr::UnOp(UnOp::Neg, Box::new(self.reg_expr(inst.b())));
519                    self.set_reg(inst.a, expr);
520                }
521                OpCode::Not => {
522                    let expr = Expr::UnOp(UnOp::Not, Box::new(self.reg_expr(inst.b())));
523                    self.set_reg(inst.a, expr);
524                }
525                OpCode::Len => {
526                    let expr = Expr::UnOp(UnOp::Len, Box::new(self.reg_expr(inst.b())));
527                    self.set_reg(inst.a, expr);
528                }
529                OpCode::Concat => {
530                    let b = inst.b();
531                    let c = inst.c();
532                    let mut expr = self.reg_expr(b);
533                    for r in (b + 1)..=c {
534                        expr = Expr::BinOp(
535                            BinOp::Concat,
536                            Box::new(expr),
537                            Box::new(self.reg_expr(r)),
538                        );
539                    }
540                    self.set_reg(inst.a, expr);
541                }
542                OpCode::Jmp => {
543                    // Jumps are handled by the CFG; skip here
544                }
545                OpCode::Eq | OpCode::Lt | OpCode::Le => {
546                    // Comparison tests: handled at block level for conditionals
547                    pc += 1; // skip following JMP
548                }
549                OpCode::Test => {
550                    // Handled at conditional level
551                    pc += 1;
552                }
553                OpCode::TestSet => {
554                    // R(A) := R(B) if R(B) <=> C, else skip
555                    pc += 1;
556                }
557                OpCode::Call => {
558                    let func = self.reg_expr(inst.a);
559                    let num_args = if inst.b() == 0 {
560                        0 // variable args - simplified
561                    } else {
562                        inst.b() - 1
563                    };
564                    let args: Vec<Expr> = (0..num_args)
565                        .map(|i| self.reg_expr(inst.a + 1 + i))
566                        .collect();
567                    let call = CallExpr { func, args };
568
569                    if inst.c() == 1 {
570                        // C==1: no return values -> statement call
571                        stmts.push(Stat::Call(call));
572                    } else if inst.c() == 0 {
573                        // C==0: variable return values (used by next CALL/RETURN/SETLIST)
574                        self.set_reg(inst.a, Expr::FuncCall(Box::new(call)));
575                    } else {
576                        // C>=2: fixed return values (C-1 results)
577                        let num_results = inst.c() - 1;
578                        if num_results == 1 {
579                            let call_expr = Expr::FuncCall(Box::new(call));
580                            // Use liveness: only create a local if the result
581                            // is used later
582                            let live = is_reg_live_after(
583                                &self.cfg, &self.liveness, pc, inst.a,
584                            );
585                            if live {
586                                self.set_reg_local(inst.a, call_expr, stmts);
587                            } else {
588                                // Result not used -> emit as statement
589                                if let Expr::FuncCall(c) = call_expr {
590                                    stmts.push(Stat::Call(*c));
591                                }
592                            }
593                        } else {
594                            // Multiple fixed return values -> local multi-assign
595                            let names: Vec<String> = (0..num_results)
596                                .map(|i| {
597                                    let r = inst.a + i;
598                                    let name = self.make_local_name(r);
599                                    self.local_names.insert(r, name.clone());
600                                    self.declared_locals.insert(r);
601                                    name
602                                })
603                                .collect();
604                            stmts.push(Stat::LocalAssign {
605                                names: names.clone(),
606                                exprs: vec![Expr::FuncCall(Box::new(call))],
607                            });
608                            for (i, name) in names.iter().enumerate() {
609                                let r = (inst.a + i as u32) as usize;
610                                if r < self.regs.len() {
611                                    self.regs[r] = Some(Expr::Name(name.clone()));
612                                }
613                            }
614                        }
615                    }
616                }
617                OpCode::TailCall => {
618                    let func = self.reg_expr(inst.a);
619                    let num_args = if inst.b() == 0 {
620                        0
621                    } else {
622                        inst.b() - 1
623                    };
624                    let args: Vec<Expr> = (0..num_args)
625                        .map(|i| self.reg_expr(inst.a + 1 + i))
626                        .collect();
627                    let call = CallExpr { func, args };
628                    stmts.push(Stat::Return(vec![Expr::FuncCall(Box::new(call))]));
629                }
630                OpCode::Return => {
631                    let num_ret = if inst.b() == 0 {
632                        0
633                    } else {
634                        inst.b() - 1
635                    };
636                    if num_ret == 0 && inst.a == 0 {
637                        // `return` with no values at end of function - may be implicit
638                        if pc != end_pc || end_pc != self.cfg.instructions.len() - 1 {
639                            stmts.push(Stat::Return(Vec::new()));
640                        }
641                    } else {
642                        let vals: Vec<Expr> = (0..num_ret)
643                            .map(|i| self.reg_expr(inst.a + i))
644                            .collect();
645                        stmts.push(Stat::Return(vals));
646                    }
647                }
648                OpCode::ForLoop | OpCode::ForPrep => {
649                    // Handled by loop lifting
650                }
651                OpCode::TForLoop => {
652                    // Handled by loop lifting
653                }
654                OpCode::SetList => {
655                    let table_reg = inst.a;
656                    let num = if inst.b() == 0 { 0 } else { inst.b() };
657                    // Items are in registers A+1 .. A+num
658                    // Flush any pending subtables in those registers first
659                    for i in 1..=num {
660                        self.flush_pending_table(table_reg + i);
661                    }
662                    // Collect values
663                    let values: Vec<Expr> = (1..=num)
664                        .map(|i| self.reg_expr(table_reg + i))
665                        .collect();
666                    if let Some(fields) = self.pending_tables.get_mut(&table_reg) {
667                        for val in values {
668                            fields.push(TableField::Value(val));
669                        }
670                    }
671                }
672                OpCode::Close => {
673                    // Internal VM operation, no visible effect in source
674                }
675                OpCode::Closure => {
676                    let proto_idx = inst.bx() as usize;
677                    let sub_func = if proto_idx < self.chunk.prototypes.len() {
678                        let sub_chunk = &self.chunk.prototypes[proto_idx];
679                        Lifter::decompile(sub_chunk)
680                    } else {
681                        Function {
682                            params: Vec::new(),
683                            is_vararg: false,
684                            body: Vec::new(),
685                        }
686                    };
687                    self.set_reg(inst.a, Expr::FunctionDef(Box::new(sub_func)));
688                }
689                OpCode::VarArg => {
690                    self.set_reg(inst.a, Expr::VarArg);
691                }
692            }
693            pc += 1;
694        }
695    }
696
697    // -- Helper methods --
698
699    fn set_reg(&mut self, reg: u32, expr: Expr) {
700        let r = reg as usize;
701        if r < self.regs.len() {
702            self.regs[r] = Some(expr);
703        }
704        // Clear local name mapping so reg_expr returns the actual expression
705        // instead of the old local name. The local is now dead (overwritten).
706        self.local_names.remove(&reg);
707    }
708
709    /// Set a register and potentially emit a `local` declaration if this is a
710    /// new local variable (register above params, first assignment).
711    fn set_reg_local(&mut self, reg: u32, expr: Expr, stmts: &mut Block) {
712        if reg >= self.num_params && !self.declared_locals.contains(&reg) {
713            // First assignment to this register -> declare local
714            self.declared_locals.insert(reg);
715            let name = self.make_local_name(reg);
716            self.local_names.insert(reg, name.clone());
717            let r = reg as usize;
718            if r < self.regs.len() {
719                self.regs[r] = Some(Expr::Name(name.clone()));
720            }
721            stmts.push(Stat::LocalAssign {
722                names: vec![name],
723                exprs: vec![expr],
724            });
725        } else if let Some(name) = self.local_names.get(&reg).cloned() {
726            // Already declared -> emit assignment
727            let r = reg as usize;
728            if r < self.regs.len() {
729                self.regs[r] = Some(Expr::Name(name.clone()));
730            }
731            stmts.push(Stat::Assign {
732                targets: vec![Expr::Name(name)],
733                values: vec![expr],
734            });
735        } else {
736            // Parameter register -> just update
737            let r = reg as usize;
738            if r < self.regs.len() {
739                self.regs[r] = Some(expr);
740            }
741        }
742    }
743
744    fn reg_expr(&self, reg: u32) -> Expr {
745        // If this register has a local name, return the name reference
746        if let Some(name) = self.local_names.get(&reg) {
747            return Expr::Name(name.clone());
748        }
749        let r = reg as usize;
750        if r < self.regs.len() {
751            self.regs[r].clone().unwrap_or(Expr::Register(reg))
752        } else {
753            Expr::Register(reg)
754        }
755    }
756
757    fn rk_expr(&self, rk: u32) -> Expr {
758        if is_k(rk) {
759            self.const_expr(index_k(rk))
760        } else {
761            self.reg_expr(rk)
762        }
763    }
764
765    fn const_expr(&self, idx: u32) -> Expr {
766        let i = idx as usize;
767        if i >= self.chunk.constants.len() {
768            return Expr::Nil;
769        }
770        match &self.chunk.constants[i] {
771            LuaConstant::Null => Expr::Nil,
772            LuaConstant::Bool(b) => Expr::Bool(*b),
773            LuaConstant::Number(n) => match n {
774                LuaNumber::Integer(v) => Expr::Number(NumLit::Int(*v)),
775                LuaNumber::Float(v) => Expr::Number(NumLit::Float(*v)),
776            },
777            LuaConstant::String(s) => Expr::StringLit(s.as_ref().to_vec()),
778            _ => Expr::Nil,
779        }
780    }
781
782    fn const_string(&self, idx: u32) -> String {
783        let i = idx as usize;
784        if i < self.chunk.constants.len() {
785            if let LuaConstant::String(s) = &self.chunk.constants[i] {
786                return String::from_utf8_lossy(s.as_ref()).into_owned();
787            }
788        }
789        format!("_K{}", idx)
790    }
791
792    fn upvalue_expr(&self, idx: u32) -> Expr {
793        let i = idx as usize;
794        if i < self.chunk.upvalue_names.len() {
795            let name = String::from_utf8_lossy(&self.chunk.upvalue_names[i]).into_owned();
796            if !name.is_empty() {
797                return Expr::Name(name);
798            }
799        }
800        Expr::Upvalue(idx)
801    }
802
803    fn local_name(&self, reg: u32, pc: usize) -> String {
804        // Try debug info: locals are ordered by register assignment
805        // In standard Lua 5.1 bytecode, the i-th local in the array corresponds
806        // to register i (for the scope range start_pc..end_pc)
807        for (i, local) in self.chunk.locals.iter().enumerate() {
808            if i == reg as usize
809                && local.start_pc as usize <= pc + 1
810                && pc < local.end_pc as usize
811            {
812                if !local.name.is_empty() && !local.name.starts_with('(') {
813                    return local.name.clone();
814                }
815            }
816        }
817        // Also scan all locals for any that match this register
818        for local in &self.chunk.locals {
819            if local.start_pc as usize <= pc + 1 && pc < local.end_pc as usize {
820                // Use locals array index as register mapping
821            }
822        }
823        // Fall back to matching by position in locals array for params
824        let r = reg as usize;
825        if r < self.chunk.locals.len() {
826            let name = &self.chunk.locals[r].name;
827            if !name.is_empty() && !name.starts_with('(') {
828                return name.clone();
829            }
830        }
831        self.make_local_name(reg)
832    }
833
834    fn make_local_name(&self, reg: u32) -> String {
835        if reg < self.num_params {
836            if self.has_debug_info {
837                return self.local_name(reg, 0);
838            }
839            return format!("a{}", reg);
840        }
841        // For stripped bytecode, try to infer a meaningful name from context.
842        // Look at what was last stored in this register.
843        if let Some(expr) = self.regs.get(reg as usize).and_then(|e| e.as_ref()) {
844            match expr {
845                // If it's a global function call, name after the function
846                Expr::FuncCall(call) => {
847                    if let Expr::Global(name) = &call.func {
848                        let short = name.to_ascii_lowercase();
849                        if short.len() <= 20 {
850                            return short;
851                        }
852                    }
853                    // Method call: use method name
854                    if let Expr::Field(_, method) = &call.func {
855                        return method.to_ascii_lowercase();
856                    }
857                }
858                _ => {}
859            }
860        }
861        format!("l_{}", reg)
862    }
863
864    /// Flush the pending table construction for a specific register.
865    fn flush_pending_table(&mut self, reg: u32) {
866        if let Some(fields) = self.pending_tables.remove(&reg) {
867            self.set_reg(reg, Expr::Table(fields));
868        }
869    }
870
871    /// Check if a block consists of only a RETURN (or RETURN with values).
872    fn is_return_block(&self, block_idx: usize) -> bool {
873        if block_idx >= self.cfg.num_blocks() {
874            return false;
875        }
876        let block = &self.cfg.blocks[block_idx];
877        let last = self.cfg.instructions[block.end];
878        matches!(last.op, OpCode::Return | OpCode::TailCall)
879            && block.successors.is_empty()
880    }
881
882    /// Find the PC of the test instruction in a conditional block.
883    fn find_test_pc(&self, block: &BasicBlock) -> Option<usize> {
884        for pc in block.start..=block.end {
885            let inst = self.cfg.instructions[pc];
886            if matches!(
887                inst.op,
888                OpCode::Eq | OpCode::Lt | OpCode::Le | OpCode::Test | OpCode::TestSet
889            ) {
890                return Some(pc);
891            }
892        }
893        None
894    }
895
896    fn find_loop_at(&self, block_idx: usize) -> Option<&NaturalLoop> {
897        self.loops.iter().find(|l| l.header == block_idx)
898    }
899
900    fn find_forprep_block(&self, header: usize) -> Option<usize> {
901        // Look for a predecessor of the header that ends with FORPREP
902        for &pred in &self.cfg.blocks[header].predecessors {
903            let pred_block = &self.cfg.blocks[pred];
904            let last = self.cfg.instructions[pred_block.end];
905            if last.op == OpCode::ForPrep {
906                return Some(pred);
907            }
908        }
909        // Also check if it's one block before header
910        if header > 0 {
911            let prev = &self.cfg.blocks[header - 1];
912            let last = self.cfg.instructions[prev.end];
913            if last.op == OpCode::ForPrep {
914                return Some(header - 1);
915            }
916        }
917        None
918    }
919
920    fn max_loop_block(&self, lp: &NaturalLoop) -> usize {
921        lp.body.iter().copied().max().unwrap_or(lp.header)
922    }
923
924    fn is_conditional_block(&self, block: &BasicBlock) -> bool {
925        block.successors.len() == 2
926            && self.cfg.edges.iter().any(|e| {
927                e.from == block.id
928                    && matches!(
929                        e.kind,
930                        EdgeKind::ConditionalTrue | EdgeKind::ConditionalFalse
931                    )
932            })
933    }
934
935    fn extract_condition(&self, block_idx: usize) -> Option<Expr> {
936        let block = &self.cfg.blocks[block_idx];
937        // Look for the test instruction (second-to-last, before JMP)
938        for pc in block.start..=block.end {
939            let inst = self.cfg.instructions[pc];
940            match inst.op {
941                OpCode::Eq => {
942                    let lhs = self.rk_expr(inst.b());
943                    let rhs = self.rk_expr(inst.c());
944                    return Some(if inst.a == 0 {
945                        Expr::BinOp(BinOp::Eq, Box::new(lhs), Box::new(rhs))
946                    } else {
947                        Expr::BinOp(BinOp::Ne, Box::new(lhs), Box::new(rhs))
948                    });
949                }
950                OpCode::Lt => {
951                    let lhs = self.rk_expr(inst.b());
952                    let rhs = self.rk_expr(inst.c());
953                    return Some(if inst.a == 0 {
954                        Expr::BinOp(BinOp::Lt, Box::new(lhs), Box::new(rhs))
955                    } else {
956                        Expr::BinOp(BinOp::Ge, Box::new(lhs), Box::new(rhs))
957                    });
958                }
959                OpCode::Le => {
960                    let lhs = self.rk_expr(inst.b());
961                    let rhs = self.rk_expr(inst.c());
962                    return Some(if inst.a == 0 {
963                        Expr::BinOp(BinOp::Le, Box::new(lhs), Box::new(rhs))
964                    } else {
965                        Expr::BinOp(BinOp::Gt, Box::new(lhs), Box::new(rhs))
966                    });
967                }
968                OpCode::Test => {
969                    let expr = self.reg_expr(inst.a);
970                    return Some(if inst.c() == 0 {
971                        expr
972                    } else {
973                        Expr::UnOp(UnOp::Not, Box::new(expr))
974                    });
975                }
976                OpCode::TestSet => {
977                    let expr = self.reg_expr(inst.b());
978                    return Some(if inst.c() == 0 {
979                        expr
980                    } else {
981                        Expr::UnOp(UnOp::Not, Box::new(expr))
982                    });
983                }
984                _ => {}
985            }
986        }
987        None
988    }
989
990    fn find_merge_point(
991        &self,
992        _cond_block: usize,
993        true_block: usize,
994        false_block: usize,
995    ) -> Option<usize> {
996        // Simple heuristic: the merge point is the smallest block ID
997        // that is a successor of both branches (or the false_block if
998        // the true block falls through to it).
999        let max_branch = true_block.max(false_block);
1000
1001        // Look for a block after both branches where control re-merges
1002        for b in (max_branch + 1)..self.cfg.num_blocks() {
1003            let block = &self.cfg.blocks[b];
1004            if block.predecessors.len() >= 2 {
1005                return Some(b);
1006            }
1007            // If this block has no predecessors from within the if branches,
1008            // it's likely the merge.
1009            if !block.predecessors.iter().all(|&p| {
1010                p >= true_block && p <= max_branch
1011            }) && block.predecessors.iter().any(|&p| p >= true_block) {
1012                return Some(b);
1013            }
1014        }
1015
1016        // Fallback: if false_block > true_block, false might be the merge
1017        if false_block > true_block {
1018            return Some(false_block);
1019        }
1020
1021        None
1022    }
1023}
1024
1025/// Build a field access or index expression.
1026fn make_index(table: Expr, key: Expr) -> Expr {
1027    // If key is a string that's a valid identifier, use Field syntax
1028    if let Expr::StringLit(ref s) = key {
1029        if let Ok(name) = std::str::from_utf8(s) {
1030            if is_identifier(name) {
1031                return Expr::Field(Box::new(table), name.to_string());
1032            }
1033        }
1034    }
1035    Expr::Index(Box::new(table), Box::new(key))
1036}
1037
1038/// Check if a string is a valid Lua identifier.
1039fn is_identifier(s: &str) -> bool {
1040    if s.is_empty() {
1041        return false;
1042    }
1043    let mut chars = s.chars();
1044    let first = chars.next().unwrap();
1045    if !first.is_ascii_alphabetic() && first != '_' {
1046        return false;
1047    }
1048    chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
1049        && !is_lua_keyword(s)
1050}
1051
1052fn is_lua_keyword(s: &str) -> bool {
1053    matches!(
1054        s,
1055        "and"
1056            | "break"
1057            | "do"
1058            | "else"
1059            | "elseif"
1060            | "end"
1061            | "false"
1062            | "for"
1063            | "function"
1064            | "if"
1065            | "in"
1066            | "local"
1067            | "nil"
1068            | "not"
1069            | "or"
1070            | "repeat"
1071            | "return"
1072            | "then"
1073            | "true"
1074            | "until"
1075            | "while"
1076    )
1077}
1078
1079/// Negate an expression (for inverting conditions).
1080fn negate_expr(expr: Expr) -> Expr {
1081    match expr {
1082        Expr::UnOp(UnOp::Not, inner) => *inner,
1083        Expr::BinOp(BinOp::Eq, a, b) => Expr::BinOp(BinOp::Ne, a, b),
1084        Expr::BinOp(BinOp::Ne, a, b) => Expr::BinOp(BinOp::Eq, a, b),
1085        Expr::BinOp(BinOp::Lt, a, b) => Expr::BinOp(BinOp::Ge, a, b),
1086        Expr::BinOp(BinOp::Ge, a, b) => Expr::BinOp(BinOp::Lt, a, b),
1087        Expr::BinOp(BinOp::Le, a, b) => Expr::BinOp(BinOp::Gt, a, b),
1088        Expr::BinOp(BinOp::Gt, a, b) => Expr::BinOp(BinOp::Le, a, b),
1089        Expr::Bool(b) => Expr::Bool(!b),
1090        other => Expr::UnOp(UnOp::Not, Box::new(other)),
1091    }
1092}