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;
use crate::vm::isa::Op;
const DIALECT: &str = "PUC 5.4";
pub(in crate::vm::dump) const HEADER: &[u8] = &[
0x1b, b'L', b'u', b'a', 0x54, 0, 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, ];
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::VarargPrep,
Kind::ExtraArg,
];
const D54: Dialect = Dialect {
name: DIALECT,
ops: OPS,
v55: false,
};
pub(super) fn undump(bytes: &[u8], heap: &mut Heap) -> Result<Gc<Proto>, Bad> {
check_header(bytes)?;
let mut r = Reader::at(bytes, HEADER.len());
r.u8()?;
let raw = read_proto(&mut r, heap, None)?;
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(&D54, raw)
}
fn check_header(bytes: &[u8]) -> Result<(), Bad> {
header::check(bytes, HEADER, header::LAYOUT_54)
}
fn read_varint(r: &mut Reader) -> Result<u64, Bad> {
let mut x: u64 = 0;
loop {
let b = r.u8()?;
if x >> 57 != 0 {
return Err(Bad::IntOverflow);
}
x = (x << 7) | (b & 0x7F) as u64;
if b & 0x80 != 0 {
return Ok(x);
}
}
}
fn read_int(r: &mut Reader) -> Result<u32, Bad> {
let v = read_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 read_string<'a>(r: &mut Reader<'a>) -> Result<Option<&'a [u8]>, Bad> {
let size = read_varint(r)?;
if size == 0 {
return Ok(None);
}
let n = r.count(size - 1, 1)?;
Ok(Some(r.take(n)?))
}
fn read_const(r: &mut Reader, heap: &mut Heap) -> Result<Value, Bad> {
Ok(match r.u8()? {
0 => Value::Nil,
1 => Value::Bool(false),
17 => Value::Bool(true),
3 => Value::Int(i64::from_le_bytes(r.take(8)?.try_into().expect("8 bytes"))),
19 => Value::Float(f64::from_le_bytes(r.take(8)?.try_into().expect("8 bytes"))),
4 | 20 => match read_string(r)? {
Some(s) => Value::Str(heap.intern(s)),
None => return Err(format!("{DIALECT} chunk: NULL string constant").into()),
},
_ => return Err(Bad::Constant),
})
}
fn read_proto(
r: &mut Reader,
heap: &mut Heap,
parent_source: Option<Gc<LuaStr>>,
) -> Result<RawProto, Bad> {
let source = match (read_string(r)?, parent_source) {
(Some(s), _) => heap.intern(s),
(None, Some(p)) => p,
(None, None) => heap.intern(b"=?"),
};
let line_defined = read_int(r)?;
let last_line_defined = read_int(r)?;
let num_params = r.u8()?;
let is_vararg = r.u8()? != 0;
let max_stack = r.u8()?;
let n = read_count(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))
.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, Some(source))))
.collect::<Result<Vec<_>, _>>()?;
let n = read_count(r, 1)?;
let deltas = r.take(n)?.to_vec();
let n = read_count(r, 2)?;
let mut abs = Vec::with_capacity(n);
for _ in 0..n {
abs.push((read_int(r)?, read_int(r)?));
}
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 {
let name = String::from_utf8_lossy(read_string(r)?.unwrap_or(b""));
locvars.push(RawLocVar {
name: name.into(),
start_pc: read_int(r)?,
end_pc: read_int(r)?,
});
}
if read_int(r)? != 0 {
for u in upvals.iter_mut() {
u.name = String::from_utf8_lossy(read_string(r)?.unwrap_or(b"")).into();
}
}
Ok(RawProto {
source,
line_defined,
last_line_defined,
num_params,
is_vararg,
has_compat_vararg_arg: false,
vararg_table: false,
max_stack,
code,
consts,
upvals,
protos,
lines,
locvars,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::vm::isa::Inst;
fn abck(op: u32, a: u32, b: u32, c: u32, k: bool) -> u32 {
op | (a << 7) | ((k as u32) << 15) | (b << 16) | (c << 24)
}
const ADDI: u32 = 21;
const ADDK: u32 = 22;
const MMBINI: u32 = 47;
const MMBINK: u32 = 48;
const EQI: u32 = 61;
const GTI: u32 = 64;
const RETURN0: u32 = 71;
const TM_ADD: u32 = 6;
const TM_SUB: u32 = 7;
fn lower(code: Vec<u32>, consts: Vec<Value>) -> Vec<Inst> {
let mut heap = Heap::new();
let mut raw = lower::test_proto(&mut heap, code, consts, 4);
translate(&mut raw).expect("translates").code
}
#[test]
fn immediate_arithmetic_takes_operator_and_operand_from_its_mmbin() {
let code = lower(
vec![
abck(ADDI, 0, 1, 127 - 1, false),
abck(MMBINI, 1, 127 + 1, TM_SUB, false),
abck(RETURN0, 0, 0, 0, false),
],
vec![],
);
assert_eq!((code[0].op(), code[0].sbx()), (Op::LoadI, 1));
let t = code[0].a();
assert_eq!(
(code[1].op(), code[1].a(), code[1].b(), code[1].c()),
(Op::Sub, 0, 1, t)
);
}
#[test]
fn subtracting_an_immediate_zero_stays_an_addition() {
let code = lower(
vec![
abck(ADDI, 0, 1, 127, false),
abck(MMBINI, 1, 127, TM_SUB, false),
abck(RETURN0, 0, 0, 0, false),
],
vec![],
);
let t = code[0].a();
assert_eq!(
(code[1].op(), code[1].b(), code[1].c(), code[1].k()),
(Op::Add, 1, t, true)
);
assert_eq!(code[1].source_op(), Op::Sub);
}
#[test]
fn a_flipped_constant_operand_stays_on_the_left() {
let code = lower(
vec![
abck(ADDK, 0, 1, 0, false),
abck(MMBINK, 1, 0, TM_ADD, true),
abck(RETURN0, 0, 0, 0, false),
],
vec![Value::Float(2.5)],
);
let t = code[0].a();
assert_eq!(code[0].op(), Op::LoadK);
assert_eq!((code[1].op(), code[1].b(), code[1].c()), (Op::Add, t, 1));
}
#[test]
fn immediate_comparisons_keep_float_literals_and_operand_order() {
let code = lower(
vec![
abck(EQI, 0, 127 + 1, 1, true),
abck(GTI, 0, 127 + 2, 0, false),
abck(RETURN0, 0, 0, 0, false),
],
vec![],
);
assert_eq!(code[0].op(), Op::LoadF, "C=1: the literal was 1.0");
assert_eq!((code[1].op(), code[1].a(), code[1].k()), (Op::Eq, 0, true));
assert_eq!(code[2].op(), Op::LoadI);
let t = code[2].a();
assert_eq!(
(code[3].op(), code[3].a(), code[3].b()),
(Op::Lt, t, 0),
"x > 2 is 2 < x"
);
}
}