From 236597956154fbeec317f792f110653f862f5dba Mon Sep 17 00:00:00 2001 From: Jeff Date: Thu, 9 Jan 2025 01:44:07 -0500 Subject: [PATCH] Optimize; Remove non-working optimizations; Improve CLI --- Cargo.lock | 4 - dust-cli/src/main.rs | 106 +++++++++--------- dust-lang/Cargo.toml | 4 +- dust-lang/src/chunk/mod.rs | 17 ++- dust-lang/src/compiler/error.rs | 24 ++-- dust-lang/src/compiler/mod.rs | 59 +++++----- dust-lang/src/dust_error.rs | 5 +- dust-lang/src/lexer.rs | 8 +- dust-lang/src/lib.rs | 2 - .../{assertion.rs => assert.rs} | 2 +- dust-lang/src/native_function/io.rs | 6 +- dust-lang/src/native_function/mod.rs | 41 ++++--- dust-lang/src/native_function/string.rs | 2 +- dust-lang/src/type.rs | 27 +++-- dust-lang/src/vm/error.rs | 39 ------- dust-lang/src/vm/mod.rs | 2 - dust-lang/src/vm/run_action.rs | 92 ++++++++------- dust-lang/src/vm/stack.rs | 19 ++-- dust-lang/src/vm/thread.rs | 11 +- 19 files changed, 211 insertions(+), 259 deletions(-) rename dust-lang/src/native_function/{assertion.rs => assert.rs} (88%) delete mode 100644 dust-lang/src/vm/error.rs diff --git a/Cargo.lock b/Cargo.lock index eecbb0c..6811bcd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -339,7 +339,6 @@ dependencies = [ "rand", "serde", "serde_json", - "smallvec", "smartstring", "tracing", ] @@ -796,9 +795,6 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" -dependencies = [ - "serde", -] [[package]] name = "smartstring" diff --git a/dust-cli/src/main.rs b/dust-cli/src/main.rs index 3d6412a..47a794f 100644 --- a/dust-cli/src/main.rs +++ b/dust-cli/src/main.rs @@ -6,70 +6,74 @@ use std::{ }; use clap::{ - builder::{styling::AnsiColor, StyledStr, Styles}, + builder::{ + styling::{AnsiColor, Color}, + StyledStr, Styles, + }, crate_authors, crate_description, crate_version, error::ErrorKind, Args, ColorChoice, Error, Parser, Subcommand, ValueHint, }; -use color_print::cstr; +use color_print::{cformat, cstr}; use dust_lang::{CompileError, Compiler, DustError, DustString, Lexer, Span, Token, Vm}; use tracing::{subscriber::set_global_default, Level}; use tracing_subscriber::FmtSubscriber; -const CLI_HELP_TEMPLATE: &str = cstr!( - r#" -Dust CLI -──────── -{about} - -Version: {version} -Author: {author} -License: GPL-3.0 -Repository: git.jeffa.io/jeff/dust - -Usage -───── -{tab}{usage} - -Modes -───── -{subcommands} - -Options -─────── -{options} -"# -); - -const MODE_HELP_TEMPLATE: &str = cstr!( +const ABOUT: &str = cstr!( r#" Dust CLI -──────── +──────── {about} -Version: {version} -Author: {author} -License: GPL-3.0 -Repository: git.jeffa.io/jeff/dust +⚙️ Version: {version} +🦀 Author: {author} +⚖️ License: GPL-3.0 +🔬 Repository: https://git.jeffa.io/jeff/dust +"# +); -Usage -───── -{usage} +const PLAIN_ABOUT: &str = r#" +{about} +"#; -Options -─────── +const USAGE: &str = cstr!( + r#" +Usage: {usage} +"# +); + +const SUBCOMMANDS: &str = cstr!( + r#" +Modes: +{subcommands} +"# +); + +const OPTIONS: &str = cstr!( + r#" +Options: {options} "# ); +const CREATE_MAIN_HELP_TEMPLATE: fn() -> String = + || cformat!("{ABOUT}{USAGE}{SUBCOMMANDS}{OPTIONS}"); + +const CREATE_MODE_HELP_TEMPLATE: fn(&str) -> String = |title| { + cformat!( + "\ + {title}\n────────\ + {PLAIN_ABOUT}{USAGE}{OPTIONS} + " + ) +}; + const STYLES: Styles = Styles::styled() - .header(AnsiColor::BrightMagenta.on_default().bold()) - .usage(AnsiColor::BrightCyan.on_default().bold()) - .literal(AnsiColor::BrightCyan.on_default()) - .placeholder(AnsiColor::BrightMagenta.on_default()) - .error(AnsiColor::BrightRed.on_default().bold()) - .valid(AnsiColor::Blue.on_default()) - .invalid(AnsiColor::BrightRed.on_default()); + .literal(AnsiColor::Cyan.on_default()) + .placeholder(AnsiColor::Cyan.on_default()) + .valid(AnsiColor::BrightCyan.on_default()) + .invalid(AnsiColor::BrightYellow.on_default()) + .error(AnsiColor::BrightRed.on_default()); #[derive(Parser)] #[clap( @@ -77,7 +81,7 @@ const STYLES: Styles = Styles::styled() author = crate_authors!(), about = crate_description!(), color = ColorChoice::Auto, - help_template = StyledStr::from(CLI_HELP_TEMPLATE), + help_template = CREATE_MAIN_HELP_TEMPLATE(), styles = STYLES, )] struct Cli { @@ -122,7 +126,7 @@ struct Input { #[derive(Args)] #[command( short_flag = 'r', - help_template = MODE_HELP_TEMPLATE + help_template = CREATE_MODE_HELP_TEMPLATE("Run Mode") )] struct Run { /// Print the time taken for compilation and execution @@ -149,7 +153,7 @@ enum Mode { /// Compile and print the bytecode disassembly #[command( short_flag = 'd', - help_template = MODE_HELP_TEMPLATE + help_template = CREATE_MODE_HELP_TEMPLATE("Disassemble Mode") )] Disassemble { /// Style disassembly output @@ -167,7 +171,7 @@ enum Mode { /// Lex the source code and print the tokens #[command( short_flag = 't', - help_template = MODE_HELP_TEMPLATE + help_template = CREATE_MODE_HELP_TEMPLATE("Tokenize Mode") )] Tokenize { /// Style token output @@ -224,7 +228,7 @@ fn main() { let (source, file_name) = get_source_and_file_name(input); let lexer = Lexer::new(&source); let program_name = name.or(file_name); - let mut compiler = match Compiler::new(lexer, program_name) { + let mut compiler = match Compiler::new(lexer, program_name, true) { Ok(compiler) => compiler, Err(error) => { handle_compile_error(error, &source); @@ -312,7 +316,7 @@ fn main() { let (source, file_name) = get_source_and_file_name(input); let lexer = Lexer::new(&source); let program_name = name.or(file_name); - let mut compiler = match Compiler::new(lexer, program_name) { + let mut compiler = match Compiler::new(lexer, program_name, true) { Ok(compiler) => compiler, Err(error) => { handle_compile_error(error, &source); diff --git a/dust-lang/Cargo.toml b/dust-lang/Cargo.toml index 28c86ac..87b0557 100644 --- a/dust-lang/Cargo.toml +++ b/dust-lang/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dust-lang" -description = "Interpreter library for the Dust programming language" +description = "Dust programming language library" authors.workspace = true edition.workspace = true license.workspace = true @@ -17,7 +17,6 @@ serde_json = "1.0.117" getrandom = { version = "0.2", features = [ "js", ] } # Indirect dependency, for wasm builds -smallvec = { version = "1.13.2", features = ["const_generics", "serde"] } smartstring = { version = "1.0.1", features = [ "serde", ], default-features = false } @@ -25,7 +24,6 @@ tracing = "0.1.41" [dev-dependencies] criterion = { version = "0.3.4", features = ["html_reports"] } -smallvec = { version = "1.13.2", features = ["const_generics"] } [[bench]] name = "addictive_addition" diff --git a/dust-lang/src/chunk/mod.rs b/dust-lang/src/chunk/mod.rs index 05eb799..8ad2e84 100644 --- a/dust-lang/src/chunk/mod.rs +++ b/dust-lang/src/chunk/mod.rs @@ -24,7 +24,6 @@ use std::fmt::{self, Debug, Display, Formatter, Write as FmtWrite}; use std::io::Write; use serde::{Deserialize, Serialize}; -use smallvec::SmallVec; use crate::{DustString, Function, FunctionType, Instruction, Span, Value}; @@ -36,10 +35,10 @@ pub struct Chunk { pub(crate) name: Option, pub(crate) r#type: FunctionType, - pub(crate) instructions: SmallVec<[Instruction; 32]>, - pub(crate) positions: SmallVec<[Span; 32]>, - pub(crate) constants: SmallVec<[Value; 16]>, - pub(crate) locals: SmallVec<[Local; 8]>, + pub(crate) instructions: Vec, + pub(crate) positions: Vec, + pub(crate) constants: Vec, + pub(crate) locals: Vec, pub(crate) prototypes: Vec, pub(crate) register_count: usize, @@ -51,10 +50,10 @@ impl Chunk { pub fn with_data( name: Option, r#type: FunctionType, - instructions: impl Into>, - positions: impl Into>, - constants: impl Into>, - locals: impl Into>, + instructions: impl Into>, + positions: impl Into>, + constants: impl Into>, + locals: impl Into>, prototypes: Vec, ) -> Self { Self { diff --git a/dust-lang/src/compiler/error.rs b/dust-lang/src/compiler/error.rs index 02d0e4b..63aee4f 100644 --- a/dust-lang/src/compiler/error.rs +++ b/dust-lang/src/compiler/error.rs @@ -1,7 +1,5 @@ use std::num::{ParseFloatError, ParseIntError}; -use smallvec::{smallvec, SmallVec}; - use crate::{AnnotatedError, LexError, Scope, Span, TokenKind, TokenOwned, Type, TypeConflict}; /// Compilation errors @@ -212,7 +210,7 @@ impl AnnotatedError for CompileError { } } - fn detail_snippets(&self) -> SmallVec<[(String, Span); 2]> { + fn detail_snippets(&self) -> Vec<(String, Span)> { match self { Self::CannotAddArguments { left_type, @@ -220,31 +218,31 @@ impl AnnotatedError for CompileError { right_type, right_position, } => { - smallvec![ + vec![ ( format!("A value of type \"{left_type}\" was used here."), - *left_position + *left_position, ), ( format!("A value of type \"{right_type}\" was used here."), - *right_position - ) + *right_position, + ), ] } Self::ReturnTypeConflict { conflict, position } => { - smallvec![( + vec![( format!( "Expected type {} but found type {}", conflict.expected, conflict.actual ), - *position + *position, )] } - _ => SmallVec::new(), + _ => Vec::with_capacity(0), } } - fn help_snippets(&self) -> SmallVec<[(String, Span); 2]> { + fn help_snippets(&self) -> Vec<(String, Span)> { match self { Self::CannotAddArguments { left_type, @@ -252,12 +250,12 @@ impl AnnotatedError for CompileError { right_type, right_position, } => { - smallvec![( + vec![( format!("Type \"{left_type}\" cannot be added to type \"{right_type}\". Try converting one of the values to the other type."), Span(left_position.0, right_position.1) )] } - _ => SmallVec::new(), + _ => Vec::with_capacity(0), } } } diff --git a/dust-lang/src/compiler/mod.rs b/dust-lang/src/compiler/mod.rs index 2e19050..d9f88f1 100644 --- a/dust-lang/src/compiler/mod.rs +++ b/dust-lang/src/compiler/mod.rs @@ -31,7 +31,6 @@ use type_checks::{check_math_type, check_math_types}; use std::mem::replace; use optimize::control_flow_register_consolidation; -use smallvec::{smallvec, SmallVec}; use crate::{ instruction::{CallNative, Close, GetLocal, Jump, LoadList, Negate, Not, Return, SetLocal}, @@ -52,8 +51,8 @@ use crate::{ /// ``` pub fn compile(program_name: Option, source: &str) -> Result { let lexer = Lexer::new(source); - let mut compiler = - Compiler::new(lexer, program_name).map_err(|error| DustError::compile(error, source))?; + let mut compiler = Compiler::new(lexer, program_name, true) + .map_err(|error| DustError::compile(error, source))?; compiler .compile() @@ -85,15 +84,15 @@ pub struct Compiler<'src> { /// Instructions, along with their types and positions, that have been compiled. The /// instructions and positions are assigned to the chunk when [`Compiler::finish`] is called. /// The types are discarded after compilation. - instructions: SmallVec<[(Instruction, Type, Span); 32]>, + instructions: Vec<(Instruction, Type, Span)>, /// Constants that have been compiled. These are assigned to the chunk when [`Compiler::finish`] /// is called. - constants: SmallVec<[Value; 16]>, + constants: Vec, /// Block-local variables and their types. The locals are assigned to the chunk when /// [`Compiler::finish`] is called. The types are discarded after compilation. - locals: SmallVec<[(Local, Type); 8]>, + locals: Vec<(Local, Type)>, /// Prototypes that have been compiled. These are assigned to the chunk when /// [`Compiler::finish`] is called. @@ -104,12 +103,11 @@ pub struct Compiler<'src> { stack_size: usize, /// The first register index that the compiler should use. This is used to avoid reusing the - /// registers that are used for the function's arguments, thus it is zero in the program's main - /// chunk. + /// registers that are used for the function's arguments. minimum_register: u8, - /// Index of the current block. This is used to determine the scope of the locals and is - /// incremented when a new block is entered. + /// Index of the current block. This is used to determine the scope of locals and is incremented + /// when a new block is entered. block_index: u8, /// The current block scope of the compiler. This is used to test if a variable is in scope. @@ -119,6 +117,10 @@ pub struct Compiler<'src> { /// that value is never read because the main chunk is not a callable function. prototype_index: u8, + /// Whether the chunk is the program's main chunk. This is used to prevent recursive calls to + /// the main chunk. + is_main: bool, + current_token: Token<'src>, current_position: Span, previous_token: Token<'src>, @@ -126,23 +128,24 @@ pub struct Compiler<'src> { } impl<'src> Compiler<'src> { - /// Creates a new top-level compiler with the given lexer. + /// Creates a new compiler. pub fn new( mut lexer: Lexer<'src>, function_name: Option, + is_main: bool, ) -> Result { let (current_token, current_position) = lexer.next_token()?; Ok(Compiler { function_name, r#type: FunctionType { - type_parameters: None, - value_parameters: None, + type_parameters: Vec::with_capacity(0), + value_parameters: Vec::with_capacity(0), return_type: Type::None, }, - instructions: SmallVec::new(), - constants: SmallVec::new(), - locals: SmallVec::new(), + instructions: Vec::new(), + constants: Vec::new(), + locals: Vec::new(), prototypes: Vec::new(), stack_size: 0, lexer, @@ -150,6 +153,7 @@ impl<'src> Compiler<'src> { block_index: 0, current_scope: Scope::default(), prototype_index: 0, + is_main, current_token, current_position, previous_token: Token::Eof, @@ -195,7 +199,7 @@ impl<'src> Compiler<'src> { /// will allow [`Compiler::function_name`] to be both the name used for recursive calls and the /// name of the function when it is compiled. The name can later be seen in the VM's call stack. pub fn finish(self) -> Chunk { - let (instructions, positions): (SmallVec<[Instruction; 32]>, SmallVec<[Span; 32]>) = self + let (instructions, positions): (Vec, Vec) = self .instructions .into_iter() .map(|(instruction, _, position)| (instruction, position)) @@ -204,14 +208,14 @@ impl<'src> Compiler<'src> { .locals .into_iter() .map(|(local, _)| local) - .collect::>(); + .collect::>(); Chunk { name: self.function_name, r#type: self.r#type, instructions, positions, - constants: self.constants, + constants: self.constants.to_vec(), locals, prototypes: self.prototypes, register_count: self.stack_size, @@ -998,7 +1002,7 @@ impl<'src> Compiler<'src> { local_index } else if let Some(native_function) = NativeFunction::from_str(identifier) { return self.parse_call_native(native_function); - } else if self.function_name.as_deref() == Some(identifier) { + } else if self.function_name.as_deref() == Some(identifier) && !self.is_main { let destination = self.next_register(); let load_self = Instruction::load_self(destination); @@ -1549,7 +1553,7 @@ impl<'src> Compiler<'src> { let mut function_compiler = if self.current_token == Token::LeftParenthesis { let function_name = identifier.map(DustString::from); - Compiler::new(self.lexer, function_name)? // This will consume the left parenthesis + Compiler::new(self.lexer, function_name, false)? // This will consume the parenthesis } else { return Err(CompileError::ExpectedToken { expected: TokenKind::LeftParenthesis, @@ -1560,7 +1564,7 @@ impl<'src> Compiler<'src> { function_compiler.prototype_index = self.prototypes.len() as u8; - let mut value_parameters: Option> = None; + let mut value_parameters: Vec<(u8, Type)> = Vec::with_capacity(3); while !function_compiler.allow(Token::RightParenthesis)? { let is_mutable = function_compiler.allow(Token::Mut)?; @@ -1594,15 +1598,10 @@ impl<'src> Compiler<'src> { function_compiler.current_scope, ); - if let Some(value_parameters) = value_parameters.as_mut() { - value_parameters.push((identifier_index, r#type)); - } else { - value_parameters = Some(smallvec![(identifier_index, r#type)]); - }; + value_parameters.push((identifier_index, r#type)); + function_compiler.allow(Token::Comma)?; function_compiler.minimum_register += 1; - - function_compiler.allow(Token::Comma)?; } let return_type = if function_compiler.allow(Token::ArrowThin)? { @@ -1618,7 +1617,7 @@ impl<'src> Compiler<'src> { Type::None }; let function_type = FunctionType { - type_parameters: None, + type_parameters: Vec::with_capacity(0), value_parameters, return_type, }; diff --git a/dust-lang/src/dust_error.rs b/dust-lang/src/dust_error.rs index 300a296..da94b40 100644 --- a/dust-lang/src/dust_error.rs +++ b/dust-lang/src/dust_error.rs @@ -3,7 +3,6 @@ use std::fmt::{self, Display, Formatter}; use annotate_snippets::{Level, Renderer, Snippet}; -use smallvec::SmallVec; use crate::{CompileError, NativeFunctionError, Span}; @@ -76,6 +75,6 @@ impl Display for DustError<'_> { pub trait AnnotatedError { fn title() -> &'static str; fn description(&self) -> &'static str; - fn detail_snippets(&self) -> SmallVec<[(String, Span); 2]>; - fn help_snippets(&self) -> SmallVec<[(String, Span); 2]>; + fn detail_snippets(&self) -> Vec<(String, Span)>; + fn help_snippets(&self) -> Vec<(String, Span)>; } diff --git a/dust-lang/src/lexer.rs b/dust-lang/src/lexer.rs index 02e7b07..0c09983 100644 --- a/dust-lang/src/lexer.rs +++ b/dust-lang/src/lexer.rs @@ -744,12 +744,12 @@ impl AnnotatedError for LexError { } } - fn detail_snippets(&self) -> smallvec::SmallVec<[(String, Span); 2]> { - todo!() + fn detail_snippets(&self) -> Vec<(String, Span)> { + Vec::with_capacity(0) } - fn help_snippets(&self) -> smallvec::SmallVec<[(String, Span); 2]> { - todo!() + fn help_snippets(&self) -> Vec<(String, Span)> { + Vec::with_capacity(0) } } diff --git a/dust-lang/src/lib.rs b/dust-lang/src/lib.rs index 14c00a9..f1e8dbd 100644 --- a/dust-lang/src/lib.rs +++ b/dust-lang/src/lib.rs @@ -28,8 +28,6 @@ //! println!("{}", report); //! ``` -#![feature(array_repeat)] - pub mod chunk; pub mod compiler; pub mod dust_error; diff --git a/dust-lang/src/native_function/assertion.rs b/dust-lang/src/native_function/assert.rs similarity index 88% rename from dust-lang/src/native_function/assertion.rs rename to dust-lang/src/native_function/assert.rs index 7592eee..d2e3cef 100644 --- a/dust-lang/src/native_function/assertion.rs +++ b/dust-lang/src/native_function/assert.rs @@ -3,7 +3,7 @@ use std::{ops::Range, panic}; use crate::vm::ThreadData; pub fn panic(data: &mut ThreadData, _: Option, argument_range: Range) -> bool { - let record = data.records.last_mut_unchecked(); + let record = &mut data.call_stack.last_mut_unchecked().record; let position = record.current_position(); let mut message = format!("Dust panic at {position}!"); diff --git a/dust-lang/src/native_function/io.rs b/dust-lang/src/native_function/io.rs index 5cc0fe3..d4e571b 100644 --- a/dust-lang/src/native_function/io.rs +++ b/dust-lang/src/native_function/io.rs @@ -11,7 +11,7 @@ pub fn read_line( destination: Option, _argument_range: Range, ) -> bool { - let record = data.records.last_mut_unchecked(); + let record = &mut data.call_stack.last_mut_unchecked().record; let destination = destination.unwrap(); let mut buffer = String::new(); @@ -31,7 +31,7 @@ pub fn read_line( } pub fn write(data: &mut ThreadData, _destination: Option, argument_range: Range) -> bool { - let record = data.records.last_mut_unchecked(); + let record = &mut data.call_stack.last_mut_unchecked().record; let mut stdout = stdout(); for register_index in argument_range { @@ -52,7 +52,7 @@ pub fn write_line( _destination: Option, argument_range: Range, ) -> bool { - let record = data.records.last_mut_unchecked(); + let record = &mut data.call_stack.last_mut_unchecked().record; let mut stdout = stdout().lock(); for register_index in argument_range { diff --git a/dust-lang/src/native_function/mod.rs b/dust-lang/src/native_function/mod.rs index c7b6c29..e56f54a 100644 --- a/dust-lang/src/native_function/mod.rs +++ b/dust-lang/src/native_function/mod.rs @@ -2,7 +2,7 @@ //! //! Native functions are used to implement features that are not possible to implement in Dust //! itself or that are more efficient to implement in Rust. -mod assertion; +mod assert; mod io; mod string; @@ -14,7 +14,6 @@ use std::{ }; use serde::{Deserialize, Serialize}; -use smallvec::{smallvec, SmallVec}; use crate::{vm::ThreadData, AnnotatedError, FunctionType, Span, Type}; @@ -134,11 +133,11 @@ define_native_function! { 3, "panic", FunctionType { - type_parameters: None, - value_parameters: None, + type_parameters: Vec::with_capacity(0), + value_parameters: Vec::with_capacity(0), return_type: Type::None }, - assertion::panic + assert::panic ), // // Type conversion @@ -151,8 +150,8 @@ define_native_function! { 8, "to_string", FunctionType { - type_parameters: None, - value_parameters: Some(smallvec![(0, Type::Any)]), + type_parameters: Vec::with_capacity(0), + value_parameters: vec![(0, Type::Any)], return_type: Type::String }, string::to_string @@ -212,8 +211,8 @@ define_native_function! { 50, "read_line", FunctionType { - type_parameters: None, - value_parameters: None, + type_parameters: Vec::with_capacity(0), + value_parameters: Vec::with_capacity(0), return_type: Type::String }, io::read_line @@ -228,8 +227,8 @@ define_native_function! { 55, "write", FunctionType { - type_parameters: None, - value_parameters: Some(smallvec![(0, Type::String)]), + type_parameters: Vec::with_capacity(0), + value_parameters: vec![(0, Type::String)], return_type: Type::None }, io::write @@ -240,8 +239,8 @@ define_native_function! { 57, "write_line", FunctionType { - type_parameters: None, - value_parameters: Some(smallvec![(0, Type::String)]), + type_parameters: Vec::with_capacity(0), + value_parameters: vec![(0, Type::String)], return_type: Type::None }, io::write_line @@ -289,29 +288,29 @@ impl AnnotatedError for NativeFunctionError { } } - fn detail_snippets(&self) -> SmallVec<[(String, Span); 2]> { + fn detail_snippets(&self) -> Vec<(String, Span)> { match self { NativeFunctionError::ExpectedArgumentCount { expected, found, position, - } => smallvec![( + } => vec![( format!("Expected {expected} arguments, found {found}"), - *position + *position, )], NativeFunctionError::Panic { message, position } => { - smallvec![(format!("Dust panic!\n{message}"), *position)] + vec![(format!("Dust panic!\n{message}"), *position)] } NativeFunctionError::Parse { error, position } => { - smallvec![(format!("{error}"), *position)] + vec![(format!("{error}"), *position)] } NativeFunctionError::Io { error, position } => { - smallvec![(format!("{error}"), *position)] + vec![(format!("{error}"), *position)] } } } - fn help_snippets(&self) -> SmallVec<[(String, Span); 2]> { - SmallVec::new() + fn help_snippets(&self) -> Vec<(String, Span)> { + Vec::with_capacity(0) } } diff --git a/dust-lang/src/native_function/string.rs b/dust-lang/src/native_function/string.rs index 525761e..48cd8b9 100644 --- a/dust-lang/src/native_function/string.rs +++ b/dust-lang/src/native_function/string.rs @@ -10,7 +10,7 @@ pub fn to_string( destination: Option, argument_range: Range, ) -> bool { - let record = data.records.last_mut_unchecked(); + let record = &mut data.call_stack.last_mut_unchecked().record; let argument_value = record.open_register_unchecked(argument_range.start); let argument_string = argument_value.display(record); let destination = destination.unwrap(); diff --git a/dust-lang/src/type.rs b/dust-lang/src/type.rs index d9a9150..e6264c4 100644 --- a/dust-lang/src/type.rs +++ b/dust-lang/src/type.rs @@ -6,7 +6,6 @@ use std::{ }; use serde::{Deserialize, Serialize}; -use smallvec::SmallVec; /// Description of a kind of value. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] @@ -25,7 +24,7 @@ pub enum Type { Integer, List(Box), Map { - pairs: Box>, + pairs: Vec<(u8, Type)>, }, None, Range { @@ -35,7 +34,7 @@ pub enum Type { String, Struct(StructType), Tuple { - fields: Box>, + fields: Vec, }, } @@ -274,20 +273,20 @@ impl Ord for Type { #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] pub struct FunctionType { - pub type_parameters: Option>, - pub value_parameters: Option>, + pub type_parameters: Vec, + pub value_parameters: Vec<(u8, Type)>, pub return_type: Type, } impl FunctionType { - pub fn new>, U: Into>>( - type_parameters: Option, - value_parameters: Option, + pub fn new>, U: Into>>( + type_parameters: T, + value_parameters: U, return_type: Type, ) -> Self { FunctionType { - type_parameters: type_parameters.map(|into_types| into_types.into()), - value_parameters: value_parameters.map(|into_values| into_values.into()), + type_parameters: type_parameters.into(), + value_parameters: value_parameters.into(), return_type, } } @@ -297,10 +296,10 @@ impl Display for FunctionType { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "fn ")?; - if let Some(type_parameters) = &self.type_parameters { + if !self.type_parameters.is_empty() { write!(f, "<")?; - for (index, type_parameter) in type_parameters.iter().enumerate() { + for (index, type_parameter) in self.type_parameters.iter().enumerate() { if index > 0 { write!(f, ", ")?; } @@ -313,8 +312,8 @@ impl Display for FunctionType { write!(f, "(")?; - if let Some(value_parameters) = &self.value_parameters { - for (index, (_, r#type)) in value_parameters.iter().enumerate() { + if !self.value_parameters.is_empty() { + for (index, (_, r#type)) in self.value_parameters.iter().enumerate() { if index > 0 { write!(f, ", ")?; } diff --git a/dust-lang/src/vm/error.rs b/dust-lang/src/vm/error.rs deleted file mode 100644 index 1c71da4..0000000 --- a/dust-lang/src/vm/error.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::fmt::{self, Display, Formatter}; - -use crate::{InstructionData, Value}; - -use super::{stack::Stack, FunctionCall}; - -#[derive(Clone, Debug, PartialEq)] -pub enum VmError { - StackUnderflow, - ExpectedFunction { - value: Value, - }, - InstructionIndexOutOfBounds { - call_stack: Stack, - ip: usize, - }, - MalformedInstruction { - instruction: InstructionData, - }, -} - -impl Display for VmError { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::StackUnderflow => { - write!(f, "Call stack underflow") - } - Self::ExpectedFunction { value } => { - write!(f, "Expected function, found {value}") - } - Self::InstructionIndexOutOfBounds { call_stack, ip } => { - write!(f, "Instruction index {} out of bounds\n{call_stack}", ip) - } - Self::MalformedInstruction { instruction } => { - write!(f, "Malformed instruction {instruction}") - } - } - } -} diff --git a/dust-lang/src/vm/mod.rs b/dust-lang/src/vm/mod.rs index f39ad72..cb5336f 100644 --- a/dust-lang/src/vm/mod.rs +++ b/dust-lang/src/vm/mod.rs @@ -1,5 +1,4 @@ //! Virtual machine and errors -mod error; mod record; mod run_action; mod stack; @@ -11,7 +10,6 @@ use std::{ thread::spawn, }; -pub use error::VmError; pub use record::Record; pub(crate) use run_action::get_next_action; pub use run_action::RunAction; diff --git a/dust-lang/src/vm/run_action.rs b/dust-lang/src/vm/run_action.rs index f636133..6cf5234 100644 --- a/dust-lang/src/vm/run_action.rs +++ b/dust-lang/src/vm/run_action.rs @@ -68,7 +68,7 @@ pub(crate) fn get_next_action(record: &mut Record) -> RunAction { } pub fn point(instruction: Instruction, data: &mut ThreadData) -> bool { - let record = data.records.last_mut_unchecked(); + let record = &mut data.call_stack.last_mut_unchecked().record; let Point { from, to } = instruction.into(); let from_register = record.get_register_unchecked(from); let from_register_is_empty = matches!(from_register, Register::Empty); @@ -85,7 +85,7 @@ pub fn point(instruction: Instruction, data: &mut ThreadData) -> bool { } pub fn close(instruction: Instruction, data: &mut ThreadData) -> bool { - let record = data.records.last_mut_unchecked(); + let record = &mut data.call_stack.last_mut_unchecked().record; let Close { from, to } = instruction.into(); for register_index in from..to { @@ -98,7 +98,7 @@ pub fn close(instruction: Instruction, data: &mut ThreadData) -> bool { } pub fn load_boolean(instruction: Instruction, data: &mut ThreadData) -> bool { - let record = data.records.last_mut_unchecked(); + let record = &mut data.call_stack.last_mut_unchecked().record; let LoadBoolean { destination, value, @@ -119,7 +119,7 @@ pub fn load_boolean(instruction: Instruction, data: &mut ThreadData) -> bool { } pub fn load_constant(instruction: Instruction, data: &mut ThreadData) -> bool { - let record = data.records.last_mut_unchecked(); + let record = &mut data.call_stack.last_mut_unchecked().record; let LoadConstant { destination, constant_index, @@ -141,7 +141,7 @@ pub fn load_constant(instruction: Instruction, data: &mut ThreadData) -> bool { } pub fn load_list(instruction: Instruction, data: &mut ThreadData) -> bool { - let record = data.records.last_mut_unchecked(); + let record = &mut data.call_stack.last_mut_unchecked().record; let LoadList { destination, start_register, @@ -183,7 +183,7 @@ pub fn load_list(instruction: Instruction, data: &mut ThreadData) -> bool { } pub fn load_function(instruction: Instruction, data: &mut ThreadData) -> bool { - let record = data.records.last_mut_unchecked(); + let record = &mut data.call_stack.last_mut_unchecked().record; let LoadFunction { destination, prototype_index, @@ -200,7 +200,7 @@ pub fn load_function(instruction: Instruction, data: &mut ThreadData) -> bool { } pub fn load_self(instruction: Instruction, data: &mut ThreadData) -> bool { - let record = data.records.last_mut_unchecked(); + let record = &mut data.call_stack.last_mut_unchecked().record; let LoadSelf { destination } = instruction.into(); let function = record.as_function(); let register = Register::Value(Value::Function(function)); @@ -213,7 +213,7 @@ pub fn load_self(instruction: Instruction, data: &mut ThreadData) -> bool { } pub fn get_local(instruction: Instruction, data: &mut ThreadData) -> bool { - let record = data.records.last_mut_unchecked(); + let record = &mut data.call_stack.last_mut_unchecked().record; let GetLocal { destination, local_index, @@ -229,7 +229,7 @@ pub fn get_local(instruction: Instruction, data: &mut ThreadData) -> bool { } pub fn set_local(instruction: Instruction, data: &mut ThreadData) -> bool { - let record = data.records.last_mut_unchecked(); + let record = &mut data.call_stack.last_mut_unchecked().record; let SetLocal { register_index, local_index, @@ -245,7 +245,7 @@ pub fn set_local(instruction: Instruction, data: &mut ThreadData) -> bool { } pub fn add(instruction: Instruction, data: &mut ThreadData) -> bool { - let record = data.records.last_mut_unchecked(); + let record = &mut data.call_stack.last_mut_unchecked().record; let Add { destination, left, @@ -264,7 +264,7 @@ pub fn add(instruction: Instruction, data: &mut ThreadData) -> bool { } pub fn subtract(instruction: Instruction, data: &mut ThreadData) -> bool { - let record = data.records.last_mut_unchecked(); + let record = &mut data.call_stack.last_mut_unchecked().record; let Subtract { destination, left, @@ -283,7 +283,7 @@ pub fn subtract(instruction: Instruction, data: &mut ThreadData) -> bool { } pub fn multiply(instruction: Instruction, data: &mut ThreadData) -> bool { - let record = data.records.last_mut_unchecked(); + let record = &mut data.call_stack.last_mut_unchecked().record; let Multiply { destination, left, @@ -310,7 +310,7 @@ pub fn multiply(instruction: Instruction, data: &mut ThreadData) -> bool { } pub fn divide(instruction: Instruction, data: &mut ThreadData) -> bool { - let record = data.records.last_mut_unchecked(); + let record = &mut data.call_stack.last_mut_unchecked().record; let Divide { destination, left, @@ -337,7 +337,7 @@ pub fn divide(instruction: Instruction, data: &mut ThreadData) -> bool { } pub fn modulo(instruction: Instruction, data: &mut ThreadData) -> bool { - let record = data.records.last_mut_unchecked(); + let record = &mut data.call_stack.last_mut_unchecked().record; let Modulo { destination, left, @@ -364,7 +364,7 @@ pub fn modulo(instruction: Instruction, data: &mut ThreadData) -> bool { } pub fn test(instruction: Instruction, data: &mut ThreadData) -> bool { - let record = data.records.last_mut_unchecked(); + let record = &mut data.call_stack.last_mut_unchecked().record; let Test { operand_register, test_value, @@ -386,7 +386,7 @@ pub fn test(instruction: Instruction, data: &mut ThreadData) -> bool { } pub fn test_set(instruction: Instruction, data: &mut ThreadData) -> bool { - let record = data.records.last_mut_unchecked(); + let record = &mut data.call_stack.last_mut_unchecked().record; let TestSet { destination, argument, @@ -416,7 +416,7 @@ pub fn test_set(instruction: Instruction, data: &mut ThreadData) -> bool { } pub fn equal(instruction: Instruction, data: &mut ThreadData) -> bool { - let record = data.records.last_mut_unchecked(); + let record = &mut data.call_stack.last_mut_unchecked().record; let Equal { value, left, right } = instruction.into(); let left = record.get_argument_unchecked(left); let right = record.get_argument_unchecked(right); @@ -432,7 +432,7 @@ pub fn equal(instruction: Instruction, data: &mut ThreadData) -> bool { } pub fn less(instruction: Instruction, data: &mut ThreadData) -> bool { - let record = data.records.last_mut_unchecked(); + let record = &mut data.call_stack.last_mut_unchecked().record; let Less { value, left, right } = instruction.into(); let left = record.get_argument_unchecked(left); let right = record.get_argument_unchecked(right); @@ -448,7 +448,7 @@ pub fn less(instruction: Instruction, data: &mut ThreadData) -> bool { } pub fn less_equal(instruction: Instruction, data: &mut ThreadData) -> bool { - let record = data.records.last_mut_unchecked(); + let record = &mut data.call_stack.last_mut_unchecked().record; let LessEqual { value, left, right } = instruction.into(); let left = record.get_argument_unchecked(left); let right = record.get_argument_unchecked(right); @@ -464,7 +464,7 @@ pub fn less_equal(instruction: Instruction, data: &mut ThreadData) -> bool { } pub fn negate(instruction: Instruction, data: &mut ThreadData) -> bool { - let record = data.records.last_mut_unchecked(); + let record = &mut data.call_stack.last_mut_unchecked().record; let Negate { destination, argument, @@ -481,7 +481,7 @@ pub fn negate(instruction: Instruction, data: &mut ThreadData) -> bool { } pub fn not(instruction: Instruction, data: &mut ThreadData) -> bool { - let record = data.records.last_mut_unchecked(); + let record = &mut data.call_stack.last_mut_unchecked().record; let Not { destination, argument, @@ -501,7 +501,7 @@ pub fn not(instruction: Instruction, data: &mut ThreadData) -> bool { } pub fn jump(instruction: Instruction, data: &mut ThreadData) -> bool { - let record = data.records.last_mut_unchecked(); + let record = &mut data.call_stack.last_mut_unchecked().record; let Jump { offset, is_positive, @@ -520,32 +520,35 @@ pub fn jump(instruction: Instruction, data: &mut ThreadData) -> bool { } pub fn call(instruction: Instruction, data: &mut ThreadData) -> bool { - let current_record = data.records.pop_unchecked(); + let current_call = data.call_stack.pop_unchecked(); let Call { destination: return_register, function_register, argument_count, is_recursive, } = instruction.into(); - let function = current_record + let function = current_call + .record .open_register_unchecked(function_register) .as_function() .unwrap(); let first_argument_register = return_register - argument_count; let prototype = if is_recursive { - current_record.chunk + current_call.record.chunk } else { - ¤t_record.chunk.prototypes[function.prototype_index as usize] + ¤t_call.record.chunk.prototypes[function.prototype_index as usize] }; - let mut next_record = Record::new(prototype); - let next_call = FunctionCall { - name: next_record.name().cloned(), + let mut next_call = FunctionCall { + name: prototype.name.clone(), return_register, - ip: current_record.ip, + ip: current_call.ip, + record: Record::new(prototype), }; for (argument_index, register_index) in (first_argument_register..return_register).enumerate() { - let argument = current_record.clone_register_value_or_constant_unchecked(register_index); + let argument = current_call + .record + .clone_register_value_or_constant_unchecked(register_index); trace!( "Passing argument \"{argument}\" to {}", @@ -555,14 +558,15 @@ pub fn call(instruction: Instruction, data: &mut ThreadData) -> bool { .unwrap_or_else(|| DustString::from("anonymous")) ); - next_record.set_register(argument_index as u8, Register::Value(argument)); + next_call + .record + .set_register(argument_index as u8, Register::Value(argument)); } - data.next_action = get_next_action(&mut next_record); + data.next_action = get_next_action(&mut next_call.record); + data.call_stack.push(current_call); data.call_stack.push(next_call); - data.records.push(current_record); - data.records.push(next_record); false } @@ -586,28 +590,30 @@ pub fn r#return(instruction: Instruction, data: &mut ThreadData) -> bool { should_return_value, return_register, } = instruction.into(); - let current_call = data.call_stack.pop_unchecked(); - let mut current_record = if data.call_stack.is_empty() { + let mut current_call = if data.call_stack.len() == 1 { if should_return_value { data.return_value_index = Some(return_register); }; return true; } else { - data.records.pop_unchecked() + data.call_stack.pop_unchecked() }; - let outer_record = data.records.last_mut_unchecked(); + let outer_call = data.call_stack.last_mut_unchecked(); let destination = current_call.return_register; if should_return_value { - let return_value = - current_record.empty_register_or_clone_constant_unchecked(return_register); + let return_value = current_call + .record + .empty_register_or_clone_constant_unchecked(return_register); - outer_record.set_register(destination, Register::Value(return_value)); + outer_call + .record + .set_register(destination, Register::Value(return_value)); } - data.next_action = get_next_action(outer_record); + data.next_action = get_next_action(&mut outer_call.record); false } diff --git a/dust-lang/src/vm/stack.rs b/dust-lang/src/vm/stack.rs index 82e8562..752dcd8 100644 --- a/dust-lang/src/vm/stack.rs +++ b/dust-lang/src/vm/stack.rs @@ -5,7 +5,7 @@ use std::{ use crate::DustString; -use super::VmError; +use super::Record; #[derive(Clone, PartialEq)] pub struct Stack { @@ -35,7 +35,7 @@ impl Stack { pub fn get_unchecked(&self, index: usize) -> &T { if cfg!(debug_assertions) { - assert!(index < self.len(), "{}", VmError::StackUnderflow); + assert!(index < self.len(), "Stack underflow"); &self.items[index] } else { @@ -61,7 +61,7 @@ impl Stack { pub fn pop_unchecked(&mut self) -> T { if cfg!(debug_assertions) { - assert!(!self.is_empty(), "{}", VmError::StackUnderflow); + assert!(!self.is_empty(), "Stack underflow"); self.items.pop().unwrap() } else { @@ -71,7 +71,7 @@ impl Stack { pub fn last_unchecked(&self) -> &T { if cfg!(debug_assertions) { - assert!(!self.is_empty(), "{}", VmError::StackUnderflow); + assert!(!self.is_empty(), "Stack underflow"); self.items.last().unwrap() } else { @@ -81,7 +81,7 @@ impl Stack { pub fn last_mut_unchecked(&mut self) -> &mut T { if cfg!(debug_assertions) { - assert!(!self.is_empty(), "{}", VmError::StackUnderflow); + assert!(!self.is_empty(), "Stack underflow"); self.items.last_mut().unwrap() } else { @@ -116,7 +116,7 @@ impl Debug for Stack { } } -impl Display for Stack { +impl Display for Stack> { fn fmt(&self, f: &mut Formatter) -> fmt::Result { writeln!(f, "-- DUST CALL STACK --")?; @@ -128,14 +128,15 @@ impl Display for Stack { } } -#[derive(Clone, Debug, PartialEq)] -pub struct FunctionCall { +#[derive(Debug)] +pub struct FunctionCall<'a> { pub name: Option, pub return_register: u8, pub ip: usize, + pub record: Record<'a>, } -impl Display for FunctionCall { +impl Display for FunctionCall<'_> { fn fmt(&self, f: &mut Formatter) -> fmt::Result { let FunctionCall { name, diff --git a/dust-lang/src/vm/thread.rs b/dust-lang/src/vm/thread.rs index 3adaaa2..462479c 100644 --- a/dust-lang/src/vm/thread.rs +++ b/dust-lang/src/vm/thread.rs @@ -23,21 +23,18 @@ impl Thread { ); 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, // Never used, the main function's return is the thread's return ip: 0, + record: Record::new(&self.chunk), }; - let main_record = Record::new(&self.chunk); call_stack.push(main_call); - records.push(main_record); let first_action = RunAction::from(*self.chunk.instructions.first().unwrap()); let mut thread_data = ThreadData { call_stack, - records, next_action: first_action, return_value_index: None, }; @@ -53,8 +50,9 @@ impl Thread { if should_end { let return_value = if let Some(register_index) = thread_data.return_value_index { let value = thread_data - .records + .call_stack .last_mut_unchecked() + .record .empty_register_or_clone_constant_unchecked(register_index); Some(value) @@ -70,8 +68,7 @@ impl Thread { #[derive(Debug)] pub struct ThreadData<'a> { - pub call_stack: Stack, - pub records: Stack>, + pub call_stack: Stack>, pub next_action: RunAction, pub return_value_index: Option, }