use std::collections::{HashMap, HashSet};
use luac_parser::LuaChunk;
use crate::lua51::ast::*;
use crate::lua51::cfg::ControlFlowGraph;
use crate::lua51::dominator::{find_loops, DominatorTree, LoopKind, NaturalLoop};
use crate::lua51::liveness::{compute_liveness, LivenessInfo};
use crate::lua51::opcodes::OpCode;
mod instruction;
mod naming;
mod or_and;
mod register;
mod util;
use util::negate_expr;
pub struct Lifter<'a> {
chunk: &'a LuaChunk,
cfg: ControlFlowGraph,
_dom: DominatorTree,
loops: Vec<NaturalLoop>,
liveness: LivenessInfo,
regs: Vec<Option<Expr>>,
pending_tables: HashMap<u32, Vec<TableField>>,
capture_aliases: HashMap<u32, Expr>,
accumulator_regs: HashSet<u32>,
visited_blocks: HashSet<usize>,
local_names: HashMap<u32, String>,
declared_locals: HashSet<u32>,
num_params: u32,
has_debug_info: bool,
resolved_upvalues: Vec<Option<Expr>>,
active_loop_headers: Vec<usize>,
active_loop_exits: Vec<usize>,
}
impl<'a> Lifter<'a> {
pub fn decompile(chunk: &'a LuaChunk) -> Function {
Self::decompile_with_upvalues(chunk, Vec::new())
}
fn decompile_with_upvalues(
chunk: &'a LuaChunk,
resolved_upvalues: Vec<Option<Expr>>,
) -> Function {
let cfg = ControlFlowGraph::build(&chunk.instructions);
let dom = DominatorTree::build(&cfg);
let loops = find_loops(&cfg, &dom);
let liveness = compute_liveness(&cfg, chunk.max_stack as usize);
let has_debug_info = !chunk.locals.is_empty();
let max_stack = chunk.max_stack as usize;
let mut lifter = Lifter {
chunk,
cfg,
_dom: dom,
loops,
liveness,
regs: vec![None; max_stack.max(256)],
pending_tables: HashMap::new(),
capture_aliases: HashMap::new(),
accumulator_regs: HashSet::new(),
visited_blocks: HashSet::new(),
local_names: HashMap::new(),
declared_locals: HashSet::new(),
num_params: chunk.num_params as u32,
has_debug_info,
resolved_upvalues,
active_loop_headers: Vec::new(),
active_loop_exits: Vec::new(),
};
lifter.accumulator_regs = lifter.find_accumulator_regs();
let params: Vec<String> = (0..chunk.num_params as u32)
.map(|i| {
let name = lifter.local_name(i, 0);
lifter.local_names.insert(i, name.clone());
lifter.declared_locals.insert(i);
lifter.set_reg(i, Expr::Name(name.clone()));
name
})
.collect();
let is_vararg = chunk.is_vararg.is_some();
let body = if lifter.cfg.num_blocks() > 0 {
lifter.lift_block_range(0, lifter.cfg.num_blocks())
} else {
Vec::new()
};
Function {
params,
is_vararg,
body,
}
}
fn lift_block_range(&mut self, start_block: usize, end_block: usize) -> Block {
let mut stmts = Vec::new();
let mut block_idx = start_block;
while block_idx < end_block && block_idx < self.cfg.num_blocks() {
if self.visited_blocks.contains(&block_idx) {
block_idx += 1;
continue;
}
if let Some(lp) = self.find_loop_at(block_idx) {
let lp = lp.clone();
let next = self.lift_loop(&lp, &mut stmts);
if next <= block_idx {
block_idx += 1;
} else {
block_idx = next;
}
continue;
}
self.visited_blocks.insert(block_idx);
let block = self.cfg.blocks[block_idx].clone();
let _last_pc = block.end;
if self.is_conditional_block(&block) {
let next = self.lift_conditional(block_idx, &mut stmts);
if next <= block_idx {
self.lift_instructions(block.start, block.end, &mut stmts);
block_idx += 1;
} else {
block_idx = next;
}
continue;
}
self.lift_instructions(block.start, block.end, &mut stmts);
let last_inst = self.cfg.instructions[block.end];
if last_inst.op == OpCode::Jmp && block.successors.len() == 1 {
let target = block.successors[0];
if self.current_loop_exit() == Some(target) {
stmts.push(Stat::Break);
}
if target > block_idx + 1 {
block_idx = target;
continue;
}
}
block_idx += 1;
}
stmts
}
fn lift_loop(&mut self, lp: &NaturalLoop, stmts: &mut Block) -> usize {
match lp.kind {
LoopKind::NumericFor => self.lift_numeric_for(lp, stmts),
LoopKind::GenericFor => self.lift_generic_for(lp, stmts),
LoopKind::WhileRepeat => self.lift_while(lp, stmts),
}
}
fn lift_numeric_for(&mut self, lp: &NaturalLoop, stmts: &mut Block) -> usize {
let header = &self.cfg.blocks[lp.header].clone();
let loop_exit = self.max_loop_block(lp) + 1;
let forprep_block = self.find_forprep_block(lp.header);
let forprep_inst = if let Some(fb) = forprep_block {
let b = &self.cfg.blocks[fb];
self.cfg.instructions[b.end]
} else {
self.cfg.instructions[header.start]
};
let base = forprep_inst.a;
let var_name = self.local_name(base + 3, header.start);
if let Some(fb) = forprep_block {
if !self.visited_blocks.contains(&fb) {
let b = &self.cfg.blocks[fb].clone();
self.visited_blocks.insert(fb);
if b.end > b.start {
self.lift_instructions(b.start, b.end - 1, stmts);
}
}
}
let start_expr = self.reg_expr(base);
let limit_expr = self.reg_expr(base + 1);
let step_expr = self.reg_expr(base + 2);
let step = if matches!(&step_expr, Expr::Number(NumLit::Int(1))) {
None
} else {
Some(step_expr)
};
self.active_loop_headers.push(lp.header);
self.active_loop_exits.push(loop_exit);
let body = self.lift_block_range(lp.header, lp.latch + 1);
self.active_loop_exits.pop();
self.active_loop_headers.pop();
stmts.push(Stat::NumericFor {
name: var_name,
start: start_expr,
limit: limit_expr,
step,
body,
});
loop_exit
}
fn lift_generic_for(&mut self, lp: &NaturalLoop, stmts: &mut Block) -> usize {
let header = &self.cfg.blocks[lp.header].clone();
let mut tforloop_inst = None;
for pc in header.start..=header.end {
if self.cfg.instructions[pc].op == OpCode::TForLoop {
tforloop_inst = Some(self.cfg.instructions[pc]);
break;
}
}
if tforloop_inst.is_none() {
let latch_block = &self.cfg.blocks[lp.latch].clone();
for pc in latch_block.start..=latch_block.end {
if self.cfg.instructions[pc].op == OpCode::TForLoop {
tforloop_inst = Some(self.cfg.instructions[pc]);
break;
}
}
}
let tfl = tforloop_inst.unwrap_or(self.cfg.instructions[header.end]);
let base = tfl.a;
let num_vars = tfl.c();
let names: Vec<String> = (0..num_vars)
.map(|i| self.local_name(base + 3 + i, header.start))
.collect();
for (i, name) in names.iter().enumerate() {
let r = base + 3 + i as u32;
self.local_names.insert(r, name.clone());
self.declared_locals.insert(r);
self.set_reg(r, Expr::Name(name.clone()));
}
let iter_expr = self.reg_expr(base);
let mut body_blocks: Vec<usize> = lp.body.iter()
.filter(|&&b| b != lp.header)
.copied()
.collect();
body_blocks.sort();
self.active_loop_headers.push(lp.header);
self.active_loop_exits.push(self.max_loop_block(lp) + 1);
let body = if !body_blocks.is_empty() {
let first = *body_blocks.first().unwrap();
let last = *body_blocks.last().unwrap();
self.lift_block_range(first, last + 1)
} else {
Vec::new()
};
self.active_loop_exits.pop();
self.active_loop_headers.pop();
stmts.push(Stat::GenericFor {
names,
iterators: vec![iter_expr],
body,
});
self.max_loop_block(lp) + 1
}
fn lift_while(&mut self, lp: &NaturalLoop, stmts: &mut Block) -> usize {
let _header = &self.cfg.blocks[lp.header].clone();
let cond = self.extract_condition(lp.header).unwrap_or(Expr::Bool(true));
self.active_loop_headers.push(lp.header);
self.active_loop_exits.push(self.max_loop_block(lp) + 1);
let body_start = lp.header + 1;
let body_end = lp.latch + 1;
let body = self.lift_block_range(body_start, body_end);
self.active_loop_exits.pop();
self.active_loop_headers.pop();
stmts.push(Stat::While { cond, body });
self.max_loop_block(lp) + 1
}
fn lift_conditional(&mut self, block_idx: usize, stmts: &mut Block) -> usize {
if let Some(next) = self.try_lift_or_and_chain(block_idx, stmts) {
return next;
}
let block = self.cfg.blocks[block_idx].clone();
let test_pc = self.find_test_pc(&block);
if let Some(tp) = test_pc {
if tp > block.start {
self.lift_instructions(block.start, tp - 1, stmts);
}
}
let cond = self.extract_condition(block_idx).unwrap_or(Expr::Bool(true));
let succs = block.successors.clone();
if succs.len() != 2 {
self.lift_instructions(block.start, block.end, stmts);
return block_idx + 1;
}
let false_target = succs[0]; let true_target = succs[1];
if self.is_return_block(true_target) && false_target > true_target
&& self.cfg.blocks[true_target].predecessors.len() <= 1
{
let guard_body = self.lift_block_range(true_target, true_target + 1);
stmts.push(Stat::If {
cond,
then_block: guard_body,
elseif_clauses: Vec::new(),
else_block: None,
});
return false_target;
}
if self.is_return_block(false_target) && true_target < false_target
&& self.cfg.blocks[false_target].predecessors.len() <= 1
{
let guard_body = self.lift_block_range(false_target, false_target + 1);
let inv_cond = negate_expr(cond);
stmts.push(Stat::If {
cond: inv_cond,
then_block: guard_body,
elseif_clauses: Vec::new(),
else_block: None,
});
return true_target;
}
let merge = self.find_merge_point(block_idx, true_target, false_target);
let then_end = merge.unwrap_or(false_target);
let then_block = self.lift_block_range(true_target, then_end);
let else_block = if let Some(merge) = merge {
if false_target < merge {
let eb = self.lift_block_range(false_target, merge);
if eb.is_empty() { None } else { Some(eb) }
} else {
None
}
} else {
None
};
stmts.push(Stat::If {
cond,
then_block,
elseif_clauses: Vec::new(),
else_block,
});
merge.unwrap_or(false_target.max(true_target) + 1)
}
}