Continue new VM implementation; Write docs
This commit is contained in:
parent
4527f7b6ef
commit
72365cd399
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -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",
|
||||
|
@ -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);
|
||||
|
@ -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"] }
|
||||
|
@ -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
|
||||
|
@ -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<Chunk>,
|
||||
|
||||
stack_size: usize,
|
||||
record_index: u8,
|
||||
}
|
||||
|
||||
impl Chunk {
|
||||
@ -56,6 +58,7 @@ impl Chunk {
|
||||
locals: impl Into<SmallVec<[Local; 8]>>,
|
||||
prototypes: impl Into<Vec<Chunk>>,
|
||||
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<DustString>,
|
||||
FunctionType,
|
||||
SmallVec<[Instruction; 32]>,
|
||||
SmallVec<[Span; 32]>,
|
||||
SmallVec<[Value; 16]>,
|
||||
SmallVec<[Local; 8]>,
|
||||
Vec<Chunk>,
|
||||
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<Record>) {
|
||||
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> {
|
||||
|
@ -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",
|
||||
|
@ -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<Chunk, DustError> {
|
||||
let lexer = Lexer::new(source);
|
||||
@ -45,12 +48,14 @@ pub fn compile(source: &str) -> Result<Chunk, DustError> {
|
||||
.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<DustString>,
|
||||
/// 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<DustString>,
|
||||
|
||||
/// 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<Self, CompileError> {
|
||||
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<impl Into<DustString>>) -> 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::<SmallVec<[Local; 8]>>();
|
||||
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 (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_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());
|
||||
|
||||
// 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(
|
||||
// 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,20 +1201,8 @@ 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() {
|
||||
let (last_instruction, _, _) = self.instructions.last().unwrap();
|
||||
let argument = match last_instruction.as_argument() {
|
||||
Some(argument) => argument,
|
||||
None => {
|
||||
return Err(CompileError::ExpectedExpression {
|
||||
@ -1202,8 +1216,7 @@ impl<'src> Compiler<'src> {
|
||||
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,
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -44,6 +44,6 @@ impl From<LoadFunction> 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)
|
||||
}
|
||||
}
|
||||
|
@ -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})"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
@ -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}")?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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(
|
||||
|
@ -8,8 +8,7 @@ use super::DustString;
|
||||
pub struct Function {
|
||||
pub name: Option<DustString>,
|
||||
pub r#type: FunctionType,
|
||||
pub record_index: usize,
|
||||
pub prototype_index: usize,
|
||||
pub record_index: u8,
|
||||
}
|
||||
|
||||
impl Display for Function {
|
||||
|
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<FunctionCall>,
|
||||
calls: Vec<FunctionCall>,
|
||||
}
|
||||
|
||||
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<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})"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use crate::{DustString, InstructionData, Value};
|
||||
use crate::{InstructionData, Value};
|
||||
|
||||
use super::call_stack::CallStack;
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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<Register>,
|
||||
last_assigned_register: Option<u8>,
|
||||
|
||||
name: Option<DustString>,
|
||||
r#type: FunctionType,
|
||||
|
||||
positions: SmallVec<[Span; 32]>,
|
||||
constants: SmallVec<[Value; 16]>,
|
||||
locals: SmallVec<[Local; 8]>,
|
||||
|
||||
last_assigned_register: Option<u8>,
|
||||
stack_size: usize,
|
||||
index: u8,
|
||||
}
|
||||
|
||||
impl Record {
|
||||
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]>,
|
||||
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}");
|
||||
|
||||
|
@ -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 {
|
@ -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<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);
|
||||
}
|
||||
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<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 {
|
||||
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::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);
|
||||
}
|
||||
ThreadSignal::Call(function_call) => {
|
||||
swap(record, &mut remaining_records[function_call.record_index]);
|
||||
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
@ -5,4 +5,4 @@ fn fib (n: int) -> int {
|
||||
fib(n - 1) + fib(n - 2)
|
||||
}
|
||||
|
||||
write_line(fib(25))
|
||||
write_line(fib(1))
|
||||
|
Loading…
Reference in New Issue
Block a user