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"
|
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",
|
||||||
|
@ -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);
|
||||||
|
@ -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"] }
|
||||||
|
@ -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
|
||||||
|
@ -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> {
|
||||||
|
@ -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",
|
||||||
|
@ -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 (mut right_instruction, _, _) = self.instructions.last_mut().unwrap();
|
let jump_distance = (self.instructions.len() - jump_index) as u8;
|
||||||
right_instruction.set_a_field(left_instruction.a_field());
|
|
||||||
|
|
||||||
if is_logic_chain {
|
|
||||||
let expression_length = self.instructions.len() - jump_index - 1;
|
|
||||||
jump_distance += expression_length as u8;
|
|
||||||
let jump = Instruction::jump(jump_distance, true);
|
let jump = Instruction::jump(jump_distance, true);
|
||||||
|
|
||||||
self.instructions
|
self.instructions
|
||||||
.insert(jump_index, (jump, Type::None, operator_position));
|
.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(())
|
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,20 +1201,8 @@ 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([
|
|
||||||
Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL,
|
|
||||||
Operation::JUMP,
|
|
||||||
Operation::LOAD_BOOLEAN,
|
|
||||||
Operation::LOAD_BOOLEAN
|
|
||||||
]),
|
|
||||||
) {
|
|
||||||
self.instructions.pop();
|
|
||||||
self.instructions.pop();
|
|
||||||
self.instructions.pop();
|
|
||||||
} else if let Some((instruction, _, _)) = self.instructions.last() {
|
|
||||||
let argument = match instruction.as_argument() {
|
|
||||||
Some(argument) => argument,
|
Some(argument) => argument,
|
||||||
None => {
|
None => {
|
||||||
return Err(CompileError::ExpectedExpression {
|
return Err(CompileError::ExpectedExpression {
|
||||||
@ -1202,8 +1216,7 @@ impl<'src> Compiler<'src> {
|
|||||||
test_value: true,
|
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,
|
||||||
|
@ -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,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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})"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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}")?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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(
|
||||||
|
@ -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 {
|
||||||
|
@ -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"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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})"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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,
|
|
||||||
}
|
|
||||||
|
@ -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}");
|
||||||
|
|
||||||
|
@ -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 {
|
@ -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,
|
||||||
|
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);
|
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,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
|
Loading…
Reference in New Issue
Block a user