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 <alekratz@gmail.com>
This commit is contained in:
2020-09-14 16:32:00 -07:00
parent 372e58f620
commit fdbb0a1307
11 changed files with 205 additions and 24 deletions

View File

@@ -10,3 +10,4 @@ pub enum Error {
} }
pub type Result<T, E = Error> = std::result::Result<T, E>; pub type Result<T, E = Error> = std::result::Result<T, E>;

View File

@@ -206,6 +206,12 @@ impl Visit for CompileBody<'_, '_> {
fn visit_atom(&mut self, atom: &Atom) -> Self::Out { fn visit_atom(&mut self, atom: &Atom) -> Self::Out {
let thunk = match atom { let thunk = match atom {
Atom::Ident(ident) => { 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 // get local
Inst::PushLocal(global_sym(ident.to_string())).into() Inst::PushLocal(global_sym(ident.to_string())).into()
} }

View File

@@ -1,6 +1,8 @@
#![feature(unsize, coerce_unsized)] #![feature(unsize, coerce_unsized)]
#![allow(dead_code)]
#[macro_use]
pub mod obj;
pub mod syn; pub mod syn;
pub mod compile; pub mod compile;
pub mod obj;
pub mod vm; pub mod vm;

View File

@@ -32,6 +32,9 @@ pub static CALL_METHOD_WRAPPER_FUN: Lazy<ObjRef<NativeFun>> = Lazy::new(|| {
// //
// struct UserFun // struct UserFun
// //
pub type UserFunRef = ObjRef<UserFun>;
#[derive(Scan)] #[derive(Scan)]
pub struct UserFun { pub struct UserFun {
vtable: Vtable, vtable: Vtable,

View File

@@ -35,7 +35,7 @@ pub trait Obj: Scan + std::fmt::Debug {
fn attrs(&self) -> &Attrs; fn attrs(&self) -> &Attrs;
fn attrs_mut(&mut self) -> Option<&mut Attrs>; fn attrs_mut(&mut self) -> Option<&mut Attrs>;
fn get_attr(&self, sym: &Sym) -> Option<ObjRef> { fn get_attr(&self, sym: Sym) -> Option<ObjRef> {
self.attrs() self.attrs()
.get(&sym) .get(&sym)
.cloned() .cloned()
@@ -44,10 +44,6 @@ pub trait Obj: Scan + std::fmt::Debug {
vtable.get(&sym).cloned() vtable.get(&sym).cloned()
}) })
} }
fn set_attr(&mut self, sym: Sym, value: ObjRef) -> Option<ObjRef> {
self.attrs_mut()?.insert(sym, value)
}
} }
// //

View File

@@ -18,7 +18,7 @@ pub struct NameInfo {
impl NameInfo { impl NameInfo {
pub fn sym_ref(&self) -> SymRef { pub fn sym_ref(&self) -> SymRef {
global_sym_ref(self.name.to_string()) global_sym_ref(self.sym)
} }
} }

View File

@@ -84,8 +84,7 @@ pub(crate) static SYM_REFS: Lazy<Mutex<BTreeMap<Sym, SymRef>>> =
/// Access or insert a globally interned symbol object. /// Access or insert a globally interned symbol object.
/// ///
/// If the given symbol name doesn't have a corresponding object, it will be created. /// If the given symbol name doesn't have a corresponding object, it will be created.
pub fn global_sym_ref(s: String) -> SymRef { pub fn global_sym_ref(sym: Sym) -> SymRef {
let sym = global_sym(s);
let mut refs = SYM_REFS.lock().unwrap(); let mut refs = SYM_REFS.lock().unwrap();
if let Some(obj) = refs.get(&sym) { if let Some(obj) = refs.get(&sym) {
obj.clone() obj.clone()

9
src/vm/error.rs Normal file
View File

@@ -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<T, E = Error> = std::result::Result<T, E>;

View File

@@ -1,6 +1,52 @@
use crate::obj::prelude::*;
use shredder::{GcSafe, Scan, Scanner};
use std::collections::BTreeMap;
/// A stack call frame. /// A stack call frame.
#[derive(Default, Debug, Clone)] #[derive(Debug, Clone)]
pub struct Frame { pub enum FrameKind {
Root,
Native,
User {
last_pc: usize, last_pc: usize,
stack_base: 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<usize, ObjRef>;
#[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
}
} }

View File

@@ -1,6 +1,6 @@
use crate::{obj::prelude::*, vm::consts::ConstHandle}; use crate::{obj::prelude::*, vm::consts::ConstHandle};
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq, Clone, Copy)]
pub enum Inst { pub enum Inst {
/// Push a literal symbol object to the stack. /// Push a literal symbol object to the stack.
PushSym(Sym), PushSym(Sym),

View File

@@ -1,52 +1,171 @@
mod frame;
pub mod inst;
pub mod consts; pub mod consts;
pub mod error;
pub mod frame;
pub mod inst;
use crate::obj::prelude::*; use crate::{
obj::{names::*, prelude::*},
use frame::*; vm::{consts::*, error::*, frame::*, inst::*},
};
use shredder::{GcSafe, Scanner, Scan};
#[derive(Debug)] #[derive(Debug)]
pub struct Vm { pub struct Vm<'c> {
stack: Vec<ObjRef>, stack: Vec<ObjRef>,
frames: Vec<Frame>, frames: Vec<Frame>,
pc: usize, pc: usize,
condition: bool, condition: bool,
const_pool: &'c ConstPool,
} }
impl Vm { unsafe impl Scan for Vm<'_> {
pub fn new() -> Self { 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 { Self {
stack: Default::default(), stack: Default::default(),
frames: vec![Default::default()], // Start with a root stack frame frames: vec![],
pc: 0, pc: 0,
condition: false, 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<ObjRef> { pub fn stack(&self) -> &Vec<ObjRef> {
&self.stack &self.stack
} }
/// Gets the stack, mutably.
pub fn stack_mut(&mut self) -> &mut Vec<ObjRef> { pub fn stack_mut(&mut self) -> &mut Vec<ObjRef> {
&mut self.stack &mut self.stack
} }
/// Pushes a value to the stack.
pub fn push(&mut self, value: ObjRef) { pub fn push(&mut self, value: ObjRef) {
self.stack_mut().push(value); self.stack_mut().push(value);
} }
/// Pops a value from the stack.
pub fn pop(&mut self) -> Option<ObjRef> { pub fn pop(&mut self) -> Option<ObjRef> {
self.stack_mut().pop() self.stack_mut().pop()
} }
/// Gets the current program counter address.
pub fn pc(&self) -> usize { pub fn pc(&self) -> usize {
self.pc 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 { pub fn condition(&self) -> bool {
self.condition 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(())
}
} }