From 1518781753fe74cdc7e4172d630b8db215c03213 Mon Sep 17 00:00:00 2001 From: _ <_@_> Date: Mon, 25 Sep 2023 00:23:53 -0500 Subject: [PATCH] :construction: wip: got the closure test working --- src/loader.rs | 19 +++-- src/main.rs | 11 +++ src/state.rs | 74 +++++++++++++++++-- src/tests.rs | 148 +++++++------------------------------- test_vectors/closure.lua | 18 ++--- test_vectors/closure.luac | Bin 342 -> 271 bytes test_vectors/is_93.luac | Bin 180 -> 274 bytes 7 files changed, 127 insertions(+), 143 deletions(-) diff --git a/src/loader.rs b/src/loader.rs index 0a0aa67..ab26d07 100644 --- a/src/loader.rs +++ b/src/loader.rs @@ -20,19 +20,25 @@ pub fn parse_inst (buf: [u8; 4]) -> Option 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 (rdr: &mut R) -> Option 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); diff --git a/src/main.rs b/src/main.rs index d18e795..7085930 100644 --- a/src/main.rs +++ b/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)); } diff --git a/src/state.rs b/src/state.rs index 71718c1..8d831ea 100644 --- a/src/state.rs +++ b/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 , stack: Vec , + pub debug_print: bool, + pub breakpoints: Vec , + 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 (); diff --git a/src/tests.rs b/src/tests.rs index 76db919..187ea6f 100644 --- a/src/tests.rs +++ b/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); -} diff --git a/test_vectors/closure.lua b/test_vectors/closure.lua index c1917de..8e4250c 100644 --- a/test_vectors/closure.lua +++ b/test_vectors/closure.lua @@ -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)) diff --git a/test_vectors/closure.luac b/test_vectors/closure.luac index 421ab9eaf1d7fc507b91b17a18ee9622b0e80ecb..b9ae82c512fb99d97c82dd23c0b23281c7a79ae3 100644 GIT binary patch delta 217 zcmX|)JqiL*41|-cYvY}))KbyHb`e=Y{AE>GE0ZU0K^~%K@Bp4d)XvWHIIdtZFkdp6 zyyYb4`YsM#BI-a2F~@o%ks5=Ui5-}Q8n7*9wo0+g#`CpdZ)ycoR0P}+7 z|14&vd(R~P0DnRFN>qK4(4z;?Z<}I|8a207Z!SkQMJ4B2m`|GRe&ATjPY9|z&IXe- Q8BQ0gO_sXgMn3#vA9UO{d;kCd literal 342 zcmYjN%SyyR5Ud_Yh`aa!>JNC*UsyG(9z+pjuOTcE#MPBNG{>Hfn<(NX-u#~6)nBqR zE3N}QUEN(ZUCho|5no~JV{@=Qut88xusXX>Bl;ZWNtQ>C$uys*+5U7k&x$nJn-wuL z$hMbCztDKoSaU!JL#((1nIQoc5kfN5V^-Yx8osXFa&}Ba&$fO@Z|-v|2Ud=(jCBUb z?p2>{2!{VJ_|^qqY%~b+Cp^^kDp|Y$rXDJ;7zowR1_-AxIMKYKhoY3;Rjn6+m}L=- p&nHqg>JjVOaZ!w|@e*iM|OYr~z diff --git a/test_vectors/is_93.luac b/test_vectors/is_93.luac index 088bfeed9df217a87d06789d4d06a5401f266804..47d463f12c7f690a8226fa9c05561089586e2b83 100644 GIT binary patch delta 179 zcmXwxF$=;_41_OfH~)*XgQL<-ad9mL6