use super::lower::{Jump, Lowered, Lowering, RawProto, Window, enc_abc, enc_abx, enc_sj};
use crate::vm::isa::Op;
#[derive(Clone, Copy, PartialEq, Eq)]
pub(in crate::vm::dump) enum Kind {
Move,
LoadK,
LoadKx,
LoadBool,
LoadNil,
GetUpval,
GetTabUp,
GetTable,
SetTabUp,
SetUpval,
SetTable,
NewTable,
SelfOp,
Arith(Op),
Unary(Op),
Concat,
Jmp,
Eq,
Lt,
Le,
Test,
TestSet,
Call,
TailCall,
Return,
ForLoop,
ForPrep,
TForCall,
TForLoop,
SetList,
Closure,
Vararg,
ExtraArg,
}
#[derive(Clone, Copy)]
struct I {
op: u8,
a: u32,
b: u32,
c: u32,
}
impl I {
fn decode(w: u32) -> I {
I {
op: (w & 0x3F) as u8,
a: (w >> 6) & 0xFF,
c: (w >> 14) & 0x1FF,
b: (w >> 23) & 0x1FF,
}
}
fn bx(self) -> u32 {
(self.b << 9) | self.c
}
fn sbx(self) -> i64 {
self.bx() as i64 - 131071
}
}
fn ax(w: u32) -> u32 {
w >> 6
}
const FIELDS_PER_FLUSH: u64 = 50;
fn kind(ops: &[Kind], w: u32) -> Option<Kind> {
ops.get(I::decode(w).op as usize).copied()
}
fn loop_windows(dialect: &str, code: &[u32], ops: &[Kind]) -> Result<Vec<Window>, String> {
let mut out = Vec::new();
for (p, &w) in code.iter().enumerate() {
if kind(ops, w) != Some(Kind::TForLoop) {
continue;
}
let i = I::decode(w);
let body = p as i64 + 1 + i.sbx();
if !(0..=p as i64).contains(&body) {
return Err(format!(
"{dialect} chunk: TFORLOOP jumps to {body} (pc {p})"
));
}
out.push(Window {
first: body as usize,
last: p,
pivot: i.a + 1,
});
}
Ok(out)
}
pub(super) fn is_env(raw: &RawProto, up: u32) -> bool {
raw.upvals
.get(up as usize)
.is_some_and(|u| &*u.name == "_ENV")
}
pub(super) fn translate(
dialect: &'static str,
ops: &[Kind],
raw: &mut RawProto,
) -> Result<Lowered, String> {
let windows = loop_windows(dialect, &raw.code, ops)?;
let mut lw = Lowering::new(dialect, raw.code.len(), raw.max_stack, windows);
let mut closed = vec![false; raw.protos.len()];
let code = &raw.code;
let mut pc = 0;
while pc < code.len() {
lw.begin(pc, raw.lines.get(pc).copied().unwrap_or(0));
let i = I::decode(code[pc]);
let Some(k) = kind(ops, code[pc]) else {
return Err(lw.err(format_args!("unknown opcode {}", i.op)));
};
let next = pc as i64 + 1;
match k {
Kind::Move => {
let (a, b) = (lw.r(i.a)?, lw.r(i.b)?);
lw.emit(enc_abc(Op::Move, a, b, 0, false)?);
}
Kind::LoadK => {
let a = lw.r(i.a)?;
lw.load_k(a, i.bx())?;
}
Kind::LoadKx => {
pc += 1;
let a = lw.r(i.a)?;
match code.get(pc) {
Some(&w) if kind(ops, w) == Some(Kind::ExtraArg) => lw.load_k(a, ax(w))?,
_ => return Err(lw.err("LOADKX without its EXTRAARG")),
}
}
Kind::LoadBool => {
let a = lw.r(i.a)?;
match (i.b != 0, i.c != 0) {
(false, false) => lw.emit(enc_abc(Op::LoadFalse, a, 0, 0, false)?),
(false, true) => lw.emit(enc_abc(Op::LFalseSkip, a, 0, 0, false)?),
(true, false) => lw.emit(enc_abc(Op::LoadTrue, a, 0, 0, false)?),
(true, true) => {
lw.emit(enc_abc(Op::LoadTrue, a, 0, 0, false)?);
lw.jump(enc_sj(Op::Jmp, 0)?, Jump::Jmp, next + 1)?;
}
}
}
Kind::LoadNil => {
let b = lw.byte(i.b, "LOADNIL B")?;
let a = lw.run(i.a, b + 1)?;
lw.emit(enc_abc(Op::LoadNil, a, b, 0, false)?);
}
Kind::GetUpval => {
let (a, b) = (lw.r(i.a)?, lw.byte(i.b, "GETUPVAL B")?);
lw.emit(enc_abc(Op::GetUpval, a, b, 0, false)?);
}
Kind::SetUpval => {
let (a, b) = (lw.r(i.a)?, lw.byte(i.b, "SETUPVAL B")?);
lw.emit(enc_abc(Op::SetUpval, a, b, 0, false)?);
}
Kind::GetTabUp => {
let (a, up) = (lw.r(i.a)?, lw.byte(i.b, "GETTABUP B")?);
if i.c & super::lower::RK_BIT != 0 {
lw.get_tabup(a, up, i.c & 0xFF, is_env(raw, up))?;
} else {
let (t, key) = (lw.temp()?, lw.r(i.c)?);
lw.emit(enc_abc(Op::GetUpval, t, up, 0, false)?);
lw.emit(enc_abc(Op::GetTable, a, t, key, false)?);
}
}
Kind::SetTabUp => {
let v = lw.rk(i.c)?;
if i.b & super::lower::RK_BIT != 0 {
lw.set_tabup(i.a, i.b & 0xFF, v, is_env(raw, i.a))?;
} else {
let (t, key) = (lw.temp()?, lw.r(i.b)?);
lw.emit(enc_abc(Op::GetUpval, t, i.a, 0, false)?);
lw.emit(enc_abc(Op::SetTable, t, key, v, false)?);
}
}
Kind::GetTable => {
let (a, b) = (lw.r(i.a)?, lw.r(i.b)?);
lw.get_table_rk(a, b, i.c)?;
}
Kind::SetTable => {
let a = lw.r(i.a)?;
lw.set_table_rk(a, i.b, i.c)?;
}
Kind::NewTable => {
let a = lw.r(i.a)?;
lw.emit(enc_abc(Op::NewTable, a, 0, 0, false)?);
}
Kind::SelfOp => lw.self_rk(i.a, i.b, i.c)?,
Kind::Arith(op) => {
let a = lw.r(i.a)?;
let b = lw.rk(i.b)?;
let c = lw.rk(i.c)?;
lw.emit(enc_abc(op, a, b, c, false)?);
}
Kind::Unary(op) => {
let (a, b) = (lw.r(i.a)?, lw.r(i.b)?);
lw.emit(enc_abc(op, a, b, 0, false)?);
}
Kind::Concat => lw.concat_range(i.a, i.b, i.c)?,
Kind::Jmp => {
let target = next + i.sbx();
if i.a == 0 {
lw.jump(enc_sj(Op::Jmp, 0)?, Jump::Jmp, target)?;
} else {
let close = lw.r(i.a - 1)?;
let guarded = pc > 0
&& matches!(
kind(ops, code[pc - 1]),
Some(Kind::Eq | Kind::Lt | Kind::Le | Kind::Test | Kind::TestSet)
);
if guarded {
lw.jump_closing(close, target)?;
} else {
lw.emit(enc_abc(Op::Close, close, 0, 0, false)?);
lw.jump(enc_sj(Op::Jmp, 0)?, Jump::Jmp, target)?;
}
}
}
Kind::Eq => lw.compare_rk(Op::Eq, i.a != 0, i.b, i.c)?,
Kind::Lt => lw.compare_rk(Op::Lt, i.a != 0, i.b, i.c)?,
Kind::Le => lw.compare_rk(Op::Le, i.a != 0, i.b, i.c)?,
Kind::Test => {
let a = lw.r(i.a)?;
lw.emit(enc_abc(Op::Test, a, 0, 0, i.c != 0)?);
}
Kind::TestSet => {
let (a, b) = (lw.r(i.a)?, lw.r(i.b)?);
lw.emit(enc_abc(Op::TestSet, a, b, 0, i.c != 0)?);
}
Kind::Call => {
let (b, c) = (lw.byte(i.b, "CALL B")?, lw.byte(i.c, "CALL C")?);
let a = lw.run(i.a, b.max(c.saturating_sub(1)).max(1))?;
lw.emit(enc_abc(Op::Call, a, b, c, false)?);
}
Kind::TailCall => {
let b = lw.byte(i.b, "TAILCALL B")?;
let a = lw.run(i.a, b.max(1))?;
lw.emit(enc_abc(Op::TailCall, a, b, 0, false)?);
}
Kind::Return => lw.ret(i.a, i.b)?,
Kind::ForPrep => {
let a = lw.run(i.a, 4)?;
lw.jump(enc_abx(Op::ForPrep, a, 0)?, Jump::ForPrep, next + i.sbx())?;
}
Kind::ForLoop => {
let a = lw.run(i.a, 4)?;
lw.jump(enc_abx(Op::ForLoop, a, 0)?, Jump::Back, next + i.sbx())?;
}
Kind::TForCall => {
let c = lw.byte(i.c, "TFORCALL C")?;
let a = lw.run(i.a, 3)?;
lw.run(i.a + 3, c.max(1))?;
lw.emit(enc_abc(Op::TForCall, a, 0, c, false)?);
}
Kind::TForLoop => {
let Some(base) = i.a.checked_sub(2) else {
return Err(lw.err(format_args!("TFORLOOP A={} below 2", i.a)));
};
let a = lw.run(base, 3)?;
lw.jump(enc_abx(Op::TForLoop, a, 0)?, Jump::Back, next + i.sbx())?;
}
Kind::SetList => {
let block = if i.c == 0 {
pc += 1;
match code.get(pc) {
Some(&w) if kind(ops, w) == Some(Kind::ExtraArg) => ax(w),
_ => return Err(lw.err("SETLIST without its EXTRAARG")),
}
} else {
i.c
};
if block == 0 {
return Err(lw.err("SETLIST block number 0"));
}
let a = lw.run(i.a, i.b + 1)?;
lw.set_list(a, i.b, (block as u64 - 1) * FIELDS_PER_FLUSH)?;
}
Kind::Closure => {
let idx = i.bx() as usize;
let Some(child) = raw.protos.get_mut(idx) else {
return Err(lw.err(format_args!("CLOSURE of missing function {idx}")));
};
if std::mem::replace(&mut closed[idx], true) {
return Err(lw.err(format_args!("function {idx} instantiated twice")));
}
for u in child.upvals.iter_mut().filter(|u| u.in_stack) {
let r = lw.r(u.index as u32)?;
u.index = r as u8;
}
let a = lw.r(i.a)?;
lw.emit(enc_abx(Op::Closure, a, idx as u32)?);
}
Kind::Vararg => {
let b = lw.byte(i.b, "VARARG B")?;
let a = lw.run(i.a, b.saturating_sub(1).max(1))?;
lw.emit(enc_abc(Op::Vararg, a, 0, b, false)?);
}
Kind::ExtraArg => return Err(lw.err("EXTRAARG without an instruction to extend")),
}
pc += 1;
}
lw.finish(&raw.locvars)
}