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:
2020-10-07 15:48:24 -07:00
parent 0ca8dc64b2
commit dd7cd04b39
9 changed files with 286 additions and 164 deletions

View File

@@ -1,51 +1,107 @@
use crate::obj::prelude::*;
use shredder::{GcSafe, Scan, Scanner};
use std::collections::BTreeMap;
use crate::{obj::prelude::*, vm::inst::Inst};
use std::{collections::BTreeMap, sync::Arc};
use std::fmt::{self, Formatter, Debug};
/// A stack call frame.
#[derive(Debug, Clone)]
pub enum FrameKind {
Native(NativeFunRef, Vec<ObjRef>),
User {
last_pc: usize,
stack_base: usize,
fun: UserFunRef,
},
pub type FrameBindings = BTreeMap<usize, ObjRef>;
#[derive(Debug)]
pub enum Frame {
User(UserFrame),
Native(NativeFrame),
}
unsafe impl Scan for FrameKind {
fn scan(&self, scanner: &mut Scanner<'_>) {
match self {
FrameKind::User { fun, .. } => scanner.scan(fun),
_ => { /* no-op */ }
impl Frame {
pub fn user_frame(&self) -> Option<&UserFrame> {
if let Frame::User(frame) = self {
Some(frame)
} else {
None
}
}
pub fn user_frame_mut(&mut self) -> Option<&mut UserFrame> {
if let Frame::User(frame) = self {
Some(frame)
} else {
None
}
}
pub fn native_frame(&self) -> Option<&NativeFrame> {
if let Frame::Native(frame) = self {
Some(frame)
} else {
None
}
}
pub fn native_frame_mut(&mut self) -> Option<&mut NativeFrame> {
if let Frame::Native(frame) = self {
Some(frame)
} else {
None
}
}
}
unsafe impl GcSafe for FrameKind {}
#[derive(Scan, Debug, Clone)]
pub struct Frame {
locals: FrameLocals,
kind: FrameKind,
#[derive(Debug, Clone)]
pub struct UserFrame {
callee: ObjRef,
bindings: FrameBindings,
last_pc: usize,
code: Arc<Vec<Inst>>,
}
impl Frame {
pub fn new(locals: FrameLocals, kind: FrameKind) -> Self {
Self { locals, kind, }
impl UserFrame {
pub fn new(callee: ObjRef, bindings: FrameBindings, last_pc: usize, code: Arc<Vec<Inst>>) -> Self {
Self { callee, bindings, last_pc, code, }
}
pub fn locals(&self) -> &FrameLocals {
&self.locals
pub fn bindings(&self) -> &FrameBindings {
&self.bindings
}
pub fn locals_mut(&mut self) -> &mut FrameLocals {
&mut self.locals
pub fn bindings_mut(&mut self) -> &mut FrameBindings {
&mut self.bindings
}
pub fn kind(&self) -> &FrameKind {
&self.kind
pub fn last_pc(&self) -> usize {
self.last_pc
}
pub fn code(&self) -> &Arc<Vec<Inst>> {
&self.code
}
pub fn callee(&self) -> &ObjRef {
&self.callee
}
}
pub type FrameLocals = BTreeMap<usize, ObjRef>;
pub struct NativeFrame {
callee: ObjRef,
fun_ptr: NativeFunPtr,
args: Vec<ObjRef>,
}
impl NativeFrame {
pub fn new(callee: ObjRef, fun_ptr: NativeFunPtr, args: Vec<ObjRef>) -> Self {
Self { callee, fun_ptr, args }
}
pub fn fun_ptr(&self) -> &NativeFunPtr {
&self.fun_ptr
}
pub fn callee(&self) -> &ObjRef {
&self.callee
}
}
impl Debug for NativeFrame {
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
fmt.debug_struct("NativeFrame")
.field("fun_ptr", &format!("{:#x}", &self.fun_ptr as *const _ as usize))
.finish()
}
}

View File

@@ -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);
}
}

View File

@@ -1,6 +1,7 @@
use crate::obj::ObjRef;
/// A signal from executing code in the VM.
#[derive(Debug, Clone)]
pub enum Signal {
Call(ObjRef, Vec<ObjRef>),
Return,