//! Provide the virtual machine
use std::{cell::RefCell, collections::HashMap};
use gc_arena::{Arena, Collect, Gc, Rootable};
use crate::{
bytecode::{ByteCode, OpCode},
value::{ListCell, Primitive, SymbolId, SymbolTable},
};
type Memory = Rootable![Vec<StackFrame<'_>>];
/// Virtual machine
pub struct Vm {
mem: Arena<Memory>,
/// Main block for execution
block: Block,
// TODO Allow for "sub-blocks" to reference other
// bytecode complied modules
}
/// Block of bytecode that supports multiple labels
pub struct Block {
code: Box<[ByteCode]>,
labels: HashMap<SymbolId, usize>,
/// Symbol table for all bytecode functions in this block
symbol_table: RefCell<SymbolTable>,
}
impl Block {
fn get_offset<S: AsRef<str>>(&self, name: S) -> Option<usize> {
self.labels
.get(&self.symbol_table.borrow_mut().id(name))
.copied()
}
fn get(&self, idx: usize) -> Option<ByteCode> {
self.code.get(idx).copied()
}
}
/// Meant to represent a full stack frame
#[derive(Debug, Collect)]
#[collect(no_drop)]
struct StackFrame<'gc> {
stack: Vec<Gc<'gc, Primitive<'gc>>>,
srl_registers: [Option<Gc<'gc, ListCell<'gc>>>; 255],
inst_ptr: usize,
}
impl Vm {
/// Create a new VM using code compiled into a [`Block`]
pub fn new(block: Block) -> Self {
let mem = Arena::new(|_mc| {
vec![StackFrame {
stack: vec![],
srl_registers: [None; 255],
// Get the offset to a label "_start", or start at the first instruction
inst_ptr: block.get_offset("_start").unwrap_or(0),
}]
});
Self { mem, block }
}
/// Execute from the given stack frame, where all values that would be returned from the
/// top-level of the script are passed to a "continuation"
pub fn execute<C>(&mut self, mut continuation: C) -> Result<(), ()>
where
C: FnMut(Primitive),
{
loop {
let Some((inst, inst_ptr)) = self.mem.mutate(|mc, root| {
root.last()
.and_then(|frame| self.block.get(frame.inst_ptr).map(|i| (i, frame.inst_ptr)))
}) else {
// Invalid bytecode location. ERROR!!!
// TODO Have an error type that can capture a VM
// stack trace for debugging
break Err(());
};
let mut next_ptr = inst_ptr + 1;
match inst.op_code {
OpCode::NoOp => {}
OpCode::PushInt => todo!(),
OpCode::PushSignedInt => todo!(),
OpCode::PushConst => todo!(),
OpCode::PushList => todo!(),
OpCode::CreateList => todo!(),
OpCode::Call => {
let a = inst.arguments.a() as usize;
let Some(jump_ptr) = self.mem.mutate_root(|_mc, root| {
root.last_mut().and_then(|frame| {
if a > frame.stack.len() {
None
} else {
let arguments: Vec<_> = frame
.stack
.drain(frame.stack.len().saturating_sub(a)..)
.collect();
let proc = frame.stack.pop();
proc.map(|prim| {
// If the inner primitive is a raw procedure (a procedure written in Rust),
// run it.
// If the inner primtive is a "bytecode reference"
// - check if we're in the right block, if so, return Some(label location)
// - if not, switch blocks, then return Some(label location)
todo!()
})
}
})
}) else {
// jump to location not found.
break Err(());
};
next_ptr = jump_ptr;
}
OpCode::TailCall => todo!(),
OpCode::Return => todo!(),
OpCode::LoadLocal => todo!(),
OpCode::StoreLocal => todo!(),
}
// Set the current frame's inst_ptr to next_ptr
if !self.mem.mutate_root(|_mc, root| {
if let Some(last) = root.last_mut() {
last.inst_ptr = next_ptr;
true
} else {
false
}
}) {
// The stack frame doesn't exist to continue executing...
// For now, assume that code is done, and exit with an Ok!
break Ok(());
}
}
}
}