use crate::lua51::cfg::ControlFlowGraph;
use crate::lua51::instruction::{is_k, Instruction};
use crate::lua51::opcodes::OpCode;
#[derive(Debug, Clone, Default)]
pub struct RegUsage {
pub defs: Vec<u32>,
pub uses: Vec<u32>,
}
pub fn instruction_reg_usage(inst: &Instruction) -> RegUsage {
let mut u = RegUsage::default();
let a = inst.a;
match inst.op {
OpCode::Move => {
u.defs.push(a);
u.uses.push(inst.b());
}
OpCode::LoadK | OpCode::LoadBool => {
u.defs.push(a);
}
OpCode::LoadNil => {
for r in a..=inst.b() {
u.defs.push(r);
}
}
OpCode::GetUpval => {
u.defs.push(a);
}
OpCode::GetGlobal => {
u.defs.push(a);
}
OpCode::GetTable => {
u.defs.push(a);
u.uses.push(inst.b());
if !is_k(inst.c()) { u.uses.push(inst.c()); }
}
OpCode::SetGlobal => {
u.uses.push(a);
}
OpCode::SetUpval => {
u.uses.push(a);
}
OpCode::SetTable => {
u.uses.push(a);
if !is_k(inst.b()) { u.uses.push(inst.b()); }
if !is_k(inst.c()) { u.uses.push(inst.c()); }
}
OpCode::NewTable => {
u.defs.push(a);
}
OpCode::Self_ => {
u.defs.push(a);
u.defs.push(a + 1);
u.uses.push(inst.b());
if !is_k(inst.c()) { u.uses.push(inst.c()); }
}
OpCode::Add | OpCode::Sub | OpCode::Mul | OpCode::Div
| OpCode::Mod | OpCode::Pow => {
u.defs.push(a);
if !is_k(inst.b()) { u.uses.push(inst.b()); }
if !is_k(inst.c()) { u.uses.push(inst.c()); }
}
OpCode::Unm | OpCode::Not | OpCode::Len => {
u.defs.push(a);
u.uses.push(inst.b());
}
OpCode::Concat => {
u.defs.push(a);
for r in inst.b()..=inst.c() {
u.uses.push(r);
}
}
OpCode::Jmp => {}
OpCode::Eq | OpCode::Lt | OpCode::Le => {
if !is_k(inst.b()) { u.uses.push(inst.b()); }
if !is_k(inst.c()) { u.uses.push(inst.c()); }
}
OpCode::Test => {
u.uses.push(a);
}
OpCode::TestSet => {
u.defs.push(a);
u.uses.push(inst.b());
}
OpCode::Call => {
let num_args = if inst.b() == 0 { 0 } else { inst.b() - 1 };
u.uses.push(a);
for i in 1..=num_args {
u.uses.push(a + i);
}
let num_results = if inst.c() == 0 { 1 } else { inst.c() - 1 };
for i in 0..num_results {
u.defs.push(a + i);
}
}
OpCode::TailCall => {
let num_args = if inst.b() == 0 { 0 } else { inst.b() - 1 };
u.uses.push(a);
for i in 1..=num_args {
u.uses.push(a + i);
}
}
OpCode::Return => {
let num_ret = if inst.b() == 0 { 0 } else { inst.b() - 1 };
for i in 0..num_ret {
u.uses.push(a + i);
}
}
OpCode::ForLoop => {
u.uses.push(a);
u.uses.push(a + 1);
u.uses.push(a + 2);
u.defs.push(a);
u.defs.push(a + 3);
}
OpCode::ForPrep => {
u.uses.push(a);
u.uses.push(a + 2);
u.defs.push(a);
}
OpCode::TForLoop => {
u.uses.push(a);
u.uses.push(a + 1);
u.uses.push(a + 2);
let num_vars = inst.c();
for i in 0..=num_vars {
u.defs.push(a + 3 + i);
}
}
OpCode::SetList => {
let num = if inst.b() == 0 { 0 } else { inst.b() };
u.uses.push(a);
for i in 1..=num {
u.uses.push(a + i);
}
}
OpCode::Close => {}
OpCode::Closure => {
u.defs.push(a);
}
OpCode::VarArg => {
let num = if inst.b() == 0 { 1 } else { inst.b() - 1 };
for i in 0..num {
u.defs.push(a + i);
}
}
}
u
}
#[derive(Debug)]
pub struct LivenessInfo {
pub live_in: Vec<Vec<bool>>,
pub live_out: Vec<Vec<bool>>,
pub max_reg: usize,
}
pub fn compute_liveness(cfg: &ControlFlowGraph, max_stack: usize) -> LivenessInfo {
let n = cfg.num_blocks();
let nregs = max_stack;
if n == 0 {
return LivenessInfo {
live_in: Vec::new(),
live_out: Vec::new(),
max_reg: nregs,
};
}
let mut use_set = vec![vec![false; nregs]; n];
let mut kill = vec![vec![false; nregs]; n];
for (bid, block) in cfg.blocks.iter().enumerate() {
for pc in block.start..=block.end {
let inst = &cfg.instructions[pc];
let usage = instruction_reg_usage(inst);
for &r in &usage.uses {
let r = r as usize;
if r < nregs && !kill[bid][r] {
use_set[bid][r] = true;
}
}
for &r in &usage.defs {
let r = r as usize;
if r < nregs {
kill[bid][r] = true;
}
}
}
}
let mut live_in = vec![vec![false; nregs]; n];
let mut live_out = vec![vec![false; nregs]; n];
let mut changed = true;
while changed {
changed = false;
for bid in (0..n).rev() {
for r in 0..nregs {
let mut out = false;
for &succ in &cfg.blocks[bid].successors {
if succ < n && live_in[succ][r] {
out = true;
break;
}
}
live_out[bid][r] = out;
}
for r in 0..nregs {
let new_in = use_set[bid][r] || (live_out[bid][r] && !kill[bid][r]);
if new_in != live_in[bid][r] {
live_in[bid][r] = new_in;
changed = true;
}
}
}
}
LivenessInfo { live_in, live_out, max_reg: nregs }
}
pub fn is_reg_live_after(
cfg: &ControlFlowGraph,
liveness: &LivenessInfo,
pc: usize,
reg: u32,
) -> bool {
let r = reg as usize;
if r >= liveness.max_reg {
return false;
}
let block_id = cfg.block_of(pc);
let block = &cfg.blocks[block_id];
let mut live = liveness.live_out[block_id][r];
for scan_pc in (pc + 1..=block.end).rev() {
let inst = &cfg.instructions[scan_pc];
let usage = instruction_reg_usage(inst);
if usage.defs.contains(®) {
live = false;
}
if usage.uses.contains(®) {
live = true;
}
}
live
}