Functions are implemented and VM should be able to handle function calls
* VM Signals are used by running functions to dictate whether a function should return, or if it should call another function. These signals can be injected at any time allowing for user functions to inject themselves at runtime. * obj::Method is gone since it's not being used yet. * Obj impls must implement as_any(&self) -> &dyn Any now. This allows for UserFun and NativeFun to be explicitly cast (among other things, in the future). Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
176
src/vm/mod.rs
176
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<ObjRef>) {
|
||||
// create stack frame
|
||||
let stack_frame = if let Some(user_fun) = std::any::Any::downcast_ref::<UserFunRef>(&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::<NativeFunRef>(&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<Signal> {
|
||||
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<ObjRef> {
|
||||
self.frames.iter()
|
||||
.rev()
|
||||
.filter_map(|frame| frame.locals().get(&name))
|
||||
.next()
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user