🚧 wip: got the closure test working

main
_ 2023-09-25 00:23:53 -05:00
parent 543dab360b
commit 1518781753
7 changed files with 127 additions and 143 deletions

View File

@ -20,19 +20,25 @@ pub fn parse_inst (buf: [u8; 4]) -> Option <Inst>
let bx = bx.try_into().ok ()?;
let sbx = bx - 65535;
let k = (buf [1] & 0x80) >> 7 == 1;
let s_j = a as i32 + ((b as i32) << 8) + 1;
Some (match opcode {
0x00 => Inst::Move (a, b),
0x01 => Inst::LoadI (a, sbx),
0x03 => Inst::LoadK (a, bx),
0x09 => Inst::GetUpVal (a, b),
0x0b => Inst::GetTabUp (a, 0, 0),
0x0b => Inst::GetTabUp (a, b, c),
0x0d => Inst::GetI (a, b, c),
0x22 => Inst::Add (a, b, c),
0x2e => Inst::MmBin (a, b, c),
0x3c => Inst::EqK (a, b, c),
0x38 => Inst::Jmp (s_j),
0x44 => Inst::Call (a, b, c),
0x46 => Inst::Return (a, b, c, k),
0x47 => Inst::Return0,
0x48 => Inst::Return1 (a),
0x4f => Inst::Closure (0, 0),
0x51 => Inst::VarArgPrep (0),
0x4f => Inst::Closure (a, bx),
0x51 => Inst::VarArgPrep (a.into ()),
_ => return None,
})
}
@ -95,7 +101,7 @@ pub fn parse_block <R: Read> (rdr: &mut R) -> Option <Block>
for _ in 0..header.inst_count {
let mut buf = [0u8; 4];
rdr.read_exact (&mut buf).ok ()?;
instructions.push (parse_inst (buf)?);
instructions.push (parse_inst (buf).expect (&format! ("{buf:?}")));
}
let constant_count = {
@ -164,6 +170,7 @@ mod tests {
for (input, expected) in [
([0x51, 0x00, 0x00, 0x00], Inst::VarArgPrep (0)),
([0x4f, 0x00, 0x00, 0x00], Inst::Closure (0, 0)),
([0xcf, 0x00, 0x00, 0x00], Inst::Closure (1, 0)),
([0x8b, 0x00, 0x00, 0x00], Inst::GetTabUp (1, 0, 0)),
([0x03, 0x81, 0x00, 0x00], Inst::LoadK (2, 1)),
([0xc4, 0x00, 0x02, 0x01], Inst::Call (1, 2, 1)),
@ -187,6 +194,10 @@ mod tests {
([0x09, 0x00, 0x01, 0x00], Inst::GetUpVal (0, 1)),
([0x48, 0x00, 0x02, 0x00], Inst::Return1 (0)),
([0x47, 0x00, 0x01, 0x00], Inst::Return0),
([0x8d, 0x00, 0x01, 0x01], Inst::GetI (1, 1, 1)),
([0xbc, 0x00, 0x01, 0x00], Inst::EqK (1, 1, 0)),
([0xb8, 0x02, 0x00, 0x80], Inst::Jmp (6)),
([0x38, 0x02, 0x00, 0x80], Inst::Jmp (5)),
] {
let actual = super::parse_inst (input).unwrap ();
assert_eq!(actual, expected);

View File

@ -12,6 +12,7 @@ fn main() {
let mut rdr = std::io::Cursor::new (data);
loader::parse_chunk (&mut rdr).unwrap ()
};
assert_eq! (lua_file.blocks.len (), 3);
let mut vm = State::default ();
if std::env::var("LUA_DEBUG").is_ok() {
@ -19,5 +20,15 @@ fn main() {
}
let upvalues = State::upvalues_from_args (std::env::args ());
vm.breakpoints.push (state::Breakpoint {
block_idx: 2,
program_counter: 3,
});
vm.breakpoints.push (state::Breakpoint {
block_idx: 0,
program_counter: 10,
});
println! ("Returned: {:?}", vm.execute_chunk (&lua_file, &upvalues));
}

View File

@ -121,6 +121,12 @@ impl Value {
_ => true,
}
}
fn take (&mut self) -> Self {
let mut x = Value::Nil;
std::mem::swap (self, &mut x);
x
}
}
pub struct Block {
@ -147,16 +153,26 @@ struct StackFrame {
register_offset: usize,
}
#[derive (Debug)]
pub struct Breakpoint {
pub block_idx: usize,
pub program_counter: i32,
}
#[derive (Debug)]
pub struct State {
registers: Vec <Value>,
stack: Vec <StackFrame>,
pub debug_print: bool,
pub breakpoints: Vec <Breakpoint>,
step_count: u32,
}
impl Default for State {
fn default () -> Self {
Self {
registers: vec! [Value::Nil; 256],
registers: vec! [Value::Nil; 16],
stack: vec! [
StackFrame {
program_counter: 0,
@ -165,6 +181,8 @@ impl Default for State {
},
],
debug_print: false,
breakpoints: Default::default(),
step_count: 0,
}
}
}
@ -199,9 +217,18 @@ impl State {
let max_iters = 2000;
for _ in 0..max_iters {
self.step_count += 1;
let frame = self.stack.last_mut ().unwrap ().clone ();
let block = chunk.blocks.get (frame.block_idx).unwrap ();
for bp in &self.breakpoints {
if frame.block_idx == bp.block_idx && frame.program_counter == bp.program_counter
{
dbg! (&self);
}
}
let mut next_pc = frame.program_counter;
let pc = usize::try_from (frame.program_counter).expect ("program_counter is not a valid usize");
@ -241,7 +268,7 @@ impl State {
// TODO: Only implement printing values for now
let a = usize::try_from (*a).unwrap ();
let r = self.register_window ();
let r = self.register_window_mut ();
let v_a = r.get (a).unwrap ();
match v_a {
@ -277,6 +304,7 @@ impl State {
assert_eq! (*c, 1);
println! ("{:?}", r.get (a + 1).unwrap ());
r [a] = r [a + 1].take ();
},
_ => {
let stack = &self.stack;
@ -287,8 +315,12 @@ impl State {
Instruction::Closure (a, b) => {
let a = usize::try_from (*a).unwrap ();
let b = usize::try_from (*b).unwrap ();
let r = self.register_window_mut ();
if self.debug_print {
println! ("OP_CLOSURE {a} {b}");
}
let r = self.register_window_mut ();
r [a] = Value::BogusClosure {
idx: b + frame.block_idx + 1,
upvalues: vec! [],
@ -343,6 +375,18 @@ impl State {
r [a] = value;
},
Instruction::GetUpVal (a, b) => {
let this_func = self.stack.last ().unwrap ().register_offset - 1;
let upvalues = match &self.registers [this_func] {
Value::BogusClosure { idx, upvalues } => upvalues,
_ => panic! ("Can't do GetUpVal outside a closure"),
};
let a = usize::try_from (*a).unwrap ();
let b = usize::try_from (*b).unwrap ();
self.register_window_mut ()[a] = upvalues [b].clone ();
},
Instruction::Jmp (s_j) => next_pc += s_j,
Instruction::LoadFalse (a) => {
let a = usize::try_from (*a).unwrap ();
@ -391,16 +435,17 @@ impl State {
let r = self.register_window_mut();
r [a] = Value::Boolean (! r [b].is_truthy());
}
Instruction::Return (a, b, _c, k) => {
Instruction::Return (a, b, c, k) => {
let a = usize::try_from (*a).unwrap ();
let b = usize::try_from (*b).unwrap ();
let c = usize::try_from (*c).unwrap ();
let popped_frame = self.stack.pop ().unwrap ();
// Build closure if needed
if *k {
let closure_idx = match &self.register_window ()[a] {
let closure_idx = match &self.registers [popped_frame.register_offset + a] {
Value::BogusClosure { idx, upvalues } => idx,
_ => panic! ("Impossible"),
};
@ -427,9 +472,22 @@ impl State {
if let Some (new_frame) = self.stack.last() {
next_pc = new_frame.program_counter;
// Shift our output registers down so the caller
// can grab them
// idk exactly why Lua does this
// Register that our function was in before we
// called it.
let offset = popped_frame.register_offset - 1;
for i in (offset)..(offset - 1 + b) {
self.registers [i] = self.registers [i + 1 + a].take ();
}
}
else {
return self.register_window ()[a..(a + b - 1)].to_vec();
// Return from the entire program
return self.registers [a..(a + b + c - 1)].to_vec();
}
},
Instruction::Return1 (a) => {
@ -450,6 +508,10 @@ impl State {
let stack_depth = self.stack.len ();
println! ("stack_depth: {stack_depth}");
}
// Shift output register down
let offset = popped_frame.register_offset;
self.registers [offset - 1] = self.registers [offset + a].take ();
},
Instruction::Test (a, _k) => {
let a = usize::try_from (*a).unwrap ();

View File

@ -85,6 +85,25 @@ fn bools () {
}
}
#[test]
fn closure () {
let bytecode = include_bytes! ("../test_vectors/closure.luac");
let mut rdr = std::io::Cursor::new (bytecode);
let file = crate::loader::parse_chunk (&mut rdr).unwrap ();
for (arg, expected) in [
// Run the same test twice so clippy won't complain about a vec of 1 element
(vec! ["_exe_name"], vec! [23.into ()]),
(vec! ["_exe_name"], vec! [23.into ()]),
] {
let mut vm = State::default ();
let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ()));
let actual = vm.execute_chunk (&file, &upvalues);
assert_eq! (actual, expected);
}
}
#[test]
fn floats () {
/*
@ -135,72 +154,9 @@ fn floats () {
#[test]
fn fma () {
/*
*/
let chunk = Chunk {
file_name: "".to_string (),
blocks: vec! [
Block {
instructions: vec! [
Inst::VarArgPrep (0),
Inst::Closure (0, 0),
Inst::Closure (1, 1),
Inst::Closure (2, 2),
Inst::Move (3, 2),
Inst::LoadI (4, 10),
Inst::LoadI (5, 11),
Inst::LoadI (6, 12),
Inst::Call (3, 4, 2),
Inst::GetTabUp (4, 0, 0),
Inst::Move (5, 3),
Inst::Call (4, 2, 1),
Inst::Return (3, 2, 1, false), // k?
Inst::Return (3, 2, 1, false), // k?
],
constants: vec! [
"print".into (),
],
upvalue_count: 1,
},
Block {
instructions: vec! [
Inst::Add (2, 0, 1),
Inst::MmBin (0, 1, 6),
Inst::Return1 (2),
Inst::Return0,
],
constants: vec! [],
upvalue_count: 0,
},
Block {
instructions: vec! [
Inst::Mul (2, 0, 1),
Inst::MmBin (0, 1, 8),
Inst::Return1 (2),
Inst::Return0,
],
constants: vec! [],
upvalue_count: 0,
},
Block {
instructions: vec! [
Inst::GetUpVal (3, 0), // add
Inst::GetUpVal (4, 1), // mul
Inst::Move (5, 0),
Inst::Move (6, 1),
Inst::Call (4, 3, 2),
Inst::Move (5, 2),
Inst::TailCall (3, 3, 0),
Inst::Return (3, 0, 0, false),
Inst::Return0,
],
constants: vec! [],
upvalue_count: 2,
},
],
};
let bytecode = include_bytes! ("../test_vectors/fma.luac");
let mut rdr = std::io::Cursor::new (bytecode);
let file = crate::loader::parse_chunk (&mut rdr).unwrap ();
for (arg, expected) in [
(vec! ["_exe_name"], vec! [122.into ()]),
@ -208,7 +164,7 @@ fn fma () {
] {
let mut vm = State::default ();
let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ()));
let actual = vm.execute_chunk (&chunk, &upvalues);
let actual = vm.execute_chunk (&file, &upvalues);
assert_eq! (actual, expected);
}
@ -216,49 +172,9 @@ fn fma () {
#[test]
fn is_93 () {
/*
if arg [1] == "93" then
print "it's 93"
return 0
else
print "it's not 93"
return 1
end
*/
let block = Block {
instructions: vec! [
Inst::VarArgPrep (0),
Inst::GetTabUp (1, 0, 0),
Inst::GetI (1, 1, 1),
Inst::EqK (1, 1, 0),
Inst::Jmp (6),
Inst::GetTabUp (1, 0, 2),
Inst::LoadK (2, 3),
Inst::Call (1, 2, 1),
Inst::LoadI (1, 0),
Inst::Return (1, 2, 1, false),
Inst::Jmp (5),
Inst::GetTabUp (1, 0, 2),
Inst::LoadK (2, 4),
Inst::Call (1, 2, 1),
Inst::LoadI (1, 1),
Inst::Return (1, 2, 1, false),
Inst::Return (1, 1, 1, false),
],
constants: vec! [
"arg",
"93",
"print",
"it's 93",
"it's not 93",
].into_iter ().map (Value::from).collect (),
upvalue_count: 1,
};
let chunk = Chunk {
blocks: vec! [block],
file_name: "".to_string (),
};
let bytecode = include_bytes! ("../test_vectors/is_93.luac");
let mut rdr = std::io::Cursor::new (bytecode);
let file = crate::loader::parse_chunk (&mut rdr).unwrap ();
for (arg, expected) in [
(vec! ["_exe_name"], vec! [1.into ()]),
@ -267,18 +183,8 @@ fn is_93 () {
] {
let mut vm = State::default ();
let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ()));
let actual = vm.execute_chunk (&chunk, &upvalues);
let actual = vm.execute_chunk (&file, &upvalues);
assert_eq! (actual, expected);
}
}
#[test]
fn loader () {
let bytecode = include_bytes! ("../test_vectors/closure.luac");
let mut rdr = std::io::Cursor::new (bytecode);
let file = crate::loader::parse_chunk (&mut rdr).unwrap ();
assert_eq! (file.file_name, "@test_vectors/closure.lua");
assert_eq! (file.blocks.len (), 3);
}

View File

@ -1,15 +1,9 @@
local function make_closure ()
local x = 5
print "B"
return function ()
print "D"
return x
local function make_closure (x)
print (x)
return function (y)
return x + y
end
end
print "A"
local f = make_closure ()
print "C"
print (f ())
print "E"
local f = make_closure (11)
print (f (12))

Binary file not shown.

Binary file not shown.