1
0

Add generic "Stack" type for call stacks and record stacks

This commit is contained in:
Jeff 2025-01-08 06:06:34 -05:00
parent e9bd9b37b0
commit 1f88d77476
5 changed files with 173 additions and 138 deletions

View File

@ -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<FunctionCall>,
}
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<FunctionCall> {
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<DustString>,
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})")
}
}

View File

@ -2,14 +2,21 @@ use std::fmt::{self, Display, Formatter};
use crate::{InstructionData, Value}; use crate::{InstructionData, Value};
use super::call_stack::CallStack; use super::{stack::Stack, FunctionCall};
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum VmError { pub enum VmError {
CallStackUnderflow, CallStackUnderflow,
ExpectedFunction { value: Value }, ExpectedFunction {
InstructionIndexOutOfBounds { call_stack: CallStack, ip: usize }, value: Value,
MalformedInstruction { instruction: InstructionData }, },
InstructionIndexOutOfBounds {
call_stack: Stack<FunctionCall>,
ip: usize,
},
MalformedInstruction {
instruction: InstructionData,
},
} }
impl Display for VmError { impl Display for VmError {

View File

@ -1,8 +1,8 @@
//! Virtual machine and errors //! Virtual machine and errors
mod call_stack;
mod error; mod error;
mod record; mod record;
mod run_action; mod run_action;
mod stack;
mod thread; mod thread;
use std::{ use std::{
@ -11,10 +11,10 @@ use std::{
thread::spawn, thread::spawn,
}; };
pub use call_stack::{CallStack, FunctionCall};
pub use error::VmError; pub use error::VmError;
pub use record::Record; pub use record::Record;
pub use run_action::RunAction; pub use run_action::RunAction;
pub use stack::{FunctionCall, Stack};
pub use thread::{Thread, ThreadSignal}; pub use thread::{Thread, ThreadSignal};
use tracing::{span, Level}; use tracing::{span, Level};

142
dust-lang/src/vm/stack.rs Normal file
View File

@ -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<T> {
items: Vec<T>,
}
impl<T> Stack<T> {
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<T> {
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<T> Default for Stack<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> Index<Range<usize>> for Stack<T> {
type Output = [T];
fn index(&self, index: Range<usize>) -> &Self::Output {
&self.items[index]
}
}
impl<T> IndexMut<Range<usize>> for Stack<T> {
fn index_mut(&mut self, index: Range<usize>) -> &mut Self::Output {
&mut self.items[index]
}
}
impl Debug for Stack<FunctionCall> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{self}")
}
}
impl Display for Stack<FunctionCall> {
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<DustString>,
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})")
}
}

View File

@ -7,7 +7,7 @@ use crate::{
Chunk, DustString, Value, Chunk, DustString, Value,
}; };
use super::{record::Record, CallStack, RunAction}; use super::{record::Record, RunAction, Stack};
pub struct Thread { pub struct Thread {
chunk: Chunk, chunk: Chunk,
@ -19,9 +19,8 @@ impl Thread {
} }
pub fn run(&mut self) -> Option<Value> { pub fn run(&mut self) -> Option<Value> {
let mut call_stack = CallStack::with_capacity(self.chunk.prototypes.len() + 1); let mut call_stack = Stack::with_capacity(self.chunk.prototypes.len() + 1);
let mut records = Vec::with_capacity(self.chunk.prototypes.len() + 1); let mut records = Stack::with_capacity(self.chunk.prototypes.len() + 1);
let main_call = FunctionCall { let main_call = FunctionCall {
name: self.chunk.name.clone(), name: self.chunk.name.clone(),
return_register: 0, return_register: 0,
@ -32,7 +31,7 @@ impl Thread {
call_stack.push(main_call); call_stack.push(main_call);
records.push(main_record); records.push(main_record);
let mut active_record = &mut records[0]; let mut active_record = records.last_mut_unchecked();
info!( info!(
"Starting thread with {}", "Starting thread with {}",
@ -62,6 +61,16 @@ impl Thread {
match signal { match signal {
ThreadSignal::Continue => {} 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 { ThreadSignal::Call {
function_register, function_register,
return_register, return_register,
@ -93,22 +102,12 @@ impl Thread {
call_stack.push(next_call); call_stack.push(next_call);
records.push(next_record); records.push(next_record);
active_record = records.last_mut().unwrap(); active_record = records.last_mut_unchecked();
for (index, argument) in arguments.into_iter().enumerate() { for (index, argument) in arguments.into_iter().enumerate() {
active_record.set_register(index as u8, Register::Value(argument)); 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 { ThreadSignal::Return {
should_return_value, should_return_value,
return_register, return_register,
@ -124,8 +123,8 @@ impl Thread {
None None
}; };
let current_call = call_stack.pop_or_panic(); let current_call = call_stack.pop_unchecked();
let _current_record = records.pop().unwrap(); let _current_record = records.pop_unchecked();
let destination = current_call.return_register; let destination = current_call.return_register;
if call_stack.is_empty() { 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 { if should_return_value {
outer_record outer_record