Files
not-python-rust/src/vm.rs

351 lines
12 KiB
Rust
Raw Normal View History

use std::sync::Arc;
use crate::builtins;
use crate::obj::*;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Op {
// Stack functions
Pop,
PushConstant(LongOpArg),
// Variables
GetLocal(LocalIndex),
SetLocal(LocalIndex),
GetGlobal(GlobalId),
SetGlobal(GlobalId),
// Attributes
GetAttr(ConstantId),
SetAttr(ConstantId),
// Jumps
Jump(JumpOpArg),
JumpFalse(JumpOpArg),
JumpTrue(JumpOpArg),
// Functions
Call(Argc),
Return,
CloseOver { depth: ShortOpArg, slot: ShortOpArg },
// VM control
Halt,
}
pub type LineRange = (usize, usize);
type ShortOpArg = u16;
type LongOpArg = u32;
pub type JumpOpArg = i32;
pub type LocalIndex = LongOpArg;
pub type LocalSlot = ShortOpArg;
pub type ConstantId = LongOpArg;
pub type GlobalId = LongOpArg;
pub type Argc = LongOpArg;
pub type FrameDepth = ShortOpArg;
#[derive(Debug, Clone)]
pub struct Local {
pub(crate) slot: LocalSlot,
pub(crate) index: LocalIndex,
pub(crate) name: String,
}
#[derive(Debug, Default, Clone)]
pub struct Chunk {
pub(crate) code: Vec<Op>,
pub(crate) lines: Vec<LineRange>,
pub(crate) locals: Vec<Local>,
}
#[derive(Debug)]
pub struct Frame {
pub(crate) name: Arc<String>,
pub(crate) chunk: Arc<Chunk>,
pub(crate) ip: usize,
pub(crate) stack_base: usize,
}
impl Frame {
pub fn new(name: Arc<String>, chunk: Arc<Chunk>, stack_base: usize) -> Self {
Self {
name,
chunk,
ip: 0,
stack_base,
}
}
}
pub struct Vm {
constants: Vec<ObjP>,
//global_names: Vec<String>,
globals: Vec<ObjP>,
stack: Vec<ObjP>,
frames: Vec<Frame>,
}
impl Vm {
/// Create a new virtual machine with the given chunk, constants, and global names.
pub fn new(chunk: Arc<Chunk>, constants: Vec<ObjP>, global_names: Vec<String>) -> Self {
// set up globals
let nil = NilInst::create();
let mut globals: Vec<_> = global_names
.iter()
.map(|_| Ptr::clone(&nil) as ObjP)
.collect();
let mut register_global = |name: &str, value: ObjP| {
let index = global_names
.iter()
.position(|global| global == name)
.expect("could not find global");
globals[index] = value;
};
register_global(
"print",
BuiltinFunctionInst::create("print".to_string(), builtins::print, 1),
);
register_global(
"println",
BuiltinFunctionInst::create("println".to_string(), builtins::println, 1),
);
// stack and frames
let stack = Vec::new();
let frames = vec![Frame::new("__main__".to_string().into(), chunk, 0)];
Vm {
constants,
//global_names,
globals,
stack,
frames,
}
}
/// Get the stack.
pub fn stack(&self) -> &Vec<ObjP> {
&self.stack
}
/// Current stack frame.
pub fn frame(&self) -> &Frame {
self.frames.last().unwrap()
}
/// Current stack frame, mutably.
pub fn frame_mut(&mut self) -> &mut Frame {
self.frames.last_mut().unwrap()
}
/// Push a new stack frame.
pub fn push_frame(&mut self, frame: Frame) {
self.frames.push(frame);
}
/// Pop the current stack frame.
pub fn pop_frame(&mut self) -> Frame {
self.frames.pop().expect("no frame")
}
/// Gets the chunk of the currently executing frame.
pub fn chunk(&self) -> &Chunk {
&self.frame().chunk
}
/// Instruction pointer of the current frame.
pub fn ip(&self) -> usize {
self.frame().ip
}
/// Update the current instruction pointer.
pub fn set_ip(&mut self, ip: usize) {
self.frame_mut().ip = ip;
}
/*
/// Gets the line of the current instruction.
fn line(&self, offset: isize) -> LineRange {
let index = (((self.ip() as isize) + offset).max(0) as usize).min(self.chunk().lines.len());
self.chunk().lines[index]
}
*/
/// Get the current instruction and advance the IP.
fn next(&mut self) -> Op {
let ip = self.ip();
self.set_ip(ip + 1);
self.chunk().code[ip]
}
/// Pop a value from the stack.
pub fn pop(&mut self) -> ObjP {
self.stack.pop().expect("stack empty")
}
/// Peek the top value of the stack.
pub fn peek(&self) -> ObjP {
self.stack.last().map(Ptr::clone).expect("stack empty")
}
/// Push a value to the stack.
pub fn push(&mut self, value: ObjP) {
self.stack.push(value);
}
pub fn run(&mut self) {
loop {
match self.next() {
Op::Pop => {
self.pop();
}
Op::PushConstant(constant_id) => {
let constant = Ptr::clone(&self.constants[constant_id as usize]);
self.push(constant);
}
Op::GetLocal(local_index) => {
let local = &self.chunk().locals[local_index as usize];
let value =
Ptr::clone(&self.stack[self.frame().stack_base + local.slot as usize]);
self.push(value);
}
Op::SetLocal(local_index) => {
let value = self.pop();
let local = &self.chunk().locals[local_index as usize];
let index = self.frame().stack_base + local.slot as usize;
self.stack[index] = value;
}
Op::GetGlobal(global_index) => {
let value = Ptr::clone(&self.globals[global_index as usize]);
self.push(value);
}
Op::SetGlobal(global_index) => {
let value = self.pop();
self.globals[global_index as usize] = value;
}
Op::GetAttr(constant_id) => {
// need both declarations to borrow cell value
let name_obj = Ptr::clone(&self.constants[constant_id as usize]);
let name =
with_obj_downcast(name_obj, |name: &StrInst| Arc::clone(&name.str_value()));
let owner = self.pop();
let value = owner.try_read().unwrap().get_attr(&name);
if let Some(value) = value {
self.push(value);
} else {
// TODO Vm::run, Op::GetAttr - throw an exception when the attribute
// doesn't exist
// BLOCKED-ON: exceptions
todo!(
"throw an error because we couldn't read attr '{}' on '{}'",
name,
owner.try_read().unwrap(),
);
}
}
Op::SetAttr(constant_id) => {
let name_obj = Ptr::clone(&self.constants[constant_id as usize]);
let name =
with_obj_downcast(name_obj, |name: &StrInst| Arc::clone(&name.str_value()));
let value = self.pop();
let target = self.pop();
let mut target_ptr = target.try_write().unwrap();
target_ptr.set_attr(&name, value);
}
Op::Jump(offset) => {
let base = (self.ip() - 1) as JumpOpArg;
assert!(base + offset > 0, "tried to jump to negative IP");
self.set_ip((base + offset) as usize);
}
Op::JumpFalse(offset) => {
let base = (self.ip() - 1) as JumpOpArg;
let value = self.peek();
if !value.try_read().unwrap().is_truthy() {
self.set_ip((base + offset) as usize);
}
}
Op::JumpTrue(offset) => {
let base = (self.ip() - 1) as JumpOpArg;
let value = self.peek();
if value.try_read().unwrap().is_truthy() {
self.set_ip((base + offset) as usize);
}
}
Op::Call(argc) => {
let argc = argc as usize;
let index = self.stack.len() - argc - 1;
let fun_ptr = Ptr::clone(&self.stack[index]);
let fun_ptr = fun_ptr.try_read().unwrap();
let arity = if let Some(arity) = fun_ptr.arity() {
arity as usize
} else {
// TODO Vm::run, Op::Call - throw an exception when the value isn't
// callable
// BLOCKED-ON: exceptions
todo!("throw an error because we couldn't call {}", fun_ptr);
};
// Methods with bound "self" parameter
// argc may be mutated
let mut argc = argc;
if let Some(method) = fun_ptr.as_any().downcast_ref::<MethodInst>() {
// shift all of the arguments over by one
// (duplicate the last item on the stack and then shift everyone else over)
self.stack
.insert(self.stack.len() - argc, Ptr::clone(method.self_binding()));
// also increment argc since we're specifying another arg
argc += 1;
}
// remove mutability
let argc = argc;
if arity != argc {
// TODO Vm::run, Op::Call - throw an exception when the number of arguments
// does not match the function's arity
// BLOCKED-ON: exceptions
todo!(
"throw an error because we passed the wrong number of arguments to {}",
fun_ptr
);
}
fun_ptr.call(self, argc as Argc);
}
Op::Return => {
let return_value = self.pop();
let old_frame = self.frames.pop().unwrap();
// stack_base is always going to be <= current stack size
self.stack
.resize_with(old_frame.stack_base, || unreachable!());
// also pop the function object off of the stack
self.stack.pop();
self.push(return_value);
}
Op::CloseOver { depth, slot } => {
// since we're closing over a value, and functions ultimately come from
// constants, we want to deep-clone this object so we don't alter any live
// objects.
// there is some room for optimization here so we aren't cloning the entire
// UserFunctionInst for every individual capture in a function.
let fun_ptr = self.pop();
let mut fun: UserFunctionInst =
with_obj_downcast(fun_ptr, UserFunctionInst::clone);
let frame_index = self.frames.len() - (depth as usize) - 1;
let stack_base = self.frames[frame_index].stack_base;
let value = Ptr::clone(&self.stack[stack_base + (slot as usize)]);
fun.push_capture(value);
self.push(make_ptr(fun));
}
Op::Halt => {
break;
}
}
}
}
}