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