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
12pub struct Lifter<'a> {
14 chunk: &'a LuaChunk,
15 cfg: ControlFlowGraph,
16 dom: DominatorTree,
17 loops: Vec<NaturalLoop>,
18 liveness: LivenessInfo,
19 regs: Vec<Option<Expr>>,
21 pending_tables: HashMap<u32, Vec<TableField>>,
23 visited_blocks: HashSet<usize>,
25 local_names: HashMap<u32, String>,
27 declared_locals: HashSet<u32>,
29 num_params: u32,
31 has_debug_info: bool,
33 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 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 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 if !self.visited_blocks.insert(block_idx) {
103 block_idx += 1;
104 continue;
105 }
106
107 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 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 if self.is_conditional_block(&block) {
125 let next = self.lift_conditional(block_idx, &mut stmts);
126 if next <= block_idx {
127 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 self.lift_instructions(block.start, block.end, &mut stmts);
138
139 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 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 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 if let Some(fb) = forprep_block {
181 let b = &self.cfg.blocks[fb].clone();
182 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 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 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 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 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 let cond = self.extract_condition(lp.header).unwrap_or(Expr::Bool(true));
270
271 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 fn lift_conditional(&mut self, block_idx: usize, stmts: &mut Block) -> usize {
283 let block = self.cfg.blocks[block_idx].clone();
284
285 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 let succs = block.successors.clone();
298 if succs.len() != 2 {
299 self.lift_instructions(block.start, block.end, stmts);
301 return block_idx + 1;
302 }
303
304 let false_target = succs[0]; let true_target = succs[1]; if self.is_return_block(true_target) && false_target > true_target {
315 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 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 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 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; }
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 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 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 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 }
545 OpCode::Eq | OpCode::Lt | OpCode::Le => {
546 pc += 1; }
549 OpCode::Test => {
550 pc += 1;
552 }
553 OpCode::TestSet => {
554 pc += 1;
556 }
557 OpCode::Call => {
558 let func = self.reg_expr(inst.a);
559 let num_args = if inst.b() == 0 {
560 0 } 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 stmts.push(Stat::Call(call));
572 } else if inst.c() == 0 {
573 self.set_reg(inst.a, Expr::FuncCall(Box::new(call)));
575 } else {
576 let num_results = inst.c() - 1;
578 if num_results == 1 {
579 let call_expr = Expr::FuncCall(Box::new(call));
580 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 if let Expr::FuncCall(c) = call_expr {
590 stmts.push(Stat::Call(*c));
591 }
592 }
593 } else {
594 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 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 }
651 OpCode::TForLoop => {
652 }
654 OpCode::SetList => {
655 let table_reg = inst.a;
656 let num = if inst.b() == 0 { 0 } else { inst.b() };
657 for i in 1..=num {
660 self.flush_pending_table(table_reg + i);
661 }
662 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 }
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 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 self.local_names.remove(®);
707 }
708
709 fn set_reg_local(&mut self, reg: u32, expr: Expr, stmts: &mut Block) {
712 if reg >= self.num_params && !self.declared_locals.contains(®) {
713 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(®).cloned() {
726 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 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 let Some(name) = self.local_names.get(®) {
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 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 for local in &self.chunk.locals {
819 if local.start_pc as usize <= pc + 1 && pc < local.end_pc as usize {
820 }
822 }
823 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 if let Some(expr) = self.regs.get(reg as usize).and_then(|e| e.as_ref()) {
844 match expr {
845 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 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 fn flush_pending_table(&mut self, reg: u32) {
866 if let Some(fields) = self.pending_tables.remove(®) {
867 self.set_reg(reg, Expr::Table(fields));
868 }
869 }
870
871 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 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 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 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 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 let max_branch = true_block.max(false_block);
1000
1001 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 !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 if false_block > true_block {
1018 return Some(false_block);
1019 }
1020
1021 None
1022 }
1023}
1024
1025fn make_index(table: Expr, key: Expr) -> Expr {
1027 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
1038fn 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
1079fn 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}