Files
lysp/src/vm/machine.rs
T

576 lines
19 KiB
Rust

use std::{cmp::Ordering, collections::HashMap, fmt, rc::Rc};
use crate::{
error::EvalError,
vm::{
instruction::{Comparison, ConstantId, Instruction, InstructionError},
module::{Module, ModuleConstant, ModuleRef},
stack::Stack,
value::{BytecodeFunction, NativeFunction, Value},
},
};
#[derive(Debug, thiserror::Error)]
pub enum MachineError {
#[error("Instruction error: {0}")]
Instruction(#[from] InstructionError),
#[error("Instruction out of bounds: {0}")]
InstructionOutOfBounds(InstructionPointer),
#[error("Instruction pointer is undefined")]
UndefinedInstructionPointer,
#[error("Data stack underflowed")]
ValueStackUnderflow,
#[error("Data stack overflowed")]
ValueStackOverflow,
#[error("Call stack underflowed")]
CallStackUnderflow,
#[error("Call stack overflowed")]
CallStackOverflow,
}
#[derive(Debug)]
pub enum EvalResult<T, E> {
Ok(T),
Err(ModuleRef, E),
LoadErr(E),
}
#[derive(Debug, Clone, PartialEq)]
pub struct InstructionPointer {
pub module: ModuleRef,
pub address: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CallFrame {
arguments: Vec<Value>,
return_address: InstructionPointer,
event: ExecutionEvent,
}
pub struct Machine {
globals: HashMap<Rc<str>, Value>,
ip: Option<InstructionPointer>,
value_stack: Stack<Value>,
call_stack: Stack<CallFrame>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ExecutionEvent {
ModuleEntry(ModuleRef),
None,
}
impl Default for Machine {
fn default() -> Self {
Self {
globals: Default::default(),
ip: None,
value_stack: Stack::new(1024),
call_stack: Stack::new(32),
}
}
}
impl Machine {
fn pop(&mut self) -> Result<Value, MachineError> {
self.value_stack
.pop()
.ok_or(MachineError::ValueStackUnderflow)
}
fn push(&mut self, value: Value) -> Result<(), MachineError> {
self.value_stack
.push(value)
.map_err(|_| MachineError::ValueStackOverflow)
}
fn execute_call(&mut self, count: usize) -> Result<(), MachineError> {
enum Callee {
Bytecode(BytecodeFunction),
Native(NativeFunction),
}
let source_ip = self.ip.clone().unwrap();
let callee = self.pop()?;
let callee = match callee {
Value::BytecodeFunction(bytecode) => Callee::Bytecode(bytecode),
Value::NativeFunction(native) => Callee::Native(native),
_ => todo!(),
};
let mut arguments = vec![];
for _ in 0..count {
arguments.push(self.pop()?);
}
match callee {
Callee::Bytecode(bytecode) => {
let BytecodeFunction { module, address } = bytecode;
let frame = CallFrame {
arguments,
event: ExecutionEvent::None,
return_address: InstructionPointer {
module: source_ip.module,
address: source_ip.address + 1,
},
};
if self.call_stack.push(frame).is_err() {
return Err(MachineError::CallStackOverflow);
}
self.ip = Some(InstructionPointer { module, address });
}
Callee::Native(native) => {
let result = native.apply(self, &arguments).unwrap();
self.push(result)?;
self.ip = Some(InstructionPointer {
module: source_ip.module,
address: source_ip.address + 1,
});
}
}
Ok(())
}
fn execute_return(&mut self) -> Result<ExecutionEvent, MachineError> {
let ip = self.ip.clone().unwrap();
if let Some(frame) = self.call_stack.pop() {
self.ip = Some(frame.return_address);
Ok(frame.event)
} else {
self.ip = None;
Ok(ExecutionEvent::ModuleEntry(ip.module))
}
}
fn execute_add(&mut self, count: usize) -> Result<(), MachineError> {
let mut accumulator = 0i64;
for _ in 0..count {
let arg = self.pop()?;
match arg {
Value::Integer(value) => {
accumulator = accumulator.wrapping_add(value);
}
_ => todo!("{arg:?}"),
}
}
self.push(Value::Integer(accumulator))?;
Ok(())
}
fn execute_sub(&mut self, count: usize) -> Result<(), MachineError> {
todo!()
}
fn execute_compare(
&mut self,
not: bool,
cmp: Comparison,
count: usize,
) -> Result<(), MachineError> {
fn ordering(a: &Value, b: &Value) -> Ordering {
match (a, b) {
(Value::Integer(a), Value::Integer(b)) => Ord::cmp(a, b),
_ => todo!(),
}
}
// TODO checks
let values = (0..count)
.map(|_| self.pop())
.collect::<Result<Vec<_>, _>>()?;
let mut accumulator = true;
for i in 0..values.len() - 1 {
let a = &values[i];
let b = &values[i + 1];
let ord = ordering(a, b);
let check = match (not, cmp) {
(true, Comparison::Gt) => ord.is_le(),
(false, Comparison::Gt) => ord.is_gt(),
(true, Comparison::Eq) => ord.is_ne(),
(false, Comparison::Eq) => ord.is_eq(),
(true, Comparison::Lt) => ord.is_ge(),
(false, Comparison::Lt) => ord.is_lt(),
};
accumulator &= check;
}
self.push(Value::Boolean(accumulator))?;
Ok(())
}
fn execute_branch(&mut self, offset: usize) -> Result<bool, MachineError> {
let value = self.pop()?;
let do_branch = match value {
Value::Boolean(true) => false,
Value::Boolean(false) => true,
Value::Nil => todo!(),
_ => todo!(),
};
if do_branch {
self.execute_jump(offset)?;
}
Ok(!do_branch)
}
fn execute_jump(&mut self, offset: usize) -> Result<(), MachineError> {
let ip = self.ip.clone().unwrap();
self.ip = Some(InstructionPointer {
module: ip.module,
address: offset,
});
Ok(())
}
fn execute_push_constant(&mut self, index: ConstantId) -> Result<(), MachineError> {
let ip = self.ip.as_ref().unwrap();
let constant = ip.module.constant(index).expect("TODO");
let value = match constant {
ModuleConstant::LocalFunction(address) => Value::BytecodeFunction(BytecodeFunction {
module: ip.module.clone(),
address,
}),
ModuleConstant::Integer(value) => Value::Integer(value),
ModuleConstant::Identifier(identifier) => Value::Identifier(identifier),
};
self.push(value)
}
fn execute_push_argument(&mut self, index: usize) -> Result<(), MachineError> {
let frame = self.call_stack.current().expect("valid call frame");
let argument = frame.arguments.get(index);
match argument {
Some(arg) => self.push(arg.clone()),
None => self.push(Value::Nil),
}
}
fn execute_get_global(&mut self) -> Result<(), MachineError> {
let ident = self.pop()?;
match ident {
Value::Identifier(ident) => {
let value = self.globals.get(&ident).cloned().unwrap();
self.push(value)
}
_ => todo!(),
}
}
fn execute_set_global(&mut self) -> Result<(), MachineError> {
let ident = self.pop()?;
let value = self.pop()?;
let Value::Identifier(ident) = ident else {
todo!();
};
self.globals.insert(ident, value);
self.push(Value::Nil)?;
Ok(())
}
pub fn set_global<S: Into<Rc<str>>>(&mut self, identifier: S, value: Value) {
self.globals.insert(identifier.into(), value);
}
pub fn execute_next(&mut self) -> Result<ExecutionEvent, MachineError> {
let ip = self.ip.clone().unwrap();
let instruction = ip
.module
.instruction(ip.address)
.ok_or_else(|| MachineError::InstructionOutOfBounds(ip.clone()))?;
let instruction = Instruction::try_from(instruction)?;
eprintln!("{ip}: {instruction:?}");
let mut advance = true;
let mut event = ExecutionEvent::None;
match instruction {
Instruction::PushNil => {
self.push(Value::Nil)?;
}
Instruction::PushInteger(value) => {
self.push(Value::Integer(value.sign_extend_i64()))?;
}
Instruction::PushBool(value) => {
self.push(Value::Boolean(value))?;
}
Instruction::PushConstant(index) => {
self.execute_push_constant(index)?;
}
Instruction::PushArgument(index) => {
self.execute_push_argument(index.into())?;
}
Instruction::Drop => {
self.pop()?;
}
Instruction::GetGlobal => {
self.execute_get_global()?;
}
Instruction::SetGlobal => {
self.execute_set_global()?;
}
Instruction::Return => {
advance = false;
event = self.execute_return()?;
}
Instruction::Call(count) => {
advance = false;
self.execute_call(count.into())?;
}
Instruction::Add(count) => {
self.execute_add(count.into())?;
}
Instruction::Sub(count) => {
self.execute_sub(count.into())?;
}
Instruction::Branch(offset) => {
advance = self.execute_branch(offset.into())?;
}
Instruction::Jump(offset) => {
advance = false;
self.execute_jump(offset.into())?;
}
Instruction::Compare(not, cmp, count) => {
self.execute_compare(not, cmp, count.into())?;
}
}
if advance {
self.ip = Some(InstructionPointer {
module: ip.module,
address: ip.address + 1,
});
}
Ok(event)
}
pub fn load_module(&mut self, module: ModuleRef) -> Result<ModuleRef, MachineError> {
let entry = module.entry();
let entry_ip = InstructionPointer {
module: module.clone(),
address: entry,
};
if let Some(ip) = self.ip.clone()
&& self
.call_stack
.push(CallFrame {
arguments: vec![],
return_address: ip,
event: ExecutionEvent::ModuleEntry(module.clone()),
})
.is_err()
{
return Err(MachineError::CallStackOverflow);
}
self.ip = Some(entry_ip);
Ok(module)
}
pub fn eval_module(&mut self, module: ModuleRef) -> EvalResult<Value, MachineError> {
let module = match self.load_module(module) {
Ok(module) => module,
Err(error) => return EvalResult::LoadErr(error),
};
let expect = ExecutionEvent::ModuleEntry(module.clone());
loop {
let event = match self.execute_next() {
Ok(event) => event,
Err(error) => return EvalResult::Err(module, error),
};
if event == expect {
break;
}
}
match self.pop() {
Ok(value) => EvalResult::Ok(value),
Err(error) => EvalResult::Err(module, error),
}
}
pub fn eval_value(&mut self, value: &Value) -> EvalResult<Value, EvalError> {
let module = match Module::compile_value(value) {
Ok(module) => module,
Err(error) => return EvalResult::LoadErr(error.into()),
};
let module = ModuleRef::from(module);
match self.eval_module(module) {
EvalResult::Ok(value) => EvalResult::Ok(value),
EvalResult::Err(module, error) => EvalResult::Err(module, error.into()),
EvalResult::LoadErr(error) => EvalResult::LoadErr(error.into()),
}
}
}
impl<T, E> EvalResult<T, E> {
pub fn unwrap(self) -> T
where
E: fmt::Display,
{
match self {
Self::Ok(value) => value,
Self::Err(module, error) => {
panic!("Unwrap called on module {module:p} error: {error}");
}
Self::LoadErr(error) => {
panic!("Unwrap called on error: {error}");
}
}
}
}
impl fmt::Display for InstructionPointer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:p}:{}", self.module, self.address)
}
}
#[cfg(test)]
mod tests {
use std::sync::atomic::{AtomicI64, Ordering};
use crate::vm::{
instruction::{Instruction, U},
machine::{InstructionPointer, Machine},
module::{Module, ModuleBuilder, ModuleConstant, ModuleRef},
value::{NativeFunction, Value},
};
fn execute_all<F: Fn(u32, &mut ModuleBuilder), G: FnOnce(&mut Machine)>(
count: usize,
build: F,
prepare: G,
) -> (Machine, Vec<Value>) {
let dummy = ModuleRef::from(Module::dummy());
let mut machine = Machine {
ip: Some(InstructionPointer {
module: dummy,
address: 0,
}),
..Default::default()
};
prepare(&mut machine);
let mut values = vec![];
for i in 0..count {
let mut builder = ModuleBuilder::new();
builder.entry(0);
build(i as u32, &mut builder);
builder.add(Instruction::Return);
let module = builder.build();
values.push(machine.eval_module(module.into()).unwrap());
}
(machine, values)
}
#[test]
fn test_basic() {
let (m, vs) = execute_all(
1,
|_, builder| {
let c0 = builder.constant(ModuleConstant::Integer(3));
builder.add_all([
Instruction::PushInteger(U::truncate(1)),
Instruction::PushInteger(U::truncate(2)),
Instruction::Add(U::truncate(2)),
Instruction::PushConstant(c0),
Instruction::Add(U::truncate(2)),
]);
},
|_| {},
);
assert!(m.value_stack.is_empty());
assert!(m.call_stack.is_empty());
assert_eq!(&vs, &[Value::Integer(6)]);
}
#[test]
fn test_local_function_call() {
let (m, vs) = execute_all(
1,
|_, builder| {
let c0 = builder.constant(ModuleConstant::LocalFunction(4));
builder.add_all([
// main
Instruction::PushInteger(U::truncate(34)),
Instruction::PushConstant(c0),
Instruction::Call(U::truncate(1)),
Instruction::Return,
// c0
Instruction::PushArgument(U::truncate(0)),
Instruction::PushInteger(U::truncate(1200)),
Instruction::Add(U::truncate(2)),
Instruction::Return,
]);
},
|_| {},
);
assert!(m.value_stack.is_empty());
assert!(m.call_stack.is_empty());
assert_eq!(&vs, &[Value::Integer(1234)]);
}
#[test]
fn test_cross_module_call() {
static NATIVE_STATE: AtomicI64 = AtomicI64::new(-1);
let (m, vs) = execute_all(
2,
|id, builder| match id {
1 => {
let c0 = builder.constant(ModuleConstant::LocalFunction(4));
let c1 = builder.constant(ModuleConstant::Identifier("extern-function".into()));
builder.add_all([
// main: (local 1)
Instruction::PushInteger(U::truncate(1)),
Instruction::PushConstant(c0),
Instruction::Call(U::truncate(1)),
Instruction::Return,
// module 0 local function
// (fn (a) (extern-function a 2))
Instruction::PushInteger(U::truncate(2)),
Instruction::PushArgument(U::truncate(0)),
Instruction::PushConstant(c1),
Instruction::GetGlobal,
Instruction::Call(U::truncate(2)),
Instruction::Return,
]);
}
0 => {
let c0 = builder.constant(ModuleConstant::Integer(3));
let c1 = builder.constant(ModuleConstant::Identifier("native".into()));
let c2 = builder.constant(ModuleConstant::LocalFunction(4));
let c3 = builder.constant(ModuleConstant::Identifier("extern-function".into()));
builder.add_all([
// main
Instruction::PushConstant(c2),
Instruction::PushConstant(c3),
Instruction::SetGlobal,
Instruction::Return,
// extern-function
// (fn (a b) (native 3 b a))
Instruction::PushArgument(U::truncate(0)),
Instruction::PushArgument(U::truncate(1)),
Instruction::PushConstant(c0),
Instruction::PushConstant(c1),
Instruction::GetGlobal,
Instruction::Call(U::truncate(3)),
Instruction::Return,
]);
}
_ => unreachable!(),
},
|m| {
m.set_global(
"native",
Value::NativeFunction(NativeFunction::new("native", |_, args| {
assert_eq!(args.len(), 3);
assert_eq!(
&args,
&[Value::Integer(3), Value::Integer(2), Value::Integer(1)]
);
NATIVE_STATE.store(4321, Ordering::Release);
Ok(Value::Integer(1234))
})),
);
},
);
assert!(m.value_stack.is_empty());
assert!(m.call_stack.is_empty());
assert_eq!(&vs, &[Value::Nil, Value::Integer(1234)]);
assert_eq!(NATIVE_STATE.load(Ordering::Acquire), 4321);
}
}