From 1f88d77476a42c4daeceb528a52f44bc42b2bf5f Mon Sep 17 00:00:00 2001 From: Jeff Date: Wed, 8 Jan 2025 06:06:34 -0500 Subject: [PATCH] Add generic "Stack" type for call stacks and record stacks --- dust-lang/src/vm/call_stack.rs | 113 -------------------------- dust-lang/src/vm/error.rs | 15 +++- dust-lang/src/vm/mod.rs | 4 +- dust-lang/src/vm/stack.rs | 142 +++++++++++++++++++++++++++++++++ dust-lang/src/vm/thread.rs | 37 +++++---- 5 files changed, 173 insertions(+), 138 deletions(-) delete mode 100644 dust-lang/src/vm/call_stack.rs create mode 100644 dust-lang/src/vm/stack.rs diff --git a/dust-lang/src/vm/call_stack.rs b/dust-lang/src/vm/call_stack.rs deleted file mode 100644 index 7580b9e..0000000 --- a/dust-lang/src/vm/call_stack.rs +++ /dev/null @@ -1,113 +0,0 @@ -use std::fmt::{self, Debug, Display, Formatter}; - -use crate::DustString; - -use super::VmError; - -#[derive(Clone, PartialEq)] -pub struct CallStack { - calls: Vec, -} - -impl CallStack { - pub fn new() -> Self { - CallStack { - calls: Vec::with_capacity(1), - } - } - - pub fn with_capacity(capacity: usize) -> Self { - CallStack { - calls: Vec::with_capacity(capacity), - } - } - - pub fn is_empty(&self) -> bool { - self.calls.is_empty() - } - - pub fn len(&self) -> usize { - self.calls.len() - } - - pub fn push(&mut self, call: FunctionCall) { - self.calls.push(call); - } - - pub fn pop(&mut self) -> Option { - self.calls.pop() - } - - pub fn last(&self) -> Option<&FunctionCall> { - self.calls.last() - } - - pub fn last_mut(&mut self) -> Option<&mut FunctionCall> { - self.calls.last_mut() - } - - pub fn pop_or_panic(&mut self) -> FunctionCall { - assert!(!self.is_empty(), "{}", VmError::CallStackUnderflow); - - self.calls.pop().unwrap() - } - - pub fn last_or_panic(&self) -> &FunctionCall { - assert!(!self.is_empty(), "{}", VmError::CallStackUnderflow); - - self.calls.last().unwrap() - } - - pub fn last_mut_or_panic(&mut self) -> &mut FunctionCall { - assert!(!self.is_empty(), "{}", VmError::CallStackUnderflow); - - self.calls.last_mut().unwrap() - } -} - -impl Default for CallStack { - fn default() -> Self { - Self::new() - } -} - -impl Debug for CallStack { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{self}") - } -} - -impl Display for CallStack { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - writeln!(f, "-- DUST CALL STACK --")?; - - for function_call in self.calls.iter().rev() { - writeln!(f, "{function_call}")?; - } - - writeln!(f, "--") - } -} - -#[derive(Clone, Debug, PartialEq)] -pub struct FunctionCall { - pub name: Option, - pub return_register: u8, - pub ip: usize, -} - -impl Display for FunctionCall { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let FunctionCall { - name, - return_register, - .. - } = self; - let name = name - .as_ref() - .map(|name| name.as_str()) - .unwrap_or("anonymous"); - - write!(f, "{name} (Return register: {return_register})") - } -} diff --git a/dust-lang/src/vm/error.rs b/dust-lang/src/vm/error.rs index 981d950..a437edf 100644 --- a/dust-lang/src/vm/error.rs +++ b/dust-lang/src/vm/error.rs @@ -2,14 +2,21 @@ use std::fmt::{self, Display, Formatter}; use crate::{InstructionData, Value}; -use super::call_stack::CallStack; +use super::{stack::Stack, FunctionCall}; #[derive(Clone, Debug, PartialEq)] pub enum VmError { CallStackUnderflow, - ExpectedFunction { value: Value }, - InstructionIndexOutOfBounds { call_stack: CallStack, ip: usize }, - MalformedInstruction { instruction: InstructionData }, + ExpectedFunction { + value: Value, + }, + InstructionIndexOutOfBounds { + call_stack: Stack, + ip: usize, + }, + MalformedInstruction { + instruction: InstructionData, + }, } impl Display for VmError { diff --git a/dust-lang/src/vm/mod.rs b/dust-lang/src/vm/mod.rs index c9fd62c..147a825 100644 --- a/dust-lang/src/vm/mod.rs +++ b/dust-lang/src/vm/mod.rs @@ -1,8 +1,8 @@ //! Virtual machine and errors -mod call_stack; mod error; mod record; mod run_action; +mod stack; mod thread; use std::{ @@ -11,10 +11,10 @@ use std::{ thread::spawn, }; -pub use call_stack::{CallStack, FunctionCall}; pub use error::VmError; pub use record::Record; pub use run_action::RunAction; +pub use stack::{FunctionCall, Stack}; pub use thread::{Thread, ThreadSignal}; use tracing::{span, Level}; diff --git a/dust-lang/src/vm/stack.rs b/dust-lang/src/vm/stack.rs new file mode 100644 index 0000000..f04a12f --- /dev/null +++ b/dust-lang/src/vm/stack.rs @@ -0,0 +1,142 @@ +use std::{ + fmt::{self, Debug, Display, Formatter}, + ops::{Index, IndexMut, Range}, +}; + +use crate::DustString; + +use super::VmError; + +#[derive(Clone, PartialEq)] +pub struct Stack { + items: Vec, +} + +impl Stack { + pub fn new() -> Self { + Stack { + items: Vec::with_capacity(1), + } + } + + pub fn with_capacity(capacity: usize) -> Self { + Stack { + items: Vec::with_capacity(capacity), + } + } + + pub fn is_empty(&self) -> bool { + self.items.is_empty() + } + + pub fn len(&self) -> usize { + self.items.len() + } + + pub fn push(&mut self, item: T) { + self.items.push(item); + } + + pub fn pop(&mut self) -> Option { + self.items.pop() + } + + pub fn last(&self) -> Option<&T> { + self.items.last() + } + + pub fn last_mut(&mut self) -> Option<&mut T> { + self.items.last_mut() + } + + pub fn pop_unchecked(&mut self) -> T { + if cfg!(debug_assertions) { + assert!(!self.is_empty(), "{}", VmError::CallStackUnderflow); + + self.items.pop().unwrap() + } else { + unsafe { self.items.pop().unwrap_unchecked() } + } + } + + pub fn last_unchecked(&self) -> &T { + if cfg!(debug_assertions) { + assert!(!self.is_empty(), "{}", VmError::CallStackUnderflow); + + self.items.last().unwrap() + } else { + unsafe { self.items.last().unwrap_unchecked() } + } + } + + pub fn last_mut_unchecked(&mut self) -> &mut T { + if cfg!(debug_assertions) { + assert!(!self.is_empty(), "{}", VmError::CallStackUnderflow); + + self.items.last_mut().unwrap() + } else { + unsafe { self.items.last_mut().unwrap_unchecked() } + } + } +} + +impl Default for Stack { + fn default() -> Self { + Self::new() + } +} + +impl Index> for Stack { + type Output = [T]; + + fn index(&self, index: Range) -> &Self::Output { + &self.items[index] + } +} + +impl IndexMut> for Stack { + fn index_mut(&mut self, index: Range) -> &mut Self::Output { + &mut self.items[index] + } +} + +impl Debug for Stack { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{self}") + } +} + +impl Display for Stack { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + writeln!(f, "-- DUST CALL STACK --")?; + + for function_call in self.items.iter().rev() { + writeln!(f, "{function_call}")?; + } + + writeln!(f, "--") + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct FunctionCall { + pub name: Option, + pub return_register: u8, + pub ip: usize, +} + +impl Display for FunctionCall { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let FunctionCall { + name, + return_register, + .. + } = self; + let name = name + .as_ref() + .map(|name| name.as_str()) + .unwrap_or("anonymous"); + + write!(f, "{name} (Return register: {return_register})") + } +} diff --git a/dust-lang/src/vm/thread.rs b/dust-lang/src/vm/thread.rs index 1afad4d..287ceec 100644 --- a/dust-lang/src/vm/thread.rs +++ b/dust-lang/src/vm/thread.rs @@ -7,7 +7,7 @@ use crate::{ Chunk, DustString, Value, }; -use super::{record::Record, CallStack, RunAction}; +use super::{record::Record, RunAction, Stack}; pub struct Thread { chunk: Chunk, @@ -19,9 +19,8 @@ impl Thread { } pub fn run(&mut self) -> Option { - let mut call_stack = CallStack::with_capacity(self.chunk.prototypes.len() + 1); - let mut records = Vec::with_capacity(self.chunk.prototypes.len() + 1); - + let mut call_stack = Stack::with_capacity(self.chunk.prototypes.len() + 1); + let mut records = Stack::with_capacity(self.chunk.prototypes.len() + 1); let main_call = FunctionCall { name: self.chunk.name.clone(), return_register: 0, @@ -32,7 +31,7 @@ impl Thread { call_stack.push(main_call); records.push(main_record); - let mut active_record = &mut records[0]; + let mut active_record = records.last_mut_unchecked(); info!( "Starting thread with {}", @@ -62,6 +61,16 @@ impl Thread { match signal { ThreadSignal::Continue => {} + ThreadSignal::LoadFunction { + destination, + prototype_index, + } => { + let function_record_index = prototype_index as usize; + let function = self.chunk.prototypes[function_record_index].as_function(); + let register = Register::Value(Value::Function(function)); + + active_record.set_register(destination, register); + } ThreadSignal::Call { function_register, return_register, @@ -93,22 +102,12 @@ impl Thread { call_stack.push(next_call); records.push(next_record); - active_record = records.last_mut().unwrap(); + active_record = records.last_mut_unchecked(); for (index, argument) in arguments.into_iter().enumerate() { active_record.set_register(index as u8, Register::Value(argument)); } } - ThreadSignal::LoadFunction { - destination, - prototype_index, - } => { - let function_record_index = prototype_index as usize; - let function = self.chunk.prototypes[function_record_index].as_function(); - let register = Register::Value(Value::Function(function)); - - active_record.set_register(destination, register); - } ThreadSignal::Return { should_return_value, return_register, @@ -124,8 +123,8 @@ impl Thread { None }; - let current_call = call_stack.pop_or_panic(); - let _current_record = records.pop().unwrap(); + let current_call = call_stack.pop_unchecked(); + let _current_record = records.pop_unchecked(); let destination = current_call.return_register; if call_stack.is_empty() { @@ -136,7 +135,7 @@ impl Thread { }; } - let outer_record = records.last_mut().unwrap(); + let outer_record = records.last_mut_unchecked(); if should_return_value { outer_record