Change up how function calls are handled
This is a big one. For a while, builtin functions were a bit cumbersome and not easily re-entrant. If you needed to call a function from within a builtin function, the only method of doing so was to take a `FunctionState` parameter, which would either be "Begin", meaning the function was being called for the first time, or "Resume", meaning the function was being re-entered. This meant that if we wanted to call another function within this function, we'd have to set up a whole `match` statement to figure out whether we were re-entering the function or starting out. It was a mess and not very ergonomic, and most importantly, made it very difficult to implement hashmaps. Now, builtin functions are handled a little more elegantly. A native function is pushed to the stack, where it is detected in the `Vm::dispatch()` function. It is then called, like normal. If the builtin function then needs to call *another* function, it will push that function to the stack and call it, and then call `Vm::resume()` to resume VM execution. `Vm::dispatch()` is then called again, this time with the current function on top of the stack. If it's another builtin function, the above is repeated. If it's a user-defined function, then bytecode is executed in the main `loop` inside of resume. Ultimately, we are able to compose builtin functions like we would any other internal function to the program. Overall this should speed things up a little, make them a whole lot easier to read, and make them a million times easier to compose with other builtin parts of Rust. Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
@@ -7,51 +7,11 @@ use crate::obj::macros::*;
|
||||
use crate::obj::prelude::*;
|
||||
use crate::vm::{Argc, Chunk, Frame, Function, Vm};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// FunctionResult, FunctionState
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// A result that instructs the VM what to do after a function finishes its execution.
|
||||
#[derive(Debug)]
|
||||
pub enum FunctionResult {
|
||||
/// Take this value and push it on the stack as its return value.
|
||||
ReturnPush(ObjP),
|
||||
|
||||
/// Return value has already been pushed to the stack.
|
||||
Return,
|
||||
|
||||
/// Yield control to the VM with a state marker, and resume execution.
|
||||
///
|
||||
/// This means that a new stack frame should have been pushed to the VM and resume execution
|
||||
/// starting from there.
|
||||
Yield(usize),
|
||||
}
|
||||
|
||||
impl From<ObjP> for FunctionResult {
|
||||
fn from(other: ObjP) -> Self {
|
||||
FunctionResult::ReturnPush(other)
|
||||
}
|
||||
}
|
||||
|
||||
/// A function's resume state.
|
||||
///
|
||||
/// When a builtin function is called, it may need to set up a stack frame for a new function,
|
||||
/// yielding its control back to the VM. If it does this, then presumably, that function would like
|
||||
/// to resume its execution where it left off. There's not really a way to capture a native
|
||||
/// function's execution state in Rust (without tons of unsafe and not-really-worth-it black
|
||||
/// magic), so instead a builtin function will accept a `FunctionState` value to give it a hint of
|
||||
/// where it left off.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum FunctionState {
|
||||
Begin,
|
||||
Resume(usize),
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// BuiltinFunction
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
pub type BuiltinFunctionPtr = fn(vm: &mut Vm, function_state: FunctionState) -> FunctionResult;
|
||||
pub type BuiltinFunctionPtr = fn(vm: &mut Vm) -> ObjP;
|
||||
|
||||
#[derive(Trace, Finalize)]
|
||||
pub struct BuiltinFunction {
|
||||
@@ -94,7 +54,7 @@ impl Debug for BuiltinFunction {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
fmt,
|
||||
"<BuiltinFunction {}/{} at 0x{:x}>",
|
||||
"<BuiltinFunction {}/{} at {:#x}>",
|
||||
self.name(),
|
||||
self.arity().unwrap(),
|
||||
self.function as *const BuiltinFunctionPtr as usize
|
||||
@@ -110,7 +70,7 @@ impl Object for BuiltinFunction {
|
||||
fn call(&self, vm: &mut Vm, argc: Argc) {
|
||||
let new_frame = Frame::new(
|
||||
Rc::clone(&self.name),
|
||||
Function::Builtin(self.function, FunctionState::Begin),
|
||||
Function::Builtin(self.function),
|
||||
vm.stack().len() - (argc as usize),
|
||||
);
|
||||
vm.push_frame(new_frame);
|
||||
@@ -268,7 +228,7 @@ impl Debug for Method {
|
||||
};
|
||||
write!(
|
||||
fmt,
|
||||
"<Method {}.{}/{} at {}>",
|
||||
"<Method {}.{}/{} at {:#x}>",
|
||||
self.self_binding().borrow().ty_name(),
|
||||
function_name,
|
||||
self.function.borrow().arity().unwrap(),
|
||||
|
||||
Reference in New Issue
Block a user