✅ test: fix up some bugs to support an embedding example
							parent
							
								
									ffb1950f80
								
							
						
					
					
						commit
						700b273a11
					
				|  | @ -1,4 +1,5 @@ | ||||||
| [workspace] | [workspace] | ||||||
|  | resolver = "2" | ||||||
| members = [ | members = [ | ||||||
| 	"lunar_wave_cli", | 	"lunar_wave_cli", | ||||||
| 	"lunar_wave_vm", | 	"lunar_wave_vm", | ||||||
|  |  | ||||||
|  | @ -1,5 +0,0 @@ | ||||||
| fn main () -> Result <(), ()> { |  | ||||||
| 	println! ("Embedding"); |  | ||||||
| 	
 |  | ||||||
| 	Ok (()) |  | ||||||
| } |  | ||||||
|  | @ -3,7 +3,7 @@ name = "lunar_wave_cli" | ||||||
| description = "A Lua CLI implementation" | description = "A Lua CLI implementation" | ||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| edition = "2021" | edition = "2021" | ||||||
| author = "ReactorScram" | authors = ["ReactorScram"] | ||||||
| 
 | 
 | ||||||
| [dependencies] | [dependencies] | ||||||
| lunar_wave_vm = { path = "../lunar_wave_vm" } | lunar_wave_vm = { path = "../lunar_wave_vm" } | ||||||
|  |  | ||||||
|  | @ -3,4 +3,4 @@ name = "lunar_wave_vm" | ||||||
| description = "A Lua virtual machine implementation" | description = "A Lua virtual machine implementation" | ||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| edition = "2021" | edition = "2021" | ||||||
| author = "ReactorScram" | authors = ["ReactorScram"] | ||||||
|  |  | ||||||
|  | @ -4,11 +4,13 @@ mod state; | ||||||
| mod value; | mod value; | ||||||
| 
 | 
 | ||||||
| pub use loader::compile_bytecode_from_file as compile_bytecode_from_file; | pub use loader::compile_bytecode_from_file as compile_bytecode_from_file; | ||||||
|  | pub use loader::compile_bytecode_from_stdin as compile_bytecode_from_stdin; | ||||||
| pub use loader::parse_chunk as parse_chunk; | pub use loader::parse_chunk as parse_chunk; | ||||||
| pub use state::Breakpoint as Breakpoint; | pub use state::Breakpoint as Breakpoint; | ||||||
| pub use state::State as State; | pub use state::State as State; | ||||||
| pub use state::StepError as StepError; | pub use state::StepError as StepError; | ||||||
| pub use state::StepOutput as StepOutput; | pub use state::StepOutput as StepOutput; | ||||||
|  | pub use value::Value as Value; | ||||||
| 
 | 
 | ||||||
| #[cfg (test)] | #[cfg (test)] | ||||||
| mod tests; | mod tests; | ||||||
|  |  | ||||||
|  | @ -36,7 +36,7 @@ pub fn compile_bytecode_from_file (path: &str) -> Vec <u8> { | ||||||
| /// 
 | /// 
 | ||||||
| /// `source` is a Vec because we move it to a worker thread
 | /// `source` is a Vec because we move it to a worker thread
 | ||||||
| 
 | 
 | ||||||
| pub (crate) fn compile_bytecode_from_stdin (source: Vec <u8>) -> Vec <u8> { | pub fn compile_bytecode_from_stdin (source: Vec <u8>) -> Vec <u8> { | ||||||
| 	use std::{ | 	use std::{ | ||||||
| 		io::Write, | 		io::Write, | ||||||
| 		process::{ | 		process::{ | ||||||
|  |  | ||||||
|  | @ -206,12 +206,12 @@ impl <'a> State <'a> { | ||||||
| 	
 | 	
 | ||||||
| 	/// Short form to get access to a register within our window
 | 	/// Short form to get access to a register within our window
 | ||||||
| 	
 | 	
 | ||||||
| 	fn reg (&self, i: u8) -> &Value { | 	pub fn reg (&self, i: u8) -> &Value { | ||||||
| 		let frame = self.stack.last ().unwrap (); | 		let frame = self.stack.last ().unwrap (); | ||||||
| 		&self.registers [frame.register_offset + i as usize] | 		&self.registers [frame.register_offset + i as usize] | ||||||
| 	} | 	} | ||||||
| 	
 | 	
 | ||||||
| 	fn reg_mut (&mut self, i: u8) -> &mut Value { | 	pub fn reg_mut (&mut self, i: u8) -> &mut Value { | ||||||
| 		let frame = self.stack.last ().unwrap (); | 		let frame = self.stack.last ().unwrap (); | ||||||
| 		&mut self.registers [frame.register_offset + i as usize] | 		&mut self.registers [frame.register_offset + i as usize] | ||||||
| 	} | 	} | ||||||
|  | @ -330,22 +330,24 @@ impl <'a> State <'a> { | ||||||
| 					Value::RsFunc (x) => { | 					Value::RsFunc (x) => { | ||||||
| 						let current_frame = self.stack.last ().unwrap (); | 						let current_frame = self.stack.last ().unwrap (); | ||||||
| 						let new_offset = current_frame.register_offset + usize::from (*a) + 1; | 						let new_offset = current_frame.register_offset + usize::from (*a) + 1; | ||||||
|  | 						
 | ||||||
|  | 						// Trash the stack frame so it doesn't point to a
 | ||||||
|  | 						// valid Lua function
 | ||||||
| 						self.stack.push (StackFrame { | 						self.stack.push (StackFrame { | ||||||
| 							program_counter: 65535, // Bogus for native functions
 | 							program_counter: 65535, // Bogus for native functions
 | ||||||
| 							block_idx: 65535, // Bogus
 | 							block_idx: 65535, // Bogus
 | ||||||
| 							register_offset: new_offset, | 							register_offset: new_offset, | ||||||
| 						}); | 						}); | ||||||
| 						
 | 						
 | ||||||
| 						// No clue what the '1' is doing here
 | 						let num_args = if b == 0 { | ||||||
| 						let b = if b == 0 { |  | ||||||
| 							self.top - *a as usize | 							self.top - *a as usize | ||||||
| 						} | 						} | ||||||
| 						else { | 						else { | ||||||
| 							b | 							b - 1 | ||||||
| 						}; | 						}; | ||||||
| 						
 | 						
 | ||||||
| 						// Call
 | 						// Call
 | ||||||
| 						let num_results = x (self, b - 1); | 						let num_results = x (self, num_args); | ||||||
| 						
 | 						
 | ||||||
| 						let popped_frame = self.stack.pop ().unwrap (); | 						let popped_frame = self.stack.pop ().unwrap (); | ||||||
| 						let offset = popped_frame.register_offset - 1; | 						let offset = popped_frame.register_offset - 1; | ||||||
|  | @ -678,7 +680,7 @@ impl <'a> State <'a> { | ||||||
| 					self.top = popped_frame.register_offset - 1 + b - 1; | 					self.top = popped_frame.register_offset - 1 + b - 1; | ||||||
| 				} | 				} | ||||||
| 				else { | 				else { | ||||||
| 					// Return from the entire program
 | 					// Return from the entire chunk
 | ||||||
| 					return Ok (Some (StepOutput::ChunkReturned (self.registers [a..(a + b - 1)].to_vec()))); | 					return Ok (Some (StepOutput::ChunkReturned (self.registers [a..(a + b - 1)].to_vec()))); | ||||||
| 				} | 				} | ||||||
| 			}, | 			}, | ||||||
|  | @ -778,35 +780,83 @@ impl <'a> State <'a> { | ||||||
| 				
 | 				
 | ||||||
| 				*self.reg_mut (*a) = x; | 				*self.reg_mut (*a) = x; | ||||||
| 			}, | 			}, | ||||||
| 			Instruction::TailCall (a, b, _c, k) => { | 			Instruction::TailCall (a, b, c, k) => { | ||||||
|  | 				let a = usize::from (*a); | ||||||
| 				assert! (!k, "closing over values in tail calls not implemented"); | 				assert! (!k, "closing over values in tail calls not implemented"); | ||||||
| 				
 | 				
 | ||||||
| 				// Shift closure and inputs into place
 | 				let offset = frame.register_offset; | ||||||
| 				let a = usize::from (*a); | 				let value = self.registers [offset + a].take (); | ||||||
| 				let b = usize::from (*b); | 				match value { | ||||||
| 				let offset = frame.register_offset - 1; | 					Value::BogusClosure (closure) => { | ||||||
| 				for i in (offset)..(offset + b) { | 						let closure = closure.borrow (); | ||||||
| 					self.registers [i] = self.registers [i + a + 1].take (); | 						
 | ||||||
|  | 						// Shift inputs into place
 | ||||||
|  | 						
 | ||||||
|  | 						let b = usize::from (*b); | ||||||
|  | 						
 | ||||||
|  | 						let num_args = if b == 0 { | ||||||
|  | 							self.top - a | ||||||
|  | 						} | ||||||
|  | 						else { | ||||||
|  | 							b - 1 | ||||||
|  | 						}; | ||||||
|  | 						
 | ||||||
|  | 						for i in (offset)..(offset + num_args) { | ||||||
|  | 							self.registers [i] = self.registers [i + a + 1].take (); | ||||||
|  | 						} | ||||||
|  | 						
 | ||||||
|  | 						// Jump into the other function
 | ||||||
|  | 						
 | ||||||
|  | 						let frame = self.stack.last_mut ().unwrap (); | ||||||
|  | 						frame.program_counter = 0; | ||||||
|  | 						frame.block_idx = closure.idx; | ||||||
|  | 						
 | ||||||
|  | 						// Skip the PC increment
 | ||||||
|  | 						return Ok (None); | ||||||
|  | 					}, | ||||||
|  | 					Value::RsFunc (x) => { | ||||||
|  | 						// Shift inputs into place
 | ||||||
|  | 						let b = usize::from (*b); | ||||||
|  | 						for i in (offset)..(offset + b) { | ||||||
|  | 							self.registers [i] = self.registers [i + a + 1].take (); | ||||||
|  | 						} | ||||||
|  | 						
 | ||||||
|  | 						let frame = self.stack.last_mut ().unwrap (); | ||||||
|  | 						// Trash the stack frame so it doesn't point
 | ||||||
|  | 						// to any valid Lua function
 | ||||||
|  | 						*frame = StackFrame { | ||||||
|  | 							block_idx: 65535, | ||||||
|  | 							program_counter: 65535, | ||||||
|  | 							register_offset: offset, | ||||||
|  | 						}; | ||||||
|  | 						
 | ||||||
|  | 						let num_args = if b == 0 { | ||||||
|  | 							self.top - a | ||||||
|  | 						} | ||||||
|  | 						else { | ||||||
|  | 							b - 1 | ||||||
|  | 						}; | ||||||
|  | 						
 | ||||||
|  | 						// Call
 | ||||||
|  | 						let num_results = x (self, num_args); | ||||||
|  | 						let popped_frame = self.stack.pop ().unwrap (); | ||||||
|  | 						
 | ||||||
|  | 						if self.stack.is_empty () { | ||||||
|  | 							// The whole chunk is exiting
 | ||||||
|  | 							return Ok (Some (StepOutput::ChunkReturned (self.registers [a..(a + num_results)].to_vec()))); | ||||||
|  | 						} | ||||||
|  | 						else { | ||||||
|  | 							// Set up top for the next call
 | ||||||
|  | 							if *c == 0 { | ||||||
|  | 								self.top = popped_frame.register_offset - 1 + num_results; | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  | 					}, | ||||||
|  | 					_ => { | ||||||
|  | 						dbg! (&self.stack); | ||||||
|  | 						panic! ("OP_TAILCALL argument must be a function"); | ||||||
|  | 					}, | ||||||
| 				} | 				} | ||||||
| 				
 |  | ||||||
| 				let value = &self.registers [offset]; |  | ||||||
| 				let closure = if let Some (x) = value.as_closure () { |  | ||||||
| 					x |  | ||||||
| 				} |  | ||||||
| 				else { |  | ||||||
| 					dbg! (self); |  | ||||||
| 					panic! ("OP_TAILCALL only implemented for closures"); |  | ||||||
| 				}; |  | ||||||
| 				let closure = closure.borrow (); |  | ||||||
| 				
 |  | ||||||
| 				// Jump into the other function
 |  | ||||||
| 				
 |  | ||||||
| 				let frame = self.stack.last_mut ().unwrap (); |  | ||||||
| 				frame.program_counter = 0; |  | ||||||
| 				frame.block_idx = closure.idx; |  | ||||||
| 				
 |  | ||||||
| 				// Skip the PC increment
 |  | ||||||
| 				return Ok (None); |  | ||||||
| 			}, | 			}, | ||||||
| 			Instruction::Test (a, k) => { | 			Instruction::Test (a, k) => { | ||||||
| 				if self.reg (*a).is_truthy() != *k { | 				if self.reg (*a).is_truthy() != *k { | ||||||
|  |  | ||||||
|  | @ -359,6 +359,25 @@ fn tables_2 () { | ||||||
| 	run_source (&[], src); | 	run_source (&[], src); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #[test] | ||||||
|  | fn tailcall () { | ||||||
|  | 	use crate::instruction::Instruction; | ||||||
|  | 	
 | ||||||
|  | 	let src = r#" | ||||||
|  | 	return tonumber ("5") | ||||||
|  | 	"#;
 | ||||||
|  | 	
 | ||||||
|  | 	let bc = loader::compile_bytecode_from_stdin (src.as_bytes ().to_vec ()); | ||||||
|  | 	let chunk = loader::parse_chunk_from_bytes (&bc).unwrap (); | ||||||
|  | 	
 | ||||||
|  | 	assert_eq! (chunk.blocks [0].instructions [3], Instruction::TailCall (0, 2, 1, false)); | ||||||
|  | 	
 | ||||||
|  | 	let actual = run_chunk (&[], &chunk); | ||||||
|  | 	let expected = vec! [Value::from (5)]; | ||||||
|  | 	
 | ||||||
|  | 	assert_eq! (actual, expected); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| #[test] | #[test] | ||||||
| fn value_size () { | fn value_size () { | ||||||
| 	// Per https://www.lua.org/doc/jucs05.pdf,
 | 	// Per https://www.lua.org/doc/jucs05.pdf,
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,42 @@ | ||||||
|  | use lunar_wave_vm as lwvm; | ||||||
|  | 
 | ||||||
|  | #[test] | ||||||
|  | fn embedding () { | ||||||
|  | 	use lwvm::{ | ||||||
|  | 		State, | ||||||
|  | 		Value, | ||||||
|  | 	}; | ||||||
|  | 	
 | ||||||
|  | 	let src = br#" | ||||||
|  | 	return host_lib.add (14, 12) | ||||||
|  | 	"#;
 | ||||||
|  | 	
 | ||||||
|  | 	fn host_add (l: &mut State, num_args: usize) -> usize { | ||||||
|  | 		assert_eq! (num_args, 2); | ||||||
|  | 		let a = l.reg (0).as_int ().unwrap (); | ||||||
|  | 		let b = l.reg (1).as_int ().unwrap (); | ||||||
|  | 		*l.reg_mut (0) = Value::from (a + b + 1993); | ||||||
|  | 		1 | ||||||
|  | 	} | ||||||
|  | 	
 | ||||||
|  | 	let bytecode = lwvm::compile_bytecode_from_stdin (src.to_vec ()); | ||||||
|  | 	let mut rdr = std::io::Cursor::new (bytecode); | ||||||
|  | 	let chunk = lwvm::parse_chunk (&mut rdr).unwrap (); | ||||||
|  | 	
 | ||||||
|  | 	let host_lib = [ | ||||||
|  | 		("add", Value::RsFunc (host_add)), | ||||||
|  | 	].into_iter ().map (|(k, v)| (k.to_string (), v)); | ||||||
|  | 	
 | ||||||
|  | 	let env = [ | ||||||
|  | 		("host_lib", Value::from_iter (host_lib.into_iter ())), | ||||||
|  | 	].into_iter ().map (|(k, v)| (k.to_string (), v)); | ||||||
|  | 	
 | ||||||
|  | 	let upvalues = vec! [ | ||||||
|  | 		Value::from_iter (env.into_iter ()), | ||||||
|  | 	]; | ||||||
|  | 	
 | ||||||
|  | 	let mut vm = State::new (&chunk, &upvalues); | ||||||
|  | 	let output = vm.execute_chunk (&vec! []).unwrap (); | ||||||
|  | 	
 | ||||||
|  | 	assert_eq! (output, vec! [Value::from (2019)]); | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue
	
	 _
						_