test: fix up some bugs to support an embedding example

main
_ 2023-10-01 19:50:50 -05:00
parent ffb1950f80
commit 700b273a11
9 changed files with 150 additions and 41 deletions

View File

@ -1,4 +1,5 @@
[workspace] [workspace]
resolver = "2"
members = [ members = [
"lunar_wave_cli", "lunar_wave_cli",
"lunar_wave_vm", "lunar_wave_vm",

View File

@ -1,5 +0,0 @@
fn main () -> Result <(), ()> {
println! ("Embedding");
Ok (())
}

View File

@ -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" }

View File

@ -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"]

View File

@ -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;

View File

@ -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::{

View File

@ -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,26 +780,30 @@ 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 ();
}
let value = &self.registers [offset]; // Shift inputs into place
let closure = if let Some (x) = value.as_closure () {
x let b = usize::from (*b);
let num_args = if b == 0 {
self.top - a
} }
else { else {
dbg! (self); b - 1
panic! ("OP_TAILCALL only implemented for closures");
}; };
let closure = closure.borrow ();
for i in (offset)..(offset + num_args) {
self.registers [i] = self.registers [i + a + 1].take ();
}
// Jump into the other function // Jump into the other function
@ -808,6 +814,50 @@ impl <'a> State <'a> {
// Skip the PC increment // Skip the PC increment
return Ok (None); 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");
},
}
},
Instruction::Test (a, k) => { Instruction::Test (a, k) => {
if self.reg (*a).is_truthy() != *k { if self.reg (*a).is_truthy() != *k {
next_pc += 1; next_pc += 1;

View File

@ -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,

View File

@ -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)]);
}