use super::lower::{self, Lowered, RawLocVar, RawProto};
use super::modern::{self, Dialect, Kind};
use crate::runtime::Value;
use crate::runtime::function::{Proto, UpvalDesc};
use crate::runtime::heap::{Gc, Heap};
use crate::runtime::string::LuaStr;
use crate::vm::dump::error::Bad;
use crate::vm::dump::header;
use crate::vm::dump::reader::{Reader, read_puc_varint};
use crate::vm::isa::Op;
const DIALECT: &str = "PUC 5.5";
pub(in crate::vm::dump) const HEADER: &[u8] = &[
0x1b, b'L', b'u', b'a', 0x55, 0, 0x19, 0x93, b'\r', b'\n', 0x1a, b'\n', 4, 0x88, 0xA9, 0xFF, 0xFF, 4, 0x78, 0x56, 0x34, 0x12, 8, 0x88, 0xA9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 8, 0, 0, 0, 0, 0, 0x28, 0x77, 0xC0, ];
const PF_VAHID: u8 = 1;
const PF_VATAB: u8 = 2;
pub(in crate::vm::dump) const OPS: &[Kind] = &[
Kind::Move,
Kind::LoadI,
Kind::LoadF,
Kind::LoadK,
Kind::LoadKx,
Kind::LoadFalse,
Kind::LFalseSkip,
Kind::LoadTrue,
Kind::LoadNil,
Kind::GetUpval,
Kind::SetUpval,
Kind::GetTabUp,
Kind::GetTable,
Kind::GetI,
Kind::GetField,
Kind::SetTabUp,
Kind::SetTable,
Kind::SetI,
Kind::SetField,
Kind::NewTable,
Kind::SelfOp,
Kind::ArithI, Kind::ArithK, Kind::ArithK, Kind::ArithK, Kind::ArithK, Kind::ArithK, Kind::ArithK, Kind::ArithK, Kind::ArithK, Kind::ArithK, Kind::ArithK, Kind::ArithI, Kind::ArithI, Kind::Arith(Op::Add),
Kind::Arith(Op::Sub),
Kind::Arith(Op::Mul),
Kind::Arith(Op::Mod),
Kind::Arith(Op::Pow),
Kind::Arith(Op::Div),
Kind::Arith(Op::IDiv),
Kind::Arith(Op::BAnd),
Kind::Arith(Op::BOr),
Kind::Arith(Op::BXor),
Kind::Arith(Op::Shl),
Kind::Arith(Op::Shr),
Kind::MmBin,
Kind::MmBinI,
Kind::MmBinK,
Kind::Unary(Op::Unm),
Kind::Unary(Op::BNot),
Kind::Unary(Op::Not),
Kind::Unary(Op::Len),
Kind::Concat,
Kind::Close,
Kind::Tbc,
Kind::Jmp,
Kind::Eq,
Kind::Lt,
Kind::Le,
Kind::EqK,
Kind::EqI,
Kind::LtI,
Kind::LeI,
Kind::GtI,
Kind::GeI,
Kind::Test,
Kind::TestSet,
Kind::Call,
Kind::TailCall,
Kind::Return,
Kind::Return0,
Kind::Return1,
Kind::ForLoop,
Kind::ForPrep,
Kind::TForPrep,
Kind::TForCall,
Kind::TForLoop,
Kind::SetList,
Kind::Closure,
Kind::Vararg,
Kind::GetVarg,
Kind::ErrNNil,
Kind::VarargPrep,
Kind::ExtraArg,
];
const D55: Dialect = Dialect {
name: DIALECT,
ops: OPS,
v55: true,
};
pub(super) fn undump_puc_55(bytes: &[u8], heap: &mut Heap) -> Result<Gc<Proto>, Bad> {
check_header(bytes)?;
let mut r = Reader::at(bytes, HEADER.len());
let n_upvals = r.u8()? as usize;
let mut strings = Vec::new();
let raw = read_proto(&mut r, heap, &mut strings)?;
if raw.upvals.len() != n_upvals {
return Err(format!(
"{DIALECT} chunk: main closure has {n_upvals} upvalues, its function {}",
raw.upvals.len()
)
.into());
}
if r.pos() != bytes.len() {
return Err(format!("{DIALECT} chunk: {} trailing bytes", bytes.len() - r.pos()).into());
}
Ok(lower::build(heap, raw, &translate)?)
}
fn translate(raw: &mut RawProto) -> Result<Lowered, String> {
modern::translate(&D55, raw)
}
fn check_header(bytes: &[u8]) -> Result<(), Bad> {
header::check(bytes, HEADER, header::LAYOUT_55)
}
fn read_int(r: &mut Reader) -> Result<u32, Bad> {
let v = read_puc_varint(r)?;
u32::try_from(v)
.ok()
.filter(|&v| v <= i32::MAX as u32)
.ok_or(Bad::IntOverflow)
}
fn read_count(r: &mut Reader, min_size: usize) -> Result<usize, Bad> {
let n = read_int(r)?;
r.count(n as u64, min_size)
}
fn align(r: &mut Reader, to: usize) -> Result<(), Bad> {
let pad = (to - r.pos() % to) % to;
r.take(pad)?;
Ok(())
}
fn read_string(
r: &mut Reader,
heap: &mut Heap,
strings: &mut Vec<Gc<LuaStr>>,
) -> Result<Option<Gc<LuaStr>>, Bad> {
let size = read_puc_varint(r)?;
if size == 0 {
let idx = read_puc_varint(r)?;
if idx == 0 {
return Ok(None);
}
return match strings.get(idx as usize - 1) {
Some(&s) => Ok(Some(s)),
None => Err(format!("{DIALECT} chunk: invalid string index {idx}").into()),
};
}
let n = r.count(size, 1)?;
let bytes = r.take(n)?;
let s = heap.intern(&bytes[..n - 1]);
strings.push(s);
Ok(Some(s))
}
fn read_name(
r: &mut Reader,
heap: &mut Heap,
strings: &mut Vec<Gc<LuaStr>>,
) -> Result<Box<str>, Bad> {
Ok(match read_string(r, heap, strings)? {
Some(s) => String::from_utf8_lossy(s.as_bytes()).into(),
None => "".into(),
})
}
fn read_const(
r: &mut Reader,
heap: &mut Heap,
strings: &mut Vec<Gc<LuaStr>>,
) -> Result<Value, Bad> {
Ok(match r.u8()? {
0 => Value::Nil,
1 => Value::Bool(false),
17 => Value::Bool(true),
3 => {
let cx = read_puc_varint(r)?;
let half = (cx >> 1) as i64;
Value::Int(if cx & 1 != 0 { !half } else { half })
}
19 => Value::Float(f64::from_le_bytes(r.take(8)?.try_into().expect("8 bytes"))),
4 | 20 => match read_string(r, heap, strings)? {
Some(s) => Value::Str(s),
None => return Err(format!("{DIALECT} chunk: NULL string constant").into()),
},
_ => return Err(Bad::Constant),
})
}
fn read_proto(
r: &mut Reader,
heap: &mut Heap,
strings: &mut Vec<Gc<LuaStr>>,
) -> Result<RawProto, Bad> {
let line_defined = read_int(r)?;
let last_line_defined = read_int(r)?;
let num_params = r.u8()?;
let flag = r.u8()?;
let max_stack = r.u8()?;
let n = read_count(r, 4)?;
align(r, 4)?;
let code = (0..n).map(|_| r.u32()).collect::<Result<Vec<_>, _>>()?;
let n = read_count(r, 1)?;
let consts = (0..n)
.map(|_| read_const(r, heap, strings))
.collect::<Result<Vec<_>, _>>()?;
let n = read_count(r, 3)?;
let mut upvals = Vec::with_capacity(n);
for _ in 0..n {
let in_stack = r.u8()? != 0;
let index = r.u8()?;
let kind = r.u8()?;
upvals.push(UpvalDesc {
in_stack,
index,
name: "".into(),
read_only: kind == 1 || kind == 3,
});
}
let n = read_count(r, 1)?;
let protos = (0..n)
.map(|_| r.nested(|r| read_proto(r, heap, strings)))
.collect::<Result<Vec<_>, _>>()?;
let source = match read_string(r, heap, strings)? {
Some(s) => s,
None => heap.intern(b"=?"),
};
let n = read_count(r, 1)?;
let deltas = r.take(n)?.to_vec();
let n = read_count(r, 8)?;
let mut abs = Vec::with_capacity(n);
if n > 0 {
align(r, 4)?;
for _ in 0..n {
let pc = u32::from_le_bytes(r.take(4)?.try_into().expect("4 bytes"));
let line = u32::from_le_bytes(r.take(4)?.try_into().expect("4 bytes"));
abs.push((pc, line));
}
}
let lines = lower::rle_lines(DIALECT, &deltas, &abs, line_defined, code.len())?;
let n = read_count(r, 3)?;
let mut locvars = Vec::with_capacity(n);
for _ in 0..n {
locvars.push(RawLocVar {
name: read_name(r, heap, strings)?,
start_pc: read_int(r)?,
end_pc: read_int(r)?,
});
}
if read_int(r)? != 0 {
for u in upvals.iter_mut() {
u.name = read_name(r, heap, strings)?;
}
}
Ok(RawProto {
source,
line_defined,
last_line_defined,
num_params,
is_vararg: flag & (PF_VAHID | PF_VATAB) != 0,
has_compat_vararg_arg: false,
vararg_table: flag & PF_VATAB != 0,
max_stack,
code,
consts,
upvals,
protos,
lines,
locvars,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::vm::isa::Inst;
fn vabck(op: u32, a: u32, vb: u32, vc: u32, k: bool) -> u32 {
op | (a << 7) | ((k as u32) << 15) | (vb << 16) | (vc << 22)
}
fn ax(op: u32, ax: u32) -> u32 {
op | (ax << 7)
}
const SETLIST: u32 = 78;
const EXTRAARG: u32 = 84;
const RETURN0: u32 = 71;
fn lower(code: Vec<u32>) -> Vec<Inst> {
let mut heap = Heap::new();
let mut raw = lower::test_proto(&mut heap, code, vec![], 8);
translate(&mut raw).expect("translates").code
}
#[test]
fn setlist_extraarg_extends_the_ten_bit_offset() {
let code = lower(vec![
vabck(SETLIST, 0, 3, 5, true),
ax(EXTRAARG, 2),
vabck(RETURN0, 0, 0, 0, false),
]);
assert!(code[0].k());
assert_eq!(code[0].b(), 3);
assert_eq!((code[1].op(), code[1].ax()), (Op::ExtraArg, 2 * 1024 + 5));
}
}