576 lines
19 KiB
Rust
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);
|
|
}
|
|
}
|