Finish up function call implementation, it appears to be working
Functions are downcasted to a `Fun` trait, which will construct the appropriate stack frame. A few other things have been shifted around that affect internal APIs while things are still under construction. Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
183
src/vm/mod.rs
183
src/vm/mod.rs
@@ -2,7 +2,7 @@ pub mod consts;
|
||||
pub mod error; // TODO : not needed?
|
||||
pub mod frame;
|
||||
pub mod inst;
|
||||
pub(crate) mod signal;
|
||||
pub mod signal;
|
||||
|
||||
use crate::{
|
||||
obj::{reserved::*, prelude::*},
|
||||
@@ -44,6 +44,11 @@ impl<'c> Vm<'c> {
|
||||
&self.frames
|
||||
}
|
||||
|
||||
/// Mutably gets the list of stack frames.
|
||||
pub(crate) fn frames_mut(&mut self) -> &mut Vec<Frame> {
|
||||
&mut self.frames
|
||||
}
|
||||
|
||||
/// Gets the stack.
|
||||
pub fn stack(&self) -> &Vec<ObjRef> {
|
||||
&self.stack
|
||||
@@ -88,92 +93,85 @@ impl<'c> Vm<'c> {
|
||||
|
||||
/// 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())
|
||||
while !self.frames().is_empty() {
|
||||
self.resume_until_return();
|
||||
}
|
||||
}
|
||||
|
||||
/// Resume execution until the currently executing function returns.
|
||||
pub fn resume_until_return(&mut self) {
|
||||
// resume execution until control is returned to the originally calling function
|
||||
let start_frames = self.frames().len();
|
||||
if start_frames == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
while self.frames().len() >= start_frames {
|
||||
let frame = self.frame().unwrap();
|
||||
let signal = match frame {
|
||||
Frame::Native(fun) => {
|
||||
let callee = fun.callee().clone();
|
||||
(*fun.fun_ptr())(callee, self, vec![])
|
||||
}
|
||||
FrameKind::User { .. } => {
|
||||
// run the user function until it returns control to the VM
|
||||
loop {
|
||||
if let Some(signal) = self.tick() {
|
||||
break signal;
|
||||
}
|
||||
}
|
||||
Frame::User(_) => {
|
||||
self.resume_user_fun()
|
||||
}
|
||||
};
|
||||
self.handle_signal(signal);
|
||||
}
|
||||
}
|
||||
|
||||
fn resume_user_fun(&mut self) -> Signal {
|
||||
while let Some(Frame::User(_)) = self.frame() {
|
||||
if let Some(signal) = self.tick() {
|
||||
return signal;
|
||||
}
|
||||
}
|
||||
Signal::Return
|
||||
}
|
||||
|
||||
/// 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::Call(callee, args) => {
|
||||
read_obj!(let callee_obj = callee);
|
||||
let frame = callee_obj.as_fun()
|
||||
.expect("callable function")
|
||||
.create_frame(callee.clone(), self, args);
|
||||
self.frames_mut().push(frame);
|
||||
// Jump to the first address of the new function call
|
||||
self.set_pc(0);
|
||||
}
|
||||
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);
|
||||
}
|
||||
// 1. pop return value
|
||||
let ret_val = self.pop().expect("return value");
|
||||
|
||||
// 2. pop stack frame
|
||||
let frame = self.frames_mut().pop().expect("stack frame");
|
||||
|
||||
// 3. reset PC
|
||||
if let Frame::User(frame) = frame {
|
||||
self.set_pc(frame.last_pc());
|
||||
}
|
||||
self.stack.push(retval);
|
||||
|
||||
// 4. push return value
|
||||
self.push(ret_val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 names: FrameLocals = {
|
||||
read_obj!(let fun_ref = user_fun);
|
||||
fun_ref.locals()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.zip(args.into_iter())
|
||||
.map(|((index, _), arg)| (index, arg.clone()))
|
||||
.collect()
|
||||
};
|
||||
// TODO : check function arity vs argument count
|
||||
|
||||
Frame::new(names, 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 {
|
||||
let user_fun = if let FrameKind::User { fun, .. } = self.frame().expect("frame").kind() {
|
||||
fun
|
||||
let frame = if let Frame::User(frame) = self.frame().expect("frame") {
|
||||
frame
|
||||
} else {
|
||||
panic!("expected user function frame")
|
||||
};
|
||||
read_obj!(let user_fun = user_fun);
|
||||
user_fun.code()[self.pc()]
|
||||
frame.code()[self.pc()]
|
||||
}
|
||||
|
||||
/// Run a single instruction - inlined version.
|
||||
@@ -196,28 +194,14 @@ impl<'c> Vm<'c> {
|
||||
self.push(obj_ref);
|
||||
}
|
||||
Inst::LoadLocal(local) => {
|
||||
let obj_ref = self.frames()
|
||||
.iter()
|
||||
.rev()
|
||||
.filter_map(|frame| frame.locals().get(&local.index()))
|
||||
.cloned()
|
||||
.next();
|
||||
if let Some(obj_ref) = obj_ref {
|
||||
self.push(obj_ref);
|
||||
} else {
|
||||
todo!("TODO: implement \"local value not found\" lookup")
|
||||
}
|
||||
let value = self.get_local(local)
|
||||
.expect("TODO: throw error for missing local");
|
||||
self.push(value);
|
||||
}
|
||||
Inst::LoadGlobal(global) => {
|
||||
let obj_ref = self.frames()
|
||||
.first()
|
||||
.and_then(|frame| frame.locals().get(&global.index()))
|
||||
.cloned();
|
||||
if let Some(obj_ref) = obj_ref {
|
||||
self.push(obj_ref);
|
||||
} else {
|
||||
todo!("TODO: implement \"local value not found\" lookup")
|
||||
}
|
||||
let value = self.get_global(global)
|
||||
.expect("TODO: throw error for missing global");
|
||||
self.push(value);
|
||||
}
|
||||
Inst::PopLocal(name) => {
|
||||
let tos = self.pop()
|
||||
@@ -328,16 +312,41 @@ impl<'c> Vm<'c> {
|
||||
|
||||
signal
|
||||
}
|
||||
|
||||
fn get_local(&self, name: Name) -> Option<ObjRef> {
|
||||
self.frame()?
|
||||
.user_frame()?
|
||||
.bindings()
|
||||
.get(&name.index())
|
||||
.cloned()
|
||||
}
|
||||
|
||||
fn set_local(&mut self, name: Name, value: ObjRef) {
|
||||
let frame = self.frame_mut().unwrap();
|
||||
let locals = frame.locals_mut();
|
||||
locals.insert(name.index(), value);
|
||||
let frame = self
|
||||
.frame_mut()
|
||||
.expect("user stack frame")
|
||||
.user_frame_mut()
|
||||
.unwrap();
|
||||
let bindings = frame.bindings_mut();
|
||||
bindings.insert(name.index(), value);
|
||||
}
|
||||
|
||||
fn get_global(&self, name: Name) -> Option<ObjRef> {
|
||||
self.frames
|
||||
.first()?
|
||||
.user_frame()?
|
||||
.bindings()
|
||||
.get(&name.index())
|
||||
.cloned()
|
||||
}
|
||||
|
||||
fn set_global(&mut self, name: Name, value: ObjRef) {
|
||||
let frame = self.frames.first_mut().unwrap();
|
||||
let locals = frame.locals_mut();
|
||||
locals.insert(name.index(), value);
|
||||
let frame = self.frames_mut()
|
||||
.first_mut()
|
||||
.expect("global stack frame")
|
||||
.user_frame_mut()
|
||||
.expect("user-defined function stack frame");
|
||||
let bindings = frame.bindings_mut();
|
||||
bindings.insert(name.index(), value);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user