From fdbb0a130791a16cf55966d2a159e47d5debeff4 Mon Sep 17 00:00:00 2001 From: Alek Ratzloff Date: Mon, 14 Sep 2020 16:32:00 -0700 Subject: [PATCH] Add some base VM implementations Some instructions are currently implemented. Others are not. This is mostly just a checkpoint so I can implement lexical name definitions. Signed-off-by: Alek Ratzloff --- src/compile/error.rs | 1 + src/compile/thunk.rs | 6 ++ src/lib.rs | 4 +- src/obj/fun.rs | 3 + src/obj/mod.rs | 6 +- src/obj/names.rs | 2 +- src/obj/sym.rs | 3 +- src/vm/error.rs | 9 +++ src/vm/frame.rs | 54 +++++++++++++++-- src/vm/inst.rs | 2 +- src/vm/mod.rs | 139 +++++++++++++++++++++++++++++++++++++++---- 11 files changed, 205 insertions(+), 24 deletions(-) create mode 100644 src/vm/error.rs diff --git a/src/compile/error.rs b/src/compile/error.rs index 04fd678..a8d8974 100644 --- a/src/compile/error.rs +++ b/src/compile/error.rs @@ -10,3 +10,4 @@ pub enum Error { } pub type Result = std::result::Result; + diff --git a/src/compile/thunk.rs b/src/compile/thunk.rs index b207dd7..7e72ae3 100644 --- a/src/compile/thunk.rs +++ b/src/compile/thunk.rs @@ -206,6 +206,12 @@ impl Visit for CompileBody<'_, '_> { fn visit_atom(&mut self, atom: &Atom) -> Self::Out { let thunk = match atom { Atom::Ident(ident) => { + // TODO : set up lexical name stack + // - think about how lexical names in function defs need to be captured, so no + // references get prematurely GC'd + // - Python uses LOAD_CLOSURE and CREATE_FUNCTION ops, but is this necessary if we + // know the names being closed over at runtime? Hm. + // get local Inst::PushLocal(global_sym(ident.to_string())).into() } diff --git a/src/lib.rs b/src/lib.rs index 2f9f66a..6ef273b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,8 @@ #![feature(unsize, coerce_unsized)] +#![allow(dead_code)] +#[macro_use] +pub mod obj; pub mod syn; pub mod compile; -pub mod obj; pub mod vm; diff --git a/src/obj/fun.rs b/src/obj/fun.rs index 051caae..04da11d 100644 --- a/src/obj/fun.rs +++ b/src/obj/fun.rs @@ -32,6 +32,9 @@ pub static CALL_METHOD_WRAPPER_FUN: Lazy> = Lazy::new(|| { // // struct UserFun // + +pub type UserFunRef = ObjRef; + #[derive(Scan)] pub struct UserFun { vtable: Vtable, diff --git a/src/obj/mod.rs b/src/obj/mod.rs index a4c53d6..c13664a 100644 --- a/src/obj/mod.rs +++ b/src/obj/mod.rs @@ -35,7 +35,7 @@ pub trait Obj: Scan + std::fmt::Debug { fn attrs(&self) -> &Attrs; fn attrs_mut(&mut self) -> Option<&mut Attrs>; - fn get_attr(&self, sym: &Sym) -> Option { + fn get_attr(&self, sym: Sym) -> Option { self.attrs() .get(&sym) .cloned() @@ -44,10 +44,6 @@ pub trait Obj: Scan + std::fmt::Debug { vtable.get(&sym).cloned() }) } - - fn set_attr(&mut self, sym: Sym, value: ObjRef) -> Option { - self.attrs_mut()?.insert(sym, value) - } } // diff --git a/src/obj/names.rs b/src/obj/names.rs index 5cc0680..bcb931c 100644 --- a/src/obj/names.rs +++ b/src/obj/names.rs @@ -18,7 +18,7 @@ pub struct NameInfo { impl NameInfo { pub fn sym_ref(&self) -> SymRef { - global_sym_ref(self.name.to_string()) + global_sym_ref(self.sym) } } diff --git a/src/obj/sym.rs b/src/obj/sym.rs index ca79c0b..b847104 100644 --- a/src/obj/sym.rs +++ b/src/obj/sym.rs @@ -84,8 +84,7 @@ pub(crate) static SYM_REFS: Lazy>> = /// Access or insert a globally interned symbol object. /// /// If the given symbol name doesn't have a corresponding object, it will be created. -pub fn global_sym_ref(s: String) -> SymRef { - let sym = global_sym(s); +pub fn global_sym_ref(sym: Sym) -> SymRef { let mut refs = SYM_REFS.lock().unwrap(); if let Some(obj) = refs.get(&sym) { obj.clone() diff --git a/src/vm/error.rs b/src/vm/error.rs new file mode 100644 index 0000000..4105658 --- /dev/null +++ b/src/vm/error.rs @@ -0,0 +1,9 @@ +use snafu::Snafu; + +#[derive(Debug, Snafu)] +pub enum Error { + #[snafu(display("attempted to pop an empty stack"))] + EmptyStack, +} + +pub type Result = std::result::Result; diff --git a/src/vm/frame.rs b/src/vm/frame.rs index e7dbcf4..dbf5d27 100644 --- a/src/vm/frame.rs +++ b/src/vm/frame.rs @@ -1,6 +1,52 @@ +use crate::obj::prelude::*; +use shredder::{GcSafe, Scan, Scanner}; +use std::collections::BTreeMap; + /// A stack call frame. -#[derive(Default, Debug, Clone)] -pub struct Frame { - last_pc: usize, - stack_base: usize, +#[derive(Debug, Clone)] +pub enum FrameKind { + Root, + Native, + User { + last_pc: usize, + stack_base: usize, + fun: UserFunRef, + }, +} + +unsafe impl Scan for FrameKind { + fn scan(&self, scanner: &mut Scanner<'_>) { + match self { + FrameKind::User { fun, .. } => scanner.scan(fun), + _ => { /* no-op */ } + } + } +} + +unsafe impl GcSafe for FrameKind {} + +pub type Locals = BTreeMap; + +#[derive(Scan, Debug, Clone)] +pub struct Frame { + locals: Locals, + kind: FrameKind, +} + +impl Frame { + pub fn new(locals: Locals, kind: FrameKind) -> Self { + Self { locals, kind, } + } + + pub fn locals(&self) -> &Locals { + &self.locals + } + + pub fn locals_mut(&mut self) -> &mut Locals { + &mut self.locals + } + + pub fn kind(&self) -> &FrameKind { + &self.kind + } } diff --git a/src/vm/inst.rs b/src/vm/inst.rs index 2041938..3abb14f 100644 --- a/src/vm/inst.rs +++ b/src/vm/inst.rs @@ -1,6 +1,6 @@ use crate::{obj::prelude::*, vm::consts::ConstHandle}; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone, Copy)] pub enum Inst { /// Push a literal symbol object to the stack. PushSym(Sym), diff --git a/src/vm/mod.rs b/src/vm/mod.rs index 0ecc3ad..4211819 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -1,52 +1,171 @@ -mod frame; -pub mod inst; pub mod consts; +pub mod error; +pub mod frame; +pub mod inst; -use crate::obj::prelude::*; - -use frame::*; +use crate::{ + obj::{names::*, prelude::*}, + vm::{consts::*, error::*, frame::*, inst::*}, +}; +use shredder::{GcSafe, Scanner, Scan}; #[derive(Debug)] -pub struct Vm { +pub struct Vm<'c> { stack: Vec, frames: Vec, pc: usize, condition: bool, + const_pool: &'c ConstPool, } -impl Vm { - pub fn new() -> Self { +unsafe impl Scan for Vm<'_> { + fn scan(&self, scanner: &mut Scanner) { + scanner.scan(&self.stack); + scanner.scan(&self.frames); + } +} + +unsafe impl GcSafe for Vm<'_> {} + +impl<'c> Vm<'c> { + pub fn new(const_pool: &'c ConstPool) -> Self { Self { stack: Default::default(), - frames: vec![Default::default()], // Start with a root stack frame + frames: vec![], pc: 0, condition: false, + const_pool, } } + /// Gets the current stack frame, if any. + pub fn frame(&self) -> Option<&Frame> { + self.frames.last() + } + + /// Gets the current stack frame mutably, if any. + pub fn frame_mut(&mut self) -> Option<&mut Frame> { + self.frames.last_mut() + } + + /// Gets the stack. pub fn stack(&self) -> &Vec { &self.stack } + /// Gets the stack, mutably. pub fn stack_mut(&mut self) -> &mut Vec { &mut self.stack } + /// Pushes a value to the stack. pub fn push(&mut self, value: ObjRef) { self.stack_mut().push(value); } + /// Pops a value from the stack. pub fn pop(&mut self) -> Option { self.stack_mut().pop() } + /// Gets the current program counter address. pub fn pc(&self) -> usize { self.pc } + /// Set the next program counter value. + /// + /// This may cause the running program to crash. Handle with care. + /// + /// TODO : consider making this `unsafe`? Is that appropriate in this context? + pub fn set_pc(&mut self, pc: usize) { + self.pc = pc; + } + + /// Gets whether the condition flag has been set or not. pub fn condition(&self) -> bool { self.condition } - //pub fn new_local(&mut self, name: String, + /// Sets the condition flag to the specified value. + pub fn set_condition(&mut self, condition: bool) { + self.condition = condition; + } + + /// Run a single instruction. + pub fn tick(&mut self) -> Result<()> { + self.do_tick() + } + + /// Gets the current instruction. + #[inline(always)] + fn load_inst(&self) -> Inst { + if let FrameKind::User { fun, .. } = self.frame().expect("frame").kind() { + read_obj!(let fun = fun); + fun.code()[self.pc()] + } else { + panic!("invalid stack frame"); + } + } + + /// Run a single instruction - inlined version. + /// + /// Since this is inlined, it is probably a bad idea to allow users to use it everywhere. The + /// exposed API function `tick()` calls this function, but does not inline itself (unless the + /// optimizer thinks it's a good idea to do so). + #[inline] + fn do_tick(&mut self) -> Result<()> { + let inst = self.load_inst(); + let mut next_pc = self.pc() + 1; + match inst { + Inst::PushSym(sym) => { + let sym_ref = global_sym_ref(sym); + self.push(sym_ref); + } + Inst::PushConst(hdl) => { + let obj_ref = self.const_pool.get(hdl).clone(); + self.push(obj_ref); + } + Inst::PushLocal(_sym) => todo!(), + Inst::Pop(Some(_sym)) => todo!(), + Inst::Pop(None) => todo!(), + Inst::GetAttr(sym) => { + let obj_ref = self.pop().expect("getattr object"); + read_obj!(let obj = obj_ref); + let attr = obj + .get_attr(sym) + .unwrap_or_else(|| global_sym_ref(NIL_NAME.sym)); + self.push(attr); + } + Inst::SetAttr(_sym) => todo!(), + Inst::Jump(addr) => { + next_pc = addr; + } + Inst::JumpTrue(addr) => { + if self.condition { + next_pc = addr; + } + } + Inst::Call(_argc) => todo!(), + Inst::Index => todo!(), + Inst::Return => todo!(), + Inst::UnNeg => todo!(), + Inst::UnPos => todo!(), + Inst::BinPlus => todo!(), + Inst::BinMinus => todo!(), + Inst::BinMul => todo!(), + Inst::BinDiv => todo!(), + Inst::BinEq => todo!(), + Inst::BinNeq => todo!(), + Inst::BinLt => todo!(), + Inst::BinLe => todo!(), + Inst::BinGt => todo!(), + Inst::BinGe => todo!(), + Inst::BinAnd => todo!(), + Inst::BinOr => todo!(), + } + self.set_pc(next_pc); + //let mut next_index = self. + Ok(()) + } }