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