diff --git a/src/obj/fun.rs b/src/obj/fun.rs index dabb8cd..11157ff 100644 --- a/src/obj/fun.rs +++ b/src/obj/fun.rs @@ -1,34 +1,8 @@ -use crate::{obj::{reserved::*, prelude::*}, vm::{inst::Inst, Vm}}; +use crate::{obj::{reserved::*, prelude::*}, vm::{inst::Inst, signal::*, Vm}}; use once_cell::sync::Lazy; use shredder::{GcSafeWrapper, Scan}; use std::fmt::{Debug, Formatter, self}; -#[derive(Debug, Scan)] -pub struct Method { - vtable: Vtable, - attrs: Attrs, -} - -impl Method { - pub fn new(this: ObjRef, func: ObjRef) -> Self { - Self { - vtable: Default::default(), - attrs: attrs! { - SELF_MEMBER_NAME.sym => this, - FUNC_MEMBER_NAME.sym => func, - } - } - } -} - -impl_obj!(Method); - -pub static CALL_METHOD_WRAPPER_FUN: Lazy> = Lazy::new(|| { - NativeFun::new_obj(Box::new(|_vm, _fun, _args| { - todo!("__call__ function") - })) -}); - // // struct UserFun // @@ -48,6 +22,27 @@ pub struct UserFun { } impl UserFun { + pub fn new_obj(code: Vec, locals: Names) -> UserFunRef { + let obj_ref = ObjRef::new(UserFun { + vtable: Default::default(), // this is a placeholder for the real vtable + attrs: Default::default(), + code, + locals, + }); + + // XXX replace the vtable before returning, since __call__ member points to the object + // itself + { + write_obj!(let obj = obj_ref); + obj.vtable = vtable! { + TY_MEMBER_NAME.sym => USER_FUN_TY.clone(), + CALL_MEMBER_NAME.sym => obj_ref.clone(), + }; + } + + obj_ref + } + pub fn code(&self) -> &Vec { &self.code } @@ -69,11 +64,14 @@ impl Debug for UserFun { } } +pub static USER_FUN_TY: Lazy> = Lazy::new(|| Ty::new_obj(USER_FUN_NAME.sym_ref())); + // // struct NativeFun // -pub type NativeFunPtr = Box) + Send + Sync>; +pub type NativeFunPtr = Box) -> Signal) + Send + Sync>; +pub type NativeFunRef = ObjRef; #[derive(Scan)] pub struct NativeFun { @@ -89,12 +87,29 @@ pub struct NativeFun { impl NativeFun { pub fn new_obj(fun: NativeFunPtr) -> ObjRef { - ObjRef::new(Self { - // TODO : vtable for NativeFun + let obj_ref = ObjRef::new(Self { vtable: Default::default(), attrs: Default::default(), fun: GcSafeWrapper::new(fun), - }) + }); + + + // XXX replace the vtable before returning, since __call__ member points to the object + // itself + { + write_obj!(let obj = obj_ref); + obj.vtable = vtable! { + TY_MEMBER_NAME.sym => NATIVE_FUN_TY.clone(), + CALL_MEMBER_NAME.sym => obj_ref.clone(), + }; + } + + obj_ref + } + + /// Gets the native function that can be invoked. + pub fn fun(&self) -> &NativeFunPtr { + &self.fun } } @@ -113,6 +128,8 @@ impl Debug for NativeFun { impl_obj!(NativeFun); +pub static NATIVE_FUN_TY: Lazy> = Lazy::new(|| Ty::new_obj(NATIVE_FUN_NAME.sym_ref())); + // // Native function defs // @@ -121,7 +138,7 @@ impl_obj!(NativeFun); // __get_attr__ *should* always bypass the __access__ function and get an attribute directly pub static GET_ATTR_MEMBER_FUN: Lazy> = Lazy::new(|| { - NativeFun::new_obj(Box::new(|_vm, _fun, _args| { + NativeFun::new_obj(Box::new(|_caller, _vm, _args| { /* let sym_ref = vm.pop(); let obj_ref = vm.pop(); diff --git a/src/obj/macros.rs b/src/obj/macros.rs index 663108d..b3ccc1e 100644 --- a/src/obj/macros.rs +++ b/src/obj/macros.rs @@ -14,6 +14,10 @@ macro_rules! impl_obj { fn attrs_mut(&mut self) -> Option<&mut $crate::obj::attrs::Attrs> { Some(&mut self.$attrs) } + + fn as_any(&self) -> &dyn std::any::Any { + self + } } }; @@ -37,6 +41,10 @@ macro_rules! impl_obj_readonly { fn attrs_mut(&mut self) -> Option<&mut $crate::obj::attrs::Attrs> { None } + + fn as_any(&self) -> &dyn std::any::Any { + self + } } }; @@ -49,8 +57,9 @@ macro_rules! impl_obj_readonly { #[macro_export] macro_rules! read_obj { (let $lhs:ident $(: &$ty:ty)? = $obj:expr) => { - let __obj $(: &std::sync::RwLock<$ty>)? = &*$obj.get(); - let $lhs $(: &$ty)? = &__obj.read().unwrap(); + let __obj = &*$obj.get(); + let __obj_deref = &__obj.read().unwrap(); + let $lhs $(: &$ty)? = std::ops::Deref::deref(__obj_deref); }; } @@ -60,7 +69,8 @@ macro_rules! read_obj { macro_rules! write_obj { (let $lhs:ident $(: &$ty:ty)? = $obj:expr) => { let __obj $(: &std::sync::RwLock<$ty>)? = &*$obj.get(); - let $lhs $(: &$ty)? = &mut __obj.write().unwrap(); + let __obj_deref = &mut __obj.write().unwrap(); + let $lhs $(: &$ty)? = std::ops::DerefMut::deref_mut(__obj_deref); }; } diff --git a/src/obj/mod.rs b/src/obj/mod.rs index 3405cd3..3b5fe64 100644 --- a/src/obj/mod.rs +++ b/src/obj/mod.rs @@ -42,6 +42,8 @@ pub trait Obj: Scan + std::fmt::Debug { vtable.get(&sym).cloned() }) } + + fn as_any(&self) -> &dyn std::any::Any; } // diff --git a/src/obj/reserved.rs b/src/obj/reserved.rs index 56e1cae..936ce6e 100644 --- a/src/obj/reserved.rs +++ b/src/obj/reserved.rs @@ -30,6 +30,8 @@ impl NameInfo { name!(INT_NAME, "Int"); name!(TY_NAME, "Type"); name!(SYM_NAME, "Sym"); +name!(USER_FUN_NAME, "Fun"); +name!(NATIVE_FUN_NAME, "NativeFun"); // // Members diff --git a/src/obj/sym.rs b/src/obj/sym.rs index 44b3e9f..c0e8610 100644 --- a/src/obj/sym.rs +++ b/src/obj/sym.rs @@ -38,6 +38,10 @@ impl Obj for Sym { fn attrs_mut(&mut self) -> Option<&mut Attrs> { None } + + fn as_any(&self) -> &dyn std::any::Any { + self + } } impl std::ops::Index for Vec { diff --git a/src/vm/consts.rs b/src/vm/consts.rs index 4768cd5..0fbf54d 100644 --- a/src/vm/consts.rs +++ b/src/vm/consts.rs @@ -7,6 +7,9 @@ pub struct ConstPool { pool: Vec, } +// Constant pools are constant, and live for the lifetime of the entire program. +impl shredder::EmptyScan for ConstPool { } + impl ConstPool { pub fn new() -> Self { Default::default() diff --git a/src/vm/frame.rs b/src/vm/frame.rs index 52b5218..cee9781 100644 --- a/src/vm/frame.rs +++ b/src/vm/frame.rs @@ -4,8 +4,7 @@ use shredder::{GcSafe, Scan, Scanner}; /// A stack call frame. #[derive(Debug, Clone)] pub enum FrameKind { - Root, - Native, + Native(NativeFunRef, Vec), User { last_pc: usize, stack_base: usize, diff --git a/src/vm/inst.rs b/src/vm/inst.rs index d1a2627..d908ffc 100644 --- a/src/vm/inst.rs +++ b/src/vm/inst.rs @@ -1,4 +1,5 @@ use crate::{obj::prelude::*, vm::consts::*}; +use shredder::EmptyScan; #[derive(Debug, PartialEq, Clone, Copy)] pub enum Inst { @@ -19,7 +20,7 @@ pub enum Inst { /// This will get an attr from the object reference pointed to by the symbol. GetAttr(Sym), - /// A target reference and a source reference from the stack. + /// Pops a target reference and a source reference from the stack. /// /// The target reference will have the given symbol attribute assigned to the source. /// @@ -144,3 +145,5 @@ impl Inst { } } } + +impl EmptyScan for Inst { } diff --git a/src/vm/mod.rs b/src/vm/mod.rs index 00077c5..ae4ad62 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -1,14 +1,14 @@ pub mod consts; -pub mod error; +pub mod error; // TODO : not needed? pub mod frame; pub mod inst; pub mod package; +pub(crate) mod signal; use crate::{ obj::{reserved::*, prelude::*}, - vm::{error::*, frame::*, inst::*, package::*}, + vm::{frame::*, inst::*, package::*, signal::*}, }; -use shredder::{GcSafe, Scanner, Scan}; #[derive(Debug)] pub struct Vm<'p> { @@ -19,15 +19,6 @@ pub struct Vm<'p> { package: &'p Package, } -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<'p> Vm<'p> { pub fn new(package: &'p Package) -> Self { Self { @@ -101,20 +92,92 @@ impl<'p> Vm<'p> { self.condition = condition; } - /// Run a single instruction. - pub fn tick(&mut self) -> Result<()> { - self.do_tick() + /// Resumes execution of the current program. + pub fn resume(&mut self) { + while let Some(frame) = self.frame().cloned() { + let signal = match frame.kind() { + FrameKind::Native(fun, args) => { + read_obj!(let fun_obj = fun); + (fun_obj.fun())(fun.clone(), self, args.clone()) + } + FrameKind::User { .. } => { + // run the user function until it returns control to the VM + loop { + if let Some(signal) = self.tick() { + break signal; + } + } + } + }; + self.handle_signal(signal); + } + } + + /// Handles a signal targeting the VM. + /// + /// The signal may originate from the VM itself, or from an external location. Signals + /// generally interrupt control flow of a program. + pub fn handle_signal(&mut self, signal: Signal) { + match signal { + Signal::Call(caller, args) => { + self.begin_call(caller, args); + } + Signal::Return => { + let retval = self.stack.pop() + .expect("return value"); + match self.frames.pop().unwrap().kind() { + // no-op; return value should be the TOS + FrameKind::Native(_, _) => { }, + FrameKind::User { last_pc, stack_base, fun: _, } => { + // pop return value, reset PC, clean stack, and push return value + self.set_pc(*last_pc); + self.stack.truncate(*stack_base); + } + } + self.stack.push(retval); + } + } + } + + /// Sets up the stack for a function call. + fn begin_call(&mut self, caller: ObjRef, args: Vec) { + // create stack frame + let stack_frame = if let Some(user_fun) = std::any::Any::downcast_ref::(&caller) { + let locals = { + read_obj!(let fun_ref = user_fun); + fun_ref.locals() + .iter() + .zip(args.into_iter()) + .map(|((k, _), v)| (*k, v)) + .collect() + }; + + Frame::new(locals, FrameKind::User { + last_pc: self.pc(), + stack_base: self.stack().len(), + fun: user_fun.clone(), + }) + } else if let Some(native_fun) = std::any::Any::downcast_ref::(&caller) { + Frame::new(Default::default(), FrameKind::Native(native_fun.clone(), args)) + } else { + // TODO : throw an error when error handling is figured out + panic!("can't call object {:?}", caller); + }; + + // push a new stack frame + self.frames.push(stack_frame); } /// 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()] + let user_fun = if let FrameKind::User { fun, .. } = self.frame().expect("frame").kind() { + fun } else { - panic!("invalid stack frame"); - } + panic!("expected user function frame") + }; + read_obj!(let user_fun = user_fun); + user_fun.code()[self.pc()] } /// Run a single instruction - inlined version. @@ -123,9 +186,10 @@ impl<'p> Vm<'p> { /// 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<()> { + fn tick(&mut self) -> Option { let inst = self.load_inst(); let mut next_pc = self.pc() + 1; + let mut signal = None; match inst { Inst::PushSym(sym) => { let sym_ref = global_sym_ref(sym); @@ -138,9 +202,28 @@ impl<'p> Vm<'p> { .clone(); self.push(obj_ref); } - Inst::LoadName(_sym) => todo!(), - Inst::Pop(Some(_sym)) => todo!(), - Inst::Pop(None) => todo!(), + Inst::LoadName(name) => { + let obj_ref = self.frames() + .iter() + .rev() + .filter_map(|frame| frame.locals().get(&name)) + .cloned() + .next(); + if let Some(obj_ref) = obj_ref { + self.push(obj_ref); + } else { + todo!("TODO: implement \"name value not found\" lookup") + } + } + Inst::Pop(name) => { + let tos = self.pop() + .expect("stack underflow"); + // pop into name + if let Some(name) = name { + self.set_local(name, tos); + } + // else discard + } Inst::GetAttr(sym) => { let obj_ref = self.pop().expect("getattr object"); read_obj!(let obj = obj_ref); @@ -149,7 +232,18 @@ impl<'p> Vm<'p> { .unwrap_or_else(|| global_sym_ref(NIL_NAME.sym)); self.push(attr); } - Inst::SetAttr(_sym) => todo!(), + Inst::SetAttr(sym) => { + let target = self.pop() + .expect("no target available for SetAttr"); + let source = self.pop() + .expect("no source available for SetAttr"); + write_obj!(let target = target); + if let Some(attrs) = target.attrs_mut() { + attrs.insert(sym, source); + } else { + todo!("TODO: throw an error for attributes that can't be set"); + } + } Inst::Jump(addr) => { next_pc = addr; } @@ -158,9 +252,14 @@ impl<'p> Vm<'p> { next_pc = addr; } } - Inst::Call(_argc) => todo!(), + Inst::Call(argc) => { + let stack_top = self.stack.len() - argc; + let args = self.stack.split_off(stack_top); + let caller = self.pop().unwrap(); + signal = Some(Signal::Call(caller, args)); + } Inst::Index => todo!(), - Inst::Return => todo!(), + Inst::Return => { signal = Some(Signal::Return); } Inst::UnNeg => todo!(), Inst::UnPos => todo!(), Inst::BinPlus => todo!(), @@ -177,7 +276,26 @@ impl<'p> Vm<'p> { Inst::BinOr => todo!(), } self.set_pc(next_pc); - //let mut next_index = self. - Ok(()) + + signal + } + + fn set_local(&mut self, name: Name, value: ObjRef) { + for frame in self.frames.iter_mut().rev() { + let locals = frame.locals_mut(); + if locals.contains_key(&name) { + locals.insert(name, value); + return; + } + } + unreachable!("unregistered name {:?}", name); + } + + fn get_local(&mut self, name: Name) -> Option { + self.frames.iter() + .rev() + .filter_map(|frame| frame.locals().get(&name)) + .next() + .cloned() } } diff --git a/src/vm/package.rs b/src/vm/package.rs index 3ca98ad..48f1a0f 100644 --- a/src/vm/package.rs +++ b/src/vm/package.rs @@ -1,8 +1,9 @@ use crate::{obj::prelude::*, vm::{consts::ConstPool, inst::Inst}}; +use shredder::Scan; use std::io::{self, Write}; /// A compiled package that can be executed by a VM. -#[derive(Debug)] +#[derive(Scan, Debug)] pub struct Package { names: Vec, // local names mappings const_pool: ConstPool, @@ -31,6 +32,7 @@ impl Package { &self.code } + /// Dumps a debug output of this package to the given writer. pub fn dump(&self, writer: &mut dyn Write) -> io::Result<()> { // column widths let addr_w = num_digits(self.code().len(), 16).max(4); @@ -48,9 +50,6 @@ impl Package { { let obj_ref = self.const_pool().get(*hdl); read_obj!(let obj = obj_ref); - // XXX weirdness with coercion, can't deref as a &dyn Obj because - // RwReadLockGuard is not Obj - but using Deref::deref works - let obj: &dyn Obj = std::ops::Deref::deref(obj); format!("{:?}", obj) }, ), diff --git a/src/vm/signal.rs b/src/vm/signal.rs new file mode 100644 index 0000000..69d3580 --- /dev/null +++ b/src/vm/signal.rs @@ -0,0 +1,8 @@ +use crate::obj::ObjRef; + +/// A signal from executing code in the VM. +pub enum Signal { + Call(ObjRef, Vec), + Return, +} +