1
0

Continue new VM implementation; Write docs

This commit is contained in:
Jeff 2024-12-17 16:31:32 -05:00
parent 4527f7b6ef
commit 72365cd399
25 changed files with 454 additions and 354 deletions

12
Cargo.lock generated
View File

@ -102,15 +102,6 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.16.0" version = "3.16.0"
@ -141,7 +132,7 @@ version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags",
"textwrap", "textwrap",
"unicode-width", "unicode-width",
] ]
@ -314,7 +305,6 @@ name = "dust-lang"
version = "0.5.0" version = "0.5.0"
dependencies = [ dependencies = [
"annotate-snippets", "annotate-snippets",
"bitflags 2.6.0",
"colored", "colored",
"criterion", "criterion",
"getrandom", "getrandom",

View File

@ -4,9 +4,11 @@ use std::{fs::read_to_string, path::PathBuf};
use clap::{Args, Parser}; use clap::{Args, Parser};
use colored::Colorize; 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}; use log::{Level, LevelFilter};
const DEFAULT_PROGRAM_NAME: &str = "Dust CLI Input";
#[derive(Parser)] #[derive(Parser)]
#[clap( #[clap(
name = env!("CARGO_PKG_NAME"), 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 compile_end = start_time.elapsed();
let vm = Vm::new(chunk); let vm = Vm::new(chunk);

View File

@ -22,7 +22,6 @@ smallvec = { version = "1.13.2", features = ["const_generics", "serde"] }
smartstring = { version = "1.0.1", features = [ smartstring = { version = "1.0.1", features = [
"serde", "serde",
], default-features = false } ], default-features = false }
bitflags = { version = "2.6.0", features = ["serde"] }
[dev-dependencies] [dev-dependencies]
criterion = { version = "0.3.4", features = ["html_reports"] } criterion = { version = "0.3.4", features = ["html_reports"] }

View File

@ -263,7 +263,7 @@ impl<'a, W: Write> Disassembler<'a, W> {
self.write_char(border[1])?; self.write_char(border[1])?;
} }
self.write_char(border[2]); self.write_char(border[2])?;
self.write_char('\n') 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> { 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 { for chunk in &self.chunk.prototypes {
chunk chunk

View File

@ -27,7 +27,8 @@ use std::io::Write;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use smallvec::SmallVec; 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. /// Representation of a Dust program or function.
/// ///
@ -44,6 +45,7 @@ pub struct Chunk {
prototypes: Vec<Chunk>, prototypes: Vec<Chunk>,
stack_size: usize, stack_size: usize,
record_index: u8,
} }
impl Chunk { impl Chunk {
@ -56,6 +58,7 @@ impl Chunk {
locals: impl Into<SmallVec<[Local; 8]>>, locals: impl Into<SmallVec<[Local; 8]>>,
prototypes: impl Into<Vec<Chunk>>, prototypes: impl Into<Vec<Chunk>>,
stack_size: usize, stack_size: usize,
record_index: u8,
) -> Self { ) -> Self {
Self { Self {
name, name,
@ -66,6 +69,7 @@ impl Chunk {
locals: locals.into(), locals: locals.into(),
prototypes: prototypes.into(), prototypes: prototypes.into(),
stack_size, stack_size,
record_index,
} }
} }
@ -88,31 +92,47 @@ impl Chunk {
locals: locals.into(), locals: locals.into(),
prototypes, prototypes,
stack_size: 0, stack_size: 0,
record_index: 0,
} }
} }
pub fn take_data( pub fn as_function(&self) -> Function {
self, Function {
) -> ( name: self.name.clone(),
Option<DustString>, r#type: self.r#type.clone(),
FunctionType, record_index: self.record_index,
SmallVec<[Instruction; 32]>, }
SmallVec<[Span; 32]>, }
SmallVec<[Value; 16]>,
SmallVec<[Local; 8]>, pub fn into_records(self, records: &mut Vec<Record>) {
Vec<Chunk>, let actions = self.instructions().iter().map(RunAction::from).collect();
usize, let record = Record::new(
) { actions,
( None,
self.name, self.name,
self.r#type, self.r#type,
self.instructions,
self.positions, self.positions,
self.constants, self.constants,
self.locals, self.locals,
self.prototypes,
self.stack_size, 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> { pub fn name(&self) -> Option<&DustString> {

View File

@ -20,7 +20,7 @@ pub enum CompileError {
}, },
// Parsing errors // Parsing errors
CannotChainComparison { ComparisonChain {
position: Span, position: Span,
}, },
ExpectedBoolean { ExpectedBoolean {
@ -176,7 +176,7 @@ impl AnnotatedError for CompileError {
match self { match self {
Self::CannotAddArguments { .. } => "Cannot add these types", Self::CannotAddArguments { .. } => "Cannot add these types",
Self::CannotAddType { .. } => "Cannot add to this type", 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::CannotDivideArguments { .. } => "Cannot divide these types",
Self::CannotDivideType { .. } => "Cannot divide this type", Self::CannotDivideType { .. } => "Cannot divide this type",
Self::CannotModuloArguments { .. } => "Cannot modulo these types", Self::CannotModuloArguments { .. } => "Cannot modulo these types",

View File

@ -1,9 +1,12 @@
//! Compilation tools and errors //! The Dust compiler and its accessories.
//! //!
//! This module provides two compilation options: //! This module provides two compilation options:
//! - [`compile`] borrows a string and returns a chunk, handling the entire compilation process and //! - [`compile`] is a simple function that borrows a string and returns a chunk, handling
//! turning any resulting [`ComplileError`] into a [`DustError`]. //! compilation and turning any resulting error into a [`DustError`], which can easily display a
//! - [`Compiler`] uses a lexer to get tokens and assembles a chunk. //! 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 error;
mod optimize; mod optimize;
@ -15,12 +18,12 @@ use std::{
}; };
use colored::Colorize; 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 smallvec::{smallvec, SmallVec};
use crate::{ use crate::{
instruction::{ 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, Argument, Chunk, ConcreteValue, DustError, DustString, FunctionType, Instruction, Lexer, Local,
NativeFunction, Operation, Scope, Span, Token, TokenKind, Type, Value, NativeFunction, Operation, Scope, Span, Token, TokenKind, Type, Value,
@ -35,7 +38,7 @@ use crate::{
/// let source = "40 + 2 == 42"; /// let source = "40 + 2 == 42";
/// let chunk = compile(source).unwrap(); /// let chunk = compile(source).unwrap();
/// ///
/// assert_eq!(chunk.len(), 3); /// assert_eq!(chunk.instructions().len(), 3);
/// ``` /// ```
pub fn compile(source: &str) -> Result<Chunk, DustError> { pub fn compile(source: &str) -> Result<Chunk, DustError> {
let lexer = Lexer::new(source); let lexer = Lexer::new(source);
@ -45,12 +48,14 @@ pub fn compile(source: &str) -> Result<Chunk, DustError> {
.compile() .compile()
.map_err(|error| DustError::compile(error, source))?; .map_err(|error| DustError::compile(error, source))?;
let chunk = compiler.finish(); let name = DustString::from("main");
let chunk = compiler.finish(Some(name));
Ok(chunk) 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. /// See the [`compile`] function an example of how to create and use a Compiler.
#[derive(Debug)] #[derive(Debug)]
@ -58,9 +63,10 @@ pub struct Compiler<'src> {
/// Used to get tokens for the compiler. /// Used to get tokens for the compiler.
lexer: Lexer<'src>, lexer: Lexer<'src>,
/// Name of the function or program being compiled. This is assigned to the chunk when /// Name of the function being compiled. This is used to identify recursive calls, so it should
/// [`Compiler::finish`] is called. /// be `None` for the main chunk. The main chunk can still be named by passing a name to
self_name: Option<DustString>, /// [`Compiler::finish`], which will override this value.
function_name: Option<DustString>,
/// Type of the function being compiled. This is assigned to the chunk when [`Compiler::finish`] /// Type of the function being compiled. This is assigned to the chunk when [`Compiler::finish`]
/// is called. /// is called.
@ -116,6 +122,7 @@ pub struct Compiler<'src> {
} }
impl<'src> Compiler<'src> { impl<'src> Compiler<'src> {
/// Creates a new compiler with the given lexer.
pub fn new(mut lexer: Lexer<'src>) -> Result<Self, CompileError> { pub fn new(mut lexer: Lexer<'src>) -> Result<Self, CompileError> {
let (current_token, current_position) = lexer.next_token()?; let (current_token, current_position) = lexer.next_token()?;
@ -126,7 +133,7 @@ impl<'src> Compiler<'src> {
); );
Ok(Compiler { Ok(Compiler {
self_name: None, function_name: None,
r#type: FunctionType { r#type: FunctionType {
type_parameters: None, type_parameters: None,
value_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<impl Into<DustString>>) -> Chunk {
log::info!("End chunk"); log::info!("End chunk");
let (instructions, positions): (SmallVec<[Instruction; 32]>, SmallVec<[Span; 32]>) = self let (instructions, positions): (SmallVec<[Instruction; 32]>, SmallVec<[Span; 32]>) = self
@ -163,9 +175,12 @@ impl<'src> Compiler<'src> {
.into_iter() .into_iter()
.map(|(local, _)| local) .map(|(local, _)| local)
.collect::<SmallVec<[Local; 8]>>(); .collect::<SmallVec<[Local; 8]>>();
let chunk_name = name
.map(|into_name| into_name.into())
.or(self.function_name);
Chunk::new( Chunk::new(
self.self_name, chunk_name,
self.r#type, self.r#type,
instructions, instructions,
positions, positions,
@ -173,9 +188,13 @@ impl<'src> Compiler<'src> {
locals, locals,
self.prototypes, self.prototypes,
self.stack_size, 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> { pub fn compile(&mut self) -> Result<(), CompileError> {
loop { loop {
self.parse(Precedence::None)?; self.parse(Precedence::None)?;
@ -852,7 +871,7 @@ impl<'src> Compiler<'src> {
if let Some([Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL, _, _]) = if let Some([Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL, _, _]) =
self.get_last_operations() self.get_last_operations()
{ {
return Err(CompileError::CannotChainComparison { return Err(CompileError::ComparisonChain {
position: self.current_position, position: self.current_position,
}); });
} }
@ -915,13 +934,13 @@ impl<'src> Compiler<'src> {
} }
}; };
let jump = Instruction::jump(1, true); let jump = Instruction::jump(1, true);
let load_false = Instruction::load_boolean(destination, false, true); let load_true = Instruction::load_boolean(destination, true, true);
let load_true = Instruction::load_boolean(destination, true, false); let load_false = Instruction::load_boolean(destination, false, false);
self.emit_instruction(comparison, Type::Boolean, operator_position); self.emit_instruction(comparison, Type::Boolean, operator_position);
self.emit_instruction(jump, Type::None, 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_true, Type::Boolean, operator_position);
self.emit_instruction(load_false, Type::Boolean, operator_position);
Ok(()) Ok(())
} }
@ -932,29 +951,19 @@ impl<'src> Compiler<'src> {
Some([Operation::TEST, Operation::JUMP, _]) 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 { // if is_logic_chain {
let destination = self // let destination = self
.instructions // .instructions
.iter() // .iter()
.rev() // .rev()
.nth(2) // .nth(2)
.map_or(0, |(instruction, _, _)| instruction.a_field()); // .map_or(0, |(instruction, _, _)| instruction.a_field());
left_instruction.set_a_field(destination); // 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
};
if !left_instruction.yields_value() { if !left_instruction.yields_value() {
return Err(CompileError::ExpectedExpression { 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 // let short_circuit_jump_index = self.instructions.len();
.push((left_instruction, left_type.clone(), left_position)); // 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 = self.current_token;
let operator_position = self.current_position; let operator_position = self.current_position;
@ -986,20 +1004,28 @@ impl<'src> Compiler<'src> {
self.advance()?; self.advance()?;
self.emit_instruction(test, Type::None, operator_position); 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)?; 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(); let (mut right_instruction, _, _) = self.instructions.last_mut().unwrap();
right_instruction.set_a_field(left_instruction.a_field()); right_instruction.set_a_field(left_instruction.a_field());
if is_logic_chain { // short_circuit_jump_distance += (self.instructions.len() - short_circuit_jump_index) as u8;
let expression_length = self.instructions.len() - jump_index - 1; // let jump = Instruction::jump(short_circuit_jump_distance, true);
jump_distance += expression_length as u8;
let jump = Instruction::jump(jump_distance, true);
self.instructions // self.instructions.insert(
.insert(jump_index, (jump, Type::None, operator_position)); // short_circuit_jump_index,
} // (jump, Type::None, operator_position),
// );
Ok(()) Ok(())
} }
@ -1021,7 +1047,7 @@ impl<'src> Compiler<'src> {
local_index local_index
} else if let Some(native_function) = NativeFunction::from_str(identifier) { } else if let Some(native_function) = NativeFunction::from_str(identifier) {
return self.parse_call_native(native_function); 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 destination = self.next_register();
let load_function = Instruction::load_function(destination, self.record_index); let load_function = Instruction::load_function(destination, self.record_index);
@ -1175,35 +1201,22 @@ impl<'src> Compiler<'src> {
self.advance()?; self.advance()?;
self.parse_expression()?; self.parse_expression()?;
if matches!( let (last_instruction, _, _) = self.instructions.last().unwrap();
self.get_last_operations(), let argument = match last_instruction.as_argument() {
Some([ Some(argument) => argument,
Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL, None => {
Operation::JUMP, return Err(CompileError::ExpectedExpression {
Operation::LOAD_BOOLEAN, found: self.previous_token.to_owned(),
Operation::LOAD_BOOLEAN position: self.previous_position,
]), });
) { }
self.instructions.pop(); };
self.instructions.pop(); let test = Instruction::from(Test {
self.instructions.pop(); argument,
} else if let Some((instruction, _, _)) = self.instructions.last() { test_value: true,
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,
});
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 = self.instructions.len();
let if_block_start_position = self.current_position; let if_block_start_position = self.current_position;
@ -1211,8 +1224,8 @@ impl<'src> Compiler<'src> {
if let Token::LeftBrace = self.current_token { if let Token::LeftBrace = self.current_token {
self.parse_block()?; self.parse_block()?;
} else { } else {
return Err(CompileError::ExpectedToken { return Err(CompileError::ExpectedTokenMultiple {
expected: TokenKind::LeftBrace, expected: &[TokenKind::If, TokenKind::LeftBrace],
found: self.current_token.to_owned(), found: self.current_token.to_owned(),
position: self.current_position, position: self.current_position,
}); });
@ -1235,15 +1248,11 @@ impl<'src> Compiler<'src> {
position: self.current_position, position: self.current_position,
}); });
} }
true
} else if if_block_type != Type::None { } else if if_block_type != Type::None {
return Err(CompileError::IfMissingElse { return Err(CompileError::IfMissingElse {
position: Span(if_block_start_position.0, self.current_position.1), position: Span(if_block_start_position.0, self.current_position.1),
}); });
} else { }
false
};
let else_block_end = self.instructions.len(); let else_block_end = self.instructions.len();
let else_block_distance = (else_block_end - if_block_end) as u8; 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 { let jump = Instruction::jump(if_block_distance, true);
offset: if_block_distance,
is_positive: true,
});
self.instructions self.instructions
.insert(if_block_start, (jump, Type::None, if_block_start_position)); .insert(if_block_start, (jump, Type::None, if_block_start_position));
optimize_test_with_explicit_booleans(self); control_flow_register_consolidation(self);
optimize_test_with_loader_arguments(self);
let else_last_register = self.next_register().saturating_sub(1); let else_last_register = self.next_register().saturating_sub(1);
let r#move = Instruction::from(Point { let point = Instruction::point(else_last_register, if_last_register);
from: else_last_register,
to: if_last_register,
});
if if_last_register < else_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(()) Ok(())
@ -1490,9 +1492,7 @@ impl<'src> Compiler<'src> {
fn parse_implicit_return(&mut self) -> Result<(), CompileError> { fn parse_implicit_return(&mut self) -> Result<(), CompileError> {
if self.allow(Token::Semicolon)? { if self.allow(Token::Semicolon)? {
let r#return = Instruction::from(Return { let r#return = Instruction::r#return(false);
should_return_value: false,
});
self.emit_instruction(r#return, Type::None, self.current_position); self.emit_instruction(r#return, Type::None, self.current_position);
} else { } else {
@ -1507,9 +1507,7 @@ impl<'src> Compiler<'src> {
} }
}); });
let should_return_value = previous_expression_type != Type::None; let should_return_value = previous_expression_type != Type::None;
let r#return = Instruction::from(Return { let r#return = Instruction::r#return(should_return_value);
should_return_value,
});
self.update_return_type(previous_expression_type.clone())?; self.update_return_type(previous_expression_type.clone())?;
self.emit_instruction(r#return, Type::None, self.current_position); self.emit_instruction(r#return, Type::None, self.current_position);
@ -1577,7 +1575,7 @@ impl<'src> Compiler<'src> {
function_compiler.advance()?; function_compiler.advance()?;
function_compiler.self_name = Some(text.into()); function_compiler.function_name = Some(text.into());
Some((text, position)) Some((text, position))
} else { } else {
@ -1664,7 +1662,8 @@ impl<'src> Compiler<'src> {
self.lexer.skip_to(self.current_position.1); self.lexer.skip_to(self.current_position.1);
let function_end = function_compiler.previous_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(); let destination = self.next_register();
self.prototypes.push(chunk); 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( self.emit_instruction(
load_function, load_function,
@ -1703,7 +1702,10 @@ impl<'src> Compiler<'src> {
position: self.previous_position, position: self.previous_position,
})?; })?;
if last_instruction.operation() != Operation::LOAD_FUNCTION { if !matches!(
last_instruction_type,
Type::Function(_) | Type::SelfFunction
) {
return Err(CompileError::ExpectedFunction { return Err(CompileError::ExpectedFunction {
found: self.previous_token.to_owned(), found: self.previous_token.to_owned(),
actual_type: last_instruction_type.clone(), actual_type: last_instruction_type.clone(),
@ -1970,29 +1972,25 @@ impl<'src> Compiler<'src> {
/// Operator precedence levels. /// Operator precedence levels.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Precedence { pub enum Precedence {
None, Primary = 9,
Assignment, Call = 8,
Conditional, Unary = 7,
LogicalOr, Factor = 6,
LogicalAnd, Term = 5,
Equality, Comparison = 4,
Comparison, LogicalAnd = 3,
Term, LogicalOr = 2,
Factor, Assignment = 1,
Unary, None = 0,
Call,
Primary,
} }
impl Precedence { impl Precedence {
fn increment(&self) -> Self { fn increment(&self) -> Self {
match self { match self {
Precedence::None => Precedence::Assignment, Precedence::None => Precedence::Assignment,
Precedence::Assignment => Precedence::Conditional, Precedence::Assignment => Precedence::LogicalOr,
Precedence::Conditional => Precedence::LogicalOr,
Precedence::LogicalOr => Precedence::LogicalAnd, Precedence::LogicalOr => Precedence::LogicalAnd,
Precedence::LogicalAnd => Precedence::Equality, Precedence::LogicalAnd => Precedence::Comparison,
Precedence::Equality => Precedence::Comparison,
Precedence::Comparison => Precedence::Term, Precedence::Comparison => Precedence::Term,
Precedence::Term => Precedence::Factor, Precedence::Term => Precedence::Factor,
Precedence::Factor => Precedence::Unary, Precedence::Factor => Precedence::Unary,
@ -2036,7 +2034,7 @@ impl From<&Token<'_>> for ParseRule<'_> {
Token::BangEqual => ParseRule { Token::BangEqual => ParseRule {
prefix: None, prefix: None,
infix: Some(Compiler::parse_comparison_binary), infix: Some(Compiler::parse_comparison_binary),
precedence: Precedence::Equality, precedence: Precedence::Comparison,
}, },
Token::Bool => ParseRule { Token::Bool => ParseRule {
prefix: Some(Compiler::expect_expression), prefix: Some(Compiler::expect_expression),
@ -2082,7 +2080,7 @@ impl From<&Token<'_>> for ParseRule<'_> {
Token::DoubleEqual => ParseRule { Token::DoubleEqual => ParseRule {
prefix: None, prefix: None,
infix: Some(Compiler::parse_comparison_binary), infix: Some(Compiler::parse_comparison_binary),
precedence: Precedence::Equality, precedence: Precedence::Comparison,
}, },
Token::DoublePipe => ParseRule { Token::DoublePipe => ParseRule {
prefix: None, prefix: None,

View File

@ -1,15 +1,11 @@
//! Functions used by the compiler to optimize a chunk's bytecode during compilation. //! 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 /// This makes the following examples compile to the same bytecode:
/// 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:
/// ///
/// ```dust /// ```dust
/// 4 == 4 /// 4 == 4
@ -19,58 +15,27 @@ use crate::{Compiler, Operation};
/// if 4 == 4 { true } else { false } /// if 4 == 4 { true } else { false }
/// ``` /// ```
/// ///
/// The instructions must be in the following order: /// When they occur in the sequence shown below, instructions can be optimized by taking advantage
/// - `EQUAL`, `LESS` or `LESS_EQUAL` /// of the loaders' ability to skip an instruction after loading a value. If these instructions are
/// - `TEST` /// the result of a binary expression, this will not change anything because they were already
/// - `JUMP` /// emitted optimally. Control flow patterns, however, can be optimized because the load
/// - `LOAD_BOOLEAN` /// instructions are from seperate expressions that each uses its own register. Since only one of
/// - `LOAD_BOOLEAN` /// the two branches will be executed, this is wasteful. It would also require the compiler to emit
pub fn optimize_test_with_explicit_booleans(compiler: &mut Compiler) { /// a `POINT` instruction to prevent the VM from encountering an empty register.
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.
/// ///
/// The instructions must be in the following order: /// The instructions must be in the following order:
/// - `TEST` /// - `EQUAL` | `LESS` | `LESS_EQUAL` | `TEST`
/// - `JUMP` /// - `JUMP`
/// - `LOAD_BOOLEAN` or `LOAD_CONSTANT` /// - `LOAD_BOOLEAN` or `LOAD_CONSTANT`
/// - `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!( if !matches!(
compiler.get_last_operations(), compiler.get_last_operations(),
Some([ Some([
Operation::TEST, Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL | Operation::TEST,
Operation::JUMP, Operation::JUMP,
Operation::LOAD_BOOLEAN | Operation::LOAD_CONSTANT, Operation::LOAD_BOOLEAN | Operation::LOAD_CONSTANT,
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"); log::debug!("Consolidating registers for control flow optimization");
let first_loader = &mut compiler.instructions.iter_mut().nth_back(1).unwrap().0; let first_loader_index = compiler.instructions.len() - 2;
let (first_loader, _, _) = &mut compiler.instructions.get_mut(first_loader_index).unwrap();
first_loader.set_c_field(true as u8);
let first_loader_destination = first_loader.a_field(); 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,
);
} }

View File

@ -26,7 +26,20 @@ impl<'src> DustError<'src> {
} }
pub fn report(&self) -> String { 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 label = format!("{}: {}", title, description);
let message = Level::Error let message = Level::Error
.title(&label) .title(&label)
@ -46,30 +59,6 @@ impl<'src> DustError<'src> {
report 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 { fn source(&self) -> &str {
match self { match self {
Self::Compile { source, .. } => source, Self::Compile { source, .. } => source,

View File

@ -44,6 +44,6 @@ impl From<LoadFunction> for Instruction {
impl Display for LoadFunction { impl Display for LoadFunction {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { 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)
} }
} }

View File

@ -105,11 +105,11 @@ mod load_function;
mod load_list; mod load_list;
mod load_self; mod load_self;
mod modulo; mod modulo;
mod r#move;
mod multiply; mod multiply;
mod negate; mod negate;
mod not; mod not;
mod operation; mod operation;
mod point;
mod r#return; mod r#return;
mod set_local; mod set_local;
mod subtract; mod subtract;
@ -136,7 +136,7 @@ pub use multiply::Multiply;
pub use negate::Negate; pub use negate::Negate;
pub use not::Not; pub use not::Not;
pub use operation::Operation; pub use operation::Operation;
pub use r#move::Point; pub use point::Point;
pub use r#return::Return; pub use r#return::Return;
pub use set_local::SetLocal; pub use set_local::SetLocal;
pub use subtract::Subtract; pub use subtract::Subtract;
@ -475,7 +475,8 @@ impl Instruction {
pub fn yields_value(&self) -> bool { pub fn yields_value(&self) -> bool {
match self.operation() { match self.operation() {
Operation::LOAD_BOOLEAN Operation::POINT
| Operation::LOAD_BOOLEAN
| Operation::LOAD_CONSTANT | Operation::LOAD_CONSTANT
| Operation::LOAD_FUNCTION | Operation::LOAD_FUNCTION
| Operation::LOAD_LIST | Operation::LOAD_LIST
@ -488,18 +489,17 @@ impl Instruction {
| Operation::MODULO | Operation::MODULO
| Operation::NEGATE | Operation::NEGATE
| Operation::NOT | Operation::NOT
| Operation::EQUAL
| Operation::LESS
| Operation::LESS_EQUAL
| Operation::CALL => true, | Operation::CALL => true,
Operation::CALL_NATIVE => { Operation::CALL_NATIVE => {
let function = NativeFunction::from(self.b_field()); let function = NativeFunction::from(self.b_field());
function.returns_value() function.returns_value()
} }
Operation::POINT Operation::CLOSE
| Operation::CLOSE
| Operation::SET_LOCAL | Operation::SET_LOCAL
| Operation::EQUAL
| Operation::LESS
| Operation::LESS_EQUAL
| Operation::TEST | Operation::TEST
| Operation::TEST_SET | Operation::TEST_SET
| Operation::JUMP | Operation::JUMP
@ -688,17 +688,17 @@ impl Instruction {
Operation::CALL => { Operation::CALL => {
let Call { let Call {
destination, destination,
function_register: record_index, function_register,
argument_count, argument_count,
} = Call::from(self); } = Call::from(self);
let arguments_start = destination.saturating_sub(argument_count); let arguments_start = destination.saturating_sub(argument_count);
match argument_count { match argument_count {
0 => format!("R{destination} = P{record_index}()"), 0 => format!("R{destination} = R{function_register}()"),
1 => format!("R{destination} = P{record_index}(R{arguments_start})"), 1 => format!("R{destination} = R{function_register}(R{arguments_start})"),
_ => { _ => {
format!( format!(
"R{destination} = P{record_index}(R{arguments_start}..R{destination})" "R{destination} = R{function_register}(R{arguments_start}..R{destination})"
) )
} }
} }

View File

@ -39,7 +39,7 @@ impl Operation {
impl Operation { impl Operation {
pub fn name(&self) -> &'static str { pub fn name(&self) -> &'static str {
match *self { match *self {
Self::POINT => "MOVE", Self::POINT => "POINT",
Self::CLOSE => "CLOSE", Self::CLOSE => "CLOSE",
Self::LOAD_BOOLEAN => "LOAD_BOOLEAN", Self::LOAD_BOOLEAN => "LOAD_BOOLEAN",
Self::LOAD_CONSTANT => "LOAD_CONSTANT", Self::LOAD_CONSTANT => "LOAD_CONSTANT",

View File

@ -17,7 +17,7 @@ pub fn panic(
let value = record.open_register(register_index); let value = record.open_register(register_index);
if let Some(string) = value.as_string() { if let Some(string) = value.as_string() {
message.push_str(&string); message.push_str(string);
message.push('\n'); message.push('\n');
} }
} }

View File

@ -314,12 +314,12 @@ impl Display for FunctionType {
write!(f, "(")?; write!(f, "(")?;
if let Some(value_parameters) = &self.value_parameters { 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 { if index > 0 {
write!(f, ", ")?; write!(f, ", ")?;
} }
write!(f, "{identifier}: {type}")?; write!(f, "{type}")?;
} }
} }

View File

@ -91,8 +91,16 @@ impl ConcreteValue {
Integer(sum) Integer(sum)
} }
(String(left), Character(_)) => todo!(), (String(left), Character(right)) => {
(String(left), String(right)) => todo!(), let concatenated = format!("{}{}", left, right);
String(DustString::from(concatenated))
}
(String(left), String(right)) => {
let concatenated = format!("{}{}", left, right);
String(DustString::from(concatenated))
}
_ => panic!( _ => panic!(
"{}", "{}",
ValueError::CannotAdd( ValueError::CannotAdd(

View File

@ -8,8 +8,7 @@ use super::DustString;
pub struct Function { pub struct Function {
pub name: Option<DustString>, pub name: Option<DustString>,
pub r#type: FunctionType, pub r#type: FunctionType,
pub record_index: usize, pub record_index: u8,
pub prototype_index: usize,
} }
impl Display for Function { impl Display for Function {

View File

@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize};
use std::fmt::{self, Debug, Display, Formatter}; 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)] #[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
pub enum Value { pub enum Value {
@ -23,9 +23,6 @@ pub enum Value {
#[serde(skip)] #[serde(skip)]
Function(Function), Function(Function),
#[serde(skip)]
SelfFunction,
} }
impl Value { impl Value {
@ -60,7 +57,6 @@ impl Value {
Type::List(Box::new(item_type.clone())) Type::List(Box::new(item_type.clone()))
} }
Value::Function(Function { r#type, .. }) => Type::Function(Box::new(r#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::AbstractList(list) => list.display(record),
Value::Concrete(concrete_value) => concrete_value.display(), Value::Concrete(concrete_value) => concrete_value.display(),
Value::Function(function) => DustString::from(function.to_string()), 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::Concrete(concrete_value) => write!(f, "{concrete_value}"),
Value::AbstractList(list) => write!(f, "{list}"), Value::AbstractList(list) => write!(f, "{list}"),
Value::Function(function) => write!(f, "{function}"), Value::Function(function) => write!(f, "{function}"),
Value::SelfFunction => write!(f, "self"),
} }
} }
} }

View File

@ -1,10 +1,12 @@
use std::fmt::{self, Debug, Display, Formatter}; use std::fmt::{self, Debug, Display, Formatter};
use super::{FunctionCall, VmError}; use crate::DustString;
use super::VmError;
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub struct CallStack { pub struct CallStack {
pub calls: Vec<FunctionCall>, calls: Vec<FunctionCall>,
} }
impl CallStack { impl CallStack {
@ -47,6 +49,18 @@ impl CallStack {
self.calls.last().unwrap() 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 { impl Debug for CallStack {
@ -60,9 +74,37 @@ impl Display for CallStack {
writeln!(f, "-- DUST CALL STACK --")?; writeln!(f, "-- DUST CALL STACK --")?;
for function_call in &self.calls { for function_call in &self.calls {
writeln!(f, "{function_call:?}")?; writeln!(f, "{function_call}")?;
} }
Ok(()) Ok(())
} }
} }
#[derive(Clone, Debug, PartialEq)]
pub struct FunctionCall {
pub name: Option<DustString>,
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})"
)
}
}

View File

@ -1,6 +1,6 @@
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use crate::{DustString, InstructionData, Value}; use crate::{InstructionData, Value};
use super::call_stack::CallStack; use super::call_stack::CallStack;

View File

@ -2,7 +2,7 @@
mod call_stack; mod call_stack;
mod error; mod error;
mod record; mod record;
mod runner; mod run_action;
mod thread; mod thread;
use std::{ use std::{
@ -11,9 +11,10 @@ use std::{
thread::spawn, thread::spawn,
}; };
pub use call_stack::CallStack; 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 thread::{Thread, ThreadSignal}; pub use thread::{Thread, ThreadSignal};
use crate::{compile, Chunk, DustError, Value}; 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,
}

View File

@ -2,41 +2,63 @@ use std::mem::replace;
use smallvec::SmallVec; 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 struct Record {
pub ip: usize, pub ip: usize,
pub actions: SmallVec<[RunAction; 32]>, pub actions: SmallVec<[RunAction; 32]>,
positions: SmallVec<[Span; 32]>,
stack: Vec<Register>, stack: Vec<Register>,
last_assigned_register: Option<u8>,
name: Option<DustString>,
r#type: FunctionType,
positions: SmallVec<[Span; 32]>,
constants: SmallVec<[Value; 16]>, constants: SmallVec<[Value; 16]>,
locals: SmallVec<[Local; 8]>, locals: SmallVec<[Local; 8]>,
last_assigned_register: Option<u8>, stack_size: usize,
index: u8,
} }
impl Record { impl Record {
pub fn new( pub fn new(
stack: Vec<Register>, actions: SmallVec<[RunAction; 32]>,
last_assigned_register: Option<u8>,
name: Option<DustString>,
r#type: FunctionType,
positions: SmallVec<[Span; 32]>,
constants: SmallVec<[Value; 16]>, constants: SmallVec<[Value; 16]>,
locals: SmallVec<[Local; 8]>, locals: SmallVec<[Local; 8]>,
actions: SmallVec<[RunAction; 32]>, stack_size: usize,
positions: SmallVec<[Span; 32]>, index: u8,
) -> Self { ) -> Self {
Self { Self {
ip: 0, ip: 0,
actions, actions,
stack: vec![Register::Empty; stack_size],
last_assigned_register,
name,
r#type,
positions, positions,
stack,
constants, constants,
locals, 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 { pub fn stack_size(&self) -> usize {
self.stack.len() self.stack.len()
} }
@ -49,6 +71,14 @@ impl Record {
self.last_assigned_register 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 { pub(crate) fn follow_pointer(&self, pointer: Pointer) -> &Value {
log::trace!("Follow pointer {pointer}"); log::trace!("Follow pointer {pointer}");

View File

@ -1,15 +1,12 @@
use smallvec::SmallVec;
use crate::{ use crate::{
instruction::{ instruction::{
Call, CallNative, Close, LoadBoolean, LoadConstant, LoadFunction, LoadList, LoadSelf, Point, Call, CallNative, Close, LoadBoolean, LoadConstant, LoadFunction, LoadList, LoadSelf, Point,
}, },
vm::VmError, vm::VmError,
AbstractList, ConcreteValue, Function, Instruction, InstructionData, NativeFunction, Type, AbstractList, ConcreteValue, Instruction, InstructionData, Type, Value,
Value,
}; };
use super::{thread::ThreadSignal, FunctionCall, Pointer, Record, Register}; use super::{thread::ThreadSignal, Pointer, Record, Register};
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub struct RunAction { pub struct RunAction {
@ -122,6 +119,8 @@ pub fn load_constant(instruction_data: InstructionData, record: &mut Record) ->
} = instruction_data.into(); } = instruction_data.into();
let register = Register::Pointer(Pointer::Constant(constant_index)); let register = Register::Pointer(Pointer::Constant(constant_index));
log::trace!("Load constant {constant_index} into R{destination}");
record.set_register(destination, register); record.set_register(destination, register);
if jump_next { if jump_next {
@ -170,18 +169,22 @@ pub fn load_list(instruction_data: InstructionData, record: &mut Record) -> Thre
ThreadSignal::Continue 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 { let LoadFunction {
destination, destination,
record_index, record_index,
} = instruction_data.into(); } = 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 { pub fn load_self(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let LoadSelf { destination } = instruction_data.into(); 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); record.set_register(destination, register);
@ -510,9 +513,9 @@ pub fn jump(instruction_data: InstructionData, record: &mut Record) -> ThreadSig
let is_positive = c != 0; let is_positive = c != 0;
if is_positive { if is_positive {
record.ip += offset + 1 record.ip += offset;
} else { } else {
record.ip -= offset record.ip -= offset + 1;
} }
ThreadSignal::Continue 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, record_index: function.record_index,
return_register: destination, return_register: destination,
argument_count, argument_count,
}) }
} }
pub fn call_native(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal { 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 first_argument_index = destination - argument_count;
let argument_range = first_argument_index..destination; let argument_range = first_argument_index..destination;
let function = NativeFunction::from(function); function
let thread_signal = function
.call(record, Some(destination), argument_range) .call(record, Some(destination), argument_range)
.unwrap_or_else(|error| panic!("{error:?}")); .unwrap_or_else(|error| panic!("{error:?}"))
thread_signal
} }
pub fn r#return(instruction_data: InstructionData, _: &mut Record) -> ThreadSignal { pub fn r#return(instruction_data: InstructionData, _: &mut Record) -> ThreadSignal {

View File

@ -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, CallStack, VmError};
use super::{record::Record, runner::RunAction, CallStack, FunctionCall, VmError};
fn create_records(chunk: Chunk, records: &mut Vec<Record>) {
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);
}
pub struct Thread { pub struct Thread {
call_stack: CallStack, call_stack: CallStack,
@ -33,7 +15,7 @@ impl Thread {
let call_stack = CallStack::with_capacity(chunk.prototypes().len() + 1); let call_stack = CallStack::with_capacity(chunk.prototypes().len() + 1);
let mut records = Vec::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 { Thread {
call_stack, call_stack,
@ -42,36 +24,104 @@ impl Thread {
} }
pub fn run(&mut self) -> Option<Value> { pub fn run(&mut self) -> Option<Value> {
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 { loop {
assert!( assert!(
record.ip < record.actions.len(), active.ip < active.actions.len(),
"{}", "{}",
VmError::InstructionIndexOutOfBounds { VmError::InstructionIndexOutOfBounds {
call_stack: self.call_stack.clone(), call_stack: self.call_stack.clone(),
ip: record.ip, ip: active.ip,
} }
); );
let action = record.actions[record.ip]; log::trace!(
let signal = (action.logic)(action.data, record); "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 { match signal {
ThreadSignal::Continue => { ThreadSignal::Continue => {}
record.ip += 1; ThreadSignal::Call {
} record_index,
ThreadSignal::Call(function_call) => { return_register,
swap(record, &mut remaining_records[function_call.record_index]); 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); 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) => { ThreadSignal::Return(should_return_value) => {
let returning_call = match self.call_stack.pop() { let returning_call = match self.call_stack.pop() {
Some(function_call) => function_call, Some(function_call) => function_call,
None => { None => {
if should_return_value { if should_return_value {
return record.last_assigned_register().map(|register| { return active.last_assigned_register().map(|register| {
record.replace_register_or_clone_constant( active.replace_register_or_clone_constant(
register, register,
Register::Empty, Register::Empty,
) )
@ -82,19 +132,26 @@ impl Thread {
} }
}; };
let outer_call = self.call_stack.last_or_panic(); 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 { if should_return_value {
let return_register = record let return_register = active
.last_assigned_register() .last_assigned_register()
.unwrap_or_else(|| panic!("Expected return value")); .unwrap_or_else(|| panic!("Expected return value"));
let value = record let return_value = active
.replace_register_or_clone_constant(return_register, Register::Empty); .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 { } 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 { pub enum ThreadSignal {
Continue, Continue,
Call(FunctionCall), Call {
record_index: u8,
return_register: u8,
argument_count: u8,
},
Return(bool), Return(bool),
LoadFunction {
from_record_index: u8,
to_register_index: u8,
},
} }

View File

@ -5,4 +5,4 @@ fn fib (n: int) -> int {
fib(n - 1) + fib(n - 2) fib(n - 1) + fib(n - 2)
} }
write_line(fib(25)) write_line(fib(1))