🚧 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
	
	 _
						_