use crate::runtime::Value;
use crate::runtime::function::Proto;
use crate::vm::isa::{Inst, Op};
fn kname(proto: &Proto, c: u32) -> Option<String> {
match proto.consts.get(c as usize) {
Some(Value::Str(s)) => Some(String::from_utf8_lossy(s.as_bytes()).into_owned()),
_ => None,
}
}
fn upvalname(proto: &Proto, u: u32) -> Option<String> {
proto.upvals.get(u as usize).map(|d| d.name.to_string())
}
fn writes_reg(i: Inst, reg: u32) -> bool {
let a = i.a();
match i.op() {
Op::LoadNil => a <= reg && reg <= a + i.b(),
Op::SelfOp => reg == a || reg == a + 1,
Op::Call | Op::TailCall | Op::Vararg => reg >= a,
Op::TForCall => reg >= a + 4,
Op::Jmp
| Op::SetUpval
| Op::SetTabUp
| Op::SetTable
| Op::SetI
| Op::SetField
| Op::Close
| Op::Tbc
| Op::Eq
| Op::Lt
| Op::Le
| Op::EqK
| Op::Test
| Op::Return
| Op::Return0
| Op::Return1
| Op::SetList
| Op::ExtraArg
| Op::TForPrep => false,
_ => reg == a,
}
}
fn find_setreg(proto: &Proto, lastpc: usize, reg: u32) -> Option<usize> {
let mut setreg: Option<usize> = None;
let mut jmptarget: usize = 0;
let code = &proto.code;
for pc in 0..lastpc {
let i = code[pc];
if i.op() == Op::Jmp {
let dest = (pc as i64 + 1 + i.sj() as i64) as usize;
if dest <= lastpc && dest > jmptarget {
jmptarget = dest;
}
continue;
}
if writes_reg(i, reg) {
setreg = if pc < jmptarget { None } else { Some(pc) };
}
}
setreg
}
fn basicgetobjname(proto: &Proto, lastpc: usize, reg: u32) -> Option<(&'static str, String)> {
if let Some(name) = getlocalname(proto, reg, lastpc) {
return Some(("local", name.to_string()));
}
let setpc = find_setreg(proto, lastpc, reg)?;
let i = proto.code[setpc];
match i.op() {
Op::Move => {
let b = i.b();
if b < i.a() {
basicgetobjname(proto, setpc, b)
} else {
None
}
}
Op::GetUpval => upvalname(proto, i.b()).map(|n| ("upvalue", n)),
Op::LoadK => kname(proto, i.bx()).map(|n| ("constant", n)),
Op::LoadKx => {
let next = proto.code.get(setpc + 1)?;
if next.op() != Op::ExtraArg {
return None;
}
kname(proto, next.ax()).map(|n| ("constant", n))
}
_ => None,
}
}
fn rname(proto: &Proto, pc: usize, c: u32) -> String {
match basicgetobjname(proto, pc, c) {
Some(("constant", n)) => n,
_ => "?".to_string(),
}
}
fn gxf(proto: &Proto, pc: usize, i: Inst, isup: bool) -> &'static str {
let t = i.b();
let tname = if isup {
upvalname(proto, t)
} else {
match basicgetobjname(proto, pc, t) {
Some((kind, n)) if kind == "local" || kind == "upvalue" => Some(n),
_ => None,
}
};
if tname.as_deref() == Some("_ENV") {
"global"
} else {
"field"
}
}
pub fn getlocalname(proto: &Proto, reg: u32, pc: usize) -> Option<&str> {
let pc = pc as u32;
proto
.locvars
.iter()
.filter(|lv| lv.reg == reg && lv.start_pc <= pc && pc < lv.end_pc)
.max_by_key(|lv| lv.start_pc)
.map(|lv| &*lv.name)
}
pub fn getobjname(proto: &Proto, lastpc: usize, reg: u32) -> Option<(&'static str, String)> {
if let Some(name) = getlocalname(proto, reg, lastpc) {
return Some(("local", name.to_string()));
}
let setpc = find_setreg(proto, lastpc, reg)?;
let i = proto.code[setpc];
match i.op() {
Op::Move => {
let b = i.b();
if b < i.a() {
getobjname(proto, setpc, b)
} else {
None
}
}
Op::GetUpval => upvalname(proto, i.b()).map(|n| ("upvalue", n)),
Op::GetTabUp => kname(proto, i.c()).map(|n| (gxf(proto, setpc, i, true), n)),
Op::GetField => kname(proto, i.c()).map(|n| (gxf(proto, setpc, i, false), n)),
Op::GetTable => Some((gxf(proto, setpc, i, false), rname(proto, setpc, i.c()))),
Op::GetI => Some(("field", "integer index".to_string())),
Op::SelfOp => {
let name = if i.k() {
kname(proto, i.c())
} else {
Some(rname(proto, setpc, i.c()))
};
name.map(|n| ("method", n))
}
_ => None,
}
}