From 72365cd39952cbdb46e91a91e3664013bd0f128e Mon Sep 17 00:00:00 2001 From: Jeff Date: Tue, 17 Dec 2024 16:31:32 -0500 Subject: [PATCH] Continue new VM implementation; Write docs --- Cargo.lock | 12 +- dust-cli/src/main.rs | 6 +- dust-lang/Cargo.toml | 1 - dust-lang/src/chunk/disassembler.rs | 4 +- dust-lang/src/chunk/mod.rs | 54 ++-- dust-lang/src/compiler/error.rs | 4 +- dust-lang/src/compiler/mod.rs | 250 +++++++++--------- dust-lang/src/compiler/optimize.rs | 86 ++---- dust-lang/src/dust_error.rs | 39 +-- dust-lang/src/instruction/load_function.rs | 2 +- dust-lang/src/instruction/mod.rs | 24 +- dust-lang/src/instruction/operation.rs | 2 +- .../src/instruction/{move.rs => point.rs} | 0 dust-lang/src/native_function/assertion.rs | 2 +- dust-lang/src/type.rs | 4 +- dust-lang/src/value/concrete_value.rs | 12 +- dust-lang/src/value/function.rs | 3 +- dust-lang/src/value/mod.rs | 8 +- dust-lang/src/vm/call_stack.rs | 48 +++- dust-lang/src/vm/error.rs | 2 +- dust-lang/src/vm/mod.rs | 12 +- dust-lang/src/vm/record.rs | 48 +++- dust-lang/src/vm/{runner.rs => run_action.rs} | 34 +-- dust-lang/src/vm/thread.rs | 149 ++++++++--- examples/fibonacci.ds | 2 +- 25 files changed, 454 insertions(+), 354 deletions(-) rename dust-lang/src/instruction/{move.rs => point.rs} (100%) rename dust-lang/src/vm/{runner.rs => run_action.rs} (96%) diff --git a/Cargo.lock b/Cargo.lock index 5579061..2f7f60e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -102,15 +102,6 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bitflags" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" -dependencies = [ - "serde", -] - [[package]] name = "bumpalo" version = "3.16.0" @@ -141,7 +132,7 @@ version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ - "bitflags 1.3.2", + "bitflags", "textwrap", "unicode-width", ] @@ -314,7 +305,6 @@ name = "dust-lang" version = "0.5.0" dependencies = [ "annotate-snippets", - "bitflags 2.6.0", "colored", "criterion", "getrandom", diff --git a/dust-cli/src/main.rs b/dust-cli/src/main.rs index 665db33..53528a7 100644 --- a/dust-cli/src/main.rs +++ b/dust-cli/src/main.rs @@ -4,9 +4,11 @@ use std::{fs::read_to_string, path::PathBuf}; use clap::{Args, Parser}; use colored::Colorize; -use dust_lang::{compile, CompileError, Compiler, DustError, Lexer, Span, Token, Vm}; +use dust_lang::{compile, CompileError, Compiler, DustError, DustString, Lexer, Span, Token, Vm}; use log::{Level, LevelFilter}; +const DEFAULT_PROGRAM_NAME: &str = "Dust CLI Input"; + #[derive(Parser)] #[clap( name = env!("CARGO_PKG_NAME"), @@ -196,7 +198,7 @@ fn main() { } } - let chunk = compiler.finish(); + let chunk = compiler.finish(Some(DEFAULT_PROGRAM_NAME)); let compile_end = start_time.elapsed(); let vm = Vm::new(chunk); diff --git a/dust-lang/Cargo.toml b/dust-lang/Cargo.toml index 2090f1a..a651f42 100644 --- a/dust-lang/Cargo.toml +++ b/dust-lang/Cargo.toml @@ -22,7 +22,6 @@ smallvec = { version = "1.13.2", features = ["const_generics", "serde"] } smartstring = { version = "1.0.1", features = [ "serde", ], default-features = false } -bitflags = { version = "2.6.0", features = ["serde"] } [dev-dependencies] criterion = { version = "0.3.4", features = ["html_reports"] } diff --git a/dust-lang/src/chunk/disassembler.rs b/dust-lang/src/chunk/disassembler.rs index 595e0bd..56baa3a 100644 --- a/dust-lang/src/chunk/disassembler.rs +++ b/dust-lang/src/chunk/disassembler.rs @@ -263,7 +263,7 @@ impl<'a, W: Write> Disassembler<'a, W> { self.write_char(border[1])?; } - self.write_char(border[2]); + self.write_char(border[2])?; self.write_char('\n') } @@ -377,7 +377,7 @@ impl<'a, W: Write> Disassembler<'a, W> { } pub fn write_prototype_section(&mut self) -> Result<(), io::Error> { - self.write_center_border_bold("Functions"); + self.write_center_border_bold("Functions")?; for chunk in &self.chunk.prototypes { chunk diff --git a/dust-lang/src/chunk/mod.rs b/dust-lang/src/chunk/mod.rs index 5fdc673..c065c5e 100644 --- a/dust-lang/src/chunk/mod.rs +++ b/dust-lang/src/chunk/mod.rs @@ -27,7 +27,8 @@ use std::io::Write; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; -use crate::{DustString, FunctionType, Instruction, Span, Value}; +use crate::vm::{Record, Register, RunAction}; +use crate::{DustString, Function, FunctionType, Instruction, Span, Value}; /// Representation of a Dust program or function. /// @@ -44,6 +45,7 @@ pub struct Chunk { prototypes: Vec, stack_size: usize, + record_index: u8, } impl Chunk { @@ -56,6 +58,7 @@ impl Chunk { locals: impl Into>, prototypes: impl Into>, stack_size: usize, + record_index: u8, ) -> Self { Self { name, @@ -66,6 +69,7 @@ impl Chunk { locals: locals.into(), prototypes: prototypes.into(), stack_size, + record_index, } } @@ -88,31 +92,47 @@ impl Chunk { locals: locals.into(), prototypes, stack_size: 0, + record_index: 0, } } - pub fn take_data( - self, - ) -> ( - Option, - FunctionType, - SmallVec<[Instruction; 32]>, - SmallVec<[Span; 32]>, - SmallVec<[Value; 16]>, - SmallVec<[Local; 8]>, - Vec, - usize, - ) { - ( + pub fn as_function(&self) -> Function { + Function { + name: self.name.clone(), + r#type: self.r#type.clone(), + record_index: self.record_index, + } + } + + pub fn into_records(self, records: &mut Vec) { + let actions = self.instructions().iter().map(RunAction::from).collect(); + let record = Record::new( + actions, + None, self.name, self.r#type, - self.instructions, self.positions, self.constants, self.locals, - self.prototypes, self.stack_size, - ) + self.record_index, + ); + + if records.is_empty() { + records.push(record); + + for chunk in self.prototypes { + chunk.into_records(records); + } + } else { + for chunk in self.prototypes { + chunk.into_records(records); + } + + debug_assert!(record.index() as usize == records.len()); + + records.push(record); + } } pub fn name(&self) -> Option<&DustString> { diff --git a/dust-lang/src/compiler/error.rs b/dust-lang/src/compiler/error.rs index 431f64e..e83ca48 100644 --- a/dust-lang/src/compiler/error.rs +++ b/dust-lang/src/compiler/error.rs @@ -20,7 +20,7 @@ pub enum CompileError { }, // Parsing errors - CannotChainComparison { + ComparisonChain { position: Span, }, ExpectedBoolean { @@ -176,7 +176,7 @@ impl AnnotatedError for CompileError { match self { Self::CannotAddArguments { .. } => "Cannot add these types", Self::CannotAddType { .. } => "Cannot add to this type", - Self::CannotChainComparison { .. } => "Cannot chain comparison operations", + Self::ComparisonChain { .. } => "Cannot chain comparison operations", Self::CannotDivideArguments { .. } => "Cannot divide these types", Self::CannotDivideType { .. } => "Cannot divide this type", Self::CannotModuloArguments { .. } => "Cannot modulo these types", diff --git a/dust-lang/src/compiler/mod.rs b/dust-lang/src/compiler/mod.rs index 69b9251..ffc2b97 100644 --- a/dust-lang/src/compiler/mod.rs +++ b/dust-lang/src/compiler/mod.rs @@ -1,9 +1,12 @@ -//! Compilation tools and errors +//! The Dust compiler and its accessories. //! //! This module provides two compilation options: -//! - [`compile`] borrows a string and returns a chunk, handling the entire compilation process and -//! turning any resulting [`ComplileError`] into a [`DustError`]. -//! - [`Compiler`] uses a lexer to get tokens and assembles a chunk. +//! - [`compile`] is a simple function that borrows a string and returns a chunk, handling +//! compilation and turning any resulting error into a [`DustError`], which can easily display a +//! detailed report. The main chunk will be named "main". +//! - [`Compiler`] is created with a [`Lexer`] and protentially emits a [`CompileError`] or +//! [`LexError`] if the input is invalid. Allows passing a name for the main chunk when +//! [`Compiler::finish`] is called. mod error; mod optimize; @@ -15,12 +18,12 @@ use std::{ }; use colored::Colorize; -use optimize::{optimize_test_with_explicit_booleans, optimize_test_with_loader_arguments}; +use optimize::control_flow_register_consolidation; use smallvec::{smallvec, SmallVec}; use crate::{ instruction::{ - CallNative, Close, GetLocal, Jump, LoadList, Negate, Not, Point, Return, SetLocal, Test, + CallNative, Close, GetLocal, Jump, LoadList, Negate, Not, Return, SetLocal, Test, }, Argument, Chunk, ConcreteValue, DustError, DustString, FunctionType, Instruction, Lexer, Local, NativeFunction, Operation, Scope, Span, Token, TokenKind, Type, Value, @@ -35,7 +38,7 @@ use crate::{ /// let source = "40 + 2 == 42"; /// let chunk = compile(source).unwrap(); /// -/// assert_eq!(chunk.len(), 3); +/// assert_eq!(chunk.instructions().len(), 3); /// ``` pub fn compile(source: &str) -> Result { let lexer = Lexer::new(source); @@ -45,12 +48,14 @@ pub fn compile(source: &str) -> Result { .compile() .map_err(|error| DustError::compile(error, source))?; - let chunk = compiler.finish(); + let name = DustString::from("main"); + let chunk = compiler.finish(Some(name)); Ok(chunk) } -/// Tool for compiling the input a token at a time while assembling a chunk. +/// The Dust compiler assembles a [`Chunk`] for the Dust VM. Any unrecognized symbols, disallowed +/// syntax or conflicting type usage will result in an error. /// /// See the [`compile`] function an example of how to create and use a Compiler. #[derive(Debug)] @@ -58,9 +63,10 @@ pub struct Compiler<'src> { /// Used to get tokens for the compiler. lexer: Lexer<'src>, - /// Name of the function or program being compiled. This is assigned to the chunk when - /// [`Compiler::finish`] is called. - self_name: Option, + /// Name of the function being compiled. This is used to identify recursive calls, so it should + /// be `None` for the main chunk. The main chunk can still be named by passing a name to + /// [`Compiler::finish`], which will override this value. + function_name: Option, /// Type of the function being compiled. This is assigned to the chunk when [`Compiler::finish`] /// is called. @@ -116,6 +122,7 @@ pub struct Compiler<'src> { } impl<'src> Compiler<'src> { + /// Creates a new compiler with the given lexer. pub fn new(mut lexer: Lexer<'src>) -> Result { let (current_token, current_position) = lexer.next_token()?; @@ -126,7 +133,7 @@ impl<'src> Compiler<'src> { ); Ok(Compiler { - self_name: None, + function_name: None, r#type: FunctionType { type_parameters: None, value_parameters: None, @@ -150,7 +157,12 @@ impl<'src> Compiler<'src> { }) } - pub fn finish(self) -> Chunk { + /// Creates a new chunk with the compiled data, optionally assigning a name to the chunk. + /// + /// Note for maintainers: Do not give a name when compiling functions, only the main chunk. This + /// 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, name: Option>) -> Chunk { log::info!("End chunk"); let (instructions, positions): (SmallVec<[Instruction; 32]>, SmallVec<[Span; 32]>) = self @@ -163,9 +175,12 @@ impl<'src> Compiler<'src> { .into_iter() .map(|(local, _)| local) .collect::>(); + let chunk_name = name + .map(|into_name| into_name.into()) + .or(self.function_name); Chunk::new( - self.self_name, + chunk_name, self.r#type, instructions, positions, @@ -173,9 +188,13 @@ impl<'src> Compiler<'src> { locals, self.prototypes, self.stack_size, + self.record_index, ) } + /// Compiles the source while checking for errors and returning a [`CompileError`] if any are + /// found. After calling this function, check its return value for an error, then call + /// [`Compiler::finish`] to get the compiled chunk. pub fn compile(&mut self) -> Result<(), CompileError> { loop { self.parse(Precedence::None)?; @@ -852,7 +871,7 @@ impl<'src> Compiler<'src> { if let Some([Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL, _, _]) = self.get_last_operations() { - return Err(CompileError::CannotChainComparison { + return Err(CompileError::ComparisonChain { position: self.current_position, }); } @@ -915,13 +934,13 @@ impl<'src> Compiler<'src> { } }; let jump = Instruction::jump(1, true); - let load_false = Instruction::load_boolean(destination, false, true); - let load_true = Instruction::load_boolean(destination, true, false); + let load_true = Instruction::load_boolean(destination, true, true); + let load_false = Instruction::load_boolean(destination, false, false); self.emit_instruction(comparison, Type::Boolean, operator_position); self.emit_instruction(jump, Type::None, operator_position); - self.emit_instruction(load_false, Type::Boolean, operator_position); self.emit_instruction(load_true, Type::Boolean, operator_position); + self.emit_instruction(load_false, Type::Boolean, operator_position); Ok(()) } @@ -932,29 +951,19 @@ impl<'src> Compiler<'src> { Some([Operation::TEST, Operation::JUMP, _]) ); - let (mut left_instruction, left_type, left_position) = self.pop_last_instruction()?; + let (left_instruction, left_type, left_position) = self.pop_last_instruction()?; + let (left, _) = self.handle_binary_argument(&left_instruction)?; - if is_logic_chain { - let destination = self - .instructions - .iter() - .rev() - .nth(2) - .map_or(0, |(instruction, _, _)| instruction.a_field()); + // if is_logic_chain { + // let destination = self + // .instructions + // .iter() + // .rev() + // .nth(2) + // .map_or(0, |(instruction, _, _)| instruction.a_field()); - left_instruction.set_a_field(destination); - } - - let jump_index = self.instructions.len().saturating_sub(1); - let mut jump_distance = if is_logic_chain { - self.instructions.pop().map_or(0, |(jump, _, _)| { - let Jump { offset, .. } = Jump::from(&jump); - - offset - }) - } else { - 0 - }; + // left_instruction.set_a_field(destination); + // } if !left_instruction.yields_value() { return Err(CompileError::ExpectedExpression { @@ -963,10 +972,19 @@ impl<'src> Compiler<'src> { }); } - let (left, _) = self.handle_binary_argument(&left_instruction)?; + // self.instructions + // .push((left_instruction, left_type.clone(), left_position)); - self.instructions - .push((left_instruction, left_type.clone(), left_position)); + // let short_circuit_jump_index = self.instructions.len(); + // let mut short_circuit_jump_distance = if is_logic_chain { + // self.instructions.pop().map_or(0, |(jump, _, _)| { + // let Jump { offset, .. } = Jump::from(&jump); + + // offset + // }) + // } else { + // 1 + // }; let operator = self.current_token; let operator_position = self.current_position; @@ -986,20 +1004,28 @@ impl<'src> Compiler<'src> { self.advance()?; self.emit_instruction(test, Type::None, operator_position); - self.emit_instruction(Instruction::jump(1, true), Type::None, operator_position); + + let jump_index = self.instructions.len(); + self.parse_sub_expression(&rule.precedence)?; + let jump_distance = (self.instructions.len() - jump_index) as u8; + let jump = Instruction::jump(jump_distance, true); + + self.instructions + .insert(jump_index, (jump, Type::None, operator_position)); + let (mut right_instruction, _, _) = self.instructions.last_mut().unwrap(); + right_instruction.set_a_field(left_instruction.a_field()); - if is_logic_chain { - let expression_length = self.instructions.len() - jump_index - 1; - jump_distance += expression_length as u8; - let jump = Instruction::jump(jump_distance, true); + // short_circuit_jump_distance += (self.instructions.len() - short_circuit_jump_index) as u8; + // let jump = Instruction::jump(short_circuit_jump_distance, true); - self.instructions - .insert(jump_index, (jump, Type::None, operator_position)); - } + // self.instructions.insert( + // short_circuit_jump_index, + // (jump, Type::None, operator_position), + // ); Ok(()) } @@ -1021,7 +1047,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.self_name.as_deref() == Some(identifier) { + } else if self.function_name.as_deref() == Some(identifier) { let destination = self.next_register(); let load_function = Instruction::load_function(destination, self.record_index); @@ -1175,35 +1201,22 @@ impl<'src> Compiler<'src> { self.advance()?; self.parse_expression()?; - if matches!( - self.get_last_operations(), - Some([ - Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL, - Operation::JUMP, - Operation::LOAD_BOOLEAN, - Operation::LOAD_BOOLEAN - ]), - ) { - self.instructions.pop(); - self.instructions.pop(); - self.instructions.pop(); - } else if let Some((instruction, _, _)) = self.instructions.last() { - let argument = match instruction.as_argument() { - Some(argument) => argument, - None => { - return Err(CompileError::ExpectedExpression { - found: self.previous_token.to_owned(), - position: self.previous_position, - }); - } - }; - let test = Instruction::from(Test { - argument, - test_value: true, - }); + let (last_instruction, _, _) = self.instructions.last().unwrap(); + let argument = match last_instruction.as_argument() { + Some(argument) => argument, + None => { + return Err(CompileError::ExpectedExpression { + found: self.previous_token.to_owned(), + position: self.previous_position, + }); + } + }; + let test = Instruction::from(Test { + argument, + test_value: true, + }); - self.emit_instruction(test, Type::None, self.current_position) - } + self.emit_instruction(test, Type::None, self.current_position); let if_block_start = self.instructions.len(); let if_block_start_position = self.current_position; @@ -1211,8 +1224,8 @@ impl<'src> Compiler<'src> { if let Token::LeftBrace = self.current_token { self.parse_block()?; } else { - return Err(CompileError::ExpectedToken { - expected: TokenKind::LeftBrace, + return Err(CompileError::ExpectedTokenMultiple { + expected: &[TokenKind::If, TokenKind::LeftBrace], found: self.current_token.to_owned(), position: self.current_position, }); @@ -1235,15 +1248,11 @@ impl<'src> Compiler<'src> { position: self.current_position, }); } - - true } else if if_block_type != Type::None { return Err(CompileError::IfMissingElse { position: Span(if_block_start_position.0, self.current_position.1), }); - } else { - false - }; + } let else_block_end = self.instructions.len(); let else_block_distance = (else_block_end - if_block_end) as u8; @@ -1288,25 +1297,18 @@ impl<'src> Compiler<'src> { } } - let jump = Instruction::from(Jump { - offset: if_block_distance, - is_positive: true, - }); + let jump = Instruction::jump(if_block_distance, true); self.instructions .insert(if_block_start, (jump, Type::None, if_block_start_position)); - optimize_test_with_explicit_booleans(self); - optimize_test_with_loader_arguments(self); + control_flow_register_consolidation(self); let else_last_register = self.next_register().saturating_sub(1); - let r#move = Instruction::from(Point { - from: else_last_register, - to: if_last_register, - }); + let point = Instruction::point(else_last_register, if_last_register); if if_last_register < else_last_register { - self.emit_instruction(r#move, Type::None, self.current_position); + self.emit_instruction(point, else_block_type, self.current_position); } Ok(()) @@ -1490,9 +1492,7 @@ impl<'src> Compiler<'src> { fn parse_implicit_return(&mut self) -> Result<(), CompileError> { if self.allow(Token::Semicolon)? { - let r#return = Instruction::from(Return { - should_return_value: false, - }); + let r#return = Instruction::r#return(false); self.emit_instruction(r#return, Type::None, self.current_position); } else { @@ -1507,9 +1507,7 @@ impl<'src> Compiler<'src> { } }); let should_return_value = previous_expression_type != Type::None; - let r#return = Instruction::from(Return { - should_return_value, - }); + let r#return = Instruction::r#return(should_return_value); self.update_return_type(previous_expression_type.clone())?; self.emit_instruction(r#return, Type::None, self.current_position); @@ -1577,7 +1575,7 @@ impl<'src> Compiler<'src> { function_compiler.advance()?; - function_compiler.self_name = Some(text.into()); + function_compiler.function_name = Some(text.into()); Some((text, position)) } else { @@ -1664,7 +1662,8 @@ impl<'src> Compiler<'src> { self.lexer.skip_to(self.current_position.1); let function_end = function_compiler.previous_position.1; - let chunk = function_compiler.finish(); + let record_index = function_compiler.record_index; + let chunk = function_compiler.finish(None::<&str>); let destination = self.next_register(); self.prototypes.push(chunk); @@ -1679,7 +1678,7 @@ impl<'src> Compiler<'src> { ); } - let load_function = Instruction::load_function(destination, self.record_index); + let load_function = Instruction::load_function(destination, record_index); self.emit_instruction( load_function, @@ -1703,7 +1702,10 @@ impl<'src> Compiler<'src> { position: self.previous_position, })?; - if last_instruction.operation() != Operation::LOAD_FUNCTION { + if !matches!( + last_instruction_type, + Type::Function(_) | Type::SelfFunction + ) { return Err(CompileError::ExpectedFunction { found: self.previous_token.to_owned(), actual_type: last_instruction_type.clone(), @@ -1970,29 +1972,25 @@ impl<'src> Compiler<'src> { /// Operator precedence levels. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum Precedence { - None, - Assignment, - Conditional, - LogicalOr, - LogicalAnd, - Equality, - Comparison, - Term, - Factor, - Unary, - Call, - Primary, + Primary = 9, + Call = 8, + Unary = 7, + Factor = 6, + Term = 5, + Comparison = 4, + LogicalAnd = 3, + LogicalOr = 2, + Assignment = 1, + None = 0, } impl Precedence { fn increment(&self) -> Self { match self { Precedence::None => Precedence::Assignment, - Precedence::Assignment => Precedence::Conditional, - Precedence::Conditional => Precedence::LogicalOr, + Precedence::Assignment => Precedence::LogicalOr, Precedence::LogicalOr => Precedence::LogicalAnd, - Precedence::LogicalAnd => Precedence::Equality, - Precedence::Equality => Precedence::Comparison, + Precedence::LogicalAnd => Precedence::Comparison, Precedence::Comparison => Precedence::Term, Precedence::Term => Precedence::Factor, Precedence::Factor => Precedence::Unary, @@ -2036,7 +2034,7 @@ impl From<&Token<'_>> for ParseRule<'_> { Token::BangEqual => ParseRule { prefix: None, infix: Some(Compiler::parse_comparison_binary), - precedence: Precedence::Equality, + precedence: Precedence::Comparison, }, Token::Bool => ParseRule { prefix: Some(Compiler::expect_expression), @@ -2082,7 +2080,7 @@ impl From<&Token<'_>> for ParseRule<'_> { Token::DoubleEqual => ParseRule { prefix: None, infix: Some(Compiler::parse_comparison_binary), - precedence: Precedence::Equality, + precedence: Precedence::Comparison, }, Token::DoublePipe => ParseRule { prefix: None, diff --git a/dust-lang/src/compiler/optimize.rs b/dust-lang/src/compiler/optimize.rs index c4ff45e..fbe1c6e 100644 --- a/dust-lang/src/compiler/optimize.rs +++ b/dust-lang/src/compiler/optimize.rs @@ -1,15 +1,11 @@ //! Functions used by the compiler to optimize a chunk's bytecode during compilation. -use crate::{Compiler, Operation}; +use crate::{Compiler, Instruction, Operation}; -/// Optimizes a control flow pattern by removing redundant instructions. +/// Optimizes a control flow pattern to use fewer registers and avoid using a `POINT` instruction. +/// Use this after parsing an if/else statement. /// -/// If a comparison instruction is followed by a test instruction, the test instruction may be -/// redundant because the comparison instruction already sets the correct value. If the test's -/// arguments (i.e. the boolean loaders) are `true` and `false` (in that order) then the boolean -/// loaders, jump and test instructions are removed, leaving a single comparison instruction. -/// -/// This makes the following two code snippets compile to the same bytecode: +/// This makes the following examples compile to the same bytecode: /// /// ```dust /// 4 == 4 @@ -19,58 +15,27 @@ use crate::{Compiler, Operation}; /// if 4 == 4 { true } else { false } /// ``` /// -/// The instructions must be in the following order: -/// - `EQUAL`, `LESS` or `LESS_EQUAL` -/// - `TEST` -/// - `JUMP` -/// - `LOAD_BOOLEAN` -/// - `LOAD_BOOLEAN` -pub fn optimize_test_with_explicit_booleans(compiler: &mut Compiler) { - if matches!( - compiler.get_last_operations(), - Some([ - Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL, - Operation::TEST, - Operation::JUMP, - Operation::LOAD_BOOLEAN, - Operation::LOAD_BOOLEAN, - ]) - ) { - log::debug!("Removing redundant test, jump and boolean loaders after comparison"); - - let first_loader = compiler.instructions.iter().nth_back(1).unwrap(); - let second_loader = compiler.instructions.last().unwrap(); - let first_boolean = first_loader.0.b_field() != 0; - let second_boolean = second_loader.0.b_field() != 0; - - if first_boolean && !second_boolean { - compiler.instructions.pop(); - compiler.instructions.pop(); - compiler.instructions.pop(); - compiler.instructions.pop(); - } - } -} - -/// Optimizes a control flow pattern. -/// -/// TEST instructions (which are always followed by a JUMP) can be optimized when the next -/// instructions are two constant or boolean loaders. The first loader is set to skip an instruction -/// if it is run while the second loader is modified to use the first's register. Foregoing the use -/// a jump instruction is an optimization but consolidating the registers is a necessity. This is -/// because test instructions are essentially control flow and a subsequent SET_LOCAL instruction -/// would not know at compile time which branch would be executed at runtime. +/// When they occur in the sequence shown below, instructions can be optimized by taking advantage +/// of the loaders' ability to skip an instruction after loading a value. If these instructions are +/// the result of a binary expression, this will not change anything because they were already +/// emitted optimally. Control flow patterns, however, can be optimized because the load +/// instructions are from seperate expressions that each uses its own register. Since only one of +/// the two branches will be executed, this is wasteful. It would also require the compiler to emit +/// a `POINT` instruction to prevent the VM from encountering an empty register. /// /// The instructions must be in the following order: -/// - `TEST` +/// - `EQUAL` | `LESS` | `LESS_EQUAL` | `TEST` /// - `JUMP` /// - `LOAD_BOOLEAN` or `LOAD_CONSTANT` /// - `LOAD_BOOLEAN` or `LOAD_CONSTANT` -pub fn optimize_test_with_loader_arguments(compiler: &mut Compiler) { +/// +/// This optimization was taken from `A No-Frills Introduction to Lua 5.1 VM Instructions` by +/// Kein-Hong Man. +pub fn control_flow_register_consolidation(compiler: &mut Compiler) { if !matches!( compiler.get_last_operations(), Some([ - Operation::TEST, + Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL | Operation::TEST, Operation::JUMP, Operation::LOAD_BOOLEAN | Operation::LOAD_CONSTANT, Operation::LOAD_BOOLEAN | Operation::LOAD_CONSTANT, @@ -81,12 +46,17 @@ pub fn optimize_test_with_loader_arguments(compiler: &mut Compiler) { log::debug!("Consolidating registers for control flow optimization"); - let first_loader = &mut compiler.instructions.iter_mut().nth_back(1).unwrap().0; - - first_loader.set_c_field(true as u8); - + let first_loader_index = compiler.instructions.len() - 2; + let (first_loader, _, _) = &mut compiler.instructions.get_mut(first_loader_index).unwrap(); let first_loader_destination = first_loader.a_field(); - let second_loader = &mut compiler.instructions.last_mut().unwrap().0; + *first_loader = + Instruction::load_boolean(first_loader.a_field(), first_loader.b_field() != 0, true); - second_loader.set_a_field(first_loader_destination); + let second_loader_index = compiler.instructions.len() - 1; + let (second_loader, _, _) = &mut compiler.instructions.get_mut(second_loader_index).unwrap(); + *second_loader = Instruction::load_boolean( + first_loader_destination, + second_loader.b_field() != 0, + false, + ); } diff --git a/dust-lang/src/dust_error.rs b/dust-lang/src/dust_error.rs index 06585c7..300a296 100644 --- a/dust-lang/src/dust_error.rs +++ b/dust-lang/src/dust_error.rs @@ -26,7 +26,20 @@ impl<'src> DustError<'src> { } pub fn report(&self) -> String { - let (title, description, detail_snippets, help_snippets) = self.error_data(); + let (title, description, detail_snippets, help_snippets) = match self { + Self::Compile { error, .. } => ( + CompileError::title(), + error.description(), + error.detail_snippets(), + error.help_snippets(), + ), + Self::NativeFunction { error, .. } => ( + NativeFunctionError::title(), + error.description(), + error.detail_snippets(), + error.help_snippets(), + ), + }; let label = format!("{}: {}", title, description); let message = Level::Error .title(&label) @@ -46,30 +59,6 @@ impl<'src> DustError<'src> { report } - fn error_data( - &self, - ) -> ( - &str, - &str, - SmallVec<[(String, Span); 2]>, - SmallVec<[(String, Span); 2]>, - ) { - match self { - Self::Compile { error, .. } => ( - CompileError::title(), - error.description(), - error.detail_snippets(), - error.help_snippets(), - ), - Self::NativeFunction { error, .. } => ( - NativeFunctionError::title(), - error.description(), - error.detail_snippets(), - error.help_snippets(), - ), - } - } - fn source(&self) -> &str { match self { Self::Compile { source, .. } => source, diff --git a/dust-lang/src/instruction/load_function.rs b/dust-lang/src/instruction/load_function.rs index caef3de..fd37fce 100644 --- a/dust-lang/src/instruction/load_function.rs +++ b/dust-lang/src/instruction/load_function.rs @@ -44,6 +44,6 @@ impl From for Instruction { impl Display for LoadFunction { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "R{} = P{}", self.destination, self.record_index) + write!(f, "R{} = F{}", self.destination, self.record_index) } } diff --git a/dust-lang/src/instruction/mod.rs b/dust-lang/src/instruction/mod.rs index 44363d7..1aaf98b 100644 --- a/dust-lang/src/instruction/mod.rs +++ b/dust-lang/src/instruction/mod.rs @@ -105,11 +105,11 @@ mod load_function; mod load_list; mod load_self; mod modulo; -mod r#move; mod multiply; mod negate; mod not; mod operation; +mod point; mod r#return; mod set_local; mod subtract; @@ -136,7 +136,7 @@ pub use multiply::Multiply; pub use negate::Negate; pub use not::Not; pub use operation::Operation; -pub use r#move::Point; +pub use point::Point; pub use r#return::Return; pub use set_local::SetLocal; pub use subtract::Subtract; @@ -475,7 +475,8 @@ impl Instruction { pub fn yields_value(&self) -> bool { match self.operation() { - Operation::LOAD_BOOLEAN + Operation::POINT + | Operation::LOAD_BOOLEAN | Operation::LOAD_CONSTANT | Operation::LOAD_FUNCTION | Operation::LOAD_LIST @@ -488,18 +489,17 @@ impl Instruction { | Operation::MODULO | Operation::NEGATE | Operation::NOT - | Operation::EQUAL - | Operation::LESS - | Operation::LESS_EQUAL | Operation::CALL => true, Operation::CALL_NATIVE => { let function = NativeFunction::from(self.b_field()); function.returns_value() } - Operation::POINT - | Operation::CLOSE + Operation::CLOSE | Operation::SET_LOCAL + | Operation::EQUAL + | Operation::LESS + | Operation::LESS_EQUAL | Operation::TEST | Operation::TEST_SET | Operation::JUMP @@ -688,17 +688,17 @@ impl Instruction { Operation::CALL => { let Call { destination, - function_register: record_index, + function_register, argument_count, } = Call::from(self); let arguments_start = destination.saturating_sub(argument_count); match argument_count { - 0 => format!("R{destination} = P{record_index}()"), - 1 => format!("R{destination} = P{record_index}(R{arguments_start})"), + 0 => format!("R{destination} = R{function_register}()"), + 1 => format!("R{destination} = R{function_register}(R{arguments_start})"), _ => { format!( - "R{destination} = P{record_index}(R{arguments_start}..R{destination})" + "R{destination} = R{function_register}(R{arguments_start}..R{destination})" ) } } diff --git a/dust-lang/src/instruction/operation.rs b/dust-lang/src/instruction/operation.rs index de4c541..0ab3851 100644 --- a/dust-lang/src/instruction/operation.rs +++ b/dust-lang/src/instruction/operation.rs @@ -39,7 +39,7 @@ impl Operation { impl Operation { pub fn name(&self) -> &'static str { match *self { - Self::POINT => "MOVE", + Self::POINT => "POINT", Self::CLOSE => "CLOSE", Self::LOAD_BOOLEAN => "LOAD_BOOLEAN", Self::LOAD_CONSTANT => "LOAD_CONSTANT", diff --git a/dust-lang/src/instruction/move.rs b/dust-lang/src/instruction/point.rs similarity index 100% rename from dust-lang/src/instruction/move.rs rename to dust-lang/src/instruction/point.rs diff --git a/dust-lang/src/native_function/assertion.rs b/dust-lang/src/native_function/assertion.rs index 1acdf29..2edc9be 100644 --- a/dust-lang/src/native_function/assertion.rs +++ b/dust-lang/src/native_function/assertion.rs @@ -17,7 +17,7 @@ pub fn panic( let value = record.open_register(register_index); if let Some(string) = value.as_string() { - message.push_str(&string); + message.push_str(string); message.push('\n'); } } diff --git a/dust-lang/src/type.rs b/dust-lang/src/type.rs index 0b75e53..d9a9150 100644 --- a/dust-lang/src/type.rs +++ b/dust-lang/src/type.rs @@ -314,12 +314,12 @@ impl Display for FunctionType { write!(f, "(")?; if let Some(value_parameters) = &self.value_parameters { - for (index, (identifier, r#type)) in value_parameters.iter().enumerate() { + for (index, (_, r#type)) in value_parameters.iter().enumerate() { if index > 0 { write!(f, ", ")?; } - write!(f, "{identifier}: {type}")?; + write!(f, "{type}")?; } } diff --git a/dust-lang/src/value/concrete_value.rs b/dust-lang/src/value/concrete_value.rs index 2abdf63..2d659bc 100644 --- a/dust-lang/src/value/concrete_value.rs +++ b/dust-lang/src/value/concrete_value.rs @@ -91,8 +91,16 @@ impl ConcreteValue { Integer(sum) } - (String(left), Character(_)) => todo!(), - (String(left), String(right)) => todo!(), + (String(left), Character(right)) => { + let concatenated = format!("{}{}", left, right); + + String(DustString::from(concatenated)) + } + (String(left), String(right)) => { + let concatenated = format!("{}{}", left, right); + + String(DustString::from(concatenated)) + } _ => panic!( "{}", ValueError::CannotAdd( diff --git a/dust-lang/src/value/function.rs b/dust-lang/src/value/function.rs index 3a2e96b..df376b0 100644 --- a/dust-lang/src/value/function.rs +++ b/dust-lang/src/value/function.rs @@ -8,8 +8,7 @@ use super::DustString; pub struct Function { pub name: Option, pub r#type: FunctionType, - pub record_index: usize, - pub prototype_index: usize, + pub record_index: u8, } impl Display for Function { diff --git a/dust-lang/src/value/mod.rs b/dust-lang/src/value/mod.rs index 8a41c4e..6ce0bb1 100644 --- a/dust-lang/src/value/mod.rs +++ b/dust-lang/src/value/mod.rs @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize}; use std::fmt::{self, Debug, Display, Formatter}; -use crate::{vm::Record, Type, Vm}; +use crate::{vm::Record, Type}; #[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)] pub enum Value { @@ -23,9 +23,6 @@ pub enum Value { #[serde(skip)] Function(Function), - - #[serde(skip)] - SelfFunction, } impl Value { @@ -60,7 +57,6 @@ impl Value { Type::List(Box::new(item_type.clone())) } Value::Function(Function { r#type, .. }) => Type::Function(Box::new(r#type.clone())), - Value::SelfFunction => Type::SelfFunction, } } @@ -163,7 +159,6 @@ impl Value { Value::AbstractList(list) => list.display(record), Value::Concrete(concrete_value) => concrete_value.display(), Value::Function(function) => DustString::from(function.to_string()), - Value::SelfFunction => DustString::from("self"), } } } @@ -174,7 +169,6 @@ impl Display for Value { Value::Concrete(concrete_value) => write!(f, "{concrete_value}"), Value::AbstractList(list) => write!(f, "{list}"), Value::Function(function) => write!(f, "{function}"), - Value::SelfFunction => write!(f, "self"), } } } diff --git a/dust-lang/src/vm/call_stack.rs b/dust-lang/src/vm/call_stack.rs index 0e1b927..e727124 100644 --- a/dust-lang/src/vm/call_stack.rs +++ b/dust-lang/src/vm/call_stack.rs @@ -1,10 +1,12 @@ use std::fmt::{self, Debug, Display, Formatter}; -use super::{FunctionCall, VmError}; +use crate::DustString; + +use super::VmError; #[derive(Clone, PartialEq)] pub struct CallStack { - pub calls: Vec, + calls: Vec, } impl CallStack { @@ -47,6 +49,18 @@ impl CallStack { 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 { @@ -60,9 +74,37 @@ impl Display for CallStack { writeln!(f, "-- DUST CALL STACK --")?; for function_call in &self.calls { - writeln!(f, "{function_call:?}")?; + writeln!(f, "{function_call}")?; } Ok(()) } } + +#[derive(Clone, Debug, PartialEq)] +pub struct FunctionCall { + pub name: Option, + pub record_index: u8, + pub return_register: u8, + pub ip: usize, +} + +impl Display for FunctionCall { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let FunctionCall { + name, + record_index, + return_register, + .. + } = self; + let name = name + .as_ref() + .map(|name| name.as_str()) + .unwrap_or("anonymous"); + + write!( + f, + "{name} (Record: {record_index}, Return register: {return_register})" + ) + } +} diff --git a/dust-lang/src/vm/error.rs b/dust-lang/src/vm/error.rs index 7b6e378..981d950 100644 --- a/dust-lang/src/vm/error.rs +++ b/dust-lang/src/vm/error.rs @@ -1,6 +1,6 @@ use std::fmt::{self, Display, Formatter}; -use crate::{DustString, InstructionData, Value}; +use crate::{InstructionData, Value}; use super::call_stack::CallStack; diff --git a/dust-lang/src/vm/mod.rs b/dust-lang/src/vm/mod.rs index d3b82fb..62c8a49 100644 --- a/dust-lang/src/vm/mod.rs +++ b/dust-lang/src/vm/mod.rs @@ -2,7 +2,7 @@ mod call_stack; mod error; mod record; -mod runner; +mod run_action; mod thread; use std::{ @@ -11,9 +11,10 @@ use std::{ thread::spawn, }; -pub use call_stack::CallStack; +pub use call_stack::{CallStack, FunctionCall}; pub use error::VmError; pub use record::Record; +pub use run_action::RunAction; pub use thread::{Thread, ThreadSignal}; use crate::{compile, Chunk, DustError, Value}; @@ -92,10 +93,3 @@ impl Display for Pointer { } } } - -#[derive(Clone, Debug, PartialEq)] -pub struct FunctionCall { - record_index: usize, - return_register: u8, - argument_count: u8, -} diff --git a/dust-lang/src/vm/record.rs b/dust-lang/src/vm/record.rs index 69a0626..ce8f08c 100644 --- a/dust-lang/src/vm/record.rs +++ b/dust-lang/src/vm/record.rs @@ -2,41 +2,63 @@ use std::mem::replace; use smallvec::SmallVec; -use crate::{Local, Span, Value}; +use crate::{DustString, Function, FunctionType, Local, Span, Value}; -use super::{runner::RunAction, Pointer, Register}; +use super::{run_action::RunAction, Pointer, Register}; pub struct Record { pub ip: usize, pub actions: SmallVec<[RunAction; 32]>, - positions: SmallVec<[Span; 32]>, stack: Vec, + last_assigned_register: Option, + + name: Option, + r#type: FunctionType, + + positions: SmallVec<[Span; 32]>, constants: SmallVec<[Value; 16]>, locals: SmallVec<[Local; 8]>, - last_assigned_register: Option, + stack_size: usize, + index: u8, } impl Record { pub fn new( - stack: Vec, + actions: SmallVec<[RunAction; 32]>, + last_assigned_register: Option, + name: Option, + r#type: FunctionType, + positions: SmallVec<[Span; 32]>, constants: SmallVec<[Value; 16]>, locals: SmallVec<[Local; 8]>, - actions: SmallVec<[RunAction; 32]>, - positions: SmallVec<[Span; 32]>, + stack_size: usize, + index: u8, ) -> Self { Self { ip: 0, actions, + stack: vec![Register::Empty; stack_size], + last_assigned_register, + name, + r#type, positions, - stack, constants, locals, - last_assigned_register: None, + stack_size, + index, } } + pub fn name(&self) -> Option<&DustString> { + self.name.as_ref() + } + + pub fn index(&self) -> u8 { + self.index + } + pub fn stack_size(&self) -> usize { self.stack.len() } @@ -49,6 +71,14 @@ impl Record { self.last_assigned_register } + pub fn as_function(&self) -> Function { + Function { + name: self.name.clone(), + r#type: self.r#type.clone(), + record_index: self.index, + } + } + pub(crate) fn follow_pointer(&self, pointer: Pointer) -> &Value { log::trace!("Follow pointer {pointer}"); diff --git a/dust-lang/src/vm/runner.rs b/dust-lang/src/vm/run_action.rs similarity index 96% rename from dust-lang/src/vm/runner.rs rename to dust-lang/src/vm/run_action.rs index 43bcb3d..b2c050b 100644 --- a/dust-lang/src/vm/runner.rs +++ b/dust-lang/src/vm/run_action.rs @@ -1,15 +1,12 @@ -use smallvec::SmallVec; - use crate::{ instruction::{ Call, CallNative, Close, LoadBoolean, LoadConstant, LoadFunction, LoadList, LoadSelf, Point, }, vm::VmError, - AbstractList, ConcreteValue, Function, Instruction, InstructionData, NativeFunction, Type, - Value, + AbstractList, ConcreteValue, Instruction, InstructionData, Type, Value, }; -use super::{thread::ThreadSignal, FunctionCall, Pointer, Record, Register}; +use super::{thread::ThreadSignal, Pointer, Record, Register}; #[derive(Clone, Copy, Debug, PartialEq)] pub struct RunAction { @@ -122,6 +119,8 @@ pub fn load_constant(instruction_data: InstructionData, record: &mut Record) -> } = instruction_data.into(); let register = Register::Pointer(Pointer::Constant(constant_index)); + log::trace!("Load constant {constant_index} into R{destination}"); + record.set_register(destination, register); if jump_next { @@ -170,18 +169,22 @@ pub fn load_list(instruction_data: InstructionData, record: &mut Record) -> Thre ThreadSignal::Continue } -pub fn load_function(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal { +pub fn load_function(instruction_data: InstructionData, _: &mut Record) -> ThreadSignal { let LoadFunction { destination, record_index, } = instruction_data.into(); - ThreadSignal::Continue + ThreadSignal::LoadFunction { + from_record_index: record_index, + to_register_index: destination, + } } pub fn load_self(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal { let LoadSelf { destination } = instruction_data.into(); - let register = Register::Value(Value::SelfFunction); + let function = record.as_function(); + let register = Register::Value(Value::Function(function)); record.set_register(destination, register); @@ -510,9 +513,9 @@ pub fn jump(instruction_data: InstructionData, record: &mut Record) -> ThreadSig let is_positive = c != 0; if is_positive { - record.ip += offset + 1 + record.ip += offset; } else { - record.ip -= offset + record.ip -= offset + 1; } ThreadSignal::Continue @@ -535,11 +538,11 @@ pub fn call(instruction_data: InstructionData, record: &mut Record) -> ThreadSig ), }; - ThreadSignal::Call(FunctionCall { + ThreadSignal::Call { record_index: function.record_index, return_register: destination, argument_count, - }) + } } pub fn call_native(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal { @@ -551,12 +554,9 @@ pub fn call_native(instruction_data: InstructionData, record: &mut Record) -> Th let first_argument_index = destination - argument_count; let argument_range = first_argument_index..destination; - let function = NativeFunction::from(function); - let thread_signal = function + function .call(record, Some(destination), argument_range) - .unwrap_or_else(|error| panic!("{error:?}")); - - thread_signal + .unwrap_or_else(|error| panic!("{error:?}")) } pub fn r#return(instruction_data: InstructionData, _: &mut Record) -> ThreadSignal { diff --git a/dust-lang/src/vm/thread.rs b/dust-lang/src/vm/thread.rs index 9b685c5..710d8e3 100644 --- a/dust-lang/src/vm/thread.rs +++ b/dust-lang/src/vm/thread.rs @@ -1,27 +1,9 @@ -use std::mem::swap; +use crate::{ + vm::{FunctionCall, Register}, + Chunk, DustString, Value, +}; -use crate::{vm::Register, Chunk, Value}; - -use super::{record::Record, runner::RunAction, CallStack, FunctionCall, VmError}; - -fn create_records(chunk: Chunk, records: &mut Vec) { - let (_, _, instructions, positions, constants, locals, prototypes, stack_size) = - chunk.take_data(); - let actions = instructions.into_iter().map(RunAction::from).collect(); - let record = Record::new( - vec![Register::Empty; stack_size], - constants, - locals, - actions, - positions, - ); - - for chunk in prototypes { - create_records(chunk, records); - } - - records.push(record); -} +use super::{record::Record, CallStack, VmError}; pub struct Thread { call_stack: CallStack, @@ -33,7 +15,7 @@ impl Thread { let call_stack = CallStack::with_capacity(chunk.prototypes().len() + 1); let mut records = Vec::with_capacity(chunk.prototypes().len() + 1); - create_records(chunk, &mut records); + chunk.into_records(&mut records); Thread { call_stack, @@ -42,36 +24,104 @@ impl Thread { } pub fn run(&mut self) -> Option { - let (record, remaining_records) = self.records.split_first_mut().unwrap(); + let mut active = &mut self.records[0]; + + log::info!( + "Starting thread with {}", + active + .as_function() + .name + .unwrap_or_else(|| DustString::from("anonymous")) + ); loop { assert!( - record.ip < record.actions.len(), + active.ip < active.actions.len(), "{}", VmError::InstructionIndexOutOfBounds { call_stack: self.call_stack.clone(), - ip: record.ip, + ip: active.ip, } ); - let action = record.actions[record.ip]; - let signal = (action.logic)(action.data, record); + log::trace!( + "Run \"{}\" | Record = {} | IP = {}", + active + .name() + .cloned() + .unwrap_or_else(|| DustString::from("anonymous")), + active.index(), + active.ip + ); + + let action = active.actions[active.ip]; + let signal = (action.logic)(action.data, active); + + active.ip += 1; match signal { - ThreadSignal::Continue => { - record.ip += 1; - } - ThreadSignal::Call(function_call) => { - swap(record, &mut remaining_records[function_call.record_index]); + ThreadSignal::Continue => {} + ThreadSignal::Call { + record_index, + return_register, + argument_count, + } => { + let record_index = record_index as usize; + let first_argument_register = return_register - argument_count; + let mut arguments = Vec::with_capacity(argument_count as usize); + + for register_index in first_argument_register..return_register { + let value = active + .replace_register_or_clone_constant(register_index, Register::Empty); + + arguments.push(value); + } + + if record_index == active.index() as usize { + log::trace!("Recursion detected"); + + self.call_stack.last_mut_or_panic().ip = active.ip; + active.ip = 0; + } + + active = &mut self.records[record_index]; + + for (index, argument) in arguments.into_iter().enumerate() { + active.set_register(index as u8, Register::Value(argument)); + } + + let function_call = FunctionCall { + name: active.name().cloned(), + record_index: active.index(), + return_register, + ip: 0, + }; + self.call_stack.push(function_call); } + ThreadSignal::LoadFunction { + from_record_index, + to_register_index, + } => { + let function_record_index = from_record_index as usize; + let original_record_index = active.index() as usize; + + active = &mut self.records[function_record_index]; + + let function = active.as_function(); + let register = Register::Value(Value::Function(function)); + + active = &mut self.records[original_record_index]; + + active.set_register(to_register_index, register); + } ThreadSignal::Return(should_return_value) => { let returning_call = match self.call_stack.pop() { Some(function_call) => function_call, None => { if should_return_value { - return record.last_assigned_register().map(|register| { - record.replace_register_or_clone_constant( + return active.last_assigned_register().map(|register| { + active.replace_register_or_clone_constant( register, Register::Empty, ) @@ -82,19 +132,26 @@ impl Thread { } }; let outer_call = self.call_stack.last_or_panic(); + let record_index = outer_call.record_index as usize; + + log::trace!("Return from {returning_call} to {outer_call}"); if should_return_value { - let return_register = record + let return_register = active .last_assigned_register() .unwrap_or_else(|| panic!("Expected return value")); - let value = record + let return_value = active .replace_register_or_clone_constant(return_register, Register::Empty); - swap(record, &mut remaining_records[outer_call.record_index]); + active = &mut self.records[record_index]; - record.set_register(returning_call.return_register, Register::Value(value)); + active.set_register( + returning_call.return_register, + Register::Value(return_value), + ); } else { - swap(record, &mut remaining_records[outer_call.record_index]); + active = &mut self.records[record_index]; + active.ip = outer_call.ip; } } } @@ -104,6 +161,14 @@ impl Thread { pub enum ThreadSignal { Continue, - Call(FunctionCall), + Call { + record_index: u8, + return_register: u8, + argument_count: u8, + }, Return(bool), + LoadFunction { + from_record_index: u8, + to_register_index: u8, + }, } diff --git a/examples/fibonacci.ds b/examples/fibonacci.ds index 7261e19..a0f4e09 100644 --- a/examples/fibonacci.ds +++ b/examples/fibonacci.ds @@ -5,4 +5,4 @@ fn fib (n: int) -> int { fib(n - 1) + fib(n - 2) } -write_line(fib(25)) +write_line(fib(1))