use super::super::reader::{Reader, read_puc_varint};
use crate::runtime::Value;
use crate::runtime::function::{JitProtoState, LocVar, Proto, UpvalDesc};
use crate::runtime::heap::{Gc, GcHeader, Heap, ObjTag};
use crate::vm::isa::{Inst, Op};
const LUA_SIGNATURE: &[u8] = b"\x1bLua";
const LUAC_VERSION: u8 = 0x55;
const LUAC_FORMAT: u8 = 0x00;
const LUAC_DATA: &[u8] = b"\x19\x93\r\n\x1a\n";
const LUAC_INT_EXPECTED: i64 = -0x5678;
const LUAC_INST_EXPECTED: u32 = 0x12345678;
const LUAC_NUM_EXPECTED: f64 = -370.5;
const LUA_VNIL: u8 = 0;
const LUA_VFALSE: u8 = 1;
const LUA_VTRUE: u8 = 1 | (1 << 4); const LUA_VNUMINT: u8 = 3;
const LUA_VNUMFLT: u8 = 3 | (1 << 4); const LUA_VSHRSTR: u8 = 4;
const LUA_VLNGSTR: u8 = 4 | (1 << 4);
const PF_VAHID: u8 = 1; const PF_VATAB: u8 = 2;
const ABSLINEINFO: i8 = -0x80;
#[allow(non_camel_case_types, dead_code, clippy::upper_case_acronyms)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[repr(u8)]
enum PucOp {
MOVE = 0,
LOADI = 1,
LOADF = 2,
LOADK = 3,
LOADKX = 4,
LOADFALSE = 5,
LFALSESKIP = 6,
LOADTRUE = 7,
LOADNIL = 8,
GETUPVAL = 9,
SETUPVAL = 10,
GETTABUP = 11,
GETTABLE = 12,
GETI = 13,
GETFIELD = 14,
SETTABUP = 15,
SETTABLE = 16,
SETI = 17,
SETFIELD = 18,
NEWTABLE = 19,
SELF = 20,
ADDI = 21,
ADDK = 22,
SUBK = 23,
MULK = 24,
MODK = 25,
POWK = 26,
DIVK = 27,
IDIVK = 28,
BANDK = 29,
BORK = 30,
BXORK = 31,
SHLI = 32,
SHRI = 33,
ADD = 34,
SUB = 35,
MUL = 36,
MOD = 37,
POW = 38,
DIV = 39,
IDIV = 40,
BAND = 41,
BOR = 42,
BXOR = 43,
SHL = 44,
SHR = 45,
MMBIN = 46,
MMBINI = 47,
MMBINK = 48,
UNM = 49,
BNOT = 50,
NOT = 51,
LEN = 52,
CONCAT = 53,
CLOSE = 54,
TBC = 55,
JMP = 56,
EQ = 57,
LT = 58,
LE = 59,
EQK = 60,
EQI = 61,
LTI = 62,
LEI = 63,
GTI = 64,
GEI = 65,
TEST = 66,
TESTSET = 67,
CALL = 68,
TAILCALL = 69,
RETURN = 70,
RETURN0 = 71,
RETURN1 = 72,
FORLOOP = 73,
FORPREP = 74,
TFORPREP = 75,
TFORCALL = 76,
TFORLOOP = 77,
SETLIST = 78,
CLOSURE = 79,
VARARG = 80,
GETVARG = 81,
ERRNNIL = 82,
VARARGPREP = 83,
EXTRAARG = 84,
}
impl PucOp {
fn from_byte(b: u8) -> Result<PucOp, String> {
if b <= PucOp::EXTRAARG as u8 {
Ok(unsafe { std::mem::transmute::<u8, PucOp>(b) })
} else {
Err(format!("unknown PUC 5.5 opcode 0x{b:02x}"))
}
}
}
const POS_A: u32 = 7;
const POS_K: u32 = 15;
const POS_B: u32 = 16;
const POS_C: u32 = 24;
const POS_VB: u32 = 16;
const POS_VC: u32 = 22;
const POS_BX: u32 = 15;
const POS_AX: u32 = 7;
const POS_SJ: u32 = 7;
const SIZE_BX: u32 = 17;
const SIZE_AX: u32 = 25;
const SIZE_SJ: u32 = 25;
const MAXARG_BX: u32 = (1 << SIZE_BX) - 1;
const MAXARG_AX: u32 = (1 << SIZE_AX) - 1;
const MAXARG_SJ: u32 = (1 << SIZE_SJ) - 1;
const OFFSET_SBX: i32 = (MAXARG_BX >> 1) as i32;
const OFFSET_SJ: i32 = (MAXARG_SJ >> 1) as i32;
const OFFSET_SC: i32 = 0xFF >> 1;
#[inline]
fn f_a(i: u32) -> u32 {
(i >> POS_A) & 0xFF
}
#[inline]
fn f_k(i: u32) -> bool {
((i >> POS_K) & 1) != 0
}
#[inline]
fn f_b(i: u32) -> u32 {
(i >> POS_B) & 0xFF
}
#[inline]
fn f_c(i: u32) -> u32 {
(i >> POS_C) & 0xFF
}
#[inline]
fn f_sb(i: u32) -> i32 {
f_b(i) as i32 - OFFSET_SC
}
#[inline]
fn f_sc(i: u32) -> i32 {
f_c(i) as i32 - OFFSET_SC
}
#[inline]
fn f_vb(i: u32) -> u32 {
(i >> POS_VB) & 0x3F
}
#[inline]
fn f_vc(i: u32) -> u32 {
(i >> POS_VC) & 0x3FF
}
#[inline]
fn f_bx(i: u32) -> u32 {
(i >> POS_BX) & MAXARG_BX
}
#[inline]
fn f_sbx(i: u32) -> i32 {
f_bx(i) as i32 - OFFSET_SBX
}
#[inline]
fn f_ax(i: u32) -> u32 {
(i >> POS_AX) & MAXARG_AX
}
#[inline]
fn f_sj(i: u32) -> i32 {
f_ax(i) as i32 - OFFSET_SJ
}
fn check_literal(r: &mut Reader, expected: &[u8], msg: &str) -> Result<(), String> {
let got = r.take(expected.len())?;
if got != expected {
return Err(format!("bad PUC 5.5 binary chunk: {msg}"));
}
Ok(())
}
fn load_byte(r: &mut Reader) -> Result<u8, String> {
r.u8()
}
fn load_unsigned(r: &mut Reader) -> Result<u64, String> {
read_puc_varint(r)
}
fn load_int(r: &mut Reader) -> Result<i32, String> {
let v = read_puc_varint(r)?;
if v > i32::MAX as u64 {
return Err("PUC 5.5 int overflows i32".to_string());
}
Ok(v as i32)
}
fn load_size(r: &mut Reader) -> Result<usize, String> {
let v = read_puc_varint(r)?;
if v > usize::MAX as u64 {
return Err("PUC 5.5 size overflows usize".to_string());
}
Ok(v as usize)
}
fn load_integer(r: &mut Reader) -> Result<i64, String> {
let cx = read_puc_varint(r)?;
let half = (cx >> 1) as i64;
if cx & 1 != 0 {
Ok(!half) } else {
Ok(half)
}
}
fn load_number(r: &mut Reader) -> Result<f64, String> {
let bytes: [u8; 8] = r.take(8)?.try_into().unwrap();
Ok(f64::from_le_bytes(bytes))
}
fn load_string(
r: &mut Reader,
heap: &mut Heap,
str_table: &mut Vec<Gc<crate::runtime::LuaStr>>,
) -> Result<Option<Gc<crate::runtime::LuaStr>>, String> {
let size = load_size(r)?;
if size == 0 {
let idx = load_unsigned(r)? as usize;
if idx == 0 {
return Ok(None);
}
if idx > str_table.len() {
return Err(format!(
"PUC 5.5 string back-reference idx {idx} out of range (table has {})",
str_table.len()
));
}
return Ok(Some(str_table[idx - 1]));
}
let real_len = size - 1;
let raw = r.take(real_len + 1)?;
let s = heap.intern(&raw[..real_len]);
str_table.push(s);
Ok(Some(s))
}
fn check_header(r: &mut Reader) -> Result<(), String> {
check_literal(r, LUA_SIGNATURE, "signature")?;
let ver = load_byte(r)?;
if ver != LUAC_VERSION {
return Err(format!(
"PUC 5.5 version byte mismatch: expected 0x{LUAC_VERSION:02x}, got 0x{ver:02x}"
));
}
let fmt = load_byte(r)?;
if fmt != LUAC_FORMAT {
return Err(format!(
"PUC 5.5 format byte mismatch: expected 0x{LUAC_FORMAT:02x}, got 0x{fmt:02x}"
));
}
check_literal(r, LUAC_DATA, "LUAC_DATA")?;
let int_size = load_byte(r)?;
if int_size != 4 {
return Err(format!("PUC 5.5 expects sizeof(int)=4 (got {int_size})"));
}
let int_val = i32::from_le_bytes(r.take(4)?.try_into().unwrap());
if int_val as i64 != LUAC_INT_EXPECTED {
return Err(format!(
"PUC 5.5 LUAC_INT mismatch: expected {LUAC_INT_EXPECTED}, got {int_val} \
(endianness / `int` size mismatch — luna only accepts LE 32-bit `int`)"
));
}
let inst_size = load_byte(r)?;
if inst_size != 4 {
return Err(format!(
"PUC 5.5 expects sizeof(Instruction)=4 (got {inst_size})"
));
}
let inst_val = u32::from_le_bytes(r.take(4)?.try_into().unwrap());
if inst_val != LUAC_INST_EXPECTED {
return Err(format!(
"PUC 5.5 LUAC_INST mismatch: expected 0x{LUAC_INST_EXPECTED:08x}, got 0x{inst_val:08x}"
));
}
let lua_int_size = load_byte(r)?;
if lua_int_size != 8 {
return Err(format!(
"PUC 5.5 expects sizeof(lua_Integer)=8 (got {lua_int_size})"
));
}
let lua_int_val = i64::from_le_bytes(r.take(8)?.try_into().unwrap());
if lua_int_val != LUAC_INT_EXPECTED {
return Err(format!(
"PUC 5.5 lua_Integer LUAC_INT mismatch: expected {LUAC_INT_EXPECTED}, got {lua_int_val}"
));
}
let lua_num_size = load_byte(r)?;
if lua_num_size != 8 {
return Err(format!(
"PUC 5.5 expects sizeof(lua_Number)=8 (got {lua_num_size})"
));
}
let lua_num_val = f64::from_le_bytes(r.take(8)?.try_into().unwrap());
if lua_num_val != LUAC_NUM_EXPECTED {
return Err(format!(
"PUC 5.5 LUAC_NUM mismatch: expected {LUAC_NUM_EXPECTED}, got {lua_num_val}"
));
}
Ok(())
}
fn load_align(r: &mut Reader, align: usize, dump_start: usize) -> Result<(), String> {
let offset = r.pos() - dump_start;
let padding = (align - (offset % align)) % align;
if padding > 0 {
r.take(padding)?;
}
Ok(())
}
pub(super) fn undump_puc_55(bytes: &[u8], heap: &mut Heap) -> Result<Gc<Proto>, String> {
let mut r = Reader::at(bytes, 0);
let dump_start = r.pos();
check_header(&mut r)?;
let nupvalues = load_byte(&mut r)?;
let mut str_table: Vec<Gc<crate::runtime::LuaStr>> = Vec::new();
let proto = load_function(&mut r, heap, &mut str_table, None, dump_start)?;
let proto_nupvals = proto.upvals.len();
if proto_nupvals != nupvalues as usize {
return Err(format!(
"PUC 5.5 closure nupvalues={nupvalues} disagrees with proto upvalues={proto_nupvals}"
));
}
Ok(proto)
}
fn load_function(
r: &mut Reader,
heap: &mut Heap,
str_table: &mut Vec<Gc<crate::runtime::LuaStr>>,
parent_source: Option<Gc<crate::runtime::LuaStr>>,
dump_start: usize,
) -> Result<Gc<Proto>, String> {
let line_defined = load_int(r)? as u32;
let last_line_defined = load_int(r)? as u32;
let num_params = load_byte(r)?;
let raw_flag = load_byte(r)?;
let is_vararg = (raw_flag & (PF_VAHID | PF_VATAB)) != 0;
let has_vararg_table_pseudo = (raw_flag & PF_VATAB) != 0;
let max_stack = load_byte(r)?;
let code_n = load_int(r)? as usize;
load_align(r, 4, dump_start)?;
let mut raw_code = Vec::with_capacity(code_n);
for _ in 0..code_n {
let bytes: [u8; 4] = r.take(4)?.try_into().unwrap();
raw_code.push(u32::from_le_bytes(bytes));
}
let k_n = load_int(r)? as usize;
let mut consts: Vec<Value> = Vec::with_capacity(k_n);
for _ in 0..k_n {
let tag = load_byte(r)?;
let v = match tag {
LUA_VNIL => Value::Nil,
LUA_VFALSE => Value::Bool(false),
LUA_VTRUE => Value::Bool(true),
LUA_VNUMINT => Value::Int(load_integer(r)?),
LUA_VNUMFLT => Value::Float(load_number(r)?),
LUA_VSHRSTR | LUA_VLNGSTR => {
let s = load_string(r, heap, str_table)?
.ok_or_else(|| "PUC 5.5 nil string constant".to_string())?;
Value::Str(s)
}
other => {
return Err(format!("PUC 5.5 unknown constant tag 0x{other:02x}"));
}
};
consts.push(v);
}
let upv_n = load_int(r)? as usize;
let mut upvals: Vec<UpvalDesc> = Vec::with_capacity(upv_n);
for _ in 0..upv_n {
let in_stack = load_byte(r)? != 0;
let idx = load_byte(r)?;
let kind = load_byte(r)?;
let read_only = kind == 1 || kind == 3;
upvals.push(UpvalDesc {
in_stack,
index: idx,
name: String::new().into(),
read_only,
});
}
let proto_n = load_int(r)? as usize;
let mut protos: Vec<Gc<Proto>> = Vec::with_capacity(proto_n);
for _ in 0..proto_n {
protos.push(load_function(
r,
heap,
str_table,
parent_source,
dump_start,
)?);
}
let source = load_string(r, heap, str_table)?
.or(parent_source)
.unwrap_or_else(|| heap.intern(b""));
let li_n = load_int(r)? as usize;
let mut lineinfo_raw: Vec<i8> = Vec::with_capacity(li_n);
for _ in 0..li_n {
lineinfo_raw.push(load_byte(r)? as i8);
}
let abs_n = load_int(r)? as usize;
let mut abslineinfo: Vec<(u32, u32)> = Vec::with_capacity(abs_n);
if abs_n > 0 {
load_align(r, 4, dump_start)?;
for _ in 0..abs_n {
let pc = u32::from_le_bytes(r.take(4)?.try_into().unwrap());
let line = u32::from_le_bytes(r.take(4)?.try_into().unwrap());
abslineinfo.push((pc, line));
}
}
let lines = expand_lineinfo(&lineinfo_raw, &abslineinfo, line_defined);
let lv_n = load_int(r)? as usize;
let mut locvars: Vec<LocVar> = Vec::with_capacity(lv_n);
for _ in 0..lv_n {
let name = load_string(r, heap, str_table)?
.map(|s| s.as_bytes().to_vec())
.unwrap_or_default();
let start_pc = load_int(r)? as u32;
let end_pc = load_int(r)? as u32;
locvars.push(LocVar {
name: String::from_utf8_lossy(&name).into_owned().into(),
reg: 0, start_pc,
end_pc,
});
}
let unames_n = load_int(r)? as usize;
let unames_n_actual = if unames_n != 0 { upv_n } else { 0 };
for i in 0..unames_n_actual {
let nm = load_string(r, heap, str_table)?
.map(|s| s.as_bytes().to_vec())
.unwrap_or_default();
if i < upvals.len() {
upvals[i].name = String::from_utf8_lossy(&nm).into_owned().into();
}
}
let translated = translate_code(&raw_code)?;
let lines_remapped: Vec<u32> = translated
.luna_to_puc_pc
.iter()
.map(|&puc_pc| lines.get(puc_pc).copied().unwrap_or(0))
.collect();
let locvars_remapped: Vec<LocVar> = locvars
.into_iter()
.map(|lv| LocVar {
start_pc: remap_pc(&translated.puc_to_luna_pc, lv.start_pc),
end_pc: remap_pc(&translated.puc_to_luna_pc, lv.end_pc),
..lv
})
.collect();
let max_stack = max_stack.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,
has_compat_vararg_arg: false,
max_stack,
lines: lines_remapped.into_boxed_slice(),
source,
line_defined,
last_line_defined,
locvars: locvars_remapped.into_boxed_slice(),
cache: std::cell::Cell::new(None),
jit: std::cell::Cell::new(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: crate::jit::send_compat::TRefLock::new(Vec::new()),
}))
}
fn expand_lineinfo(raw: &[i8], abs: &[(u32, u32)], line_defined: u32) -> Vec<u32> {
if raw.is_empty() {
return Vec::new();
}
let mut out = Vec::with_capacity(raw.len());
let mut line = line_defined as i32;
let mut abs_iter = abs.iter();
let mut next_abs = abs_iter.next();
for (pc, &delta) in raw.iter().enumerate() {
if delta == ABSLINEINFO {
if let Some(&(_apc, aline)) = next_abs {
line = aline as i32;
next_abs = abs_iter.next();
} else {
}
let _ = pc;
} else {
line += delta as i32;
}
out.push(line.max(0) as u32);
}
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(raw: &[u32]) -> Result<Translated, String> {
let mut code: Vec<Inst> = Vec::with_capacity(raw.len());
let mut puc_to_luna_pc: Vec<Option<u32>> = Vec::with_capacity(raw.len());
let mut luna_to_puc_pc: Vec<usize> = Vec::with_capacity(raw.len());
let mut max_temp_bump: u8 = 0;
let mut jump_fixups: Vec<(usize, i64)> = Vec::new();
for (puc_pc, &word) in raw.iter().enumerate() {
let op_byte = (word & 0x7F) as u8;
let puc_op = PucOp::from_byte(op_byte).map_err(|e| format!("pc {puc_pc}: {e}"))?;
puc_to_luna_pc.push(Some(code.len() as u32));
match puc_op {
PucOp::ADDI => {
let a = f_a(word);
let b = f_b(word);
let sc = f_sc(word);
let tmp = a.max(b) + 1;
let pair = super::lower_i_imm(Op::Add, a, b, sc, tmp, &mut max_temp_bump)?;
luna_to_puc_pc.push(puc_pc);
code.push(pair[0]);
luna_to_puc_pc.push(puc_pc);
code.push(pair[1]);
}
PucOp::SHRI => {
let a = f_a(word);
let b = f_b(word);
let sc = f_sc(word);
let tmp = a.max(b) + 1;
let pair = super::lower_i_imm(Op::Shr, a, b, sc, tmp, &mut max_temp_bump)?;
luna_to_puc_pc.push(puc_pc);
code.push(pair[0]);
luna_to_puc_pc.push(puc_pc);
code.push(pair[1]);
}
PucOp::SHLI => {
let a = f_a(word);
let b = f_b(word);
let sc = f_sc(word);
let tmp = a.max(b) + 1;
if tmp > 0xFF {
return Err(format!(
"PUC 5.5 SHLI lowering at pc {puc_pc}: temp register {tmp} exceeds 255"
));
}
max_temp_bump = max_temp_bump.max(tmp as u8 + 1);
luna_to_puc_pc.push(puc_pc);
code.push(Inst::iasbx(Op::LoadI, tmp, sc));
luna_to_puc_pc.push(puc_pc);
code.push(Inst::iabc(Op::Shl, a, tmp, b, false));
}
PucOp::EQI => {
let a = f_a(word);
let sb = f_sb(word);
let k = f_k(word);
let tmp = a + 1;
if tmp > 0xFF {
return Err(format!(
"PUC 5.5 EQI lowering at pc {puc_pc}: temp register {tmp} exceeds 255"
));
}
max_temp_bump = max_temp_bump.max(tmp as u8 + 1);
luna_to_puc_pc.push(puc_pc);
code.push(Inst::iasbx(Op::LoadI, tmp, sb));
luna_to_puc_pc.push(puc_pc);
code.push(Inst::iabc(Op::Eq, a, tmp, 0, k));
}
PucOp::LTI => {
let a = f_a(word);
let sb = f_sb(word);
let k = f_k(word);
let tmp = a + 1;
if tmp > 0xFF {
return Err(format!(
"PUC 5.5 LTI lowering at pc {puc_pc}: temp register {tmp} exceeds 255"
));
}
max_temp_bump = max_temp_bump.max(tmp as u8 + 1);
luna_to_puc_pc.push(puc_pc);
code.push(Inst::iasbx(Op::LoadI, tmp, sb));
luna_to_puc_pc.push(puc_pc);
code.push(Inst::iabc(Op::Lt, a, tmp, 0, k));
}
PucOp::LEI => {
let a = f_a(word);
let sb = f_sb(word);
let k = f_k(word);
let tmp = a + 1;
if tmp > 0xFF {
return Err(format!(
"PUC 5.5 LEI lowering at pc {puc_pc}: temp register {tmp} exceeds 255"
));
}
max_temp_bump = max_temp_bump.max(tmp as u8 + 1);
luna_to_puc_pc.push(puc_pc);
code.push(Inst::iasbx(Op::LoadI, tmp, sb));
luna_to_puc_pc.push(puc_pc);
code.push(Inst::iabc(Op::Le, a, tmp, 0, k));
}
PucOp::GTI => {
let a = f_a(word);
let sb = f_sb(word);
let k = f_k(word);
let tmp = a + 1;
if tmp > 0xFF {
return Err(format!(
"PUC 5.5 GTI lowering at pc {puc_pc}: temp register {tmp} exceeds 255"
));
}
max_temp_bump = max_temp_bump.max(tmp as u8 + 1);
luna_to_puc_pc.push(puc_pc);
code.push(Inst::iasbx(Op::LoadI, tmp, sb));
luna_to_puc_pc.push(puc_pc);
code.push(Inst::iabc(Op::Lt, tmp, a, 0, k));
}
PucOp::GEI => {
let a = f_a(word);
let sb = f_sb(word);
let k = f_k(word);
let tmp = a + 1;
if tmp > 0xFF {
return Err(format!(
"PUC 5.5 GEI lowering at pc {puc_pc}: temp register {tmp} exceeds 255"
));
}
max_temp_bump = max_temp_bump.max(tmp as u8 + 1);
luna_to_puc_pc.push(puc_pc);
code.push(Inst::iasbx(Op::LoadI, tmp, sb));
luna_to_puc_pc.push(puc_pc);
code.push(Inst::iabc(Op::Le, tmp, a, 0, k));
}
PucOp::JMP => {
let sj = f_sj(word);
let next_puc_pc = puc_pc as i64 + 1;
let target_puc_pc = next_puc_pc + sj as i64;
let luna_pc = code.len();
jump_fixups.push((luna_pc, target_puc_pc));
luna_to_puc_pc.push(puc_pc);
code.push(Inst::isj(Op::Jmp, 0)); }
_ => {
luna_to_puc_pc.push(puc_pc);
let inst = translate_one(puc_op, word)?;
code.push(inst);
}
}
}
for (luna_pc, target_puc_pc) in jump_fixups {
let target_luna_pc: i64 = if target_puc_pc < 0 {
return Err(format!(
"PUC 5.5 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,
})
}
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 translate_one(op: PucOp, word: u32) -> Result<Inst, String> {
use Op as L;
let a = f_a(word);
let b = f_b(word);
let c = f_c(word);
let k = f_k(word);
let bx = f_bx(word);
let sbx = f_sbx(word);
let sj = f_sj(word);
let ax = f_ax(word);
let vb = f_vb(word);
let vc = f_vc(word);
let sb = f_sb(word);
let sc = f_sc(word);
let nop = |reg: u32| Inst::iabc(L::Move, reg, reg, 0, false);
Ok(match op {
PucOp::MOVE => Inst::iabc(L::Move, a, b, c, k),
PucOp::LOADFALSE => Inst::iabc(L::LoadFalse, a, 0, 0, false),
PucOp::LFALSESKIP => Inst::iabc(L::LFalseSkip, a, 0, 0, false),
PucOp::LOADTRUE => Inst::iabc(L::LoadTrue, a, 0, 0, false),
PucOp::LOADNIL => Inst::iabc(L::LoadNil, a, b, 0, false),
PucOp::GETUPVAL => Inst::iabc(L::GetUpval, a, b, 0, false),
PucOp::SETUPVAL => Inst::iabc(L::SetUpval, a, b, 0, false),
PucOp::GETTABUP => Inst::iabc(L::GetTabUp, a, b, c, false),
PucOp::GETTABLE => Inst::iabc(L::GetTable, a, b, c, false),
PucOp::GETI => Inst::iabc(L::GetI, a, b, c, false),
PucOp::GETFIELD => Inst::iabc(L::GetField, a, b, c, false),
PucOp::SETTABUP => Inst::iabc(L::SetTabUp, a, b, c, k),
PucOp::SETTABLE => Inst::iabc(L::SetTable, a, b, c, k),
PucOp::SETI => Inst::iabc(L::SetI, a, b, c, k),
PucOp::SETFIELD => Inst::iabc(L::SetField, a, b, c, k),
PucOp::SELF => Inst::iabc(L::SelfOp, a, b, c, k),
PucOp::ADD => Inst::iabc(L::Add, a, b, c, false),
PucOp::SUB => Inst::iabc(L::Sub, a, b, c, false),
PucOp::MUL => Inst::iabc(L::Mul, a, b, c, false),
PucOp::MOD => Inst::iabc(L::Mod, a, b, c, false),
PucOp::POW => Inst::iabc(L::Pow, a, b, c, false),
PucOp::DIV => Inst::iabc(L::Div, a, b, c, false),
PucOp::IDIV => Inst::iabc(L::IDiv, a, b, c, false),
PucOp::BAND => Inst::iabc(L::BAnd, a, b, c, false),
PucOp::BOR => Inst::iabc(L::BOr, a, b, c, false),
PucOp::BXOR => Inst::iabc(L::BXor, a, b, c, false),
PucOp::SHL => Inst::iabc(L::Shl, a, b, c, false),
PucOp::SHR => Inst::iabc(L::Shr, a, b, c, false),
PucOp::UNM => Inst::iabc(L::Unm, a, b, 0, false),
PucOp::BNOT => Inst::iabc(L::BNot, a, b, 0, false),
PucOp::NOT => Inst::iabc(L::Not, a, b, 0, false),
PucOp::LEN => Inst::iabc(L::Len, a, b, 0, false),
PucOp::CONCAT => Inst::iabc(L::Concat, a, b, 0, false),
PucOp::CLOSE => Inst::iabc(L::Close, a, 0, 0, false),
PucOp::TBC => Inst::iabc(L::Tbc, a, 0, 0, false),
PucOp::EQ => Inst::iabc(L::Eq, a, b, c, k),
PucOp::LT => Inst::iabc(L::Lt, a, b, c, k),
PucOp::LE => Inst::iabc(L::Le, a, b, c, k),
PucOp::EQK => Inst::iabc(L::EqK, a, b, c, k),
PucOp::TEST => Inst::iabc(L::Test, a, 0, c, k),
PucOp::TESTSET => Inst::iabc(L::TestSet, a, b, c, k),
PucOp::CALL => Inst::iabc(L::Call, a, b, c, k),
PucOp::TAILCALL => Inst::iabc(L::TailCall, a, b, c, k),
PucOp::RETURN => Inst::iabc(L::Return, a, b, c, k),
PucOp::RETURN0 => Inst::iabc(L::Return0, 0, 0, 0, false),
PucOp::RETURN1 => Inst::iabc(L::Return1, a, 0, 0, false),
PucOp::TFORCALL => Inst::iabc(L::TForCall, a, 0, c, false),
PucOp::VARARG => Inst::iabc(L::Vararg, a, b, c, k),
PucOp::GETVARG => Inst::iabc(L::VargIdx, a, b, c, false),
PucOp::LOADI => Inst::iasbx(L::LoadI, a, sbx),
PucOp::LOADF => Inst::iasbx(L::LoadF, a, sbx),
PucOp::LOADK => Inst::iabx(L::LoadK, a, bx),
PucOp::LOADKX => Inst::iabx(L::LoadKx, a, 0),
PucOp::FORLOOP => Inst::iabx(L::ForLoop, a, bx),
PucOp::FORPREP => Inst::iabx(L::ForPrep, a, bx),
PucOp::TFORPREP => Inst::iabx(L::TForPrep, a, bx),
PucOp::TFORLOOP => Inst::iabx(L::TForLoop, a, bx),
PucOp::CLOSURE => Inst::iabx(L::Closure, a, bx),
PucOp::ERRNNIL => Inst::iabx(L::ErrNNil, a, bx),
PucOp::JMP => Inst::isj(L::Jmp, sj),
PucOp::EXTRAARG => Inst::iax(L::ExtraArg, ax),
PucOp::ADDK => Inst::iabc(L::Add, a, b, c, true),
PucOp::SUBK => Inst::iabc(L::Sub, a, b, c, true),
PucOp::MULK => Inst::iabc(L::Mul, a, b, c, true),
PucOp::MODK => Inst::iabc(L::Mod, a, b, c, true),
PucOp::POWK => Inst::iabc(L::Pow, a, b, c, true),
PucOp::DIVK => Inst::iabc(L::Div, a, b, c, true),
PucOp::IDIVK => Inst::iabc(L::IDiv, a, b, c, true),
PucOp::BANDK => Inst::iabc(L::BAnd, a, b, c, true),
PucOp::BORK => Inst::iabc(L::BOr, a, b, c, true),
PucOp::BXORK => Inst::iabc(L::BXor, a, b, c, true),
PucOp::ADDI => {
return Err(format!(
"PUC 5.5 OP_ADDI(A={a}, B={b}, sC={sc}): I-imm lowering is \
handled in translate_code (Wave 2); translate_one is \
single-Inst only"
));
}
PucOp::SHLI => {
return Err(format!(
"PUC 5.5 OP_SHLI(A={a}, B={b}, sC={sc}): I-imm lowering is \
handled in translate_code (Wave 2); translate_one is \
single-Inst only"
));
}
PucOp::SHRI => {
return Err(format!(
"PUC 5.5 OP_SHRI(A={a}, B={b}, sC={sc}): I-imm lowering is \
handled in translate_code (Wave 2); translate_one is \
single-Inst only"
));
}
PucOp::EQI | PucOp::LTI | PucOp::LEI | PucOp::GTI | PucOp::GEI => {
return Err(format!(
"PUC 5.5 {op:?}(A={a}, sB={sb}, k={k}): cmp-imm lowering is \
handled in translate_code (Wave 2); translate_one is \
single-Inst only"
));
}
PucOp::MMBIN | PucOp::MMBINI | PucOp::MMBINK => nop(a),
PucOp::VARARGPREP => nop(0),
PucOp::NEWTABLE => {
let b_clamped = if vb > 0xFF { 0 } else { vb };
let c_clamped = if vc > 0xFF { 0 } else { vc };
Inst::iabc(L::NewTable, a, b_clamped, c_clamped, k)
}
PucOp::SETLIST => {
let b_clamped = if vb > 0xFF { 0xFF } else { vb };
let c_clamped = if vc > 0xFF { 0 } else { vc };
Inst::iabc(L::SetList, a, b_clamped, c_clamped, k)
}
})
}
#[cfg(test)]
#[allow(clippy::identity_op)] mod tests {
use super::*;
fn encode_varint(mut x: u64) -> Vec<u8> {
let mut buf: Vec<u8> = Vec::new();
buf.push((x & 0x7f) as u8); x >>= 7;
while x != 0 {
buf.push(((x & 0x7f) as u8) | 0x80);
x >>= 7;
}
buf.reverse();
buf
}
fn header() -> Vec<u8> {
let mut h = Vec::new();
h.extend_from_slice(LUA_SIGNATURE);
h.push(LUAC_VERSION);
h.push(LUAC_FORMAT);
h.extend_from_slice(LUAC_DATA);
h.push(4);
h.extend_from_slice(&(LUAC_INT_EXPECTED as i32).to_le_bytes());
h.push(4);
h.extend_from_slice(&LUAC_INST_EXPECTED.to_le_bytes());
h.push(8);
h.extend_from_slice(&LUAC_INT_EXPECTED.to_le_bytes());
h.push(8);
h.extend_from_slice(&LUAC_NUM_EXPECTED.to_le_bytes());
h
}
#[test]
fn varint_roundtrip() {
for v in [
0u64,
1,
127,
128,
200,
16383,
16384,
1_000_000,
u32::MAX as u64,
] {
let encoded = encode_varint(v);
let mut r = Reader::at(&encoded, 0);
let got = read_puc_varint(&mut r).unwrap();
assert_eq!(got, v, "roundtrip failed for {v}");
assert_eq!(r.pos(), encoded.len(), "didn't consume all bytes for {v}");
}
}
#[test]
fn header_validates() {
let h = header();
let mut r = Reader::at(&h, 0);
check_header(&mut r).unwrap();
assert_eq!(r.pos(), h.len());
}
#[test]
fn header_rejects_wrong_version() {
let mut h = header();
h[4] = 0x54; let mut r = Reader::at(&h, 0);
let err = check_header(&mut r).unwrap_err();
assert!(err.contains("version"), "got: {err}");
}
#[test]
fn translates_simple_arith() {
let word = (PucOp::ADD as u32) | (0u32 << 7) | (1u32 << 16) | (2u32 << 24);
let inst = translate_one(PucOp::ADD, word).unwrap();
assert_eq!(inst.op(), Op::Add);
assert_eq!(inst.a(), 0);
assert_eq!(inst.b(), 1);
assert_eq!(inst.c(), 2);
assert!(!inst.k());
}
#[test]
fn translates_addk_to_k_bit_form() {
let word = (PucOp::ADDK as u32) | (3u32 << 7) | (1u32 << 16) | (4u32 << 24);
let inst = translate_one(PucOp::ADDK, word).unwrap();
assert_eq!(inst.op(), Op::Add);
assert!(inst.k(), "ADDK must set k=1");
assert_eq!(inst.a(), 3);
assert_eq!(inst.b(), 1);
assert_eq!(inst.c(), 4);
}
#[test]
fn drops_varargprep_to_nop() {
let word = PucOp::VARARGPREP as u32;
let inst = translate_one(PucOp::VARARGPREP, word).unwrap();
assert_eq!(inst.op(), Op::Move);
assert_eq!(inst.a(), 0);
assert_eq!(inst.b(), 0);
}
#[test]
fn drops_mmbin_to_nop() {
let word = (PucOp::MMBIN as u32) | (2u32 << 7);
let inst = translate_one(PucOp::MMBIN, word).unwrap();
assert_eq!(inst.op(), Op::Move);
assert_eq!(inst.a(), 2);
assert_eq!(inst.b(), 2);
}
#[test]
fn jmp_sj_roundtrip() {
let sj_encoded = (-5i32 + OFFSET_SJ) as u32;
let word = (PucOp::JMP as u32) | (sj_encoded << POS_AX);
let inst = translate_one(PucOp::JMP, word).unwrap();
assert_eq!(inst.op(), Op::Jmp);
assert_eq!(inst.sj(), -5);
}
#[test]
fn loadi_sbx_roundtrip() {
let sbx_encoded = (42i32 + OFFSET_SBX) as u32;
let word = (PucOp::LOADI as u32) | (0u32 << 7) | (sbx_encoded << POS_BX);
let inst = translate_one(PucOp::LOADI, word).unwrap();
assert_eq!(inst.op(), Op::LoadI);
assert_eq!(inst.sbx(), 42);
}
#[test]
fn translate_eqi_via_inline_cmp() {
let sb_enc = (10 + OFFSET_SC) as u32;
let word = (PucOp::EQI as u32) | (2u32 << 7) | (1u32 << POS_K) | (sb_enc << 16);
let translated = translate_code(&[word]).unwrap();
assert_eq!(translated.code.len(), 2);
assert_eq!(translated.code[0].op(), Op::LoadI);
assert_eq!(translated.code[0].a(), 3, "tmp = a + 1 = 3");
assert_eq!(translated.code[0].sbx(), 10);
assert_eq!(translated.code[1].op(), Op::Eq);
assert_eq!(translated.code[1].a(), 2);
assert_eq!(translated.code[1].b(), 3);
assert!(translated.code[1].k(), "k must propagate to Eq");
assert_eq!(translated.max_temp_bump, 4);
}
#[test]
fn translate_lti_via_inline_cmp() {
let sb_enc = (-3 + OFFSET_SC) as u32;
let word = (PucOp::LTI as u32) | (0u32 << 7) | (sb_enc << 16);
let translated = translate_code(&[word]).unwrap();
assert_eq!(translated.code.len(), 2);
assert_eq!(translated.code[0].op(), Op::LoadI);
assert_eq!(translated.code[0].a(), 1, "tmp = a + 1 = 1");
assert_eq!(translated.code[0].sbx(), -3, "negative immediate roundtrip");
assert_eq!(translated.code[1].op(), Op::Lt);
assert_eq!(translated.code[1].a(), 0);
assert_eq!(translated.code[1].b(), 1);
assert!(!translated.code[1].k());
}
#[test]
fn translate_lei_via_inline_cmp() {
let sb_enc = (7 + OFFSET_SC) as u32;
let word = (PucOp::LEI as u32) | (4u32 << 7) | (1u32 << POS_K) | (sb_enc << 16);
let translated = translate_code(&[word]).unwrap();
assert_eq!(translated.code[0].op(), Op::LoadI);
assert_eq!(translated.code[0].a(), 5);
assert_eq!(translated.code[0].sbx(), 7);
assert_eq!(translated.code[1].op(), Op::Le);
assert_eq!(translated.code[1].a(), 4);
assert_eq!(translated.code[1].b(), 5);
assert!(translated.code[1].k());
}
#[test]
fn translate_gti_via_inline_cmp_swap() {
let sb_enc = (8 + OFFSET_SC) as u32;
let word = (PucOp::GTI as u32) | (2u32 << 7) | (1u32 << POS_K) | (sb_enc << 16);
let translated = translate_code(&[word]).unwrap();
assert_eq!(translated.code[0].op(), Op::LoadI);
assert_eq!(translated.code[0].a(), 3);
assert_eq!(translated.code[0].sbx(), 8);
assert_eq!(
translated.code[1].op(),
Op::Lt,
"GTI lowers to Lt with operand swap"
);
assert_eq!(
translated.code[1].a(),
3,
"Lt's A is the tmp (sB on the left)"
);
assert_eq!(
translated.code[1].b(),
2,
"Lt's B is the original A (sB on the right)"
);
assert!(translated.code[1].k());
}
#[test]
fn translate_gei_via_inline_cmp_swap() {
let sb_enc = (4 + OFFSET_SC) as u32;
let word = (PucOp::GEI as u32) | (2u32 << 7) | (sb_enc << 16);
let translated = translate_code(&[word]).unwrap();
assert_eq!(translated.code[0].op(), Op::LoadI);
assert_eq!(translated.code[0].a(), 3);
assert_eq!(translated.code[0].sbx(), 4);
assert_eq!(
translated.code[1].op(),
Op::Le,
"GEI lowers to Le with operand swap"
);
assert_eq!(translated.code[1].a(), 3);
assert_eq!(translated.code[1].b(), 2);
assert!(!translated.code[1].k());
}
#[test]
fn translate_shli_inline_swap() {
let sc_enc = (5 + OFFSET_SC) as u32;
let word = (PucOp::SHLI as u32) | (1u32 << 7) | (3u32 << 16) | (sc_enc << 24);
let translated = translate_code(&[word]).unwrap();
assert_eq!(translated.code.len(), 2);
assert_eq!(translated.code[0].op(), Op::LoadI);
assert_eq!(translated.code[0].sbx(), 5);
let tmp = translated.code[0].a();
assert_eq!(tmp, 4, "tmp must be max(a, b) + 1 = max(1, 3) + 1 = 4");
assert_eq!(translated.code[1].op(), Op::Shl);
assert_eq!(translated.code[1].a(), 1);
assert_eq!(translated.code[1].b(), tmp, "B (left operand) is the tmp");
assert_eq!(
translated.code[1].c(),
3,
"C (right operand) is the source reg"
);
assert_eq!(translated.max_temp_bump, 5);
}
#[test]
fn translate_shri_via_lower_i_imm() {
let sc_enc = (3 + OFFSET_SC) as u32;
let word = (PucOp::SHRI as u32) | (2u32 << 7) | (4u32 << 16) | (sc_enc << 24);
let translated = translate_code(&[word]).unwrap();
assert_eq!(translated.code.len(), 2);
assert_eq!(translated.code[0].op(), Op::LoadI);
assert_eq!(translated.code[0].sbx(), 3);
let tmp = translated.code[0].a();
assert_eq!(tmp, 5, "tmp must be max(a, b) + 1 = max(2, 4) + 1 = 5");
assert_eq!(translated.code[1].op(), Op::Shr);
assert_eq!(translated.code[1].a(), 2);
assert_eq!(translated.code[1].b(), 4);
assert_eq!(translated.code[1].c(), tmp);
assert!(!translated.code[1].k());
assert_eq!(translated.max_temp_bump, 6);
}
#[test]
fn translate_addi_via_lower_i_imm() {
let sc_enc = (42 + OFFSET_SC) as u32;
let word = (PucOp::ADDI as u32) | (5u32 << 7) | (3u32 << 16) | (sc_enc << 24);
let translated = translate_code(&[word]).unwrap();
assert_eq!(translated.code.len(), 2);
assert_eq!(translated.code[0].op(), Op::LoadI);
assert_eq!(translated.code[0].sbx(), 42);
let tmp = translated.code[0].a();
assert_eq!(tmp, 6, "tmp must be max(a, b) + 1 = max(5, 3) + 1 = 6");
assert_eq!(translated.code[1].op(), Op::Add);
assert_eq!(translated.code[1].a(), 5);
assert_eq!(translated.code[1].b(), 3);
assert_eq!(translated.code[1].c(), tmp);
assert!(!translated.code[1].k(), "I-imm arith never sets the k bit");
assert_eq!(translated.max_temp_bump, 7);
assert_eq!(translated.puc_to_luna_pc, vec![Some(0)]);
assert_eq!(translated.luna_to_puc_pc, vec![0, 0]);
}
}