Skip to content

Commit

Permalink
Working fibonacci recursive calls
Browse files Browse the repository at this point in the history
  • Loading branch information
vangroan committed May 5, 2024
1 parent 5f7fe21 commit 6308373
Show file tree
Hide file tree
Showing 6 changed files with 307 additions and 73 deletions.
4 changes: 4 additions & 0 deletions crates/vuur_vm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ vuur_parse = { path = "../vuur_parse" }

# Dynamic Objects
bytemuck = "1.13"

[features]
# Prints opcode instructions as they are interpreted.
trace_ops = []
8 changes: 4 additions & 4 deletions crates/vuur_vm/src/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::rc::Rc;
pub use std::rc::Weak;

/// Shared reference counted handle
pub struct Handle<T>(Rc<RefCell<T>>);
pub struct Handle<T: ?Sized>(Rc<RefCell<T>>);

impl<T> Handle<T> {
#[inline(always)]
Expand Down Expand Up @@ -40,7 +40,7 @@ impl<T> Handle<T> {
}
}

impl<T> Clone for Handle<T> {
impl<T: ?Sized> Clone for Handle<T> {
#[inline(always)]
fn clone(&self) -> Self {
Self(self.0.clone())
Expand All @@ -49,13 +49,13 @@ impl<T> Clone for Handle<T> {

impl<T> fmt::Debug for Handle<T>
where
T: fmt::Debug,
T: ?Sized + fmt::Debug,
{
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let mut debug = f.debug_tuple("Handle");

match self.0.try_borrow() {
Ok(value) => debug.field(&*value).finish(),
Ok(value) => debug.field(&&*value).finish(),
Err(_) => debug.field(&"_").finish(),
}
}
Expand Down
16 changes: 6 additions & 10 deletions crates/vuur_vm/src/instruction_set.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::fmt;
use std::fmt::Formatter;

use crate::value::{GlobalId, LocalId, UpValueId};
use crate::value::{ConstantId, GlobalId, LocalId, UpValueId};

/// Instruction set.
#[derive(Debug, Clone, Copy)]
Expand All @@ -25,7 +25,6 @@ pub enum Op {
I32_Greater,
I32_LessEq,
I32_GreaterEq,
I32_Cmp,

/// Push a constant int32 value onto the operand stack.
I32_Const {
Expand Down Expand Up @@ -72,10 +71,10 @@ pub enum Op {
func_id: u16,
},
Return,
/// Create a closure instance.
///
/// Expects a function definition to be on the top of the stack.
Closure_Create,

/// Create a closure instance from the function definition stored
/// in the constant table of the current call frame.
Closure(ConstantId),

// ------------------------------------------------------------------------
// Control Flow
Expand Down Expand Up @@ -109,7 +108,6 @@ impl Op {
Op::I32_Greater => -1,
Op::I32_LessEq => -1,
Op::I32_GreaterEq => -1,
Op::I32_Cmp => -1,
Op::I32_Const { .. } => 1,
Op::I32_Const_Inline { .. } => 1,
Op::Store_Global { .. } => 0,
Expand All @@ -122,7 +120,7 @@ impl Op {
Op::Call_Closure { arity } => -(*arity as isize) + 1,
Op::Call_Method { arity, .. } => -(*arity as isize), // remember receiver
Op::Return => -1,
Op::Closure_Create => 1,
Op::Closure(_) => 1,
Op::Jump => 0,
Op::Jump_False { .. } => -1,
Op::End => 0,
Expand All @@ -131,8 +129,6 @@ impl Op {
}
}

pub type ConstantId = u16;

/// Bytecode argument packed into 24 bits, encoded in little-endian.
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Arg24([u8; 3]);
Expand Down
59 changes: 45 additions & 14 deletions crates/vuur_vm/src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use crate::value::{GlobalId, LocalId, Program};
use crate::value::{Slot, Value};
use crate::{
handle::Handle,
instruction_set::{Arg24, Op},
value::{Closure, Module, ScriptFunc},
value::{Closure, ConstantId, GlobalId, LocalId, Module, Program, ScriptFunc},
vm_v2::{Store, VM},
};
use std::rc::Rc;

/// Create a recursive fibonacci script function.
fn fibonacci(store: &mut Store, module: Handle<Module>) {
// func fib(n: Int) {
fn fibonacci(module: Handle<Module>) -> Rc<ScriptFunc> {
// func fib(n: Int) -> Int {
// if n <= 1 {
// return n
// } else {
Expand All @@ -25,7 +25,7 @@ fn fibonacci(store: &mut Store, module: Handle<Module>) {
},
Op::I32_LessEq,
Op::Jump_False {
addr: Arg24::from_u32(0),
addr: Arg24::from_u32(6),
},
Op::Load_Local { local_id: n },
Op::Return,
Expand All @@ -49,27 +49,58 @@ fn fibonacci(store: &mut Store, module: Handle<Module>) {
Op::Call_Closure { arity: 1 },
// fib(n - 1) + fib(n - 2)
Op::I32_Add,
Op::Return,
];

Rc::new(ScriptFunc {
constants: vec![],
code: code.into_boxed_slice(),
module: module.downgrade(),
})
}

#[test]
fn test_vm_v2() {
let fib_arg_1 = 10;

let module = Handle::new(Module::new("__main__"));

// Global variable slots would be determined by top-level `var` and `func` statements.
for _ in 0..1 {
module.borrow_mut().vars.push(Value::Nil);
}

let fib_func = fibonacci(module.clone());

let code = vec![
// func fib(n: Int) -> Int:
Op::Closure(ConstantId::new(0)), // create closure
Op::Store_Global {
global_id: GlobalId::new(0),
}, // Store closure in variable
// fib(5)
Op::Load_Global {
global_id: GlobalId::new(0),
}, // Load closure from variable
Op::I32_Const_Inline {
arg: Arg24::from_i32(1),
},
Op::I32_Const_Inline {
arg: Arg24::from_i32(2),
arg: Arg24::from_i32(fib_arg_1),
},
Op::I32_Add,
Op::Call_Closure { arity: 1 },
// Op::I32_Const_Inline {
// arg: Arg24::from_i32(1),
// },
// Op::I32_Const_Inline {
// arg: Arg24::from_i32(2),
// },
// Op::I32_Add,
Op::Return,
];

// Module top-level code.
let func = Rc::new(ScriptFunc {
constants: vec![],
constants: vec![
Value::Func(fib_func), // ConstantId(0)
],
code: code.into_boxed_slice(),
module: module.downgrade(),
});
Expand All @@ -79,7 +110,7 @@ fn test_vm_v2() {

// ---------------------------------------------------------------------------------------------
let mut vm = VM::new();
let slot = vm.run_program(&program);
println!("{slot:?}");
assert_eq!(slot.unwrap().raw(), 3);
let value = vm.run_program(&program);
println!("{value:?}");
assert_eq!(value.unwrap().into_i32().unwrap(), 55);
}
111 changes: 108 additions & 3 deletions crates/vuur_vm/src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,90 @@ symbol_impl!(
);

symbol_impl!(
/// Local variable Id.
/// Up-value variable Id.
#[derive(Debug, Clone, Copy)] pub struct UpValueId(u16)
);

symbol_impl!(
/// Constant Id.
#[derive(Debug, Clone, Copy)] pub struct ConstantId(u16)
);

/// Dynamically typed value.
///
/// This is to simplify the internals of the VM for the short term.
/// In the future the VM will be statically typed.
///
/// See [`Slot`]
#[derive(Clone)]
pub enum Value {
Nil,
Bool(bool),
Int(i32),
Float(f32),
Str(Handle<String>),

// ------------------------------------------------------------------------
// Reference type objects.
Func(Rc<ScriptFunc>),
Closure(Handle<Closure>),
Native(Handle<NativeFunc>),
}

impl fmt::Debug for Value {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
use Value::*;

// There are plenty of opportunities for circular
// references, so we don't recurse into complex objects.
match self {
Nil => write!(f, "Nil"),
Bool(v) => f.debug_tuple("Bool").field(&v).finish(),
Int(v) => f.debug_tuple("Int").field(&v).finish(),
Float(v) => f.debug_tuple("Float").field(&v).finish(),
Str(v) => f.debug_tuple("Str").field(&v).finish(),
Func(_) => write!(f, "Func(...)"),
Closure(_) => write!(f, "Closure(...)"),
Native(_) => write!(f, "Native(...)"),
}
}
}

impl Value {
#[inline(always)]
pub fn into_func(self) -> Result<Rc<ScriptFunc>, String> {
match self {
Value::Func(func) => Ok(func),
_ => Err(self.type_error()),
}
}

#[inline(always)]
pub fn into_closure(self) -> Result<Handle<Closure>, String> {
match self {
Value::Closure(closure) => Ok(closure),
_ => Err(self.type_error()),
}
}

#[inline(always)]
pub fn into_i32(self) -> Result<i32, String> {
match self {
Value::Int(int) => Ok(int),
_ => Err(self.type_error()),
}
}

#[inline(always)]
pub fn from_i32(value: i32) -> Self {
Self::Int(value)
}

fn type_error(&self) -> String {
format!("unexpected value type: {self:?}")
}
}

/// An executable Vuur program.
pub struct Program {
/// An executable closure object holding the top-level code of the main module.
Expand All @@ -44,8 +124,18 @@ impl Program {
///
/// It holds the raw bits of a value. The encoding is
/// specific to the current platform.
///
/// FIXME: Storing reference type object pointers in a slot.
///
/// To keep the VM simple, the standard library `Rc` is used
/// for reference types. It doesn't expose its internal pointer,
/// making it hard to build unsafe internals around it.
///
/// When we have a proper garbage collector with our own
/// handle types we can revisit `Slot`.
#[derive(Clone, Copy)]
#[repr(transparent)]
#[allow(dead_code)]
pub(crate) struct Slot(u64);

impl Slot {
Expand Down Expand Up @@ -75,6 +165,16 @@ impl Slot {
pub(crate) fn to_f32(self) -> f32 {
f32::from_bits(self.0 as u32)
}

#[inline(always)]
pub(crate) fn from_ptr<T>(ptr: *const T) -> Self {
Self(ptr as usize as u64)
}

#[inline(always)]
pub(crate) unsafe fn to_ptr<T>(self) -> *mut T {
self.0 as usize as *mut T
}
}

impl fmt::Debug for Slot {
Expand All @@ -89,7 +189,7 @@ pub struct Module {
/// Name of the module.
pub name: String,
/// Module level global variables.
pub vars: SymbolTable<GlobalId, Slot>,
pub vars: SymbolTable<GlobalId, Value>,
}

impl Module {
Expand Down Expand Up @@ -161,7 +261,10 @@ pub enum UpValue {
/// so it can be stored without `RefCell`.
#[derive(Debug)]
pub struct ScriptFunc {
pub constants: Vec<u32>,
/// Values defined in the function body that do not change.
pub constants: Vec<Value>,

/// Interpreter bytecode instructions to be executed.
pub code: Box<[Op]>,

/// The function keeps a reference to the module it lexically belongs to.
Expand All @@ -178,6 +281,7 @@ pub struct ScriptFunc {

pub type NativeFuncPtr = fn() -> ();

/// Host function defined in Rust.
#[derive(Debug)]
pub struct NativeFunc {
pub ptr: NativeFuncPtr,
Expand All @@ -192,6 +296,7 @@ mod test {
/// Ensure that a slot can hold a pointer on the current architecture.
#[test]
fn test_slot_size() {
println!("{}", std::mem::size_of::<Value>());
assert!(std::mem::size_of::<*const [u8; 1024]>() <= std::mem::size_of::<Slot>());
assert!(std::mem::size_of::<Handle<[u8; 1024]>>() <= std::mem::size_of::<Slot>());
assert!(std::mem::size_of::<Rc<[u8; 1024]>>() <= std::mem::size_of::<Slot>());
Expand Down
Loading

0 comments on commit 6308373

Please sign in to comment.