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:
@@ -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>;
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
9
src/vm/error.rs
Normal 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>;
|
||||||
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
139
src/vm/mod.rs
139
src/vm/mod.rs
@@ -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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user