♻️ refactor: make register access easier to read
							parent
							
								
									2bd1ff3d59
								
							
						
					
					
						commit
						5466e6ec56
					
				|  | @ -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 <u8>) -> Vec <u8> { | ||||
| 	use std::{ | ||||
|  | @ -51,8 +53,7 @@ pub fn parse_inst (buf: [u8; 4]) -> Option <Inst> | |||
| 		(((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 <Inst> | |||
| 		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 <R: Read> (rdr: &mut R) -> Option <Chunk> { | |||
| 	}) | ||||
| } | ||||
| 
 | ||||
| pub fn parse_chunk_from_bytes (b: &[u8]) -> Option <Chunk> { | ||||
| 	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)), | ||||
|  |  | |||
							
								
								
									
										135
									
								
								src/state.rs
								
								
								
								
							
							
						
						
									
										135
									
								
								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 <Value> { | ||||
| 		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; | ||||
| 					} | ||||
| 				}, | ||||
|  |  | |||
							
								
								
									
										78
									
								
								src/tests.rs
								
								
								
								
							
							
						
						
									
										78
									
								
								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: 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 <Value> { | ||||
| 	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 <Value> { | ||||
| 	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 <Value> { | ||||
| 	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 <Value> = 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] | ||||
|  |  | |||
|  | @ -61,6 +61,12 @@ impl fmt::Display for Value { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl From <bool> for Value { | ||||
| 	fn from (x: bool) -> Self { | ||||
| 		Self::Boolean (x) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl From <String> for Value { | ||||
| 	fn from (x: String) -> Self { | ||||
| 		Self::String (x.into ()) | ||||
|  |  | |||
|  | @ -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 | ||||
		Loading…
	
		Reference in New Issue
	
	 _
						_