use crate::runtime::Value;
use crate::runtime::function::{LocVar, Proto, UpvalDesc};
use crate::runtime::heap::{Gc, GcHeader, Heap, ObjTag};
use crate::vm::dump::reader::Reader;
use crate::vm::isa::{Inst, Op};
fn read_varint_54(r: &mut Reader) -> Result<u64, String> {
let mut acc: u64 = 0;
for _ in 0..10 {
let byte = r.u8()?;
if acc >> 57 != 0 {
return Err("PUC 5.4 varint value overflows u64".to_string());
}
acc = (acc << 7) | (byte & 0x7f) as u64;
if byte & 0x80 != 0 {
return Ok(acc);
}
}
Err("PUC 5.4 varint value too long (max 10 bytes)".to_string())
}
const HEADER_54_TAIL: &[u8] = &[
0x00, 0x19, 0x93, b'\r', b'\n', 0x1a, b'\n', 4, 8, 8, 0x78, 0x56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x28, 0x77, 0x40, ];
const TAG_NIL: u8 = 0;
const TAG_FALSE: u8 = 1;
const TAG_TRUE: u8 = 1 | (1 << 4); const TAG_NUMINT: u8 = 3;
const TAG_NUMFLT: u8 = 3 | (1 << 4); const TAG_SHRSTR: u8 = 4;
const TAG_LNGSTR: u8 = 4 | (1 << 4);
mod puc_op {
pub const MOVE: u8 = 0;
pub const LOADI: u8 = 1;
pub const LOADF: u8 = 2;
pub const LOADK: u8 = 3;
pub const LOADKX: u8 = 4;
pub const LOADFALSE: u8 = 5;
pub const LFALSESKIP: u8 = 6;
pub const LOADTRUE: u8 = 7;
pub const LOADNIL: u8 = 8;
pub const GETUPVAL: u8 = 9;
pub const SETUPVAL: u8 = 10;
pub const GETTABUP: u8 = 11;
pub const GETTABLE: u8 = 12;
pub const GETI: u8 = 13;
pub const GETFIELD: u8 = 14;
pub const SETTABUP: u8 = 15;
pub const SETTABLE: u8 = 16;
pub const SETI: u8 = 17;
pub const SETFIELD: u8 = 18;
pub const NEWTABLE: u8 = 19;
pub const SELF: u8 = 20;
pub const ADDI: u8 = 21;
pub const ADDK: u8 = 22;
pub const SUBK: u8 = 23;
pub const MULK: u8 = 24;
pub const MODK: u8 = 25;
pub const POWK: u8 = 26;
pub const DIVK: u8 = 27;
pub const IDIVK: u8 = 28;
pub const BANDK: u8 = 29;
pub const BORK: u8 = 30;
pub const BXORK: u8 = 31;
pub const SHRI: u8 = 32;
pub const SHLI: u8 = 33;
pub const ADD: u8 = 34;
pub const SUB: u8 = 35;
pub const MUL: u8 = 36;
pub const MOD: u8 = 37;
pub const POW: u8 = 38;
pub const DIV: u8 = 39;
pub const IDIV: u8 = 40;
pub const BAND: u8 = 41;
pub const BOR: u8 = 42;
pub const BXOR: u8 = 43;
pub const SHL: u8 = 44;
pub const SHR: u8 = 45;
pub const MMBIN: u8 = 46;
pub const MMBINI: u8 = 47;
pub const MMBINK: u8 = 48;
pub const UNM: u8 = 49;
pub const BNOT: u8 = 50;
pub const NOT: u8 = 51;
pub const LEN: u8 = 52;
pub const CONCAT: u8 = 53;
pub const CLOSE: u8 = 54;
pub const TBC: u8 = 55;
pub const JMP: u8 = 56;
pub const EQ: u8 = 57;
pub const LT: u8 = 58;
pub const LE: u8 = 59;
pub const EQK: u8 = 60;
pub const EQI: u8 = 61;
pub const LTI: u8 = 62;
pub const LEI: u8 = 63;
pub const GTI: u8 = 64;
pub const GEI: u8 = 65;
pub const TEST: u8 = 66;
pub const TESTSET: u8 = 67;
pub const CALL: u8 = 68;
pub const TAILCALL: u8 = 69;
pub const RETURN: u8 = 70;
pub const RETURN0: u8 = 71;
pub const RETURN1: u8 = 72;
pub const FORLOOP: u8 = 73;
pub const FORPREP: u8 = 74;
pub const TFORPREP: u8 = 75;
pub const TFORCALL: u8 = 76;
pub const TFORLOOP: u8 = 77;
pub const SETLIST: u8 = 78;
pub const CLOSURE: u8 = 79;
pub const VARARG: u8 = 80;
pub const VARARGPREP: u8 = 81;
pub const EXTRAARG: u8 = 82;
}
const PUC_MAXARG_BX: u32 = (1 << 17) - 1;
const PUC_OFFSET_SBX: i32 = (PUC_MAXARG_BX >> 1) as i32;
const PUC_MAXARG_SJ: u32 = (1 << 25) - 1;
const PUC_OFFSET_SJ: i32 = (PUC_MAXARG_SJ >> 1) as i32;
#[inline]
fn op_of(w: u32) -> u8 {
(w & 0x7F) as u8
}
#[inline]
fn a_of(w: u32) -> u32 {
(w >> 7) & 0xFF
}
#[inline]
fn k_of(w: u32) -> bool {
((w >> 15) & 1) != 0
}
#[inline]
fn b_of(w: u32) -> u32 {
(w >> 16) & 0xFF
}
#[inline]
fn c_of(w: u32) -> u32 {
(w >> 24) & 0xFF
}
#[inline]
fn sb_of(w: u32) -> i32 {
let b = b_of(w) as i32;
b - 0x80 }
#[inline]
fn sc_of(w: u32) -> i32 {
let c = c_of(w) as i32;
c - 0x80
}
#[inline]
fn bx_of(w: u32) -> u32 {
w >> 15
}
#[inline]
fn sbx_of(w: u32) -> i32 {
(bx_of(w) as i32) - PUC_OFFSET_SBX
}
#[inline]
fn sj_of(w: u32) -> i32 {
(((w >> 7) & PUC_MAXARG_SJ) as i32) - PUC_OFFSET_SJ
}
pub(super) fn undump(bytes: &[u8], heap: &mut Heap) -> Result<Gc<Proto>, String> {
if bytes.len() < 5 + HEADER_54_TAIL.len() {
return Err("truncated PUC 5.4 chunk header".to_string());
}
if &bytes[0..5] != b"\x1bLua\x54" {
return Err("not a PUC 5.4 binary chunk".to_string());
}
if &bytes[5..5 + HEADER_54_TAIL.len()] != HEADER_54_TAIL {
return Err("bad PUC 5.4 chunk header (sizeof / sanity mismatch)".to_string());
}
let body_off = 5 + HEADER_54_TAIL.len();
let mut r = Reader::at(bytes, body_off);
let _top_upvals = r.u8()?;
let proto = decode_proto(&mut r, heap, None)?;
if r.pos() != bytes.len() {
return Err(format!(
"trailing bytes after PUC 5.4 chunk ({} unread)",
bytes.len() - r.pos()
));
}
Ok(proto)
}
fn read_string<'a>(r: &mut Reader<'a>) -> Result<Option<&'a [u8]>, String> {
let raw = read_varint_54(r)?;
if raw == 0 {
return Ok(None);
}
let len = (raw - 1) as usize;
Ok(Some(r.take(len)?))
}
fn read_lua_integer(r: &mut Reader) -> Result<i64, String> {
Ok(i64::from_le_bytes(r.take(8)?.try_into().unwrap()))
}
fn read_lua_number(r: &mut Reader) -> Result<f64, String> {
Ok(f64::from_bits(u64::from_le_bytes(
r.take(8)?.try_into().unwrap(),
)))
}
fn read_const(r: &mut Reader, heap: &mut Heap) -> Result<Value, String> {
let tag = r.u8()?;
Ok(match tag {
TAG_NIL => Value::Nil,
TAG_FALSE => Value::Bool(false),
TAG_TRUE => Value::Bool(true),
TAG_NUMINT => Value::Int(read_lua_integer(r)?),
TAG_NUMFLT => Value::Float(read_lua_number(r)?),
TAG_SHRSTR | TAG_LNGSTR => {
let s = read_string(r)?.ok_or("nil string constant")?;
Value::Str(heap.intern(s))
}
t => return Err(format!("bad PUC 5.4 constant tag 0x{t:02x}")),
})
}
fn decode_proto(
r: &mut Reader,
heap: &mut Heap,
parent_source: Option<Gc<crate::runtime::LuaStr>>,
) -> Result<Gc<Proto>, String> {
let raw_source = read_string(r)?;
let source = match raw_source {
Some(b) => heap.intern(b),
None => parent_source.unwrap_or_else(|| heap.intern(b"")),
};
let line_defined = read_varint_54(r)? as u32;
let last_line_defined = read_varint_54(r)? as u32;
let num_params = r.u8()?;
let is_vararg = r.u8()? != 0;
let max_stack_puc = r.u8()?;
let n_code = read_varint_54(r)? as usize;
let mut puc_code = Vec::with_capacity(n_code);
for _ in 0..n_code {
puc_code.push(r.u32()?);
}
let n_const = read_varint_54(r)? as usize;
let mut consts = Vec::with_capacity(n_const);
for _ in 0..n_const {
consts.push(read_const(r, heap)?);
}
let n_up = read_varint_54(r)? as usize;
let mut upvals: Vec<UpvalDesc> = Vec::with_capacity(n_up);
for _ in 0..n_up {
let in_stack = r.u8()? != 0;
let index = r.u8()?;
let kind = r.u8()?; upvals.push(UpvalDesc {
in_stack,
index,
name: Box::from(""), read_only: kind != 0, });
}
let n_proto = read_varint_54(r)? as usize;
let mut protos = Vec::with_capacity(n_proto);
for _ in 0..n_proto {
protos.push(decode_proto(r, heap, Some(source))?);
}
let n_lineinfo = read_varint_54(r)? as usize;
let mut puc_lineinfo = Vec::with_capacity(n_lineinfo);
for _ in 0..n_lineinfo {
puc_lineinfo.push(r.u8()? as i8);
}
let n_absline = read_varint_54(r)? as usize;
let mut abslineinfo = Vec::with_capacity(n_absline);
for _ in 0..n_absline {
let pc = read_varint_54(r)? as u32;
let line = read_varint_54(r)? as u32;
abslineinfo.push((pc, line));
}
let n_loc = read_varint_54(r)? as usize;
let mut locvars_raw = Vec::with_capacity(n_loc);
for _ in 0..n_loc {
let name = read_string(r)?.unwrap_or(b"");
let start_pc = read_varint_54(r)? as u32;
let end_pc = read_varint_54(r)? as u32;
locvars_raw.push((name.to_vec(), start_pc, end_pc));
}
let n_up_names_raw = read_varint_54(r)? as usize;
let n_to_read = if n_up_names_raw == 0 { 0 } else { upvals.len() };
for u in upvals.iter_mut().take(n_to_read) {
let name = read_string(r)?.unwrap_or(b"");
u.name = String::from_utf8_lossy(name).into_owned().into();
}
let translated = translate_code(&puc_code, &consts)?;
let puc_lines = decode_lineinfo(&puc_lineinfo, &abslineinfo, line_defined, n_code)?;
let mut lines = Vec::with_capacity(translated.code.len());
for &puc_pc in translated.luna_to_puc_pc.iter() {
lines.push(puc_lines.get(puc_pc).copied().unwrap_or(0));
}
let mut locvars = Vec::with_capacity(locvars_raw.len());
for (name, start_pc, end_pc) in locvars_raw {
let new_start = remap_pc(&translated.puc_to_luna_pc, start_pc);
let new_end = remap_pc(&translated.puc_to_luna_pc, end_pc);
locvars.push(LocVar {
name: String::from_utf8_lossy(&name).into_owned().into(),
reg: 0, start_pc: new_start,
end_pc: new_end,
});
}
let max_stack = max_stack_puc.saturating_add(translated.max_temp_bump);
let env_upval_idx = upvals
.iter()
.take(u8::MAX as usize)
.position(|u| &*u.name == "_ENV")
.map_or(u8::MAX, |i| i as u8);
Ok(heap.adopt_proto(Proto {
hdr: GcHeader::new(ObjTag::Proto),
code: translated.code.into_boxed_slice(),
consts: consts.into_boxed_slice(),
protos: protos.into_boxed_slice(),
upvals: upvals.into_boxed_slice(),
num_params,
is_vararg,
has_vararg_table_pseudo: false,
has_compat_vararg_arg: false,
max_stack,
lines: lines.into_boxed_slice(),
source,
line_defined,
last_line_defined,
locvars: locvars.into_boxed_slice(),
cache: std::cell::Cell::new(None),
jit: std::cell::Cell::new(crate::runtime::function::JitProtoState::Untried),
env_upval_idx,
trace_hot_count: std::cell::Cell::new(0),
call_hot_count: std::cell::Cell::new(0),
trace_discard_count: std::cell::Cell::new(0),
trace_gave_up: std::cell::Cell::new(false),
traces: std::cell::RefCell::new(Vec::new()),
}))
}
fn remap_pc(map: &[Option<u32>], puc_pc: u32) -> u32 {
let idx = puc_pc as usize;
if idx >= map.len() {
return map
.iter()
.rev()
.find_map(|x| *x)
.map(|p| p + 1)
.unwrap_or(0);
}
map[idx].unwrap_or_else(|| {
for &slot in &map[idx + 1..] {
if let Some(p) = slot {
return p;
}
}
map.iter()
.rev()
.find_map(|x| *x)
.map(|p| p + 1)
.unwrap_or(0)
})
}
fn decode_lineinfo(
lineinfo: &[i8],
abslineinfo: &[(u32, u32)],
line_defined: u32,
n_code: usize,
) -> Result<Vec<u32>, String> {
if lineinfo.is_empty() && n_code > 0 {
return Ok(vec![0; n_code]);
}
if lineinfo.len() != n_code {
return Err(format!(
"PUC 5.4 lineinfo length {} mismatches code length {}",
lineinfo.len(),
n_code
));
}
let mut out = Vec::with_capacity(n_code);
let mut current_line = line_defined as i64;
let mut abs_cursor = 0usize;
for (pc, &delta) in lineinfo.iter().enumerate() {
if delta == -128 {
while abs_cursor < abslineinfo.len() && (abslineinfo[abs_cursor].0 as usize) < pc {
abs_cursor += 1;
}
if abs_cursor >= abslineinfo.len() || abslineinfo[abs_cursor].0 as usize != pc {
return Err(format!(
"PUC 5.4 ABSLINEINFO at pc {pc} has no matching abslineinfo entry"
));
}
current_line = abslineinfo[abs_cursor].1 as i64;
abs_cursor += 1;
} else {
current_line += delta as i64;
}
if current_line < 0 {
return Err(format!(
"PUC 5.4 lineinfo produced negative line {current_line} at pc {pc}"
));
}
out.push(current_line as u32);
}
Ok(out)
}
struct Translated {
code: Vec<Inst>,
puc_to_luna_pc: Vec<Option<u32>>,
luna_to_puc_pc: Vec<usize>,
max_temp_bump: u8,
}
fn translate_code(puc_code: &[u32], _consts: &[Value]) -> Result<Translated, String> {
let mut code: Vec<Inst> = Vec::with_capacity(puc_code.len());
let mut puc_to_luna_pc: Vec<Option<u32>> = Vec::with_capacity(puc_code.len());
let mut luna_to_puc_pc: Vec<usize> = Vec::with_capacity(puc_code.len());
let mut max_temp_bump: u8 = 0;
let mut jump_fixups: Vec<(usize, i64)> = Vec::new();
for (puc_pc, &w) in puc_code.iter().enumerate() {
let op = op_of(w);
let a = a_of(w);
let b = b_of(w);
let c = c_of(w);
let k = k_of(w);
let emit = |slot: &mut Vec<Inst>,
map_p2l: &mut Vec<Option<u32>>,
map_l2p: &mut Vec<usize>,
inst: Inst,
first: bool| {
if first {
map_p2l.push(Some(slot.len() as u32));
}
map_l2p.push(puc_pc);
slot.push(inst);
};
let drop_inst = |map_p2l: &mut Vec<Option<u32>>| {
map_p2l.push(None);
};
match op {
puc_op::MOVE => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Move, a, b, 0, false),
true,
),
puc_op::LOADI => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iasbx(Op::LoadI, a, sbx_of(w)),
true,
),
puc_op::LOADF => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iasbx(Op::LoadF, a, sbx_of(w)),
true,
),
puc_op::LOADK => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabx(Op::LoadK, a, bx_of(w)),
true,
),
puc_op::LOADKX => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::LoadKx, a, 0, 0, false),
true,
),
puc_op::LOADFALSE => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::LoadFalse, a, 0, 0, false),
true,
),
puc_op::LFALSESKIP => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::LFalseSkip, a, 0, 0, false),
true,
),
puc_op::LOADTRUE => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::LoadTrue, a, 0, 0, false),
true,
),
puc_op::LOADNIL => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::LoadNil, a, b, 0, false),
true,
),
puc_op::GETUPVAL => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::GetUpval, a, b, 0, false),
true,
),
puc_op::SETUPVAL => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::SetUpval, a, b, 0, false),
true,
),
puc_op::GETTABUP => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::GetTabUp, a, b, c, false),
true,
),
puc_op::SETTABUP => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::SetTabUp, a, b, c, k),
true,
),
puc_op::GETTABLE => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::GetTable, a, b, c, false),
true,
),
puc_op::SETTABLE => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::SetTable, a, b, c, k),
true,
),
puc_op::GETI => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::GetI, a, b, c, false),
true,
),
puc_op::SETI => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::SetI, a, b, c, k),
true,
),
puc_op::GETFIELD => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::GetField, a, b, c, false),
true,
),
puc_op::SETFIELD => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::SetField, a, b, c, k),
true,
),
puc_op::NEWTABLE => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::NewTable, a, b, c, k),
true,
),
puc_op::SELF => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::SelfOp, a, b, c, k),
true,
),
puc_op::ADDK => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Add, a, b, c, true),
true,
),
puc_op::SUBK => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Sub, a, b, c, true),
true,
),
puc_op::MULK => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Mul, a, b, c, true),
true,
),
puc_op::MODK => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Mod, a, b, c, true),
true,
),
puc_op::POWK => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Pow, a, b, c, true),
true,
),
puc_op::DIVK => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Div, a, b, c, true),
true,
),
puc_op::IDIVK => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::IDiv, a, b, c, true),
true,
),
puc_op::BANDK => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::BAnd, a, b, c, true),
true,
),
puc_op::BORK => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::BOr, a, b, c, true),
true,
),
puc_op::BXORK => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::BXor, a, b, c, true),
true,
),
puc_op::ADD => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Add, a, b, c, false),
true,
),
puc_op::SUB => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Sub, a, b, c, false),
true,
),
puc_op::MUL => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Mul, a, b, c, false),
true,
),
puc_op::MOD => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Mod, a, b, c, false),
true,
),
puc_op::POW => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Pow, a, b, c, false),
true,
),
puc_op::DIV => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Div, a, b, c, false),
true,
),
puc_op::IDIV => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::IDiv, a, b, c, false),
true,
),
puc_op::BAND => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::BAnd, a, b, c, false),
true,
),
puc_op::BOR => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::BOr, a, b, c, false),
true,
),
puc_op::BXOR => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::BXor, a, b, c, false),
true,
),
puc_op::SHL => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Shl, a, b, c, false),
true,
),
puc_op::SHR => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Shr, a, b, c, false),
true,
),
puc_op::ADDI => {
let tmp = b.max(a) + 1;
if tmp > 0xFF {
return Err("PUC 5.4 ADDI lowering: temp register exceeds 255".to_string());
}
max_temp_bump = max_temp_bump.max(tmp as u8 + 1);
let imm = sc_of(w);
emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iasbx(Op::LoadI, tmp, imm),
true,
);
emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Add, a, b, tmp, false),
false,
);
}
puc_op::SHRI => {
let tmp = b.max(a) + 1;
if tmp > 0xFF {
return Err("PUC 5.4 SHRI lowering: temp register exceeds 255".to_string());
}
max_temp_bump = max_temp_bump.max(tmp as u8 + 1);
let imm = sc_of(w);
emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iasbx(Op::LoadI, tmp, imm),
true,
);
emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Shr, a, b, tmp, false),
false,
);
}
puc_op::SHLI => {
let tmp = b.max(a) + 1;
if tmp > 0xFF {
return Err("PUC 5.4 SHLI lowering: temp register exceeds 255".to_string());
}
max_temp_bump = max_temp_bump.max(tmp as u8 + 1);
let imm = sc_of(w);
emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iasbx(Op::LoadI, tmp, imm),
true,
);
emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Shl, a, tmp, b, false),
false,
);
}
puc_op::MMBIN | puc_op::MMBINI | puc_op::MMBINK => {
drop_inst(&mut puc_to_luna_pc);
}
puc_op::UNM => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Unm, a, b, 0, false),
true,
),
puc_op::BNOT => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::BNot, a, b, 0, false),
true,
),
puc_op::NOT => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Not, a, b, 0, false),
true,
),
puc_op::LEN => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Len, a, b, 0, false),
true,
),
puc_op::CONCAT => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Concat, a, b, 0, false),
true,
),
puc_op::CLOSE => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Close, a, 0, 0, false),
true,
),
puc_op::TBC => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Tbc, a, 0, 0, false),
true,
),
puc_op::JMP => {
let next_puc_pc = puc_pc as i64 + 1;
let target_puc_pc = next_puc_pc + sj_of(w) as i64;
let luna_pc = code.len();
puc_to_luna_pc.push(Some(luna_pc as u32));
luna_to_puc_pc.push(puc_pc);
code.push(Inst::isj(Op::Jmp, 0)); jump_fixups.push((luna_pc, target_puc_pc));
}
puc_op::EQ => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Eq, a, b, 0, k),
true,
),
puc_op::LT => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Lt, a, b, 0, k),
true,
),
puc_op::LE => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Le, a, b, 0, k),
true,
),
puc_op::EQK => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::EqK, a, b, 0, k),
true,
),
puc_op::EQI => {
let tmp = a + 1;
if tmp > 0xFF {
return Err("PUC 5.4 EQI lowering: temp register exceeds 255".to_string());
}
max_temp_bump = max_temp_bump.max(tmp as u8 + 1);
let imm = sb_of(w);
emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iasbx(Op::LoadI, tmp, imm),
true,
);
emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Eq, a, tmp, 0, k),
false,
);
}
puc_op::LTI => {
let tmp = a + 1;
if tmp > 0xFF {
return Err("PUC 5.4 LTI lowering: temp register exceeds 255".to_string());
}
max_temp_bump = max_temp_bump.max(tmp as u8 + 1);
let imm = sb_of(w);
emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iasbx(Op::LoadI, tmp, imm),
true,
);
emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Lt, a, tmp, 0, k),
false,
);
}
puc_op::LEI => {
let tmp = a + 1;
if tmp > 0xFF {
return Err("PUC 5.4 LEI lowering: temp register exceeds 255".to_string());
}
max_temp_bump = max_temp_bump.max(tmp as u8 + 1);
let imm = sb_of(w);
emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iasbx(Op::LoadI, tmp, imm),
true,
);
emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Le, a, tmp, 0, k),
false,
);
}
puc_op::GTI => {
let tmp = a + 1;
if tmp > 0xFF {
return Err("PUC 5.4 GTI lowering: temp register exceeds 255".to_string());
}
max_temp_bump = max_temp_bump.max(tmp as u8 + 1);
let imm = sb_of(w);
emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iasbx(Op::LoadI, tmp, imm),
true,
);
emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Lt, tmp, a, 0, k),
false,
);
}
puc_op::GEI => {
let tmp = a + 1;
if tmp > 0xFF {
return Err("PUC 5.4 GEI lowering: temp register exceeds 255".to_string());
}
max_temp_bump = max_temp_bump.max(tmp as u8 + 1);
let imm = sb_of(w);
emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iasbx(Op::LoadI, tmp, imm),
true,
);
emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Le, tmp, a, 0, k),
false,
);
}
puc_op::TEST => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Test, a, 0, 0, k),
true,
),
puc_op::TESTSET => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::TestSet, a, b, 0, k),
true,
),
puc_op::CALL => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Call, a, b, c, false),
true,
),
puc_op::TAILCALL => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::TailCall, a, b, c, k),
true,
),
puc_op::RETURN => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Return, a, b, c, k),
true,
),
puc_op::RETURN0 => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Return0, a, 0, 0, false),
true,
),
puc_op::RETURN1 => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Return1, a, 0, 0, false),
true,
),
puc_op::FORLOOP => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabx(Op::ForLoop, a, bx_of(w)),
true,
),
puc_op::FORPREP => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabx(Op::ForPrep, a, bx_of(w)),
true,
),
puc_op::TFORPREP => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabx(Op::TForPrep, a, bx_of(w)),
true,
),
puc_op::TFORCALL => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::TForCall, a, 0, c, false),
true,
),
puc_op::TFORLOOP => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabx(Op::TForLoop, a, bx_of(w)),
true,
),
puc_op::SETLIST => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::SetList, a, b, c, k),
true,
),
puc_op::CLOSURE => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabx(Op::Closure, a, bx_of(w)),
true,
),
puc_op::VARARG => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iabc(Op::Vararg, a, 0, c, false),
true,
),
puc_op::VARARGPREP => {
drop_inst(&mut puc_to_luna_pc);
}
puc_op::EXTRAARG => emit(
&mut code,
&mut puc_to_luna_pc,
&mut luna_to_puc_pc,
Inst::iax(Op::ExtraArg, w >> 7),
true,
),
other => {
return Err(format!(
"unknown PUC 5.4 opcode {other} at pc {puc_pc} (0x{w:08x})"
));
}
}
}
for (luna_pc, target_puc_pc) in jump_fixups {
let target_luna_pc = if target_puc_pc < 0 {
return Err(format!(
"PUC 5.4 JMP at luna pc {luna_pc} targets negative pc {target_puc_pc}"
));
} else if target_puc_pc as usize >= puc_to_luna_pc.len() {
code.len() as i64
} else {
let mut t = target_puc_pc as usize;
loop {
if t >= puc_to_luna_pc.len() {
break code.len() as i64;
}
if let Some(p) = puc_to_luna_pc[t] {
break p as i64;
}
t += 1;
}
};
let next_pc = luna_pc as i64 + 1;
let sj = (target_luna_pc - next_pc) as i32;
code[luna_pc] = Inst::isj(Op::Jmp, sj);
}
Ok(Translated {
code,
puc_to_luna_pc,
luna_to_puc_pc,
max_temp_bump,
})
}
#[cfg(test)]
#[allow(clippy::identity_op, clippy::erasing_op)]
mod tests {
use super::*;
#[test]
fn lineinfo_rle_decodes_absent_block() {
let li: Vec<i8> = vec![0, 0, 0, 0];
let abs: Vec<(u32, u32)> = vec![];
let lines = decode_lineinfo(&li, &abs, 10, 4).unwrap();
assert_eq!(lines, vec![10, 10, 10, 10]);
}
#[test]
fn lineinfo_rle_handles_positive_deltas() {
let li: Vec<i8> = vec![0, 1, 2, 0]; let abs: Vec<(u32, u32)> = vec![];
let lines = decode_lineinfo(&li, &abs, 10, 4).unwrap();
assert_eq!(lines, vec![10, 11, 13, 13]);
}
#[test]
fn lineinfo_rle_handles_abslineinfo_sentinel() {
let li: Vec<i8> = vec![0, -128, 1];
let abs: Vec<(u32, u32)> = vec![(1, 1000)];
let lines = decode_lineinfo(&li, &abs, 10, 3).unwrap();
assert_eq!(lines, vec![10, 1000, 1001]);
}
#[test]
fn lineinfo_rle_negative_delta() {
let li: Vec<i8> = vec![5, -3];
let abs: Vec<(u32, u32)> = vec![];
let lines = decode_lineinfo(&li, &abs, 10, 2).unwrap();
assert_eq!(lines, vec![15, 12]);
}
#[test]
fn pc_remap_skips_dropped() {
let map: Vec<Option<u32>> = vec![Some(0), Some(1), None, Some(2), Some(3)];
assert_eq!(remap_pc(&map, 2), 2);
assert_eq!(remap_pc(&map, 4), 3);
assert_eq!(remap_pc(&map, 5), 4);
}
#[test]
fn handcrafted_minimal_chunk_translates() {
let mut buf: Vec<u8> = Vec::new();
buf.extend_from_slice(b"\x1bLua\x54");
buf.extend_from_slice(HEADER_54_TAIL);
buf.push(0u8);
buf.push(0x80); buf.push(0x80);
buf.push(0x80);
buf.push(0);
buf.push(1);
buf.push(2);
buf.push(0x80 | 5);
let push_inst = |buf: &mut Vec<u8>, w: u32| buf.extend_from_slice(&w.to_le_bytes());
push_inst(&mut buf, puc_op::VARARGPREP as u32);
let sbx_payload = (42i32 + PUC_OFFSET_SBX) as u32;
let loadi = puc_op::LOADI as u32 | (0u32 << 7) | (sbx_payload << 15);
push_inst(&mut buf, loadi);
let addk = puc_op::ADDK as u32 | (1u32 << 7) | (0u32 << 16) | (0u32 << 24);
push_inst(&mut buf, addk);
let mmbin = puc_op::MMBIN as u32 | (1u32 << 7);
push_inst(&mut buf, mmbin);
let ret0 = puc_op::RETURN0 as u32 | (0u32 << 7);
push_inst(&mut buf, ret0);
buf.push(0x80 | 1);
buf.push(TAG_NUMINT);
buf.extend_from_slice(&7i64.to_le_bytes());
buf.push(0x80);
buf.push(0x80);
buf.push(0x80 | 5);
for _ in 0..5 {
buf.push(0i8 as u8);
}
buf.push(0x80);
buf.push(0x80);
buf.push(0x80);
let mut heap = Heap::new();
let proto = undump(&buf, &mut heap).expect("translator should succeed");
assert_eq!(proto.code.len(), 3, "expected 3 luna ops after drops");
assert_eq!(proto.code[0].op(), Op::LoadI);
assert_eq!(proto.code[0].sbx(), 42);
assert_eq!(proto.code[1].op(), Op::Add);
assert!(proto.code[1].k(), "ADDK should map to luna Add with k=1");
assert_eq!(proto.code[2].op(), Op::Return0);
assert_eq!(proto.consts.len(), 1);
assert!(matches!(proto.consts[0], Value::Int(7)));
assert_eq!(proto.max_stack, 2);
assert_eq!(proto.num_params, 0);
assert!(proto.is_vararg);
}
}