diff options
Diffstat (limited to 'crates/sloth_vm/src')
| -rw-r--r-- | crates/sloth_vm/src/lib.rs | 146 | ||||
| -rw-r--r-- | crates/sloth_vm/src/native.rs | 18 | ||||
| -rw-r--r-- | crates/sloth_vm/src/sloth_std/mod.rs | 24 | ||||
| -rw-r--r-- | crates/sloth_vm/src/sloth_std/rand.rs | 39 | ||||
| -rw-r--r-- | crates/sloth_vm/src/sloth_std/stdio.rs | 51 | ||||
| -rw-r--r-- | crates/sloth_vm/src/value.rs | 53 | ||||
| -rw-r--r-- | crates/sloth_vm/src/vm.rs | 599 |
7 files changed, 919 insertions, 11 deletions
diff --git a/crates/sloth_vm/src/lib.rs b/crates/sloth_vm/src/lib.rs index 1dfb191..295827d 100644 --- a/crates/sloth_vm/src/lib.rs +++ b/crates/sloth_vm/src/lib.rs @@ -7,24 +7,148 @@ unused_lifetimes )] -const STACK_SIZE: usize = 1024; +pub mod native; +pub mod sloth_std; +pub mod value; +pub mod vm; + +use std::ops::{Index, IndexMut}; + +use value::{Object, ObjectType}; + +use crate::value::Primitive; +pub use crate::vm::VM; pub struct Chunk { + constants: Vec<Primitive>, code: Vec<u8>, - constants: Vec<u64>, } -pub struct VM { - stack: [u8; STACK_SIZE], - constants: Vec<u8>, +const STACK_SIZE: usize = 1024; + +#[derive(Debug)] +pub struct Stack { + stack: [Primitive; STACK_SIZE], + top: usize, +} + +impl Default for Stack { + fn default() -> Self { + Self { + top: Default::default(), + stack: [Primitive::Empty; STACK_SIZE], + } + } } -impl VM { - // +impl Stack { + #[inline(always)] + pub fn push(&mut self, value: Primitive) { + if self.top >= STACK_SIZE { + panic!("Stack overflow"); + } + + self.stack[self.top] = value; + self.top += 1; + } + + #[inline(always)] + pub fn pop(&mut self) -> Primitive { + if self.top == 0 { + panic!("Stack underflow"); + } + + self.top -= 1; + self.stack[self.top] + } + + #[inline(always)] + pub fn pop2(&mut self) -> (Primitive, Primitive) { + (self.pop(), self.pop()) + } + + #[inline(always)] + pub fn peek(&self) -> Primitive { + self.stack[self.top - 1] + } + + #[inline(always)] + pub fn peek_nth(&self, nth: usize) -> Primitive { + self.stack[self.top - 1 - nth] + } +} + +impl Index<usize> for Stack { + type Output = Primitive; + + fn index(&self, index: usize) -> &Self::Output { + &self.stack[index] + } } -#[cfg(test)] -mod tests { - #[test] - fn add_program() {} +impl IndexMut<usize> for Stack { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.stack[index] + } +} + +pub struct ObjectMap { + free: usize, + heap: Vec<Object>, +} + +impl Default for ObjectMap { + fn default() -> Self { + Self::with_capacity(32) + } +} + +impl From<Vec<Object>> for ObjectMap { + fn from(heap: Vec<Object>) -> Self { + let mut free = heap.len(); + for (idx, obj) in heap.iter().enumerate() { + if let ObjectType::Free { .. } = obj.typ { + free = idx; + break; + } + } + + Self { free, heap } + } +} + +impl ObjectMap { + pub fn with_capacity(capacity: usize) -> Self { + let mut heap = Vec::with_capacity(capacity); + for i in 0..capacity { + heap.push(Object::new(ObjectType::Free { next: i + 1 })); + } + + Self { free: 0, heap } + } + + pub fn allocate(&mut self, object: Object) -> usize { + let current = self.free; + if current >= self.heap.len() { + self.heap + .push(Object::new(ObjectType::Free { next: current + 1 })) + } + + let ObjectType::Free { next } = self.heap[current].typ else { + panic!("Allocation failed: Expected free location wasn't free"); + }; + + self.heap[current] = object; + self.free = next; + + current + } + + pub fn get(&self, idx: usize) -> Option<&Object> { + self.heap.get(idx) + } + + pub fn get_mut(&mut self, idx: usize) -> Option<&mut Object> { + self.heap.get_mut(idx) + } } diff --git a/crates/sloth_vm/src/native.rs b/crates/sloth_vm/src/native.rs new file mode 100644 index 0000000..8790cbd --- /dev/null +++ b/crates/sloth_vm/src/native.rs @@ -0,0 +1,18 @@ +use crate::value::Primitive; +use crate::VM; + +pub type NativeFunctionResult = Result<Primitive, Error>; +pub type NativeFunctionInput = fn(&mut VM, &[Primitive]) -> NativeFunctionResult; + +pub enum Error { + InvalidArgument, + Unknown(String), +} + +#[allow(clippy::type_complexity)] +pub struct NativeFunction { + pub name: &'static str, + pub function: NativeFunctionInput, + pub arity: u8, + pub returns_value: bool, +} diff --git a/crates/sloth_vm/src/sloth_std/mod.rs b/crates/sloth_vm/src/sloth_std/mod.rs new file mode 100644 index 0000000..86611d7 --- /dev/null +++ b/crates/sloth_vm/src/sloth_std/mod.rs @@ -0,0 +1,24 @@ +use std::collections::HashMap; + +use once_cell::sync::Lazy; + +use crate::native::NativeFunction; + +pub mod rand; +pub mod stdio; + +pub static NATIVE_LIBRARY: Lazy<HashMap<&'static str, NativeFunction>> = Lazy::new(|| { + let mut map = HashMap::new(); + + // rand + map.insert("rand$gen", rand::GEN_FUNCTION); + map.insert("rand$gen_range", rand::GEN_RANGE_FUNCTION); + + // stdio + map.insert("write", stdio::WRITE_FUNCTION); + map.insert("read", stdio::READ_FUNCTION); + + // filesystem + + map +}); diff --git a/crates/sloth_vm/src/sloth_std/rand.rs b/crates/sloth_vm/src/sloth_std/rand.rs new file mode 100644 index 0000000..bae0606 --- /dev/null +++ b/crates/sloth_vm/src/sloth_std/rand.rs @@ -0,0 +1,39 @@ +use rand::Rng; + +use crate::native::{self, NativeFunction, NativeFunctionResult}; +use crate::value::Primitive; +use crate::value::Primitive::{Float, Integer}; +use crate::VM; + +fn gen(_vm: &mut VM, _args: &[Primitive]) -> NativeFunctionResult { + let value = rand::thread_rng().gen_range(0.0..1.0); + + Ok(Float(value)) +} + +pub const GEN_FUNCTION: NativeFunction = NativeFunction { + name: "rand$gen", + function: gen, + arity: 0, + returns_value: true, +}; + +fn gen_range(_vm: &mut VM, args: &[Primitive]) -> NativeFunctionResult { + let min = args.get(0).cloned(); + let max = args.get(1).cloned(); + + let (Some(Integer(min)), Some(Integer(max))) = (min, max) else { + return Err(native::Error::InvalidArgument); + }; + + let value = rand::thread_rng().gen_range(min..max); + + Ok(Integer(value)) +} + +pub const GEN_RANGE_FUNCTION: NativeFunction = NativeFunction { + name: "rand$gen_range", + function: gen_range, + arity: 2, + returns_value: true, +}; diff --git a/crates/sloth_vm/src/sloth_std/stdio.rs b/crates/sloth_vm/src/sloth_std/stdio.rs new file mode 100644 index 0000000..a743ad1 --- /dev/null +++ b/crates/sloth_vm/src/sloth_std/stdio.rs @@ -0,0 +1,51 @@ +use std::io::{stdin, BufRead}; + +use crate::native::{self, NativeFunction, NativeFunctionResult}; +use crate::value::{Object, ObjectType, Primitive}; +use crate::VM; + +fn write(vm: &mut VM, args: &[Primitive]) -> NativeFunctionResult { + let Some(Primitive::Object(ptr)) = args.get(0).cloned() else { + return Err(native::Error::InvalidArgument); + }; + + let object = vm + .objects() + .get(ptr as usize) + .ok_or(native::Error::InvalidArgument)?; + + let ObjectType::String(str) = &object.typ else { + return Err(native::Error::InvalidArgument); + }; + + println!("{str}"); + + Ok(Primitive::Empty) +} + +pub const WRITE_FUNCTION: NativeFunction = NativeFunction { + name: "write", + function: write, + arity: 1, + returns_value: false, +}; + +fn read(vm: &mut VM, _args: &[Primitive]) -> NativeFunctionResult { + let mut line = String::new(); + stdin() + .lock() + .read_line(&mut line) + .map_err(|it| native::Error::Unknown(it.to_string()))?; + + let object = Object::new(ObjectType::String(line)); + let ptr = vm.objects_mut().allocate(object); + + Ok(Primitive::Object(ptr as u32)) +} + +pub const READ_FUNCTION: NativeFunction = NativeFunction { + name: "read", + function: read, + arity: 0, + returns_value: true, +}; diff --git a/crates/sloth_vm/src/value.rs b/crates/sloth_vm/src/value.rs new file mode 100644 index 0000000..f149c0e --- /dev/null +++ b/crates/sloth_vm/src/value.rs @@ -0,0 +1,53 @@ +use crate::native::NativeFunction; +use crate::Chunk; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Primitive { + Integer(i128), + Float(f64), + Bool(bool), + /// Pointer to an object living on heap + Object(u32), + Empty, +} + +pub struct Object { + /// If the object has been marked by the VM or not + pub(crate) marked: bool, + pub(crate) typ: ObjectType, +} + +impl Object { + pub fn new(typ: ObjectType) -> Self { + Self { marked: false, typ } + } +} + +pub enum ObjectType { + Box(Primitive), + String(String), + List(Vec<Primitive>), + + Function(Function), + NativeFunction(NativeFunction), + + Free { next: usize }, +} + +pub struct Function { + pub(crate) name: Option<String>, + pub(crate) chunk: Chunk, + pub(crate) arity: u8, + pub(crate) returns_value: bool, +} + +impl Function { + pub(crate) fn root(chunk: Chunk) -> Self { + Self { + name: None, + chunk, + arity: 0, + returns_value: false, + } + } +} diff --git a/crates/sloth_vm/src/vm.rs b/crates/sloth_vm/src/vm.rs new file mode 100644 index 0000000..2d58c3d --- /dev/null +++ b/crates/sloth_vm/src/vm.rs @@ -0,0 +1,599 @@ +use std::mem::MaybeUninit; + +use sloth_bytecode::Opcode; + +use crate::value::{Function, Object, ObjectType, Primitive}; +use crate::{native, ObjectMap, Stack}; + +#[derive(Clone, Copy)] +pub struct CallFrame { + pointer: usize, + stack_offset: usize, + function: *const Function, // TODO: Safety +} + +impl CallFrame { + fn new(stack_offset: usize, function: &Function) -> Self { + Self { + pointer: 0, + stack_offset, + function: function as *const _, + } + } + + #[inline] + fn function(&self) -> &Function { + unsafe { &*self.function } + } +} + +const CALL_STACK_SIZE: usize = 1024; + +pub struct CallStack { + top: usize, + frames: [MaybeUninit<CallFrame>; CALL_STACK_SIZE], +} + +impl Default for CallStack { + fn default() -> Self { + Self { + top: 0, + frames: [MaybeUninit::uninit(); CALL_STACK_SIZE], + } + } +} + +impl CallStack { + fn push(&mut self, frame: CallFrame) { + self.frames[self.top].write(frame); + self.top += 1; + } + + fn pop(&mut self) { + self.top -= 1; + } + + fn peek(&self) -> &CallFrame { + unsafe { self.frames[self.top - 1].assume_init_ref() } + } + + fn peek_mut(&mut self) -> &mut CallFrame { + unsafe { self.frames[self.top - 1].assume_init_mut() } + } +} + +pub struct VM { + stack: Stack, + call_stack: CallStack, + objects: ObjectMap, +} + +impl Default for VM { + fn default() -> Self { + Self::init(ObjectMap::default()) + } +} + +impl VM { + pub fn init(objects: ObjectMap) -> Self { + Self { + stack: Stack::default(), + call_stack: CallStack::default(), + objects, + } + } + + pub fn new(objects: ObjectMap, mut root: Function) -> Self { + let mut this = Self::init(objects); + + // Allocating the root function + root.chunk.code.push(Opcode::Halt as u8); + this.call_stack.push(CallFrame::new(0, &root)); + this.objects + .allocate(Object::new(ObjectType::Function(root))); + + this + } + + pub fn step(&mut self) -> bool { + use Primitive::*; + + let opcode = self.read_u8(); + + match Opcode::from_u8(opcode) { + Opcode::Constant => { + let idx = self.read_u16() as usize; + let value = self.call_stack.peek().function().chunk.constants[idx]; + + self.stack.push(value); + } + + Opcode::Dup => { + let value = self.stack.pop(); + self.stack.push(value); + self.stack.push(value); + } + Opcode::Pop => { + self.stack.pop(); + } + Opcode::GetLocal => { + let idx = self.read_u16() as usize; + let value = self.stack[self.call_stack.peek().stack_offset + idx]; + + self.stack.push(value); + } + Opcode::SetLocal => { + let idx = self.read_u16() as usize; + let value = self.stack.pop(); + + self.stack[self.call_stack.peek().stack_offset + idx] = value; + } + + Opcode::Add => { + let value = match self.stack.pop2() { + (Integer(lhs), Integer(rhs)) => Integer(lhs + rhs), + (Float(lhs), Float(rhs)) => Float(lhs + rhs), + _ => panic!(), + }; + + self.stack.push(value); + } + Opcode::Sub => { + let value = match self.stack.pop2() { + (Integer(lhs), Integer(rhs)) => Integer(lhs - rhs), + (Float(lhs), Float(rhs)) => Float(lhs - rhs), + _ => panic!(), + }; + + self.stack.push(value); + } + Opcode::Mul => { + let value = match self.stack.pop2() { + (Integer(lhs), Integer(rhs)) => Integer(lhs * rhs), + (Float(lhs), Float(rhs)) => Float(lhs * rhs), + _ => panic!(), + }; + + self.stack.push(value); + } + Opcode::Div => { + let value = match self.stack.pop2() { + (Integer(_), Integer(0)) => panic!("Divide by 0"), + (Integer(lhs), Integer(rhs)) => Integer(lhs / rhs), + (Float(lhs), Float(rhs)) => Float(lhs / rhs), + _ => panic!(), + }; + + self.stack.push(value); + } + Opcode::Mod => { + let value = match self.stack.pop2() { + (Integer(lhs), Integer(rhs)) => Integer(lhs % rhs), + (Float(lhs), Float(rhs)) => Float(lhs % rhs), + _ => panic!(), + }; + + self.stack.push(value); + } + + Opcode::Eq => { + let value = match self.stack.pop2() { + (Integer(lhs), Integer(rhs)) => Bool(lhs == rhs), + (Float(lhs), Float(rhs)) => Bool(lhs == rhs), + (Bool(lhs), Bool(rhs)) => Bool(lhs == rhs), + (Object(lhs), Object(rhs)) => Bool(lhs == rhs), + (Empty, Empty) => Bool(true), + _ => Bool(false), + }; + + self.stack.push(value); + } + Opcode::Ne => { + let value = match self.stack.pop2() { + (Integer(lhs), Integer(rhs)) => Bool(lhs != rhs), + (Float(lhs), Float(rhs)) => Bool(lhs != rhs), + (Bool(lhs), Bool(rhs)) => Bool(lhs != rhs), + (Object(lhs), Object(rhs)) => Bool(lhs != rhs), + (Empty, Empty) => Bool(false), + _ => Bool(false), + }; + + self.stack.push(value); + } + + Opcode::Jump => { + let to = self.read_u16(); + self.call_stack.peek_mut().pointer = to as usize; + } + Opcode::JumpIf => { + let to = self.read_u16(); + let value = self.stack.pop(); + + if let Bool(true) = value { + self.call_stack.peek_mut().pointer = to as usize; + } + } + + Opcode::Call => { + let Primitive::Object(ptr) = self.stack.pop() else { + panic!("Last element on stack was not an object"); + }; + + self.call(ptr as usize); + } + + Opcode::Return => { + self.call_return(); + } + + Opcode::Halt => return false, + + opcode => unimplemented!("Opcode {:?} unimplemented", opcode), + } + + true + } + + pub fn run(&mut self) { + while self.step() {} + } + + pub fn call(&mut self, ptr: usize) { + let Some(obj) = self.objects.get(ptr) else { + panic!("Pointer referenced nothing"); + }; + + match &obj.typ { + ObjectType::Function(function) => { + // Add a callstack entry for the function + let offset = self.stack.top - (function.arity as usize); + self.call_stack.push(CallFrame::new(offset, function)); + } + ObjectType::NativeFunction(function) => { + let mut args = Vec::with_capacity(function.arity as usize); + for _ in 0..function.arity { + args.push(self.stack.pop()); + } + + let name = function.name; + let returns_value = function.returns_value; + + let internal = function.function; + let result = internal(self, &args); + + match result { + Ok(value) => { + if returns_value { + self.stack.push(value); + } + } + Err(error) => match error { + native::Error::InvalidArgument => { + panic!("Invalid argument provided to '{name}'"); + } + native::Error::Unknown(msg) => { + panic!("Native function '{name}' failed due to '{msg}'"); + } + }, + } + } + _ => panic!("Object was not a function"), + } + } + + fn call_return(&mut self) { + let function = self.call_stack.peek().function(); + let stack_offset = self.call_stack.peek().stack_offset; + + let return_value = if function.returns_value { + Some(self.stack.pop()) + } else { + None + }; + + self.stack.top = stack_offset; + + if let Some(return_value) = return_value { + self.stack.push(return_value); + } + + self.call_stack.pop(); + } + + fn unwind(&mut self) { + unimplemented!("Implement unwinding for error handling"); + } + + #[inline(always)] + fn read_u8(&mut self) -> u8 { + let frame = self.call_stack.peek_mut(); + let function = frame.function(); + let byte = function.chunk.code[frame.pointer]; + frame.pointer += 1; + byte + } + + #[inline(always)] + fn read_u16(&mut self) -> u16 { + let frame = self.call_stack.peek_mut(); + let chunk = &frame.function().chunk; + + let bytes = (chunk.code[frame.pointer], chunk.code[frame.pointer + 1]); + + frame.pointer += 2; + + ((bytes.0 as u16) << 8) + (bytes.1 as u16) + } + + #[inline(always)] + pub fn objects(&self) -> &ObjectMap { + &self.objects + } + + #[inline(always)] + pub fn objects_mut(&mut self) -> &mut ObjectMap { + &mut self.objects + } +} + +#[cfg(test)] +mod tests { + use crate::value::{Function, Object, ObjectType, Primitive}; + use crate::{sloth_std, Chunk, ObjectMap, VM}; + + #[test] + fn arithmetic_ops() { + // Addition + let mut vm = VM::new( + ObjectMap::default(), + Function::root(Chunk { + constants: vec![Primitive::Integer(7)], + code: vec![ + 0x00, 0, 0, // Load constant from 0 + 0x10, // Duplicate + 0x20, // Add + 0xE0, + ], + }), + ); + + vm.run(); + assert_eq!(vm.stack.peek(), Primitive::Integer(14)); + + let mut vm = VM::new( + ObjectMap::default(), + Function::root(Chunk { + constants: vec![Primitive::Integer(2), Primitive::Integer(11)], + code: vec![ + 0x00, 0, 0, // Load constant from 0 + 0x00, 0, 1, // Load constant from 1 + 0x20, // Add + 0xE0, + ], + }), + ); + + vm.run(); + assert_eq!(vm.stack.peek(), Primitive::Integer(13)); + } + + #[test] + fn basic_function() { + let mut vm = VM::new( + ObjectMap::from(vec![Object::new(ObjectType::Function(Function { + name: Some("add".to_string()), + chunk: Chunk { + constants: vec![], + code: vec![0x14, 0, 0, 0x14, 0, 1, 0x20, 0x52], + }, + arity: 2, + returns_value: true, + }))]), + Function::root(Chunk { + constants: vec![ + Primitive::Integer(6), + Primitive::Integer(3), + Primitive::Object(0), + Primitive::Object(1), + Primitive::Object(2), + ], + code: vec![ + 0x00, 0, 0, // Load first function parameter from 0 + 0x00, 0, 1, // Load second function parameter from 1 + 0x00, 0, 2, // Load function constant from 2 + 0x50, // Call function + ], + }), + ); + + vm.run(); + + assert_eq!(vm.stack.peek(), Primitive::Integer(9)); + } + + #[test] + fn native_function() { + let mut vm = VM::new( + ObjectMap::from(vec![ + Object::new(ObjectType::NativeFunction(sloth_std::rand::GEN_FUNCTION)), + Object::new(ObjectType::NativeFunction( + sloth_std::rand::GEN_RANGE_FUNCTION, + )), + ]), + Function::root(Chunk { + constants: vec![ + Primitive::Object(0), + Primitive::Object(1), + Primitive::Integer(5), + Primitive::Integer(10), + ], + code: vec![ + // First part + 0x00, 0, 0, // + 0x50, // + 0xE0, // + // Second part + 0x00, 0, 3, // + 0x00, 0, 2, // + 0x00, 0, 1, // + 0x50, // + ], + }), + ); + + vm.run(); + + assert!({ + let Primitive::Float(i) = vm.stack.peek() else { panic!(); }; + (0.0..=1.0).contains(&i) + }); + + vm.run(); + + assert!({ + let Primitive::Integer(i) = vm.stack.peek() else { panic!(); }; + (5..10).contains(&i) + }); + } + + #[test] + fn fibonacci() { + #[rustfmt::skip] + let mut vm = VM::new( + ObjectMap::default(), + Function::root(Chunk { + constants: vec![ + Primitive::Integer(0), + Primitive::Integer(1), + Primitive::Integer(10), + ], + code: vec![ + // Load variables + 0x00, 0, 0, // 0 Index + 0x00, 0, 0, // 3 Me + 0x00, 0, 0, // 6 Parent + 0x00, 0, 1, // 9 Grandparent + + // Load parent and grandparent, sum them and put the value in me + 0x14, 0, 2, // 12 + 0x14, 0, 3, // 15 + 0x20, // 16 + 0x15, 0, 1, // 19 + + // Set grandparent to parent + 0x14, 0, 2, // 22 + 0x15, 0, 3, // 25 + + // Set parent to me + 0x14, 0, 1, // 28 + 0x15, 0, 2, // 31 + + // Increment Index by 1 + 0x00, 0, 1, // 34 + 0x14, 0, 0, // 37 Index + 0x20, // 40 + 0x15, 0, 0, // 41 Index + + // Load me + 0x14, 0, 1, // 44 + 0xE0, // 47 + 0x11, // 48 + + // Repeat until Index is 9 + 0x00, 0, 2, // 49 + 0x14, 0, 0, // 52 Index + 0x31, // 55 + 0x41, 0, 12, // 56 + ], + }), + ); + + let mut values = Vec::new(); + for _ in 0..10 { + vm.run(); + values.push(vm.stack.peek()); + } + + assert_eq!(&values, &[ + Primitive::Integer(1), + Primitive::Integer(1), + Primitive::Integer(2), + Primitive::Integer(3), + Primitive::Integer(5), + Primitive::Integer(8), + Primitive::Integer(13), + Primitive::Integer(21), + Primitive::Integer(34), + Primitive::Integer(55), + ]); + } + + #[test] + fn fibonacci_recursive() { + #[rustfmt::skip] + let mut vm = VM::new( + ObjectMap::from(vec![Object::new(ObjectType::Function(Function { + name: Some("fib".to_owned()), + chunk: Chunk { + constants: vec![ + Primitive::Object(0), + Primitive::Integer(0), + Primitive::Integer(1), + Primitive::Integer(2), + ], + code: vec![ + 0x14, 0, 0, // 0 + 0x00, 0, 1, // 3 + 0x31, // 6 + 0x41, 0, 14, // 7 + 0x00, 0, 1, // 10 + 0x52, // 13 + + 0x14, 0, 0, // 14 + 0x00, 0, 2, // 17 + 0x31, // 20 + 0x41, 0, 28, // 21 + 0x00, 0, 2, // 24 + 0x52, // 27 + + // fib(n - 1) + 0x00, 0, 2, // 28 + 0x14, 0, 0, // 31 + 0x21, // 34 + 0x00, 0, 0, // 35 + 0x50, // 38 + + // fib(n - 2) + 0x00, 0, 3, // 39 + 0x14, 0, 0, // 42 + 0x21, // 45 + 0x00, 0, 0, // 46 + 0x50, // 49 + + // add & return + 0x20, // 50 + 0x52, // 51 + ], + }, + arity: 1, + returns_value: true, + }))]), + Function::root(Chunk { + constants: vec![ + Primitive::Object(0), + Primitive::Integer(10), + ], + code: vec![ + // Load n and the function and call it + 0x00, 0, 1, // 0 + 0x00, 0, 0, // 3 + 0x50, // 6 + ], + }), + ); + + vm.run(); + + assert_eq!(Primitive::Integer(55), vm.stack.peek()); + } +} |
