use super::error::Bad;
use super::header;
use super::reader::Reader;
use crate::runtime::Value;
use crate::runtime::function::{LocVar, Proto, UpvalDesc};
use crate::runtime::heap::{Gc, GcHeader, Heap, ObjTag};
use crate::version::LuaVersion;
const HEADER_55: &[u8] = &[
0x1b, b'L', b'u', b'a', 0x55, 0x00, 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 HEADER_54: &[u8] = &[
0x1b, b'L', b'u', b'a', 0x54, 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 HEADER_53: &[u8] = &[
0x1b, b'L', b'u', b'a', 0x53, 0x00, 0x19, 0x93, b'\r', b'\n', 0x1a, b'\n',
4, 8, 4, 8, 8, 0x78, 0x56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x28, 0x77, 0x40, ];
pub(super) fn header_for(version: LuaVersion) -> &'static [u8] {
header_and_layout(version).0
}
fn header_and_layout(version: LuaVersion) -> (&'static [u8], &'static [(usize, Bad)]) {
match version {
LuaVersion::Lua53 => (HEADER_53, header::LAYOUT_53),
LuaVersion::Lua54 => (HEADER_54, header::LAYOUT_54),
LuaVersion::Lua51 | LuaVersion::Lua52 | LuaVersion::Lua55 | LuaVersion::MacroLua => {
(HEADER_55, header::LAYOUT_55)
}
}
}
pub(super) const BODY_TAG: &[u8] = b"\x00LunaV1\x00";
fn w_u32(out: &mut Vec<u8>, v: u32) {
out.extend_from_slice(&v.to_le_bytes());
}
fn w_bytes(out: &mut Vec<u8>, b: &[u8]) {
w_u32(out, b.len() as u32);
out.extend_from_slice(b);
}
type Saved = Option<std::collections::HashMap<Vec<u8>, u32>>;
fn w_const(out: &mut Vec<u8>, v: Value, saved: &mut Saved) {
match v {
Value::Nil => out.push(0),
Value::Bool(false) => out.push(1),
Value::Bool(true) => out.push(2),
Value::Int(i) => {
out.push(3);
out.extend_from_slice(&i.to_le_bytes());
}
Value::Float(f) => {
out.push(4);
out.extend_from_slice(&f.to_bits().to_le_bytes());
}
Value::Str(s) => {
if let Some(map) = saved {
if let Some(&idx) = map.get(s.as_bytes()) {
out.push(6);
w_u32(out, idx);
return;
}
let idx = map.len() as u32;
map.insert(s.as_bytes().to_vec(), idx);
}
out.push(5);
w_bytes(out, s.as_bytes());
}
other => unreachable!("non-serialisable constant: {}", other.type_name()),
}
}
fn w_proto(
out: &mut Vec<u8>,
p: &Proto,
strip: bool,
parent_source: Option<&[u8]>,
saved: &mut Saved,
) {
out.push(p.num_params);
out.push(p.is_vararg as u8);
out.push(p.max_stack);
w_u32(out, p.line_defined);
w_u32(out, p.last_line_defined);
let source = p.source.as_bytes();
let inherits = parent_source == Some(source);
w_bytes(out, if strip || inherits { b"" } else { source });
w_u32(out, p.code.len() as u32);
for inst in p.code.iter() {
w_u32(out, inst.0);
}
let lines: &[u32] = if strip { &[] } else { &p.lines };
w_u32(out, lines.len() as u32);
for &ln in lines.iter() {
w_u32(out, ln);
}
w_u32(out, p.consts.len() as u32);
for &k in p.consts.iter() {
w_const(out, k, saved);
}
w_u32(out, p.upvals.len() as u32);
for u in p.upvals.iter() {
out.push(u.in_stack as u8);
out.push(u.index);
out.push(u.read_only as u8);
w_bytes(out, if strip { b"" } else { u.name.as_bytes() });
}
w_u32(out, p.protos.len() as u32);
for sub in p.protos.iter() {
w_proto(out, sub, strip, Some(source), saved);
}
if strip {
w_u32(out, 0);
} else {
w_u32(out, p.locvars.len() as u32);
for lv in p.locvars.iter() {
w_bytes(out, lv.name.as_bytes());
w_u32(out, lv.reg);
w_u32(out, lv.start_pc);
w_u32(out, lv.end_pc);
}
}
}
pub(super) fn dump(proto: &Proto, strip: bool, version: LuaVersion) -> Vec<u8> {
let header = header_for(version);
let mut out = Vec::with_capacity(header.len() + BODY_TAG.len() + proto.code.len() * 4);
out.extend_from_slice(header);
out.extend_from_slice(BODY_TAG);
let mut saved: Saved = (version >= LuaVersion::Lua55).then(Default::default);
w_proto(&mut out, proto, strip, None, &mut saved);
out
}
fn r_const(
r: &mut Reader,
heap: &mut Heap,
strings: &mut Vec<Gc<crate::runtime::LuaStr>>,
) -> Result<Value, Bad> {
Ok(match r.u8()? {
0 => Value::Nil,
1 => Value::Bool(false),
2 => Value::Bool(true),
3 => Value::Int(i64::from_le_bytes(r.take(8)?.try_into().unwrap())),
4 => Value::Float(f64::from_bits(u64::from_le_bytes(
r.take(8)?.try_into().unwrap(),
))),
5 => {
let b = r.bytes()?;
let s = heap.intern(b);
strings.push(s);
Value::Str(s)
}
6 => {
let idx = r.u32()? as usize;
Value::Str(
*strings
.get(idx)
.ok_or(Bad::Code(format!("saved string {idx} out of range")))?,
)
}
_ => return Err(Bad::Constant),
})
}
fn read_count(r: &mut Reader, min_size: usize) -> Result<usize, Bad> {
let n = r.u32()?;
r.count(u64::from(n), min_size)
}
fn r_proto(
r: &mut Reader,
heap: &mut Heap,
parent_source: Option<Gc<crate::runtime::LuaStr>>,
strings: &mut Vec<Gc<crate::runtime::LuaStr>>,
) -> Result<Gc<Proto>, Bad> {
let num_params = r.u8()?;
let is_vararg = r.u8()? != 0;
let max_stack = r.u8()?;
let line_defined = r.u32()?;
let last_line_defined = r.u32()?;
let raw = r.bytes()?;
let source = if raw.is_empty() {
parent_source.unwrap_or_else(|| heap.intern(b""))
} else {
heap.intern(raw)
};
let n = read_count(r, 4)?;
let mut code = Vec::with_capacity(n);
for _ in 0..n {
code.push(crate::vm::isa::Inst(r.u32()?));
}
let n = read_count(r, 4)?;
let mut lines = Vec::with_capacity(n);
for _ in 0..n {
lines.push(r.u32()?);
}
let n = read_count(r, 1)?;
let mut consts = Vec::with_capacity(n);
for _ in 0..n {
consts.push(r_const(r, heap, strings)?);
}
let n = read_count(r, 7)?;
let mut upvals = Vec::with_capacity(n);
for _ in 0..n {
let in_stack = r.u8()? != 0;
let index = r.u8()?;
let read_only = r.u8()? != 0;
let name = String::from_utf8_lossy(r.bytes()?).into_owned().into();
upvals.push(UpvalDesc {
in_stack,
index,
name,
read_only,
});
}
let n = read_count(r, 4)?;
let mut protos = Vec::with_capacity(n);
for _ in 0..n {
protos.push(r.nested(|r| r_proto(r, heap, Some(source), strings))?);
}
let n = read_count(r, 16)?;
let mut locvars = Vec::with_capacity(n);
for _ in 0..n {
let name = String::from_utf8_lossy(r.bytes()?).into_owned().into();
let reg = r.u32()?;
let start_pc = r.u32()?;
let end_pc = r.u32()?;
locvars.push(LocVar {
name,
reg,
start_pc,
end_pc,
});
}
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: 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),
trace_compile_failures: crate::jit::send_compat::TRefLock::new(Vec::new()),
traces: crate::jit::send_compat::TRefLock::new(Vec::new()),
}))
}
pub(super) fn undump(bytes: &[u8], heap: &mut Heap, version: LuaVersion) -> Result<Gc<Proto>, Bad> {
let (header, layout) = header_and_layout(version);
header::check(bytes, header, layout)?;
let body = header.len();
let tag = bytes
.get(body..body + BODY_TAG.len())
.ok_or(Bad::Truncated)?;
if tag != BODY_TAG {
return Err(Bad::Code("not a luna chunk body".to_string()));
}
let mut r = Reader::at(bytes, body + BODY_TAG.len());
let proto = r_proto(&mut r, heap, None, &mut Vec::new())?;
if r.pos() != bytes.len() {
return Err(Bad::Code(format!(
"{} trailing bytes",
bytes.len() - r.pos()
)));
}
Ok(proto)
}