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 capture_aliases: HashMap<u32, Expr>,
25 accumulator_regs: HashSet<u32>,
27 visited_blocks: HashSet<usize>,
29 local_names: HashMap<u32, String>,
31 declared_locals: HashSet<u32>,
33 num_params: u32,
35 has_debug_info: bool,
37 resolved_upvalues: Vec<Option<Expr>>,
39 active_loop_headers: Vec<usize>,
41 active_loop_exits: Vec<usize>,
43}
44
45impl<'a> Lifter<'a> {
46 pub fn decompile(chunk: &'a LuaChunk) -> Function {
47 Self::decompile_with_upvalues(chunk, Vec::new())
48 }
49
50 fn decompile_with_upvalues(
51 chunk: &'a LuaChunk,
52 resolved_upvalues: Vec<Option<Expr>>,
53 ) -> Function {
54 let cfg = ControlFlowGraph::build(&chunk.instructions);
55 let dom = DominatorTree::build(&cfg);
56 let loops = find_loops(&cfg, &dom);
57 let liveness = compute_liveness(&cfg, chunk.max_stack as usize);
58 let has_debug_info = !chunk.locals.is_empty();
59
60 let max_stack = chunk.max_stack as usize;
61 let mut lifter = Lifter {
62 chunk,
63 cfg,
64 _dom: dom,
65 loops,
66 liveness,
67 regs: vec![None; max_stack.max(256)],
68 pending_tables: HashMap::new(),
69 capture_aliases: HashMap::new(),
70 accumulator_regs: HashSet::new(),
71 visited_blocks: HashSet::new(),
72 local_names: HashMap::new(),
73 declared_locals: HashSet::new(),
74 num_params: chunk.num_params as u32,
75 has_debug_info,
76 resolved_upvalues,
77 active_loop_headers: Vec::new(),
78 active_loop_exits: Vec::new(),
79 };
80
81 lifter.accumulator_regs = lifter.find_accumulator_regs();
82
83 let params: Vec<String> = (0..chunk.num_params as u32)
84 .map(|i| {
85 let name = lifter.local_name(i, 0);
86 lifter.local_names.insert(i, name.clone());
87 lifter.declared_locals.insert(i);
88 lifter.set_reg(i, Expr::Name(name.clone()));
89 name
90 })
91 .collect();
92 let is_vararg = chunk.is_vararg.is_some();
93
94 let body = if lifter.cfg.num_blocks() > 0 {
95 lifter.lift_block_range(0, lifter.cfg.num_blocks())
96 } else {
97 Vec::new()
98 };
99
100 Function {
101 params,
102 is_vararg,
103 body,
104 }
105 }
106
107 fn lift_block_range(&mut self, start_block: usize, end_block: usize) -> Block {
109 let mut stmts = Vec::new();
110 let mut block_idx = start_block;
111
112 while block_idx < end_block && block_idx < self.cfg.num_blocks() {
113 if self.visited_blocks.contains(&block_idx) {
115 block_idx += 1;
116 continue;
117 }
118
119 if let Some(lp) = self.find_loop_at(block_idx) {
121 let lp = lp.clone();
122 let next = self.lift_loop(&lp, &mut stmts);
123 if next <= block_idx {
124 block_idx += 1;
126 } else {
127 block_idx = next;
128 }
129 continue;
130 }
131
132 self.visited_blocks.insert(block_idx);
133
134 let block = self.cfg.blocks[block_idx].clone();
135 let _last_pc = block.end;
136
137 if self.is_conditional_block(&block) {
139 let next = self.lift_conditional(block_idx, &mut stmts);
140 if next <= block_idx {
141 self.lift_instructions(block.start, block.end, &mut stmts);
143 block_idx += 1;
144 } else {
145 block_idx = next;
146 }
147 continue;
148 }
149
150 self.lift_instructions(block.start, block.end, &mut stmts);
152
153 let last_inst = self.cfg.instructions[block.end];
155 if last_inst.op == OpCode::Jmp && block.successors.len() == 1 {
156 let target = block.successors[0];
157 if self.current_loop_exit() == Some(target) {
158 stmts.push(Stat::Break);
159 }
160 if target > block_idx + 1 {
163 block_idx = target;
164 continue;
165 }
166 }
167
168 block_idx += 1;
169 }
170
171 stmts
172 }
173
174 fn lift_loop(&mut self, lp: &NaturalLoop, stmts: &mut Block) -> usize {
176 match lp.kind {
177 LoopKind::NumericFor => self.lift_numeric_for(lp, stmts),
178 LoopKind::GenericFor => self.lift_generic_for(lp, stmts),
179 LoopKind::WhileRepeat => self.lift_while(lp, stmts),
180 }
181 }
182
183 fn lift_numeric_for(&mut self, lp: &NaturalLoop, stmts: &mut Block) -> usize {
184 let header = &self.cfg.blocks[lp.header].clone();
185 let loop_exit = self.max_loop_block(lp) + 1;
186
187 let forprep_block = self.find_forprep_block(lp.header);
190 let forprep_inst = if let Some(fb) = forprep_block {
191 let b = &self.cfg.blocks[fb];
192 self.cfg.instructions[b.end]
193 } else {
194 self.cfg.instructions[header.start]
195 };
196
197 let base = forprep_inst.a;
198 let var_name = self.local_name(base + 3, header.start);
199
200 if let Some(fb) = forprep_block {
202 if !self.visited_blocks.contains(&fb) {
203 let b = &self.cfg.blocks[fb].clone();
204 self.visited_blocks.insert(fb);
205 if b.end > b.start {
207 self.lift_instructions(b.start, b.end - 1, stmts);
208 }
209 }
210 }
211
212 let start_expr = self.reg_expr(base);
213 let limit_expr = self.reg_expr(base + 1);
214 let step_expr = self.reg_expr(base + 2);
215 let step = if matches!(&step_expr, Expr::Number(NumLit::Int(1))) {
216 None
217 } else {
218 Some(step_expr)
219 };
220
221 self.active_loop_headers.push(lp.header);
222 self.active_loop_exits.push(loop_exit);
223 let body = self.lift_block_range(lp.header, lp.latch + 1);
224 self.active_loop_exits.pop();
225 self.active_loop_headers.pop();
226
227 stmts.push(Stat::NumericFor {
228 name: var_name,
229 start: start_expr,
230 limit: limit_expr,
231 step,
232 body,
233 });
234
235 loop_exit
237 }
238
239 fn lift_generic_for(&mut self, lp: &NaturalLoop, stmts: &mut Block) -> usize {
240 let header = &self.cfg.blocks[lp.header].clone();
241
242 let mut tforloop_inst = None;
244 for pc in header.start..=header.end {
245 if self.cfg.instructions[pc].op == OpCode::TForLoop {
246 tforloop_inst = Some(self.cfg.instructions[pc]);
247 break;
248 }
249 }
250 if tforloop_inst.is_none() {
251 let latch_block = &self.cfg.blocks[lp.latch].clone();
252 for pc in latch_block.start..=latch_block.end {
253 if self.cfg.instructions[pc].op == OpCode::TForLoop {
254 tforloop_inst = Some(self.cfg.instructions[pc]);
255 break;
256 }
257 }
258 }
259 let tfl = tforloop_inst.unwrap_or(self.cfg.instructions[header.end]);
260
261 let base = tfl.a;
262 let num_vars = tfl.c();
263
264 let names: Vec<String> = (0..num_vars)
265 .map(|i| self.local_name(base + 3 + i, header.start))
266 .collect();
267
268 for (i, name) in names.iter().enumerate() {
270 let r = base + 3 + i as u32;
271 self.local_names.insert(r, name.clone());
272 self.declared_locals.insert(r);
273 self.set_reg(r, Expr::Name(name.clone()));
274 }
275
276 let iter_expr = self.reg_expr(base);
277
278 let mut body_blocks: Vec<usize> = lp.body.iter()
280 .filter(|&&b| b != lp.header)
281 .copied()
282 .collect();
283 body_blocks.sort();
284
285 self.active_loop_headers.push(lp.header);
286 self.active_loop_exits.push(self.max_loop_block(lp) + 1);
287 let body = if !body_blocks.is_empty() {
288 let first = *body_blocks.first().unwrap();
289 let last = *body_blocks.last().unwrap();
290 self.lift_block_range(first, last + 1)
291 } else {
292 Vec::new()
293 };
294 self.active_loop_exits.pop();
295 self.active_loop_headers.pop();
296
297 stmts.push(Stat::GenericFor {
298 names,
299 iterators: vec![iter_expr],
300 body,
301 });
302
303 self.max_loop_block(lp) + 1
304 }
305
306 fn lift_while(&mut self, lp: &NaturalLoop, stmts: &mut Block) -> usize {
307 let _header = &self.cfg.blocks[lp.header].clone();
308
309 let cond = self.extract_condition(lp.header).unwrap_or(Expr::Bool(true));
311
312 self.active_loop_headers.push(lp.header);
314 self.active_loop_exits.push(self.max_loop_block(lp) + 1);
315 let body_start = lp.header + 1;
316 let body_end = lp.latch + 1;
317 let body = self.lift_block_range(body_start, body_end);
318 self.active_loop_exits.pop();
319 self.active_loop_headers.pop();
320
321 stmts.push(Stat::While { cond, body });
322
323 self.max_loop_block(lp) + 1
324 }
325
326 fn lift_conditional(&mut self, block_idx: usize, stmts: &mut Block) -> usize {
328 if let Some(next) = self.try_lift_or_and_chain(block_idx, stmts) {
330 return next;
331 }
332
333 let block = self.cfg.blocks[block_idx].clone();
334
335 let test_pc = self.find_test_pc(&block);
338 if let Some(tp) = test_pc {
339 if tp > block.start {
340 self.lift_instructions(block.start, tp - 1, stmts);
341 }
342 }
343
344 let cond = self.extract_condition(block_idx).unwrap_or(Expr::Bool(true));
345
346 let succs = block.successors.clone();
348 if succs.len() != 2 {
349 self.lift_instructions(block.start, block.end, stmts);
351 return block_idx + 1;
352 }
353
354 let false_target = succs[0]; let true_target = succs[1]; if self.is_return_block(true_target) && false_target > true_target
366 && self.cfg.blocks[true_target].predecessors.len() <= 1
367 {
368 let guard_body = self.lift_block_range(true_target, true_target + 1);
370 stmts.push(Stat::If {
371 cond,
372 then_block: guard_body,
373 elseif_clauses: Vec::new(),
374 else_block: None,
375 });
376 return false_target;
377 }
378
379 if self.is_return_block(false_target) && true_target < false_target
383 && self.cfg.blocks[false_target].predecessors.len() <= 1
384 {
385 let guard_body = self.lift_block_range(false_target, false_target + 1);
386 let inv_cond = negate_expr(cond);
387 stmts.push(Stat::If {
388 cond: inv_cond,
389 then_block: guard_body,
390 elseif_clauses: Vec::new(),
391 else_block: None,
392 });
393 return true_target;
394 }
395
396 let merge = self.find_merge_point(block_idx, true_target, false_target);
398
399 let then_end = merge.unwrap_or(false_target);
400 let then_block = self.lift_block_range(true_target, then_end);
401
402 let else_block = if let Some(merge) = merge {
403 if false_target < merge {
404 let eb = self.lift_block_range(false_target, merge);
405 if eb.is_empty() { None } else { Some(eb) }
406 } else {
407 None
408 }
409 } else {
410 None
411 };
412
413 stmts.push(Stat::If {
414 cond,
415 then_block,
416 elseif_clauses: Vec::new(),
417 else_block,
418 });
419
420 merge.unwrap_or(false_target.max(true_target) + 1)
421 }
422
423 fn try_lift_or_and_chain(&mut self, start: usize, stmts: &mut Block) -> Option<usize> {
434 let block = &self.cfg.blocks[start];
435 if block.successors.len() != 2 { return None; }
436 if self.block_contains_testset(start) {
437 return None;
438 }
439
440 let _false0 = block.successors[0]; let true0 = block.successors[1]; if true0 >= self.cfg.num_blocks() { return None; }
445 if !self.is_conditional_block(&self.cfg.blocks[true0]) { return None; }
446 if self.block_contains_testset(true0) {
447 return None;
448 }
449
450 if let Some(result) = self.try_or_chain(start, stmts) {
452 return Some(result);
453 }
454
455 if let Some(result) = self.try_and_chain(start, stmts) {
457 return Some(result);
458 }
459
460 None
461 }
462
463 fn try_or_chain(&mut self, start: usize, stmts: &mut Block) -> Option<usize> {
468 let block = &self.cfg.blocks[start];
469 let false0 = block.successors[0]; let true0 = block.successors[1]; let body_target = false0;
473 let mut chain = vec![start]; let mut current = true0;
475
476 loop {
478 if current >= self.cfg.num_blocks() { return None; }
479 if !self.is_conditional_block(&self.cfg.blocks[current]) { return None; }
480 if self.block_contains_testset(current) { return None; }
481
482 let cur_block = &self.cfg.blocks[current];
483 let cur_false = cur_block.successors[0]; let cur_true = cur_block.successors[1]; if cur_false == body_target {
487 chain.push(current);
489 current = cur_true;
490 } else if cur_true == body_target {
491 chain.push(current);
493 let continuation = cur_false;
494
495 return Some(self.emit_or_chain(&chain, body_target, continuation, stmts));
497 } else {
498 return None;
500 }
501 }
502 }
503
504 fn emit_or_chain(
506 &mut self,
507 chain: &[usize],
508 body_target: usize,
509 continuation: usize,
510 stmts: &mut Block,
511 ) -> usize {
512 let mut parts = Vec::new();
513
514 for (i, &block_idx) in chain.iter().enumerate() {
515 let block = self.cfg.blocks[block_idx].clone();
516 let test_pc = self.find_test_pc(&block);
517
518 if let Some(tp) = test_pc {
520 if tp > block.start {
521 self.lift_instructions(block.start, tp - 1, stmts);
522 }
523 }
524
525 let cond = self.extract_condition(block_idx).unwrap_or(Expr::Bool(true));
526 self.visited_blocks.insert(block_idx);
527
528 let is_last = i == chain.len() - 1;
529 if is_last {
530 parts.push(cond);
532 } else {
533 parts.push(negate_expr(cond));
535 }
536 }
537
538 let combined = parts.into_iter().reduce(|a, b| {
540 Expr::BinOp(BinOp::Or, Box::new(a), Box::new(b))
541 }).unwrap_or(Expr::Bool(true));
542
543 if self.is_return_block(body_target) {
546 let then_block = self.lift_block_range(body_target, body_target + 1);
547 stmts.push(Stat::If {
548 cond: combined,
549 then_block,
550 elseif_clauses: Vec::new(),
551 else_block: None,
552 });
553 return continuation;
554 }
555
556 let merge = if self.block_flows_to(body_target, continuation) {
558 Some(continuation)
559 } else {
560 self.find_merge_point(
561 *chain.first().unwrap(),
562 body_target,
563 continuation,
564 )
565 };
566 let then_end = merge.unwrap_or(continuation);
567 let then_block = self.lift_block_range(body_target, then_end);
568
569 let else_block = if let Some(m) = merge {
570 if continuation < m {
571 let eb = self.lift_block_range(continuation, m);
572 if eb.is_empty() { None } else { Some(eb) }
573 } else {
574 None
575 }
576 } else {
577 None
578 };
579
580 stmts.push(Stat::If {
581 cond: combined,
582 then_block,
583 elseif_clauses: Vec::new(),
584 else_block,
585 });
586
587 merge.unwrap_or(continuation.max(body_target) + 1)
588 }
589
590 fn try_and_chain(&mut self, start: usize, stmts: &mut Block) -> Option<usize> {
595 let block = &self.cfg.blocks[start];
596 let false0 = block.successors[0]; let true0 = block.successors[1]; let end_target = false0;
600 let mut chain = vec![start];
601 let mut current = true0;
602
603 loop {
605 if current >= self.cfg.num_blocks() {
606 break;
608 }
609 if !self.is_conditional_block(&self.cfg.blocks[current]) {
610 break;
612 }
613 if self.block_contains_testset(current) {
614 return None;
615 }
616
617 let cur_block = &self.cfg.blocks[current];
618 let cur_false = cur_block.successors[0];
619 let cur_true = cur_block.successors[1];
620
621 if cur_false == end_target {
622 chain.push(current);
624 current = cur_true;
625 } else {
626 return None;
628 }
629 }
630
631 if chain.len() < 2 { return None; }
633
634 let body_target = current;
635
636 let mut parts = Vec::new();
638
639 for &block_idx in &chain {
640 let block = self.cfg.blocks[block_idx].clone();
641 let test_pc = self.find_test_pc(&block);
642
643 if let Some(tp) = test_pc {
644 if tp > block.start {
645 self.lift_instructions(block.start, tp - 1, stmts);
646 }
647 }
648
649 let cond = self.extract_condition(block_idx).unwrap_or(Expr::Bool(true));
650 self.visited_blocks.insert(block_idx);
651 parts.push(cond);
652 }
653
654 let combined = parts.into_iter().reduce(|a, b| {
656 Expr::BinOp(BinOp::And, Box::new(a), Box::new(b))
657 }).unwrap_or(Expr::Bool(true));
658
659 let merge = if self.block_flows_to(body_target, end_target) {
661 Some(end_target)
662 } else {
663 self.find_merge_point(
664 *chain.first().unwrap(),
665 body_target,
666 end_target,
667 )
668 };
669 let then_end = merge.unwrap_or(end_target);
670 let then_block = self.lift_block_range(body_target, then_end);
671
672 let else_block = if let Some(m) = merge {
673 if end_target < m {
674 let eb = self.lift_block_range(end_target, m);
675 if eb.is_empty() { None } else { Some(eb) }
676 } else {
677 None
678 }
679 } else {
680 None
681 };
682
683 stmts.push(Stat::If {
684 cond: combined,
685 then_block,
686 elseif_clauses: Vec::new(),
687 else_block,
688 });
689
690 Some(merge.unwrap_or(end_target.max(body_target) + 1))
691 }
692
693 fn lift_instructions(&mut self, start_pc: usize, end_pc: usize, stmts: &mut Block) {
695 let mut pc = start_pc;
696 while pc <= end_pc {
697 let inst = self.cfg.instructions[pc];
698 match inst.op {
699 OpCode::Move => {
700 let src = self.reg_expr(inst.b());
701 self.assign_reg_expr(pc, inst.a, src, stmts);
702 }
703 OpCode::LoadK => {
704 let expr = self.const_expr(inst.bx());
705 self.assign_reg_expr(pc, inst.a, expr, stmts);
706 }
707 OpCode::LoadBool => {
708 self.assign_reg_expr(pc, inst.a, Expr::Bool(inst.b() != 0), stmts);
709 if inst.c() != 0 {
710 pc += 1; }
712 }
713 OpCode::LoadNil => {
714 for r in inst.a..=inst.b() {
715 self.assign_reg_expr(pc, r, Expr::Nil, stmts);
716 }
717 }
718 OpCode::GetUpval => {
719 let expr = self.upvalue_expr(inst.b());
720 self.assign_reg_expr(pc, inst.a, expr, stmts);
721 }
722 OpCode::GetGlobal => {
723 let name = self.const_string(inst.bx());
724 self.assign_reg_expr(pc, inst.a, Expr::Global(name), stmts);
725 }
726 OpCode::GetTable => {
727 let table = self.reg_expr(inst.b());
728 let key = self.rk_expr(inst.c());
729 let expr = make_index(table, key);
730 self.assign_reg_expr(pc, inst.a, expr, stmts);
731 }
732 OpCode::SetGlobal => {
733 self.flush_pending_table(inst.a);
734 let name = self.const_string(inst.bx());
735 let val = self.reg_expr(inst.a);
736 stmts.push(Stat::Assign {
737 targets: vec![Expr::Global(name)],
738 values: vec![val],
739 });
740 self.capture_aliases
741 .insert(inst.a, Expr::Global(self.const_string(inst.bx())));
742 }
743 OpCode::SetUpval => {
744 let val = self.reg_expr(inst.a);
745 let uv = self.upvalue_expr(inst.b());
746 stmts.push(Stat::Assign {
747 targets: vec![uv],
748 values: vec![val],
749 });
750 }
751 OpCode::SetTable => {
752 let is_pending = self.pending_tables.contains_key(&inst.a);
754 if is_pending {
755 let key = self.rk_expr(inst.b());
756 let val = self.rk_expr(inst.c());
757 let fields = self.pending_tables.get_mut(&inst.a).unwrap();
758 if let Expr::StringLit(ref s) = key {
760 if let Ok(name) = std::str::from_utf8(s) {
761 if is_identifier(name) {
762 fields.push(TableField::NameField(
763 name.to_string(),
764 val,
765 ));
766 pc += 1;
767 continue;
768 }
769 }
770 }
771 fields.push(TableField::IndexField(key, val));
772 pc += 1;
773 continue;
774 }
775 self.flush_pending_table(inst.a);
777 let table = self.reg_expr(inst.a);
778 let key = self.rk_expr(inst.b());
779 let val = self.rk_expr(inst.c());
780 let target = make_index(table, key);
781 stmts.push(Stat::Assign {
782 targets: vec![target.clone()],
783 values: vec![val],
784 });
785 if !is_k(inst.c()) {
786 self.capture_aliases.insert(inst.c(), target);
787 }
788 }
789 OpCode::NewTable => {
790 self.assign_reg_expr(pc, inst.a, Expr::Table(Vec::new()), stmts);
791 self.pending_tables.insert(inst.a, Vec::new());
792 }
793 OpCode::Self_ => {
794 let table = self.reg_expr(inst.b());
795 let method = self.rk_expr(inst.c());
796 let method_ref = make_index(table.clone(), method);
797 self.assign_reg_expr(pc, inst.a + 1, table, stmts);
798 self.assign_reg_expr(pc, inst.a, method_ref, stmts);
799 }
800 OpCode::Add => {
801 let expr = Expr::BinOp(
802 BinOp::Add,
803 Box::new(self.rk_expr(inst.b())),
804 Box::new(self.rk_expr(inst.c())),
805 );
806 self.assign_reg_expr(pc, inst.a, expr, stmts);
807 }
808 OpCode::Sub => {
809 let expr = Expr::BinOp(
810 BinOp::Sub,
811 Box::new(self.rk_expr(inst.b())),
812 Box::new(self.rk_expr(inst.c())),
813 );
814 self.assign_reg_expr(pc, inst.a, expr, stmts);
815 }
816 OpCode::Mul => {
817 let expr = Expr::BinOp(
818 BinOp::Mul,
819 Box::new(self.rk_expr(inst.b())),
820 Box::new(self.rk_expr(inst.c())),
821 );
822 self.assign_reg_expr(pc, inst.a, expr, stmts);
823 }
824 OpCode::Div => {
825 let expr = Expr::BinOp(
826 BinOp::Div,
827 Box::new(self.rk_expr(inst.b())),
828 Box::new(self.rk_expr(inst.c())),
829 );
830 self.assign_reg_expr(pc, inst.a, expr, stmts);
831 }
832 OpCode::Mod => {
833 let expr = Expr::BinOp(
834 BinOp::Mod,
835 Box::new(self.rk_expr(inst.b())),
836 Box::new(self.rk_expr(inst.c())),
837 );
838 self.assign_reg_expr(pc, inst.a, expr, stmts);
839 }
840 OpCode::Pow => {
841 let expr = Expr::BinOp(
842 BinOp::Pow,
843 Box::new(self.rk_expr(inst.b())),
844 Box::new(self.rk_expr(inst.c())),
845 );
846 self.assign_reg_expr(pc, inst.a, expr, stmts);
847 }
848 OpCode::Unm => {
849 let expr = Expr::UnOp(UnOp::Neg, Box::new(self.reg_expr(inst.b())));
850 self.assign_reg_expr(pc, inst.a, expr, stmts);
851 }
852 OpCode::Not => {
853 let expr = Expr::UnOp(UnOp::Not, Box::new(self.reg_expr(inst.b())));
854 self.assign_reg_expr(pc, inst.a, expr, stmts);
855 }
856 OpCode::Len => {
857 let expr = Expr::UnOp(UnOp::Len, Box::new(self.reg_expr(inst.b())));
858 self.assign_reg_expr(pc, inst.a, expr, stmts);
859 }
860 OpCode::Concat => {
861 let b = inst.b();
862 let c = inst.c();
863 let mut expr = self.reg_expr(b);
864 for r in (b + 1)..=c {
865 expr = Expr::BinOp(
866 BinOp::Concat,
867 Box::new(expr),
868 Box::new(self.reg_expr(r)),
869 );
870 }
871 self.assign_reg_expr(pc, inst.a, expr, stmts);
872 }
873 OpCode::Jmp => {
874 }
876 OpCode::Eq | OpCode::Lt | OpCode::Le => {
877 pc += 1; }
880 OpCode::Test => {
881 pc += 1;
883 }
884 OpCode::TestSet => {
885 pc += 1;
887 }
888 OpCode::Call => {
889 let func = self.reg_expr(inst.a);
890 let num_args = if inst.b() == 0 {
891 0 } else {
893 inst.b() - 1
894 };
895 let args: Vec<Expr> = (0..num_args)
896 .map(|i| self.reg_expr(inst.a + 1 + i))
897 .collect();
898 let call = CallExpr { func, args };
899
900 if inst.c() == 1 {
901 stmts.push(Stat::Call(call));
903 } else if inst.c() == 0 {
904 self.set_reg(inst.a, Expr::FuncCall(Box::new(call)));
906 } else {
907 let num_results = inst.c() - 1;
909 if num_results == 1 {
910 let call_expr = Expr::FuncCall(Box::new(call));
911 let live = is_reg_live_after(
914 &self.cfg, &self.liveness, pc, inst.a,
915 );
916 if live {
917 self.set_reg_local(inst.a, call_expr, stmts);
918 } else {
919 if let Expr::FuncCall(c) = call_expr {
921 stmts.push(Stat::Call(*c));
922 }
923 }
924 } else {
925 let names: Vec<String> = (0..num_results)
927 .map(|i| {
928 let r = inst.a + i;
929 let name = self.make_local_name(r);
930 self.local_names.insert(r, name.clone());
931 self.declared_locals.insert(r);
932 name
933 })
934 .collect();
935 stmts.push(Stat::LocalAssign {
936 names: names.clone(),
937 exprs: vec![Expr::FuncCall(Box::new(call))],
938 });
939 for (i, name) in names.iter().enumerate() {
940 let r = (inst.a + i as u32) as usize;
941 if r < self.regs.len() {
942 self.regs[r] = Some(Expr::Name(name.clone()));
943 }
944 }
945 }
946 }
947 }
948 OpCode::TailCall => {
949 let func = self.reg_expr(inst.a);
950 let num_args = if inst.b() == 0 {
951 0
952 } else {
953 inst.b() - 1
954 };
955 let args: Vec<Expr> = (0..num_args)
956 .map(|i| self.reg_expr(inst.a + 1 + i))
957 .collect();
958 let call = CallExpr { func, args };
959 stmts.push(Stat::Return(vec![Expr::FuncCall(Box::new(call))]));
960 }
961 OpCode::Return => {
962 let num_ret = if inst.b() == 0 {
963 0
964 } else {
965 inst.b() - 1
966 };
967 if num_ret == 0 && inst.a == 0 {
968 if pc != end_pc || end_pc != self.cfg.instructions.len() - 1 {
970 stmts.push(Stat::Return(Vec::new()));
971 }
972 } else {
973 let vals: Vec<Expr> = (0..num_ret)
974 .map(|i| self.reg_expr(inst.a + i))
975 .collect();
976 stmts.push(Stat::Return(vals));
977 }
978 }
979 OpCode::ForLoop | OpCode::ForPrep => {
980 }
982 OpCode::TForLoop => {
983 }
985 OpCode::SetList => {
986 let table_reg = inst.a;
987 let num = if inst.b() == 0 { 0 } else { inst.b() };
988 for i in 1..=num {
991 self.flush_pending_table(table_reg + i);
992 }
993 let values: Vec<Expr> = (1..=num)
995 .map(|i| self.reg_expr(table_reg + i))
996 .collect();
997 if let Some(fields) = self.pending_tables.get_mut(&table_reg) {
998 for val in values {
999 fields.push(TableField::Value(val));
1000 }
1001 }
1002 }
1003 OpCode::Close => {
1004 }
1006 OpCode::Closure => {
1007 let closure_pc = pc;
1008 let proto_idx = inst.bx() as usize;
1009 let sub_func = if proto_idx < self.chunk.prototypes.len() {
1010 let sub_chunk = &self.chunk.prototypes[proto_idx];
1011 let resolved = self.resolve_closure_upvalues(pc, sub_chunk, stmts);
1012 Lifter::decompile_with_upvalues(sub_chunk, resolved)
1013 } else {
1014 Function {
1015 params: Vec::new(),
1016 is_vararg: false,
1017 body: Vec::new(),
1018 }
1019 };
1020 if proto_idx < self.chunk.prototypes.len() {
1021 pc += self.chunk.prototypes[proto_idx].num_upvalues as usize;
1022 }
1023 self.assign_reg_expr(
1024 closure_pc,
1025 inst.a,
1026 Expr::FunctionDef(Box::new(sub_func)),
1027 stmts,
1028 );
1029 }
1030 OpCode::VarArg => {
1031 self.assign_reg_expr(pc, inst.a, Expr::VarArg, stmts);
1032 }
1033 }
1034 pc += 1;
1035 }
1036 }
1037
1038 fn set_reg(&mut self, reg: u32, expr: Expr) {
1041 let r = reg as usize;
1042 if r < self.regs.len() {
1043 self.regs[r] = Some(expr);
1044 }
1045 self.local_names.remove(®);
1048 self.pending_tables.remove(®);
1052 self.capture_aliases.remove(®);
1053 }
1054
1055 fn set_reg_local(&mut self, reg: u32, expr: Expr, stmts: &mut Block) {
1058 if reg >= self.num_params && !self.declared_locals.contains(®) {
1059 self.declared_locals.insert(reg);
1061 let name = self.make_local_name_for_expr(reg, &expr);
1062 self.local_names.insert(reg, name.clone());
1063 let r = reg as usize;
1064 if r < self.regs.len() {
1065 self.regs[r] = Some(Expr::Name(name.clone()));
1066 }
1067 stmts.push(Stat::LocalAssign {
1068 names: vec![name],
1069 exprs: vec![expr],
1070 });
1071 } else if let Some(name) = self.local_names.get(®).cloned() {
1072 let r = reg as usize;
1074 if r < self.regs.len() {
1075 self.regs[r] = Some(Expr::Name(name.clone()));
1076 }
1077 stmts.push(Stat::Assign {
1078 targets: vec![Expr::Name(name)],
1079 values: vec![expr],
1080 });
1081 } else {
1082 let r = reg as usize;
1084 if r < self.regs.len() {
1085 self.regs[r] = Some(expr);
1086 }
1087 }
1088 }
1089
1090 fn assign_reg_expr(&mut self, pc: usize, reg: u32, expr: Expr, stmts: &mut Block) {
1091 let block_id = self.cfg.block_of(pc);
1092 let live_out_of_block = (reg as usize) < self.liveness.max_reg
1093 && self.liveness.live_out[block_id][reg as usize];
1094
1095 if self.accumulator_regs.contains(®)
1096 && reg >= self.num_params
1097 && live_out_of_block
1098 {
1099 self.set_reg_local(reg, expr, stmts);
1100 } else {
1101 self.set_reg(reg, expr);
1102 }
1103 }
1104
1105 fn resolve_closure_upvalues(
1106 &mut self,
1107 pc: usize,
1108 sub_chunk: &LuaChunk,
1109 stmts: &mut Block,
1110 ) -> Vec<Option<Expr>> {
1111 let num_upvalues = sub_chunk.num_upvalues as usize;
1112 let mut resolved = Vec::with_capacity(num_upvalues);
1113
1114 for offset in 0..num_upvalues {
1115 let capture_pc = pc + 1 + offset;
1116 if capture_pc >= self.cfg.instructions.len() {
1117 resolved.push(None);
1118 continue;
1119 }
1120
1121 let capture = self.cfg.instructions[capture_pc];
1122 let expr = match capture.op {
1123 OpCode::Move => Some(self.capture_stack_upvalue(capture.b(), capture_pc, stmts)),
1124 OpCode::GetUpval => Some(self.upvalue_expr(capture.b())),
1125 _ => None,
1126 };
1127 resolved.push(expr);
1128 }
1129
1130 resolved
1131 }
1132
1133 fn capture_stack_upvalue(&mut self, reg: u32, pc: usize, stmts: &mut Block) -> Expr {
1134 if let Some(name) = self.local_names.get(®).cloned() {
1135 return Expr::Name(name);
1136 }
1137
1138 if reg < self.num_params {
1139 let name = self.local_name(reg, pc);
1140 self.local_names.insert(reg, name.clone());
1141 let r = reg as usize;
1142 if r < self.regs.len() {
1143 self.regs[r] = Some(Expr::Name(name.clone()));
1144 }
1145 return Expr::Name(name);
1146 }
1147
1148 if let Some(alias) = self.capture_aliases.get(®).cloned() {
1149 return alias;
1150 }
1151
1152 match self.reg_expr(reg) {
1153 Expr::Name(name) => Expr::Name(name),
1154 Expr::Global(name) => Expr::Global(name),
1155 Expr::Upvalue(idx) => self.upvalue_expr(idx),
1156 Expr::Field(table, field) => Expr::Field(table, field),
1157 expr => {
1158 let name = self.local_name(reg, pc);
1159 self.local_names.insert(reg, name.clone());
1160 self.declared_locals.insert(reg);
1161 let r = reg as usize;
1162 if r < self.regs.len() {
1163 self.regs[r] = Some(Expr::Name(name.clone()));
1164 }
1165 self.pending_tables.remove(®);
1166 self.capture_aliases.remove(®);
1167 stmts.push(Stat::LocalAssign {
1168 names: vec![name.clone()],
1169 exprs: vec![expr],
1170 });
1171 Expr::Name(name)
1172 }
1173 }
1174 }
1175
1176 fn reg_expr(&self, reg: u32) -> Expr {
1177 if let Some(name) = self.local_names.get(®) {
1179 return Expr::Name(name.clone());
1180 }
1181 if let Some(fields) = self.pending_tables.get(®) {
1183 if !fields.is_empty() {
1184 return Expr::Table(fields.clone());
1185 }
1186 }
1187 let r = reg as usize;
1188 if r < self.regs.len() {
1189 self.regs[r].clone().unwrap_or(Expr::Register(reg))
1190 } else {
1191 Expr::Register(reg)
1192 }
1193 }
1194
1195 fn rk_expr(&self, rk: u32) -> Expr {
1196 if is_k(rk) {
1197 self.const_expr(index_k(rk))
1198 } else {
1199 self.reg_expr(rk)
1200 }
1201 }
1202
1203 fn const_expr(&self, idx: u32) -> Expr {
1204 let i = idx as usize;
1205 if i >= self.chunk.constants.len() {
1206 return Expr::Nil;
1207 }
1208 match &self.chunk.constants[i] {
1209 LuaConstant::Null => Expr::Nil,
1210 LuaConstant::Bool(b) => Expr::Bool(*b),
1211 LuaConstant::Number(n) => match n {
1212 LuaNumber::Integer(v) => Expr::Number(NumLit::Int(*v)),
1213 LuaNumber::Float(v) => Expr::Number(NumLit::Float(*v)),
1214 },
1215 LuaConstant::String(s) => Expr::StringLit(s.as_ref().to_vec()),
1216 _ => Expr::Nil,
1217 }
1218 }
1219
1220 fn const_string(&self, idx: u32) -> String {
1221 let i = idx as usize;
1222 if i < self.chunk.constants.len() {
1223 if let LuaConstant::String(s) = &self.chunk.constants[i] {
1224 return String::from_utf8_lossy(s.as_ref()).into_owned();
1225 }
1226 }
1227 format!("_K{}", idx)
1228 }
1229
1230 fn upvalue_expr(&self, idx: u32) -> Expr {
1231 let i = idx as usize;
1232 if i < self.resolved_upvalues.len() {
1233 if let Some(expr) = &self.resolved_upvalues[i] {
1234 return expr.clone();
1235 }
1236 }
1237 if i < self.chunk.upvalue_names.len() {
1238 let name = String::from_utf8_lossy(&self.chunk.upvalue_names[i]).into_owned();
1239 if !name.is_empty() {
1240 return Expr::Name(name);
1241 }
1242 }
1243 Expr::Upvalue(idx)
1244 }
1245
1246 fn local_name(&self, reg: u32, pc: usize) -> String {
1247 for (i, local) in self.chunk.locals.iter().enumerate() {
1251 if i == reg as usize
1252 && local.start_pc as usize <= pc + 1
1253 && pc < local.end_pc as usize
1254 {
1255 if !local.name.is_empty() && !local.name.starts_with('(') {
1256 return local.name.clone();
1257 }
1258 }
1259 }
1260 for local in &self.chunk.locals {
1262 if local.start_pc as usize <= pc + 1 && pc < local.end_pc as usize {
1263 }
1265 }
1266 let r = reg as usize;
1268 if r < self.chunk.locals.len() {
1269 let name = &self.chunk.locals[r].name;
1270 if !name.is_empty() && !name.starts_with('(') {
1271 return name.clone();
1272 }
1273 }
1274 self.make_local_name(reg)
1275 }
1276
1277 fn make_local_name(&self, reg: u32) -> String {
1278 let current_expr = self.regs.get(reg as usize).and_then(|e| e.as_ref());
1279 self.make_local_name_from_known_expr(reg, current_expr)
1280 }
1281
1282 fn make_local_name_for_expr(&self, reg: u32, expr: &Expr) -> String {
1283 self.make_local_name_from_known_expr(reg, Some(expr))
1284 }
1285
1286 fn make_local_name_from_known_expr(&self, reg: u32, current_expr: Option<&Expr>) -> String {
1287 if reg < self.num_params {
1288 if self.has_debug_info {
1289 return self.local_name(reg, 0);
1290 }
1291 return format!("a{}", reg);
1292 }
1293 if let Some(name) = self.infer_accumulator_name(reg, current_expr) {
1294 return self.uniquify_local_name(name);
1295 }
1296 if let Some(fields) = self.pending_tables.get(®) {
1297 if !fields.is_empty() {
1298 if let Some(name) = self.infer_table_local_name(fields) {
1299 return self.uniquify_local_name(name);
1300 }
1301 }
1302 }
1303 if let Some(expr) = current_expr {
1306 match expr {
1307 Expr::Table(fields) => {
1308 if let Some(name) = self.infer_table_local_name(fields) {
1309 return self.uniquify_local_name(name);
1310 }
1311 }
1312 Expr::FuncCall(call) => {
1314 if let Some(name) = self.infer_call_local_name(call) {
1315 return self.uniquify_local_name(name);
1316 }
1317 if let Expr::Global(name) = &call.func {
1318 let short = normalize_call_name(name);
1319 if short.len() <= 20 {
1320 return self.uniquify_local_name(short);
1321 }
1322 }
1323 if let Expr::Field(_, method) = &call.func {
1325 return self.uniquify_local_name(normalize_call_name(method));
1326 }
1327 }
1328 _ => {}
1329 }
1330 }
1331 format!("l_{}", reg)
1332 }
1333
1334 fn infer_accumulator_name(&self, reg: u32, expr: Option<&Expr>) -> Option<String> {
1335 if !self.accumulator_regs.contains(®) || !self.is_returned_reg(reg) {
1336 return None;
1337 }
1338
1339 match expr {
1340 Some(Expr::Number(_)) | None => Some("result".to_string()),
1341 _ => None,
1342 }
1343 }
1344
1345 fn infer_call_local_name(&self, call: &CallExpr) -> Option<String> {
1346 let method = match &call.func {
1347 Expr::Field(_, method) => method.as_str(),
1348 Expr::Global(name) => name.as_str(),
1349 _ => return None,
1350 };
1351
1352 let first_int_arg = match call.args.first() {
1353 Some(Expr::Number(NumLit::Int(value))) => Some(*value),
1354 Some(Expr::Number(NumLit::Float(value))) if value.fract() == 0.0 => Some(*value as i64),
1355 _ => None,
1356 };
1357
1358 match method {
1359 "IsHaveBuff" => Some(match first_int_arg {
1360 Some(id) => format!("has_buff_{}", id),
1361 None => "has_buff".to_string(),
1362 }),
1363 "GetBuff" | "GetBuffByOwner" => Some(match first_int_arg {
1364 Some(id) => format!("buff_{}", id),
1365 None => "buff".to_string(),
1366 }),
1367 "GetSkillLevel" => first_int_arg.map(|id| format!("skill_{}", id)),
1368 "GetEndTime" => Some("end_time".to_string()),
1369 "GetLogicFrameCount" => Some("logic_frame_count".to_string()),
1370 _ => None,
1371 }
1372 }
1373
1374 fn is_returned_reg(&self, reg: u32) -> bool {
1375 self.cfg.instructions.iter().any(|inst| {
1376 inst.op == OpCode::Return && inst.a == reg && inst.b() == 2
1377 })
1378 }
1379
1380 fn uniquify_local_name(&self, base: String) -> String {
1381 if !self.local_names.values().any(|name| name == &base) {
1382 return base;
1383 }
1384
1385 let mut suffix = 1;
1386 loop {
1387 let candidate = format!("{}_{}", base, suffix);
1388 if !self.local_names.values().any(|name| name == &candidate) {
1389 return candidate;
1390 }
1391 suffix += 1;
1392 }
1393 }
1394
1395 fn infer_table_local_name(&self, fields: &[TableField]) -> Option<String> {
1396 if fields.is_empty() {
1397 return None;
1398 }
1399
1400 if fields.iter().all(|field| matches!(field, TableField::IndexField(_, Expr::Bool(true)))) {
1401 if fields.iter().any(|field| {
1402 matches!(field, TableField::IndexField(key, _) if self.expr_mentions_field(key, "ENUM"))
1403 }) {
1404 return Some("enum_lookup".to_string());
1405 }
1406 return Some("lookup".to_string());
1407 }
1408
1409 if fields.iter().all(|field| matches!(field, TableField::NameField(_, Expr::Number(_)))) {
1410 let keys: Vec<&str> = fields
1411 .iter()
1412 .filter_map(|field| match field {
1413 TableField::NameField(name, _) => Some(name.as_str()),
1414 _ => None,
1415 })
1416 .collect();
1417 if keys.iter().any(|name| name.contains("NOT_")) {
1418 return Some("penalties".to_string());
1419 }
1420 if keys.iter().all(|name| is_upper_ident(name)) {
1421 return Some("modifiers".to_string());
1422 }
1423 }
1424
1425 None
1426 }
1427
1428 fn expr_mentions_field(&self, expr: &Expr, field_name: &str) -> bool {
1429 match expr {
1430 Expr::Field(table, field) => field == field_name || self.expr_mentions_field(table, field_name),
1431 Expr::Index(table, key) => {
1432 self.expr_mentions_field(table, field_name)
1433 || self.expr_mentions_field(key, field_name)
1434 }
1435 Expr::MethodCall(call) | Expr::FuncCall(call) => {
1436 self.expr_mentions_field(&call.func, field_name)
1437 || call.args.iter().any(|arg| self.expr_mentions_field(arg, field_name))
1438 }
1439 Expr::BinOp(_, lhs, rhs) => {
1440 self.expr_mentions_field(lhs, field_name)
1441 || self.expr_mentions_field(rhs, field_name)
1442 }
1443 Expr::UnOp(_, inner) => self.expr_mentions_field(inner, field_name),
1444 Expr::Table(fields) => fields.iter().any(|field| match field {
1445 TableField::IndexField(key, value) => {
1446 self.expr_mentions_field(key, field_name)
1447 || self.expr_mentions_field(value, field_name)
1448 }
1449 TableField::NameField(_, value) | TableField::Value(value) => {
1450 self.expr_mentions_field(value, field_name)
1451 }
1452 }),
1453 _ => false,
1454 }
1455 }
1456
1457 fn flush_pending_table(&mut self, reg: u32) {
1459 if let Some(fields) = self.pending_tables.remove(®) {
1460 self.set_reg(reg, Expr::Table(fields));
1461 }
1462 }
1463
1464 fn is_return_block(&self, block_idx: usize) -> bool {
1466 if block_idx >= self.cfg.num_blocks() {
1467 return false;
1468 }
1469 let block = &self.cfg.blocks[block_idx];
1470 let last = self.cfg.instructions[block.end];
1471 matches!(last.op, OpCode::Return | OpCode::TailCall)
1472 && block.successors.is_empty()
1473 }
1474
1475 fn find_test_pc(&self, block: &BasicBlock) -> Option<usize> {
1477 for pc in block.start..=block.end {
1478 let inst = self.cfg.instructions[pc];
1479 if matches!(
1480 inst.op,
1481 OpCode::Eq | OpCode::Lt | OpCode::Le | OpCode::Test | OpCode::TestSet
1482 ) {
1483 return Some(pc);
1484 }
1485 }
1486 None
1487 }
1488
1489 fn find_loop_at(&self, block_idx: usize) -> Option<&NaturalLoop> {
1490 if self.active_loop_headers.contains(&block_idx) {
1491 return None;
1492 }
1493 self.loops.iter().find(|l| l.header == block_idx)
1494 }
1495
1496 fn current_loop_exit(&self) -> Option<usize> {
1497 self.active_loop_exits.last().copied()
1498 }
1499
1500 fn find_forprep_block(&self, header: usize) -> Option<usize> {
1501 for &pred in &self.cfg.blocks[header].predecessors {
1503 let pred_block = &self.cfg.blocks[pred];
1504 let last = self.cfg.instructions[pred_block.end];
1505 if last.op == OpCode::ForPrep {
1506 return Some(pred);
1507 }
1508 }
1509 if header > 0 {
1511 let prev = &self.cfg.blocks[header - 1];
1512 let last = self.cfg.instructions[prev.end];
1513 if last.op == OpCode::ForPrep {
1514 return Some(header - 1);
1515 }
1516 }
1517 None
1518 }
1519
1520 fn max_loop_block(&self, lp: &NaturalLoop) -> usize {
1521 lp.body.iter().copied().max().unwrap_or(lp.header)
1522 }
1523
1524 fn is_conditional_block(&self, block: &BasicBlock) -> bool {
1525 block.successors.len() == 2
1526 && self.cfg.edges.iter().any(|e| {
1527 e.from == block.id
1528 && matches!(
1529 e.kind,
1530 EdgeKind::ConditionalTrue | EdgeKind::ConditionalFalse
1531 )
1532 })
1533 }
1534
1535 fn block_contains_testset(&self, block_idx: usize) -> bool {
1536 if block_idx >= self.cfg.num_blocks() {
1537 return false;
1538 }
1539 let block = &self.cfg.blocks[block_idx];
1540 (block.start..=block.end).any(|pc| self.cfg.instructions[pc].op == OpCode::TestSet)
1541 }
1542
1543 fn block_flows_to(&self, from_block: usize, target_block: usize) -> bool {
1544 if from_block >= self.cfg.num_blocks() || target_block >= self.cfg.num_blocks() {
1545 return false;
1546 }
1547 self.cfg.blocks[from_block].successors.iter().all(|&succ| succ == target_block)
1548 }
1549
1550 fn find_accumulator_regs(&self) -> HashSet<u32> {
1551 let mut regs = HashSet::new();
1552
1553 for inst in &self.cfg.instructions {
1554 match inst.op {
1555 OpCode::Add | OpCode::Sub | OpCode::Mul | OpCode::Div | OpCode::Mod | OpCode::Pow => {
1556 let uses_target = (!is_k(inst.b()) && inst.a == inst.b())
1557 || (!is_k(inst.c()) && inst.a == inst.c());
1558 if uses_target {
1559 regs.insert(inst.a);
1560 }
1561 }
1562 OpCode::Concat => {
1563 if inst.a >= inst.b() && inst.a <= inst.c() {
1564 regs.insert(inst.a);
1565 }
1566 }
1567 _ => {}
1568 }
1569 }
1570
1571 regs
1572 }
1573
1574 fn extract_condition(&self, block_idx: usize) -> Option<Expr> {
1575 let block = &self.cfg.blocks[block_idx];
1576 for pc in block.start..=block.end {
1578 let inst = self.cfg.instructions[pc];
1579 match inst.op {
1580 OpCode::Eq => {
1581 let lhs = self.rk_expr(inst.b());
1582 let rhs = self.rk_expr(inst.c());
1583 return Some(if inst.a == 0 {
1584 Expr::BinOp(BinOp::Eq, Box::new(lhs), Box::new(rhs))
1585 } else {
1586 Expr::BinOp(BinOp::Ne, Box::new(lhs), Box::new(rhs))
1587 });
1588 }
1589 OpCode::Lt => {
1590 let lhs = self.rk_expr(inst.b());
1591 let rhs = self.rk_expr(inst.c());
1592 return Some(if inst.a == 0 {
1593 Expr::BinOp(BinOp::Lt, Box::new(lhs), Box::new(rhs))
1594 } else {
1595 Expr::BinOp(BinOp::Ge, Box::new(lhs), Box::new(rhs))
1596 });
1597 }
1598 OpCode::Le => {
1599 let lhs = self.rk_expr(inst.b());
1600 let rhs = self.rk_expr(inst.c());
1601 return Some(if inst.a == 0 {
1602 Expr::BinOp(BinOp::Le, Box::new(lhs), Box::new(rhs))
1603 } else {
1604 Expr::BinOp(BinOp::Gt, Box::new(lhs), Box::new(rhs))
1605 });
1606 }
1607 OpCode::Test => {
1608 let expr = self.reg_expr(inst.a);
1609 return Some(if inst.c() == 0 {
1610 expr
1611 } else {
1612 Expr::UnOp(UnOp::Not, Box::new(expr))
1613 });
1614 }
1615 OpCode::TestSet => {
1616 let expr = self.reg_expr(inst.b());
1617 return Some(if inst.c() == 0 {
1618 expr
1619 } else {
1620 Expr::UnOp(UnOp::Not, Box::new(expr))
1621 });
1622 }
1623 _ => {}
1624 }
1625 }
1626 None
1627 }
1628
1629 fn find_merge_point(
1630 &self,
1631 cond_block: usize,
1632 true_block: usize,
1633 false_block: usize,
1634 ) -> Option<usize> {
1635 if false_block < self.cfg.num_blocks() {
1636 let false_preds = &self.cfg.blocks[false_block].predecessors;
1637 if false_preds.len() >= 3
1638 && false_preds.contains(&cond_block)
1639 && false_preds
1640 .iter()
1641 .any(|&pred| pred != cond_block && pred >= true_block && pred < false_block)
1642 {
1643 return Some(false_block);
1644 }
1645 }
1646
1647 let max_branch = true_block.max(false_block);
1651
1652 for b in (max_branch + 1)..self.cfg.num_blocks() {
1654 let block = &self.cfg.blocks[b];
1655 if block.predecessors.len() >= 2 {
1656 return Some(b);
1657 }
1658 if !block
1659 .predecessors
1660 .iter()
1661 .all(|&p| p >= true_block && p <= max_branch)
1662 && block.predecessors.iter().any(|&p| p >= true_block)
1663 {
1664 return Some(b);
1665 }
1666 }
1667
1668 if false_block > true_block && false_block > cond_block {
1669 return Some(false_block);
1670 }
1671
1672 None
1673 }
1674}
1675
1676fn make_index(table: Expr, key: Expr) -> Expr {
1678 if let Expr::StringLit(ref s) = key {
1680 if let Ok(name) = std::str::from_utf8(s) {
1681 if is_identifier(name) {
1682 return Expr::Field(Box::new(table), name.to_string());
1683 }
1684 }
1685 }
1686 Expr::Index(Box::new(table), Box::new(key))
1687}
1688
1689fn is_identifier(s: &str) -> bool {
1691 if s.is_empty() {
1692 return false;
1693 }
1694 let mut chars = s.chars();
1695 let first = chars.next().unwrap();
1696 if !first.is_ascii_alphabetic() && first != '_' {
1697 return false;
1698 }
1699 chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
1700 && !is_lua_keyword(s)
1701}
1702
1703fn is_upper_ident(s: &str) -> bool {
1704 !s.is_empty()
1705 && s
1706 .chars()
1707 .all(|c| c == '_' || c.is_ascii_uppercase() || c.is_ascii_digit())
1708}
1709
1710fn normalize_call_name(name: &str) -> String {
1711 let snake = camel_to_snake(name);
1712 snake
1713 .strip_prefix("get_")
1714 .or_else(|| snake.strip_prefix("is_"))
1715 .map(ToOwned::to_owned)
1716 .unwrap_or(snake)
1717}
1718
1719fn camel_to_snake(name: &str) -> String {
1720 let mut out = String::new();
1721 for (idx, ch) in name.chars().enumerate() {
1722 if ch.is_ascii_uppercase() {
1723 if idx != 0 {
1724 out.push('_');
1725 }
1726 out.push(ch.to_ascii_lowercase());
1727 } else {
1728 out.push(ch);
1729 }
1730 }
1731 out
1732}
1733
1734fn is_lua_keyword(s: &str) -> bool {
1735 matches!(
1736 s,
1737 "and"
1738 | "break"
1739 | "do"
1740 | "else"
1741 | "elseif"
1742 | "end"
1743 | "false"
1744 | "for"
1745 | "function"
1746 | "if"
1747 | "in"
1748 | "local"
1749 | "nil"
1750 | "not"
1751 | "or"
1752 | "repeat"
1753 | "return"
1754 | "then"
1755 | "true"
1756 | "until"
1757 | "while"
1758 )
1759}
1760
1761fn negate_expr(expr: Expr) -> Expr {
1763 match expr {
1764 Expr::UnOp(UnOp::Not, inner) => *inner,
1765 Expr::BinOp(BinOp::Eq, a, b) => Expr::BinOp(BinOp::Ne, a, b),
1766 Expr::BinOp(BinOp::Ne, a, b) => Expr::BinOp(BinOp::Eq, a, b),
1767 Expr::BinOp(BinOp::Lt, a, b) => Expr::BinOp(BinOp::Ge, a, b),
1768 Expr::BinOp(BinOp::Ge, a, b) => Expr::BinOp(BinOp::Lt, a, b),
1769 Expr::BinOp(BinOp::Le, a, b) => Expr::BinOp(BinOp::Gt, a, b),
1770 Expr::BinOp(BinOp::Gt, a, b) => Expr::BinOp(BinOp::Le, a, b),
1771 Expr::Bool(b) => Expr::Bool(!b),
1772 other => Expr::UnOp(UnOp::Not, Box::new(other)),
1773 }
1774}