🚧 wip: got the closure test working
parent
543dab360b
commit
1518781753
|
@ -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);
|
||||
|
|
11
src/main.rs
11
src/main.rs
|
@ -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));
|
||||
}
|
||||
|
|
74
src/state.rs
74
src/state.rs
|
@ -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 ();
|
||||
|
|
148
src/tests.rs
148
src/tests.rs
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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.
Loading…
Reference in New Issue