diff --git a/src/loader.rs b/src/loader.rs index 61835f2..a7cf343 100644 --- a/src/loader.rs +++ b/src/loader.rs @@ -8,6 +8,8 @@ use crate::state::{ /// Invoke `luac` as a subprocess /// Luckily luac is single-pass, so we can just pipe in and out +/// +/// `source` is a Vec because we move it to a worker thread pub (crate) fn compile_bytecode (source: Vec ) -> Vec { use std::{ @@ -51,8 +53,7 @@ pub fn parse_inst (buf: [u8; 4]) -> Option (((buf [1] >> 7) as u32) << 0) | ((buf [2] as u32) << 1) | ((buf [3] as u32) << 9); - let bx = bx.try_into().ok ()?; - let sbx = bx - 65535; + let sbx = i32::try_from (bx).ok ()? - 65535; let k = (buf [1] & 0x80) >> 7 == 1; let s_j = a as i32 + ((b as i32) << 8) + 1; @@ -73,7 +74,7 @@ pub fn parse_inst (buf: [u8; 4]) -> Option 0x24 => Inst::Mul (a, b, c), 0x2e => Inst::MmBin (a, b, c), 0x33 => Inst::Not (a, b), - 0x3c => Inst::EqK (a, b, c), + 0x3c => Inst::EqK (a, b, k), 0x38 => Inst::Jmp (s_j), 0x42 => Inst::Test (a, k), 0x44 => Inst::Call (a, b, c), @@ -230,6 +231,11 @@ pub fn parse_chunk (rdr: &mut R) -> Option { }) } +pub fn parse_chunk_from_bytes (b: &[u8]) -> Option { + let mut rdr = std::io::Cursor::new (b); + parse_chunk (&mut rdr) +} + #[cfg (test)] mod tests { #[test] @@ -264,7 +270,7 @@ mod tests { ([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)), + ([0xbc, 0x00, 0x01, 0x00], Inst::EqK (1, 1, false)), ([0xb8, 0x02, 0x00, 0x80], Inst::Jmp (6)), ([0x38, 0x02, 0x00, 0x80], Inst::Jmp (5)), ([0x52, 0x00, 0x00, 0x00], Inst::ExtraArg (0)), diff --git a/src/state.rs b/src/state.rs index 0758795..30d01b8 100644 --- a/src/state.rs +++ b/src/state.rs @@ -11,10 +11,10 @@ pub enum Instruction { Add (u8, u8, u8), Call (u8, u8, u8), - Closure (u8, i32), + Closure (u8, u32), // Equals Constant? - EqK (u8, u8, u8), + EqK (u8, u8, bool), ExtraArg (u32), @@ -38,7 +38,7 @@ pub enum Instruction { LoadI (u8, i32), // Load Constant - LoadK (u8, i32), + LoadK (u8, u32), LoadNil (u8), @@ -150,10 +150,22 @@ impl State { } fn register_window_mut (&mut self) -> &mut [Value] { - let frame = self.stack.last_mut ().unwrap (); + let frame = self.stack.last ().unwrap (); &mut self.registers [frame.register_offset..] } + /// Short form to get access to a register within our window + + fn reg (&self, i: u8) -> &Value { + let frame = self.stack.last ().unwrap (); + &self.registers [frame.register_offset + i as usize] + } + + fn reg_mut (&mut self, i: u8) -> &mut Value { + let frame = self.stack.last ().unwrap (); + &mut self.registers [frame.register_offset + i as usize] + } + pub fn execute_chunk (&mut self, chunk: &Chunk, upvalues: &[Value]) -> Vec { let max_iters = 2000; @@ -187,16 +199,10 @@ impl State { match instruction { Instruction::Add (a, b, c) => { - let r = self.register_window_mut (); + let v_b = self.reg (*b).as_float ().unwrap (); + let v_c = self.reg (*c).as_float ().unwrap (); - let a = usize::try_from (*a).unwrap (); - let b = usize::try_from (*b).unwrap (); - let c = usize::try_from (*c).unwrap (); - - let v_b = r [b].as_float ().unwrap (); - let v_c = r [c].as_float ().unwrap (); - - r [a] = (v_b + v_c).into (); + *self.reg_mut (*a) = Value::from (v_b + v_c); }, Instruction::Call (a, _b, c) => { // Take arguments from registers [a + 1, a + b) @@ -209,9 +215,7 @@ impl State { // TODO: Only implement printing values for now - let a = usize::try_from (*a).unwrap (); - let r = self.register_window_mut (); - let v_a = r.get (a).unwrap (); + let v_a = self.reg (*a); match v_a { Value::BogusClosure (rc) => { @@ -225,7 +229,7 @@ impl State { self.stack.push (StackFrame { program_counter: 0, block_idx: target_block, - register_offset: current_frame.register_offset + a + 1, + register_offset: current_frame.register_offset + *a as usize + 1, }); if self.debug_print { @@ -244,10 +248,10 @@ impl State { // assert_eq! (*b, 2); assert_eq! (*c, 1); - let value = r.get (a + 1).unwrap (); + let value = self.reg (a + 1); println! ("{}", value); - r [a] = r [a + 1].take (); + *self.reg_mut (*a) = self.reg_mut (*a + 1).take (); }, _ => { let stack = &self.stack; @@ -256,30 +260,18 @@ impl State { } }, Instruction::Closure (a, b) => { - let a = usize::try_from (*a).unwrap (); let b = usize::try_from (*b).unwrap (); - if self.debug_print { - println! ("OP_CLOSURE {a} {b}"); - } - - let r = self.register_window_mut (); - r [a] = Value::BogusClosure (BogusClosure { + *self.reg_mut (*a) = Value::BogusClosure (BogusClosure { idx: b + frame.block_idx + 1, upvalues: vec! [], }.into ()); }, - Instruction::EqK (a, b, c_k) => { - let a = usize::try_from (*a).unwrap (); - let b = usize::try_from (*b).unwrap (); - let r = self.register_window (); + Instruction::EqK (a, b, k_flag) => { + let b = usize::from (*b); - let equal = r [a] == k [b]; - - match (equal, c_k) { - (true, 0) => next_pc += 1, - (false, 1) => next_pc += 1, - _ => (), + if (*self.reg (*a) == k [b]) != *k_flag { + next_pc += 1; } }, Instruction::ExtraArg (ax) => { @@ -289,7 +281,6 @@ impl State { assert_eq! (*ax, 0, "implemented only for ax == 0"); }, Instruction::GetTabUp (a, b, c) => { - let a = usize::try_from (*a).unwrap (); let b = usize::try_from (*b).unwrap (); let c = usize::try_from (*c).unwrap (); @@ -303,26 +294,19 @@ impl State { _ => panic! ("GetTabUp only supports string keys"), }; - let value = env.get (key).unwrap (); - - - self.register_window_mut() [a] = value.clone (); + *self.reg_mut (*a) = value.clone (); }, Instruction::GetI (a, b, c) => { - let a = usize::try_from (*a).unwrap (); - let b = usize::try_from (*b).unwrap (); let c = usize::try_from (*c).unwrap (); - let r = self.register_window_mut (); - - let table = r.get (b).unwrap (); + let table = self.reg (*b); let value = match table { Value::BogusArg (arg) => arg.get (c).map (|x| x.as_str().into ()).unwrap_or_default(), _ => unimplemented!(), }; - r [a] = value; + *self.reg_mut (*a) = value; }, Instruction::GetUpVal (a, b) => { let this_func = self.stack.last ().unwrap ().register_offset - 1; @@ -331,46 +315,34 @@ impl State { _ => 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 (); + *self.reg_mut (*a) = upvalues [b].clone (); }, Instruction::Jmp (s_j) => next_pc += s_j, Instruction::LoadF (a, sbx) => { - let a = usize::try_from (*a).unwrap (); - self.register_window_mut ()[a] = Value::Float (*sbx as f64); + *self.reg_mut (*a) = Value::Float (*sbx as f64); } Instruction::LoadFalse (a) => { - let a = usize::try_from (*a).unwrap (); - self.register_window_mut ()[a] = Value::Boolean (false); + *self.reg_mut (*a) = false.into (); }, Instruction::LoadI (a, sbx) => { - let a = usize::try_from (*a).unwrap (); - - self.register_window_mut ()[a] = Value::Integer (*sbx as i64); + *self.reg_mut (*a) = Value::Integer (*sbx as i64); }, Instruction::LoadK (a, bx) => { - let a = usize::try_from (*a).unwrap (); let bx = usize::try_from (*bx).unwrap (); - self.register_window_mut ()[a] = k [bx].clone (); + *self.reg_mut (*a) = k [bx].clone (); }, Instruction::LoadNil (a) => { - let a = usize::try_from (*a).unwrap (); - self.register_window_mut ()[a] = Value::Nil; + *self.reg_mut (*a) = Value::Nil; }, Instruction::LoadTrue (a) => { - let a = usize::try_from (*a).unwrap (); - self.register_window_mut ()[a] = Value::Boolean (true); + *self.reg_mut (*a) = true.into (); }, Instruction::MmBin (a, b, _c) => { - let a = usize::try_from (*a).unwrap (); - let b = usize::try_from (*b).unwrap (); - - let r = self.register_window(); - let a = &r [a]; - let b = &r [b]; + let a = self.reg (*a); + let b = self.reg (*b); if a.as_float().is_some() && b.as_float().is_some () { // No need for metamethods @@ -380,27 +352,18 @@ impl State { } }, Instruction::Move (a, b) => { - let a = usize::try_from (*a).unwrap (); - let b = usize::try_from (*b).unwrap (); - - let r = self.register_window_mut(); - r [a] = r [b].clone (); + *self.reg_mut (*a) = self.reg (*b).clone (); }, Instruction::Mul (_a, _b, _c) => unimplemented!(), Instruction::NewTable (a) => { - let a = usize::try_from (*a).unwrap (); - self.register_window_mut ()[a] = Value::Table (Default::default ()); + *self.reg_mut (*a) = Value::Table (Default::default ()); }, Instruction::Not (a, b) => { - let a = usize::try_from (*a).unwrap (); - let b = usize::try_from (*b).unwrap (); - let r = self.register_window_mut(); - r [a] = Value::Boolean (! r [b].is_truthy()); + *self.reg_mut (*a) = Value::Boolean (! self.reg (*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 (); @@ -479,15 +442,7 @@ impl State { Instruction::SetTabUp (_a, _b, _c) => unimplemented! (), Instruction::TailCall (_a, _b, _c, _k) => unimplemented! (), Instruction::Test (a, _k) => { - let a = usize::try_from (*a).unwrap (); - - let a = self.register_window ().get (a).unwrap (); - - if self.debug_print { - println! ("Test {a:?}"); - } - - if a.is_truthy() { + if self.reg (*a).is_truthy() { next_pc += 1; } }, diff --git a/src/tests.rs b/src/tests.rs index 1aa6d04..90cd600 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,4 +1,7 @@ +use std::hash::Hash; + use crate::{ + loader, state::{ Block, Chunk, @@ -8,6 +11,41 @@ use crate::{ value::Value, }; +fn calculate_hash(t: &T) -> u64 { + use std::hash::Hasher; + + let mut s = std::collections::hash_map::DefaultHasher::new (); + t.hash(&mut s); + s.finish() +} + +/// Takes arguments and a parsed Lua chunk, runs its, +/// and returns the output + +fn run_chunk (args: &[&str], chunk: &Chunk) -> Vec { + let mut vm = State::default (); + let upvalues = State::upvalues_from_args (args.into_iter ().map (|s| s.to_string ())); + vm.execute_chunk (chunk, &upvalues) +} + +/// Takes arguments and Lua bytecode, loads it, runs it, +/// and return the output + +fn run_bytecode (args: &[&str], bc: &[u8]) -> Vec { + let chunk = loader::parse_chunk_from_bytes (&bc).unwrap (); + run_chunk (args, &chunk) +} + +/// Takes arguments and Lua source code, +/// invokes `luac` to compile it to bytecode, +/// runs it, +/// and returns the output + +fn run_source (args: &[&str], s: &str) -> Vec { + let bc = loader::compile_bytecode (s.as_bytes ().to_vec ()); + run_bytecode (args, &bc) +} + #[test] fn bools () { /* @@ -178,27 +216,31 @@ fn fma () { #[test] fn is_93 () { - let source = include_bytes! ("../test_vectors/is_93.lua"); - assert_eq! (&blake3::hash (source).to_hex (), "a058869ed5142cbc8ccb2372991ef8b37657af38dc3f5e34814a7274b14d1e52"); + assert_eq! (Value::from ("93"), Value::from ("93")); + assert_ne! (Value::from ("94"), Value::from ("93")); + assert_ne! (calculate_hash (&Value::from ("94")), calculate_hash (&Value::from ("93"))); + assert_ne! (Value::Nil, Value::from ("93")); - let bytecode = crate::loader::compile_bytecode (source.to_vec ()); - assert_eq! (&blake3::hash (&bytecode).to_hex (), "b59ce3e054500beb109c247e784cc4a0ff09ac42f8086dc5cdbf711bce1ba071"); + let src = r#" + if arg [1] == "93" then + print "it's 93" + return 0 + else + print "it's not 93" + return 1 + end + "#; - let mut rdr = std::io::Cursor::new (bytecode); - let file = crate::loader::parse_chunk (&mut rdr).unwrap (); + let bc = loader::compile_bytecode (src.as_bytes ().to_vec ()); + let chunk = loader::parse_chunk_from_bytes (&bc).unwrap (); - for (arg, expected) in [ - (vec! ["_exe_name"], vec! [1.into ()]), - (vec! ["_exe_name", "93"], vec! [0.into ()]), - (vec! ["_exe_name", "94"], vec! [1.into ()]), - ] { - let expected: Vec = expected; - 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); - } + assert_eq! (chunk.blocks [0].instructions [3], Inst::EqK (0, 1, false)); + + let run = run_chunk; + + assert_eq! (run (&[""], &chunk), vec! [Value::from (1)]); + assert_eq! (run (&["", "93"], &chunk), vec! [Value::from (0)]); + assert_eq! (run (&["", "94"], &chunk), vec! [Value::from (1)]); } #[test] diff --git a/src/value.rs b/src/value.rs index 5c21c54..6c6ff68 100644 --- a/src/value.rs +++ b/src/value.rs @@ -61,6 +61,12 @@ impl fmt::Display for Value { } } +impl From for Value { + fn from (x: bool) -> Self { + Self::Boolean (x) + } +} + impl From for Value { fn from (x: String) -> Self { Self::String (x.into ()) diff --git a/test_vectors/is_93.lua b/test_vectors/is_93.lua deleted file mode 100644 index 4df7016..0000000 --- a/test_vectors/is_93.lua +++ /dev/null @@ -1,11 +0,0 @@ -local function unused_fn () - print "unused" -end - -if arg [1] == "93" then - print "it's 93" - return 0 -else - print "it's not 93" - return 1 -end