aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock10
-rw-r--r--Cargo.toml1
-rw-r--r--crates/sloth_bytecode/Cargo.toml2
-rw-r--r--crates/sloth_bytecode/macros/Cargo.toml15
-rw-r--r--crates/sloth_bytecode/macros/src/lib.rs164
-rw-r--r--crates/sloth_bytecode/src/lib.rs89
-rw-r--r--crates/sloth_vm/src/lib.rs111
-rw-r--r--crates/sloth_vm/src/obj.rs0
-rw-r--r--crates/sloth_vm/src/value.rs49
-rw-r--r--crates/sloth_vm/src/vm.rs387
10 files changed, 450 insertions, 378 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 1684d93..fa3bcfa 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -54,16 +54,6 @@ name = "sloth_bytecode"
version = "0.1.0"
dependencies = [
"byteorder",
- "sloth_bytecode_macros",
-]
-
-[[package]]
-name = "sloth_bytecode_macros"
-version = "0.1.0"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index fed9008..57e0bc4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,7 +2,6 @@
members = [
"crates/sloth",
"crates/sloth_bytecode",
- "crates/sloth_bytecode/macros",
"crates/sloth_vm",
]
diff --git a/crates/sloth_bytecode/Cargo.toml b/crates/sloth_bytecode/Cargo.toml
index 76448ee..0de211f 100644
--- a/crates/sloth_bytecode/Cargo.toml
+++ b/crates/sloth_bytecode/Cargo.toml
@@ -6,6 +6,4 @@ version.workspace = true
edition.workspace = true
[dependencies]
-sloth_bytecode_macros = { path = "./macros" }
-
byteorder = "1.4.3"
diff --git a/crates/sloth_bytecode/macros/Cargo.toml b/crates/sloth_bytecode/macros/Cargo.toml
deleted file mode 100644
index 649d88a..0000000
--- a/crates/sloth_bytecode/macros/Cargo.toml
+++ /dev/null
@@ -1,15 +0,0 @@
-[package]
-name = "sloth_bytecode_macros"
-
-license.workspace = true
-version.workspace = true
-edition.workspace = true
-
-[dependencies]
-proc-macro2 = "1.0.54"
-quote = "1.0.26"
-syn = "2.0.12"
-
-[lib]
-proc-macro = true
-
diff --git a/crates/sloth_bytecode/macros/src/lib.rs b/crates/sloth_bytecode/macros/src/lib.rs
deleted file mode 100644
index 1690c17..0000000
--- a/crates/sloth_bytecode/macros/src/lib.rs
+++ /dev/null
@@ -1,164 +0,0 @@
-use proc_macro2::{Ident, TokenStream};
-use quote::quote;
-use syn::parse::Parse;
-use syn::punctuated::Punctuated;
-use syn::{bracketed, parse_macro_input, LitInt, LitStr, Token};
-
-// TODO: Rename args to operands?
-
-struct DslInstructionInput {
- opcode: LitInt,
- name: Ident,
- args: Punctuated<Ident, Token![,]>,
- description: LitStr,
-}
-
-impl Parse for DslInstructionInput {
- fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
- let args_content;
- Ok(Self {
- opcode: input.parse()?,
- name: input.parse()?,
- args: {
- bracketed!(args_content in input);
- args_content.parse_terminated(Ident::parse, Token![,])?
- },
- description: input.parse()?,
- })
- }
-}
-
-struct DslInstructionsInput {
- name: Ident,
- instructions: Punctuated<DslInstructionInput, Token![,]>,
-}
-
-impl Parse for DslInstructionsInput {
- fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
- Ok(Self {
- name: input.parse()?,
- instructions: {
- input.parse::<Token![;]>()?;
- input.parse_terminated(DslInstructionInput::parse, Token![,])?
- },
- })
- }
-}
-
-fn into_enum_field(instruction: &DslInstructionInput) -> TokenStream {
- let DslInstructionInput {
- opcode,
- name,
- args,
- description,
- } = instruction;
-
- let args = args.iter();
-
- if args.len() > 0 {
- quote! {
- #[doc = #description]
- #name ( #( #args ),* ) = #opcode
- }
- } else {
- quote! {
- #[doc = #description]
- #name = #opcode
- }
- }
-}
-
-fn into_bytecode_parser(instruction: &DslInstructionInput) -> TokenStream {
- let DslInstructionInput {
- opcode,
- name,
- args,
- description: _,
- } = instruction;
-
- if args.is_empty() {
- return quote! {
- #opcode => Self :: #name
- };
- }
-
- let mut arg_params = Vec::new();
- for arg in args {
- let size = match arg.to_string().as_str() {
- "u128" => 128,
- "u64" => 64,
- "u32" => 32,
- "u16" => 16,
- "u8" => 8,
- typ => panic!("Unsupported instruction arg type '{typ}'"),
- } as usize;
-
- let bytes = size / 8;
-
- let mut chunks = Vec::new();
- for byte in 0..bytes {
- let shift_amount = size - (byte + 1) * 8;
- chunks.push(quote! {
- ((chunk[*offset + #byte] as #arg) << #shift_amount)
- });
- }
-
- arg_params.push(quote! {
- let value = #( #chunks )+* ;
- *offset += #bytes ;
- value
- });
- }
-
- quote! {
- #opcode => {
- Self :: #name (
- #( { #arg_params } ),*
- )
- }
- }
-}
-
-#[proc_macro]
-pub fn instructions(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
- let input = parse_macro_input!(input as DslInstructionsInput);
-
- // Getting values to construct the enum
- let enum_name = input.name;
- let enum_fields = input
- .instructions
- .iter()
- .map(into_enum_field)
- .collect::<Vec<_>>();
-
- // Getting the values to parse bytecode
- let bytecode_parsers = input
- .instructions
- .iter()
- .map(into_bytecode_parser)
- .collect::<Vec<_>>();
-
- // Building out the expanded code
- quote! {
- #[repr(u8)]
- #[derive(Clone, Debug, Eq, PartialEq)]
- pub enum #enum_name {
- #( #enum_fields ),*
- }
-
- impl #enum_name {
- pub fn disassemble(chunk: &[u8], offset: &mut usize) -> #enum_name {
- let opcode = chunk[*offset];
- *offset += 1;
-
- let instruction = match opcode {
- #( #bytecode_parsers , )*
- _ => panic!("Unknown bytecode encountered"),
- };
-
- instruction
- }
- }
- }
- .into()
-}
diff --git a/crates/sloth_bytecode/src/lib.rs b/crates/sloth_bytecode/src/lib.rs
index bf20068..df91c41 100644
--- a/crates/sloth_bytecode/src/lib.rs
+++ b/crates/sloth_bytecode/src/lib.rs
@@ -7,65 +7,64 @@
unused_lifetimes
)]
-use sloth_bytecode_macros::instructions;
-
pub enum Error {
UnknownOpcode(u8),
InvalidArguments,
Eof,
}
-instructions! {
- Instruction;
-
- 0x00 Constant [u32] "Push a constant value onto the stack",
- 0x01 Load [u32] "Load a value from a variable",
- 0x02 Push [u32] "Push a value to a variable",
+macro_rules! opcodes {
+ ( $( $code:literal $name:ident $docs:literal ),* ) => {
+ #[repr(u8)]
+ #[derive(Clone, Copy, Eq, PartialEq)]
+ pub enum Opcode {
+ $(
+ #[doc = $docs]
+ $name = $code
+ ),*
+ }
- 0x10 Dup [] "Duplicate a value on the stack",
- 0x11 Del [] "Delete a value from the stack",
+ impl Opcode {
+ pub fn into_u8(self) -> u8 {
+ self as u8
+ }
- 0x20 Add [] "Add the last 2 values on the stack",
- 0x21 Sub [] "Subtract the last 2 values on the stack",
- 0x22 Mul [] "Multiply the last 2 values on the stack",
- 0x23 Div [] "Divide the last 2 values on the stack",
- 0x24 Mod [] "Modulo the last 2 values on the stack",
+ pub fn from_u8(value: u8) -> Opcode {
+ match value {
+ $( $code => Self:: $name , )*
+ _ => panic!("Invalid opcode"),
+ }
+ }
+ }
+ };
+}
- 0x30 Eq [] "Check if the last 2 values on the stack are equal",
- 0x31 Ne [] "Check if the last 2 values on the stack are not equal",
+opcodes! {
+ 0x00 Constant "Push a constant value onto the stack",
+ 0x01 Load "Load a value from a variable",
+ 0x02 Push "Push a value to a variable",
- 0x40 Jmp [u32] "Jump to a specific point in the program",
- 0x41 JmpIf [u32] "Jump to a specific point in the program if true is on the stack",
+ 0x10 Dup "Duplicate a value on the stack",
+ 0x11 Del "Delete a value from the stack",
- 0xE0 Hlt [] "Halt the program",
- 0xE1 Exit [] "Exit the program",
+ 0x20 Add "Add the last 2 values on the stack",
+ 0x21 Sub "Subtract the last 2 values on the stack",
+ 0x22 Mul "Multiply the last 2 values on the stack",
+ 0x23 Div "Divide the last 2 values on the stack",
+ 0x24 Mod "Modulo the last 2 values on the stack",
- 0xF0 VMReturn [] "[DEBUG] Pop value from stack and return it from the program",
-}
+ 0x30 Eq "Check if the last 2 values on the stack are equal",
+ 0x31 Ne "Check if the last 2 values on the stack are not equal",
-#[cfg(test)]
-mod tests {
- use crate::Instruction;
+ 0x40 Jmp "Jump to a specific point in the program",
+ 0x41 JmpIf "Jump to a specific point in the program if true is on the stack",
- #[test]
- #[rustfmt::skip]
- fn decompile_basic_instructions() {
- let code = [
- // Load constant 0
- 0x00, 0, 0, 0, 0,
- // Pop, Del, Add, Sub, Mul, Div, Mod
- 0x10, 0x11, 0x20, 0x21, 0x22, 0x23, 0x24,
- ];
+ 0x50 Call "Call function on stack",
+ 0x51 Return "Return from function on stack",
- let mut offset = 0;
+ 0xE0 Hlt "Halt the program",
+ 0xE1 Exit "Exit the program",
- assert_eq!(Instruction::disassemble(&code, &mut offset), Instruction::Constant(0));
- assert_eq!(Instruction::disassemble(&code, &mut offset), Instruction::Dup);
- assert_eq!(Instruction::disassemble(&code, &mut offset), Instruction::Del);
- assert_eq!(Instruction::disassemble(&code, &mut offset), Instruction::Add);
- assert_eq!(Instruction::disassemble(&code, &mut offset), Instruction::Sub);
- assert_eq!(Instruction::disassemble(&code, &mut offset), Instruction::Mul);
- assert_eq!(Instruction::disassemble(&code, &mut offset), Instruction::Div);
- assert_eq!(Instruction::disassemble(&code, &mut offset), Instruction::Mod);
- }
+ 0xF0 VMReturn "[DEBUG] Pop value from stack and return it fromthe program",
+ 0xF1 VMPrint "[DEBUG] Print value to console"
}
diff --git a/crates/sloth_vm/src/lib.rs b/crates/sloth_vm/src/lib.rs
index 77f7a25..be134d8 100644
--- a/crates/sloth_vm/src/lib.rs
+++ b/crates/sloth_vm/src/lib.rs
@@ -8,70 +8,125 @@
)]
pub mod native;
-pub mod obj;
+pub mod value;
pub mod vm;
+use value::{Object, ObjectType};
+
+use crate::value::Primitive;
pub use crate::vm::VM;
pub struct Chunk {
- constants: Vec<Data>,
+ constants: Vec<Primitive>,
code: Vec<u8>,
}
-pub struct Function {
- chunk: Chunk,
-}
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum Data {
- Integer(i128),
- Float(f64),
- Bool(bool),
- Empty,
-}
-
const STACK_SIZE: usize = 1024;
#[derive(Debug)]
pub struct Stack {
- pointer: usize,
- stack: [Data; STACK_SIZE],
+ top: usize,
+ stack: [Primitive; STACK_SIZE],
}
impl Default for Stack {
fn default() -> Self {
Self {
- pointer: Default::default(),
- stack: [Data::Empty; STACK_SIZE],
+ top: Default::default(),
+ stack: [Primitive::Empty; STACK_SIZE],
}
}
}
impl Stack {
#[inline(always)]
- pub fn push(&mut self, value: Data) {
- if self.pointer >= STACK_SIZE {
+ pub fn push(&mut self, value: Primitive) {
+ if self.top >= STACK_SIZE {
panic!("Stack overflow");
}
- self.stack[self.pointer] = value;
- self.pointer += 1;
+ self.stack[self.top] = value;
+ self.top += 1;
}
#[inline(always)]
- pub fn pop(&mut self) -> Data {
- if self.pointer == 0 {
+ pub fn pop(&mut self) -> Primitive {
+ if self.top == 0 {
panic!("Stack underflow");
}
- self.pointer -= 1;
- self.stack[self.pointer]
+ self.top -= 1;
+ self.stack[self.top]
}
#[inline(always)]
- pub fn pop2(&mut self) -> (Data, Data) {
+ pub fn pop2(&mut self) -> (Primitive, Primitive) {
(self.pop(), self.pop())
}
+
+ #[inline(always)]
+ pub fn peek(&self) -> Primitive {
+ self.stack[self.top - 1]
+ }
}
-pub struct ObjectMap {}
+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/obj.rs b/crates/sloth_vm/src/obj.rs
deleted file mode 100644
index e69de29..0000000
--- a/crates/sloth_vm/src/obj.rs
+++ /dev/null
diff --git a/crates/sloth_vm/src/value.rs b/crates/sloth_vm/src/value.rs
new file mode 100644
index 0000000..773da89
--- /dev/null
+++ b/crates/sloth_vm/src/value.rs
@@ -0,0 +1,49 @@
+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),
+
+ Free { next: usize },
+}
+
+pub struct Function {
+ pub(crate) name: Option<String>,
+ pub(crate) chunk: Chunk,
+ pub(crate) arity: u8,
+}
+
+impl Function {
+ pub(crate) fn root(chunk: Chunk) -> Self {
+ Self {
+ name: None,
+ chunk,
+ arity: 0,
+ }
+ }
+}
diff --git a/crates/sloth_vm/src/vm.rs b/crates/sloth_vm/src/vm.rs
index 22b769b..ded7aa6 100644
--- a/crates/sloth_vm/src/vm.rs
+++ b/crates/sloth_vm/src/vm.rs
@@ -1,140 +1,301 @@
-use sloth_bytecode::Instruction;
+use std::mem::MaybeUninit;
-use crate::{Chunk, Data, Stack};
+use sloth_bytecode::Opcode;
+
+use crate::value::{Function, Object, ObjectType, Primitive};
+use crate::{ObjectMap, Stack};
+
+#[derive(Clone, Copy)]
+pub struct CallFrame {
+ pointer: usize,
+ stack_offset: usize,
+ function: *const Function, // TODO: Safety
+}
+
+impl CallFrame {
+ #[inline]
+ fn function(&self) -> &Function {
+ unsafe { &*self.function }
+ }
+}
+
+impl From<&Function> for CallFrame {
+ fn from(value: &Function) -> Self {
+ Self {
+ pointer: 0,
+ stack_offset: 0,
+ function: value as *const _,
+ }
+ }
+}
+
+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 {
- vm_return: Option<Data>,
stack: Stack,
+ call_stack: CallStack,
+ objects: ObjectMap,
+}
+
+impl Default for VM {
+ fn default() -> Self {
+ Self::init(ObjectMap::default())
+ }
}
impl VM {
- fn new() -> Self {
+ pub fn init(objects: ObjectMap) -> Self {
Self {
- vm_return: None,
stack: Stack::default(),
+ call_stack: CallStack::default(),
+ objects,
}
}
- fn execute(&mut self) {
- loop {
- self.execute_once();
- }
+ pub fn new(objects: ObjectMap, mut root: Function) -> Self {
+ let mut this = Self::init(objects);
+
+ // Allocating the root function
+ root.chunk.code.push(Opcode::Hlt as u8);
+ this.call_stack.push(CallFrame::from(&root));
+ this.objects
+ .allocate(Object::new(ObjectType::Function(root)));
+
+ this
}
- fn execute_once(&mut self) {
- //
- }
-
- fn run(&mut self, chunk: &Chunk) {
- let mut pointer = 0;
-
- loop {
- let instruction = Instruction::disassemble(&chunk.code, &mut pointer);
-
- match instruction {
- Instruction::Constant(idx) => {
- let value = chunk.constants[idx as usize];
- self.stack.push(value);
- }
- Instruction::Load(_) => todo!(),
- Instruction::Push(_) => todo!(),
- Instruction::Dup => {
- let value = self.stack.pop();
- self.stack.push(value);
- self.stack.push(value);
- }
- Instruction::Del => {
- self.stack.pop();
- }
- Instruction::Add => {
- let value = match self.stack.pop2() {
- (Data::Integer(lhs), Data::Integer(rhs)) => Data::Integer(lhs + rhs),
- (Data::Float(lhs), Data::Float(rhs)) => Data::Float(lhs + rhs),
- _ => panic!(),
- };
-
- self.stack.push(value);
- }
- Instruction::Sub => {
- let value = match self.stack.pop2() {
- (Data::Integer(lhs), Data::Integer(rhs)) => Data::Integer(lhs - rhs),
- (Data::Float(lhs), Data::Float(rhs)) => Data::Float(lhs - rhs),
- _ => panic!(),
- };
-
- self.stack.push(value);
- }
- Instruction::Mul => {
- let value = match self.stack.pop2() {
- (Data::Integer(lhs), Data::Integer(rhs)) => Data::Integer(lhs * rhs),
- (Data::Float(lhs), Data::Float(rhs)) => Data::Float(lhs * rhs),
- _ => panic!(),
- };
-
- self.stack.push(value);
- }
- Instruction::Div => {
- let value = match self.stack.pop2() {
- (Data::Integer(lhs), Data::Integer(rhs)) => Data::Integer(lhs / rhs),
- (Data::Float(lhs), Data::Float(rhs)) => Data::Float(lhs / rhs),
- _ => panic!(),
- };
-
- self.stack.push(value);
- }
- Instruction::Mod => {
- let value = match self.stack.pop2() {
- (Data::Integer(lhs), Data::Integer(rhs)) => Data::Integer(lhs % rhs),
- (Data::Float(lhs), Data::Float(rhs)) => Data::Float(lhs % rhs),
- _ => panic!(),
- };
-
- self.stack.push(value);
- }
- Instruction::Hlt => break,
- Instruction::Exit => break,
- Instruction::VMReturn => {
- let value = self.stack.pop();
- self.vm_return = Some(value);
- break;
- }
- _ => unimplemented!(),
+ 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();
+ let value = self.call_stack.peek().function().chunk.constants[idx as usize];
+
+ self.stack.push(value);
+ }
+ Opcode::Dup => {
+ let value = self.stack.pop();
+ self.stack.push(value);
+ self.stack.push(value);
}
+ Opcode::Del => {
+ self.stack.pop();
+ }
+
+ 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::Call => {
+ let Primitive::Object(ptr) = self.stack.pop() else {
+ panic!("Last element on stack was not an object");
+ };
+
+ let Some(obj) = self.objects.get(ptr as usize) else {
+ panic!("Pointer referenced nothing");
+ };
+
+ let ObjectType::Function(function) = &obj.typ else {
+ panic!("Object was not a function");
+ };
+
+ // Push the function onto the call stack
+ self.call_stack.push(CallFrame::from(function));
+ }
+
+ Opcode::Return => {
+ // TODO: Return values
+
+ self.call_stack.pop();
+ }
+
+ Opcode::Hlt => return false,
+ Opcode::Exit => return false,
+
+ _ => unimplemented!(),
}
+
+ true
+ }
+
+ pub fn run(&mut self) {
+ while self.step() {}
+ }
+
+ #[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)
}
}
#[cfg(test)]
mod tests {
- use crate::{Chunk, Data, VM};
+ use crate::value::{Function, Object, ObjectType, Primitive};
+ use crate::{Chunk, ObjectMap, VM};
#[test]
fn arithmetic_ops() {
- let mut vm = VM::new();
-
// Addition
- vm.run(&Chunk {
- constants: vec![Data::Integer(7)],
- code: vec![
- 0x00, 0, 0, 0, 0, // Load constant from 0
- 0x10, // Duplicate
- 0x20, // Add
- 0xF0, // Return VM
- ],
- });
-
- assert_eq!(vm.vm_return, Some(Data::Integer(14)));
-
- vm.run(&Chunk {
- constants: vec![Data::Integer(2), Data::Integer(11)],
- code: vec![
- 0x00, 0, 0, 0, 0, // Load constant from 0
- 0x00, 0, 0, 0, 1, // Load constant from 1
- 0x20, // Add
- 0xF0, // Return VM
- ],
- });
-
- assert_eq!(vm.vm_return, Some(Data::Integer(13)));
+ 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 allocation() {
+ let mut vm = VM::new(
+ ObjectMap::from(vec![
+ Object::new(ObjectType::String("Hello World!".to_owned())),
+ Object::new(ObjectType::String("Hello Slothlang!".to_owned())),
+ Object::new(ObjectType::Function(Function {
+ name: Some("foo".to_string()),
+ chunk: Chunk {
+ constants: vec![Primitive::Integer(7)],
+ code: vec![0x00, 0, 0, 0x10, 0x20, 0x51],
+ },
+ arity: 0,
+ })),
+ ]),
+ Function::root(Chunk {
+ constants: vec![
+ Primitive::Object(0),
+ Primitive::Object(1),
+ Primitive::Object(2),
+ ],
+ code: vec![
+ 0x00, 0, 0, // Load constant from 0
+ 0x00, 0, 1, // Load constant from 1
+ 0x00, 0, 2, // Load constant from 2
+ 0x50, 0xE0,
+ ],
+ }),
+ );
+
+ vm.run();
+
+ assert_eq!(vm.stack.peek(), Primitive::Integer(14));
}
}