1
0

Begin VM conversion to multi-thread

This commit is contained in:
Jeff 2024-12-17 03:22:44 -05:00
parent b59d51d620
commit bd590e0643
28 changed files with 1132 additions and 828 deletions

View File

@ -18,7 +18,7 @@ serde_json = "1.0.117"
getrandom = { version = "0.2", features = [
"js",
] } # Indirect dependency, for wasm builds
smallvec = { version = "1.13.2", features = ["serde"] }
smallvec = { version = "1.13.2", features = ["const_generics", "serde"] }
smartstring = { version = "1.0.1", features = [
"serde",
], default-features = false }

View File

@ -46,7 +46,7 @@ use std::{
use colored::{ColoredString, Colorize};
use crate::{value::ConcreteValue, Chunk, Local, Value};
use crate::{Chunk, Local};
const INSTRUCTION_COLUMNS: [(&str, usize); 4] =
[("i", 5), ("POSITION", 12), ("OPERATION", 17), ("INFO", 24)];
@ -93,7 +93,8 @@ pub struct Disassembler<'a, W> {
// Options
style: bool,
indent: usize,
output_width: usize,
width: usize,
show_type: bool,
}
impl<'a, W: Write> Disassembler<'a, W> {
@ -104,7 +105,8 @@ impl<'a, W: Write> Disassembler<'a, W> {
source: None,
style: false,
indent: 0,
output_width: Self::content_length(),
width: Self::content_length(),
show_type: false,
}
}
@ -120,6 +122,18 @@ impl<'a, W: Write> Disassembler<'a, W> {
self
}
pub fn width(mut self, width: usize) -> Self {
self.width = width.max(Self::content_length());
self
}
pub fn show_type(mut self, show_type: bool) -> Self {
self.show_type = show_type;
self
}
fn indent(mut self, indent: usize) -> Self {
self.indent = indent;
@ -135,7 +149,7 @@ impl<'a, W: Write> Disassembler<'a, W> {
fn line_length(&self) -> usize {
let indentation_length = INDENTATION.chars().count();
self.output_width + (indentation_length * self.indent) + 2 // Left and right border
self.width + (indentation_length * self.indent) + 2 // Left and right border
}
fn write_char(&mut self, c: char) -> Result<(), io::Error> {
@ -159,10 +173,10 @@ impl<'a, W: Write> Disassembler<'a, W> {
add_border: bool,
) -> Result<(), io::Error> {
let (line_content, overflow) = {
if text.len() > self.output_width {
if text.len() > self.width {
let split_index = text
.char_indices()
.nth(self.output_width)
.nth(self.width)
.map(|(index, _)| index)
.unwrap_or_else(|| text.len());
@ -226,15 +240,15 @@ impl<'a, W: Write> Disassembler<'a, W> {
Ok(())
}
fn write_centered_with_border(&mut self, text: &str) -> Result<(), io::Error> {
fn write_center_border(&mut self, text: &str) -> Result<(), io::Error> {
self.write_content(text, true, false, false, true)
}
fn write_centered_with_border_dim(&mut self, text: &str) -> Result<(), io::Error> {
fn write_center_border_dim(&mut self, text: &str) -> Result<(), io::Error> {
self.write_content(text, true, false, self.style, true)
}
fn write_centered_with_border_bold(&mut self, text: &str) -> Result<(), io::Error> {
fn write_center_border_bold(&mut self, text: &str) -> Result<(), io::Error> {
self.write_content(text, true, self.style, false, true)
}
@ -249,7 +263,8 @@ 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')
}
fn write_instruction_section(&mut self) -> Result<(), io::Error> {
@ -260,21 +275,26 @@ impl<'a, W: Write> Disassembler<'a, W> {
}
column_name_line.push('│');
self.write_centered_with_border_bold("Instructions")?;
self.write_centered_with_border(INSTRUCTION_BORDERS[0])?;
self.write_centered_with_border(&column_name_line)?;
self.write_centered_with_border(INSTRUCTION_BORDERS[1])?;
self.write_center_border_bold("Instructions")?;
self.write_center_border(INSTRUCTION_BORDERS[0])?;
self.write_center_border(&column_name_line)?;
self.write_center_border(INSTRUCTION_BORDERS[1])?;
for (index, (instruction, position)) in self.chunk.instructions().iter().enumerate() {
let position = position.to_string();
for (index, instruction) in self.chunk.instructions().iter().enumerate() {
let position = self
.chunk
.positions()
.get(index)
.map(|position| position.to_string())
.unwrap_or("stripped".to_string());
let operation = instruction.operation().to_string();
let info = instruction.disassembly_info();
let row = format!("{index:^5}{position:^12}{operation:^17}{info:^24}");
self.write_centered_with_border(&row)?;
self.write_center_border(&row)?;
}
self.write_centered_with_border(INSTRUCTION_BORDERS[2])?;
self.write_center_border(INSTRUCTION_BORDERS[2])?;
Ok(())
}
@ -287,10 +307,10 @@ impl<'a, W: Write> Disassembler<'a, W> {
}
column_name_line.push('│');
self.write_centered_with_border_bold("Locals")?;
self.write_centered_with_border(LOCAL_BORDERS[0])?;
self.write_centered_with_border(&column_name_line)?;
self.write_centered_with_border(LOCAL_BORDERS[1])?;
self.write_center_border_bold("Locals")?;
self.write_center_border(LOCAL_BORDERS[0])?;
self.write_center_border(&column_name_line)?;
self.write_center_border(LOCAL_BORDERS[1])?;
for (
index,
@ -314,10 +334,10 @@ impl<'a, W: Write> Disassembler<'a, W> {
"│{index:^5}│{identifier_display:^16}│{register_display:^10}│{scope:^7}│{is_mutable:^7}│"
);
self.write_centered_with_border(&row)?;
self.write_center_border(&row)?;
}
self.write_centered_with_border(LOCAL_BORDERS[2])?;
self.write_center_border(LOCAL_BORDERS[2])?;
Ok(())
}
@ -330,17 +350,14 @@ impl<'a, W: Write> Disassembler<'a, W> {
}
column_name_line.push('│');
self.write_centered_with_border_bold("Constants")?;
self.write_centered_with_border(CONSTANT_BORDERS[0])?;
self.write_centered_with_border(&column_name_line)?;
self.write_centered_with_border(CONSTANT_BORDERS[1])?;
self.write_center_border_bold("Constants")?;
self.write_center_border(CONSTANT_BORDERS[0])?;
self.write_center_border(&column_name_line)?;
self.write_center_border(CONSTANT_BORDERS[1])?;
for (index, value) in self.chunk.constants().iter().enumerate() {
let is_function = matches!(value, Value::Concrete(ConcreteValue::Function(_)));
let type_display = value.r#type().to_string();
let value_display = if is_function {
"Function displayed below".to_string()
} else {
let value_display = {
let mut value_string = value.to_string();
if value_string.len() > 26 {
@ -351,29 +368,35 @@ impl<'a, W: Write> Disassembler<'a, W> {
};
let constant_display = format!("{index:^5}{type_display:^26}{value_display:^26}");
self.write_centered_with_border(&constant_display)?;
if let Value::Concrete(ConcreteValue::Function(chunk)) = value {
let function_disassembler = chunk
.disassembler(self.writer)
.style(self.style)
.indent(self.indent + 1);
let original_output_width = self.output_width;
self.output_width = function_disassembler.output_width;
function_disassembler.disassemble()?;
self.write_char('\n')?;
self.output_width = original_output_width;
}
self.write_center_border(&constant_display)?;
}
self.write_centered_with_border(CONSTANT_BORDERS[2])?;
self.write_center_border(CONSTANT_BORDERS[2])?;
Ok(())
}
pub fn disassemble(mut self) -> Result<(), io::Error> {
pub fn write_prototype_section(&mut self) -> Result<(), io::Error> {
self.write_center_border_bold("Functions");
for chunk in &self.chunk.prototypes {
chunk
.disassembler(self.writer)
.indent(self.indent + 1)
.width(self.width)
.style(true)
.show_type(true)
.disassemble()?;
self.write_center_border("")?;
}
Ok(())
}
pub fn disassemble(&mut self) -> Result<(), io::Error> {
self.write_page_border(TOP_BORDER)?;
let name_display = self
.chunk
.name()
@ -393,16 +416,20 @@ impl<'a, W: Write> Disassembler<'a, W> {
.unwrap_or("Dust Chunk Disassembly".to_string())
});
self.write_page_border(TOP_BORDER)?;
self.write_char('\n')?;
self.write_centered_with_border_bold(&name_display)?;
self.write_center_border_bold(&name_display)?;
if self.show_type {
let type_display = self.chunk.r#type.to_string();
self.write_center_border(&type_display)?;
}
if let Some(source) = self.source {
let lazily_formatted = source.split_whitespace().collect::<Vec<&str>>().join(" ");
self.write_centered_with_border("")?;
self.write_centered_with_border(&lazily_formatted)?;
self.write_centered_with_border("")?;
self.write_center_border("")?;
self.write_center_border(&lazily_formatted)?;
self.write_center_border("")?;
}
let info_line = format!(
@ -413,21 +440,25 @@ impl<'a, W: Write> Disassembler<'a, W> {
self.chunk.r#type.return_type
);
self.write_centered_with_border_dim(&info_line)?;
self.write_centered_with_border("")?;
self.write_center_border_dim(&info_line)?;
self.write_center_border("")?;
if !self.chunk.is_empty() {
if !self.chunk.instructions.is_empty() {
self.write_instruction_section()?;
}
if !self.chunk.locals().is_empty() {
if !self.chunk.locals.is_empty() {
self.write_local_section()?;
}
if !self.chunk.constants().is_empty() {
if !self.chunk.constants.is_empty() {
self.write_constant_section()?;
}
if !self.chunk.prototypes.is_empty() {
self.write_prototype_section()?;
}
self.write_page_border(BOTTOM_BORDER)
}
}

View File

@ -1,8 +1,18 @@
//! In-memory representation of a Dust program or function.
//! Representation of a Dust program or function.
//!
//! A chunk consists of a sequence of instructions and their positions, a list of constants, and a
//! list of locals that can be executed by the Dust virtual machine. Chunks have a name when they
//! belong to a named function.
//! A chunk is output by the compiler to represent all of the information needed to execute a Dust
//! program. In addition to the program itself, each function in the source is compiled into its own
//! chunk and stored in the `prototypes` field of its parent. Thus, a chunk is also the
//! representation of a function prototype, i.e. a function declaration as opposed to an individual
//! instance.
//!
//! Chunks have a name when they belong to a named function. They also have a type, so the input
//! parameters and the type of the return value are statically known. The [`Chunk::stack_size`]
//! method can provide the necessary stack size that will be needed by the virtual machine. Chunks
//! cannot be instantiated directly and must be created by the compiler. However, when the Rust
//! compiler is in the "test" configuration (used for all types of test), [`Chunk::with_data`] can
//! be used to create a chunk for comparison to the compiler output. Do not try to run these chunks
//! in a virtual machine. Due to their missing stack size, they will cause a panic.
mod disassembler;
mod local;
mod scope;
@ -19,7 +29,7 @@ use smallvec::SmallVec;
use crate::{DustString, FunctionType, Instruction, Span, Value};
/// In-memory representation of a Dust program or function.
/// Representation of a Dust program or function.
///
/// See the [module-level documentation](index.html) for more information.
#[derive(Clone, PartialOrd, Serialize, Deserialize)]
@ -27,49 +37,84 @@ pub struct Chunk {
name: Option<DustString>,
r#type: FunctionType,
instructions: SmallVec<[(Instruction, Span); 32]>,
instructions: SmallVec<[Instruction; 32]>,
positions: SmallVec<[Span; 32]>,
constants: SmallVec<[Value; 16]>,
locals: SmallVec<[Local; 8]>,
prototypes: Vec<Chunk>,
stack_size: usize,
}
impl Chunk {
pub fn new(
pub(crate) fn new(
name: Option<DustString>,
r#type: FunctionType,
instructions: SmallVec<[(Instruction, Span); 32]>,
constants: SmallVec<[Value; 16]>,
locals: SmallVec<[Local; 8]>,
instructions: impl Into<SmallVec<[Instruction; 32]>>,
positions: impl Into<SmallVec<[Span; 32]>>,
constants: impl Into<SmallVec<[Value; 16]>>,
locals: impl Into<SmallVec<[Local; 8]>>,
prototypes: impl Into<Vec<Chunk>>,
stack_size: usize,
) -> Self {
Self {
name,
r#type,
instructions,
constants,
locals,
instructions: instructions.into(),
positions: positions.into(),
constants: constants.into(),
locals: locals.into(),
prototypes: prototypes.into(),
stack_size,
}
}
#[cfg(test)]
pub fn with_data(
name: Option<DustString>,
r#type: FunctionType,
instructions: impl Into<SmallVec<[(Instruction, Span); 32]>>,
instructions: impl Into<SmallVec<[Instruction; 32]>>,
positions: impl Into<SmallVec<[Span; 32]>>,
constants: impl Into<SmallVec<[Value; 16]>>,
locals: impl Into<SmallVec<[Local; 8]>>,
prototypes: Vec<Chunk>,
) -> Self {
Self {
name,
r#type,
instructions: instructions.into(),
positions: positions.into(),
constants: constants.into(),
locals: locals.into(),
prototypes,
stack_size: 0,
}
}
pub fn take_data(
self,
) -> (
Option<DustString>,
FunctionType,
SmallVec<[Instruction; 32]>,
SmallVec<[Span; 32]>,
SmallVec<[Value; 16]>,
SmallVec<[Local; 8]>,
Vec<Chunk>,
usize,
) {
(
self.name,
self.r#type,
self.instructions,
self.positions,
self.constants,
self.locals,
self.prototypes,
self.stack_size,
)
}
pub fn name(&self) -> Option<&DustString> {
self.name.as_ref()
}
@ -78,22 +123,26 @@ impl Chunk {
&self.r#type
}
pub fn is_empty(&self) -> bool {
self.instructions.is_empty()
pub fn instructions(&self) -> &SmallVec<[Instruction; 32]> {
&self.instructions
}
pub fn positions(&self) -> &SmallVec<[Span; 32]> {
&self.positions
}
pub fn constants(&self) -> &SmallVec<[Value; 16]> {
&self.constants
}
pub fn instructions(&self) -> &SmallVec<[(Instruction, Span); 32]> {
&self.instructions
}
pub fn locals(&self) -> &SmallVec<[Local; 8]> {
&self.locals
}
pub fn prototypes(&self) -> &Vec<Chunk> {
&self.prototypes
}
pub fn stack_size(&self) -> usize {
self.stack_size
}
@ -141,8 +190,14 @@ impl Eq for Chunk {}
impl PartialEq for Chunk {
fn eq(&self, other: &Self) -> bool {
self.instructions == other.instructions
// Do not compare stack size because the chunks created for testing will not have one due to
// not being compiled.
self.name == other.name
&& self.r#type == other.r#type
&& self.instructions == other.instructions
&& self.constants == other.constants
&& self.locals == other.locals
&& self.prototypes == other.prototypes
}
}

View File

@ -20,8 +20,8 @@ use smallvec::{smallvec, SmallVec};
use crate::{
instruction::{
Call, CallNative, Close, GetLocal, Jump, LoadConstant, LoadList, LoadSelf, Move, Negate,
Not, Return, SetLocal, Test,
CallNative, Close, GetLocal, Jump, LoadList, LoadSelf, Negate, Not, Point, Return,
SetLocal, Test,
},
Argument, Chunk, ConcreteValue, DustError, DustString, FunctionType, Instruction, Lexer, Local,
NativeFunction, Operation, Scope, Span, Token, TokenKind, Type, Value,
@ -56,23 +56,58 @@ pub fn compile(source: &str) -> Result<Chunk, DustError> {
/// See the [`compile`] function an example of how to create and use a Compiler.
#[derive(Debug)]
pub struct Compiler<'src> {
self_name: Option<DustString>,
instructions: SmallVec<[(Instruction, Type, Span); 32]>,
constants: SmallVec<[Value; 16]>,
locals: SmallVec<[(Local, Type); 8]>,
stack_size: usize,
/// 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>,
/// Return type of the function being compiled. This is assigned to the chunk when
/// [`Compiler::finish`] is called.
return_type: Option<Type>,
/// Instructions, along with their types and positions, that have been compiled. The
/// instructions and positions are assigned to the chunk when [`Compiler::finish`] is called.
/// The types are discarded after compilation.
instructions: SmallVec<[(Instruction, Type, Span); 32]>,
/// Constants that have been compiled. These are assigned to the chunk when [`Compiler::finish`]
/// is called.
constants: SmallVec<[Value; 16]>,
/// Locals that have been compiled. These are assigned to the chunk when [`Compiler::finish`] is
/// called.
locals: SmallVec<[(Local, Type); 8]>,
/// Prototypes that have been compiled. These are assigned to the chunk when
/// [`Compiler::finish`] is called.
prototypes: Vec<Chunk>,
/// Maximum stack size required by the chunk. This is assigned to the chunk when
/// [`Compiler::finish`] is called.
stack_size: usize,
current_token: Token<'src>,
current_position: Span,
previous_token: Token<'src>,
previous_position: Span,
return_type: Option<Type>,
/// The first register index that the compiler should use. This is used to avoid reusing the
/// registers that are used for the function's arguments, thus it is zero in the program's main
/// chunk.
minimum_register: u8,
/// Index of the current block. This is used to determine the scope of the locals and is
/// incremented when a new block is entered.
block_index: u8,
/// The current scope of the compiler. This is used to test if a variable is in scope.
current_scope: Scope,
/// Index of the record (i.e. runtime data) that the VM will use when calling the function. This
/// is a depth-first index.
record_index: u8,
}
impl<'src> Compiler<'src> {
@ -90,6 +125,7 @@ impl<'src> Compiler<'src> {
instructions: SmallVec::new(),
constants: SmallVec::new(),
locals: SmallVec::new(),
prototypes: Vec::new(),
stack_size: 0,
lexer,
current_token,
@ -100,6 +136,7 @@ impl<'src> Compiler<'src> {
minimum_register: 0,
block_index: 0,
current_scope: Scope::default(),
record_index: 0,
})
}
@ -115,11 +152,11 @@ impl<'src> Compiler<'src> {
value_parameters,
return_type: self.return_type.unwrap_or(Type::None),
};
let instructions = self
let (instructions, positions): (SmallVec<[Instruction; 32]>, SmallVec<[Span; 32]>) = self
.instructions
.into_iter()
.map(|(instruction, _, position)| (instruction, position))
.collect::<SmallVec<[(Instruction, Span); 32]>>();
.unzip();
let locals = self
.locals
.into_iter()
@ -130,8 +167,10 @@ impl<'src> Compiler<'src> {
self.self_name,
r#type,
instructions,
positions,
self.constants,
locals,
self.prototypes,
self.stack_size,
)
}
@ -347,7 +386,7 @@ impl<'src> Compiler<'src> {
}
if let Operation::LOAD_SELF = operation {
return Ok(Type::SelfChunk);
return Ok(Type::SelfFunction);
}
if instruction.yields_value() {
@ -989,7 +1028,7 @@ impl<'src> Compiler<'src> {
let destination = self.next_register();
let load_self = Instruction::from(LoadSelf { destination });
self.emit_instruction(load_self, Type::SelfChunk, start_position);
self.emit_instruction(load_self, Type::SelfFunction, start_position);
return Ok(());
} else {
@ -1264,7 +1303,7 @@ impl<'src> Compiler<'src> {
optimize_test_with_loader_arguments(self);
let else_last_register = self.next_register().saturating_sub(1);
let r#move = Instruction::from(Move {
let r#move = Instruction::from(Point {
from: else_last_register,
to: if_last_register,
});
@ -1532,6 +1571,10 @@ impl<'src> Compiler<'src> {
fn parse_function(&mut self) -> Result<(), CompileError> {
let function_start = self.current_position.0;
let mut function_compiler = Compiler::new(self.lexer)?;
self.record_index += 1;
function_compiler.record_index = self.record_index;
let identifier_info = if let Token::Identifier(text) = function_compiler.current_token {
let position = function_compiler.current_position;
@ -1614,13 +1657,12 @@ impl<'src> Compiler<'src> {
self.previous_position = function_compiler.previous_position;
self.current_token = function_compiler.current_token;
self.current_position = function_compiler.current_position;
self.record_index = function_compiler.record_index;
self.lexer.skip_to(self.current_position.1);
let function_end = function_compiler.previous_position.1;
let chunk = function_compiler.finish(None, value_parameters.clone());
let function = Value::Concrete(ConcreteValue::function(chunk));
let constant_index = self.push_or_get_constant(function);
let prototype = function_compiler.finish(None, value_parameters.clone());
let destination = self.next_register();
let function_type = FunctionType {
type_parameters: None,
@ -1628,6 +1670,8 @@ impl<'src> Compiler<'src> {
return_type,
};
self.prototypes.push(prototype);
if let Some((identifier, _)) = identifier_info {
self.declare_local(
identifier,
@ -1636,23 +1680,11 @@ impl<'src> Compiler<'src> {
false,
self.current_scope,
);
let load_constant = Instruction::load_constant(destination, constant_index, false);
self.emit_instruction(
load_constant,
Type::function(function_type),
Span(function_start, function_end),
);
} else {
let load_constant = Instruction::from(LoadConstant {
destination,
constant_index,
jump_next: false,
});
let load_function = Instruction::load_function(destination, self.record_index);
self.emit_instruction(
load_constant,
load_function,
Type::function(function_type),
Span(function_start, function_end),
);
@ -1670,23 +1702,18 @@ impl<'src> Compiler<'src> {
position: self.previous_position,
})?;
if !last_instruction.yields_value() {
return Err(CompileError::ExpectedExpression {
if last_instruction.operation() != Operation::LOAD_FUNCTION {
return Err(CompileError::ExpectedFunction {
found: self.previous_token.to_owned(),
actual_type: last_instruction_type.clone(),
position: self.previous_position,
});
}
let argument =
last_instruction
.as_argument()
.ok_or_else(|| CompileError::ExpectedExpression {
found: self.previous_token.to_owned(),
position: self.previous_position,
})?;
let prototype_index = last_instruction.b_field();
let function_return_type = match last_instruction_type {
Type::Function(function_type) => function_type.return_type.clone(),
Type::SelfChunk => self.return_type.clone().unwrap_or(Type::None),
Type::SelfFunction => self.return_type.clone().unwrap_or(Type::None),
_ => {
return Err(CompileError::ExpectedFunction {
found: self.previous_token.to_owned(),
@ -1725,11 +1752,7 @@ impl<'src> Compiler<'src> {
let end = self.current_position.1;
let destination = self.next_register();
let call = Instruction::from(Call {
destination,
function: argument,
argument_count,
});
let call = Instruction::call(destination, prototype_index, argument_count);
self.emit_instruction(call, function_return_type, Span(start, end));

View File

@ -1,20 +1,20 @@
use crate::{Argument, Instruction, Operation};
use crate::{Instruction, Operation};
pub struct Call {
pub destination: u8,
pub function: Argument,
pub prototype_index: u8,
pub argument_count: u8,
}
impl From<&Instruction> for Call {
fn from(instruction: &Instruction) -> Self {
let destination = instruction.a_field();
let function = instruction.b_as_argument();
let prototype_index = instruction.b_field();
let argument_count = instruction.c_field();
Call {
destination,
function,
prototype_index,
argument_count,
}
}
@ -23,9 +23,9 @@ impl From<&Instruction> for Call {
impl From<Call> for Instruction {
fn from(call: Call) -> Self {
let a = call.destination;
let (b, b_is_constant) = call.function.as_index_and_constant_flag();
let b = call.prototype_index;
let c = call.argument_count;
Instruction::new(Operation::CALL, a, b, c, b_is_constant, false, false)
Instruction::new(Operation::CALL, a, b, c, false, false, false)
}
}

View File

@ -1,5 +1,7 @@
use crate::{Instruction, Operation};
use super::InstructionData;
pub struct Close {
pub from: u8,
pub to: u8,
@ -14,6 +16,15 @@ impl From<&Instruction> for Close {
}
}
impl From<InstructionData> for Close {
fn from(instruction: InstructionData) -> Self {
Close {
from: instruction.b,
to: instruction.c,
}
}
}
impl From<Close> for Instruction {
fn from(close: Close) -> Self {
let operation = Operation::CLOSE;

View File

@ -1,16 +1,18 @@
use crate::{Instruction, Operation};
use super::InstructionData;
pub struct LoadBoolean {
pub destination: u8,
pub value: bool,
pub jump_next: bool,
}
impl From<&Instruction> for LoadBoolean {
fn from(instruction: &Instruction) -> Self {
let destination = instruction.a_field();
let value = instruction.b_field() != 0;
let jump_next = instruction.c_field() != 0;
impl From<InstructionData> for LoadBoolean {
fn from(instruction: InstructionData) -> Self {
let destination = instruction.a;
let value = instruction.b != 0;
let jump_next = instruction.c != 0;
LoadBoolean {
destination,

View File

@ -1,5 +1,9 @@
use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionData;
pub struct LoadConstant {
pub destination: u8,
pub constant_index: u8,
@ -20,6 +24,20 @@ impl From<&Instruction> for LoadConstant {
}
}
impl From<InstructionData> for LoadConstant {
fn from(instruction: InstructionData) -> Self {
let destination = instruction.a;
let constant_index = instruction.b;
let jump_next = instruction.c != 0;
LoadConstant {
destination,
constant_index,
jump_next,
}
}
}
impl From<LoadConstant> for Instruction {
fn from(load_constant: LoadConstant) -> Self {
let operation = Operation::LOAD_CONSTANT;
@ -30,3 +48,21 @@ impl From<LoadConstant> for Instruction {
Instruction::new(operation, a, b, c, false, false, false)
}
}
impl Display for LoadConstant {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let LoadConstant {
destination,
constant_index,
jump_next,
} = self;
write!(f, "R{destination} = Constant {constant_index}")?;
if *jump_next {
write!(f, " JUMP +1")
} else {
Ok(())
}
}
}

View File

@ -0,0 +1,42 @@
use std::fmt::{self, Display, Formatter};
use super::{Instruction, Operation};
pub struct LoadFunction {
pub destination: u8,
pub prototype_index: u8,
}
impl From<&Instruction> for LoadFunction {
fn from(instruction: &Instruction) -> Self {
let destination = instruction.a_field();
let prototype_index = instruction.b_field();
LoadFunction {
destination,
prototype_index,
}
}
}
impl From<LoadFunction> for Instruction {
fn from(load_function: LoadFunction) -> Self {
let operation = Operation::LOAD_FUNCTION;
Instruction::new(
operation,
load_function.destination,
load_function.prototype_index,
0,
false,
false,
false,
)
}
}
impl Display for LoadFunction {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "R{} = P{}", self.destination, self.prototype_index)
}
}

View File

@ -76,18 +76,18 @@
//! // - `a = 2 + a`
//!
//! let operation = mystery_instruction.operation();
//!
//! match operation {
//! let is_add_assign = match operation {
//! Operation::Add => {
//! let Add { destination, left, right } = Add::from(&mystery_instruction);
//! let is_add_assign =
//! left == Argument::Register(destination)
//! || right == Argument::Register(destination);
//!
//! assert!(is_add_assign);
//! left == Argument::Register(destination)
//! || right == Argument::Register(destination);
//!
//! }
//! _ => {} // Handle other operations...
//! }
//! _ => false,
//! };
//!
//! assert!(is_add_assign);
//! ```
mod add;
mod call;
@ -101,6 +101,7 @@ mod less;
mod less_equal;
mod load_boolean;
mod load_constant;
mod load_function;
mod load_list;
mod load_self;
mod modulo;
@ -127,6 +128,7 @@ pub use less::Less;
pub use less_equal::LessEqual;
pub use load_boolean::LoadBoolean;
pub use load_constant::LoadConstant;
pub use load_function::LoadFunction;
pub use load_list::LoadList;
pub use load_self::LoadSelf;
pub use modulo::Modulo;
@ -134,7 +136,7 @@ pub use multiply::Multiply;
pub use negate::Negate;
pub use not::Not;
pub use operation::Operation;
pub use r#move::Move;
pub use r#move::Point;
pub use r#return::Return;
pub use set_local::SetLocal;
pub use subtract::Subtract;
@ -239,8 +241,8 @@ impl Instruction {
)
}
pub fn r#move(from: u8, to: u8) -> Instruction {
Instruction::from(Move { from, to })
pub fn point(from: u8, to: u8) -> Instruction {
Instruction::from(Point { from, to })
}
pub fn close(from: u8, to: u8) -> Instruction {
@ -263,6 +265,13 @@ impl Instruction {
})
}
pub fn load_function(destination: u8, prototype_index: u8) -> Instruction {
Instruction::from(LoadFunction {
destination,
prototype_index,
})
}
pub fn load_list(destination: u8, start_register: u8) -> Instruction {
Instruction::from(LoadList {
destination,
@ -376,10 +385,10 @@ impl Instruction {
})
}
pub fn call(destination: u8, function: Argument, argument_count: u8) -> Instruction {
pub fn call(destination: u8, prototype_index: u8, argument_count: u8) -> Instruction {
Instruction::from(Call {
destination,
function,
prototype_index,
argument_count,
})
}
@ -497,7 +506,7 @@ impl Instruction {
function.returns_value()
}
Operation::MOVE
Operation::POINT
| Operation::CLOSE
| Operation::SET_LOCAL
| Operation::TEST
@ -509,14 +518,12 @@ impl Instruction {
}
pub fn disassembly_info(&self) -> String {
match self.operation() {
Operation::MOVE => {
let Move { from, to } = Move::from(self);
let (operation, data) = self.decode();
format!("R{to} = R{from}")
}
match operation {
Operation::POINT => Point::from(data).to_string(),
Operation::CLOSE => {
let Close { from, to } = Close::from(self);
let Close { from, to } = Close::from(data);
format!("R{from}..R{to}")
}
@ -525,7 +532,7 @@ impl Instruction {
destination,
value,
jump_next,
} = LoadBoolean::from(self);
} = LoadBoolean::from(data);
if jump_next {
format!("R{destination} = {value} && JUMP +1")
@ -546,6 +553,7 @@ impl Instruction {
format!("R{destination} = C{constant_index}")
}
}
Operation::LOAD_FUNCTION => LoadFunction::from(self).to_string(),
Operation::LOAD_LIST => {
let LoadList {
destination,
@ -689,17 +697,16 @@ impl Instruction {
Operation::CALL => {
let Call {
destination,
function,
prototype_index,
argument_count,
} = Call::from(self);
let arguments_start = destination.saturating_sub(argument_count);
let arguments_end = arguments_start + argument_count;
match argument_count {
0 => format!("R{destination} = {function}()"),
1 => format!("R{destination} = {function}(R{arguments_start})"),
0 => format!("R{destination} = P{prototype_index}()"),
1 => format!("R{destination} = P{prototype_index}(R{arguments_start})"),
_ => {
format!("R{destination} = {function}(R{arguments_start}..R{arguments_end})")
format!("R{destination} = P{prototype_index}(R{arguments_start}..R{destination})")
}
}
}

View File

@ -1,22 +1,43 @@
use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
pub struct Move {
use super::InstructionData;
pub struct Point {
pub from: u8,
pub to: u8,
}
impl From<&Instruction> for Move {
impl From<&Instruction> for Point {
fn from(instruction: &Instruction) -> Self {
Move {
Point {
from: instruction.b_field(),
to: instruction.c_field(),
}
}
}
impl From<Move> for Instruction {
fn from(r#move: Move) -> Self {
let operation = Operation::MOVE;
impl From<InstructionData> for Point {
fn from(instruction: InstructionData) -> Self {
Point {
from: instruction.b,
to: instruction.c,
}
}
}
impl Display for Point {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let Point { from, to } = self;
write!(f, "{from} -> {to}")
}
}
impl From<Point> for Instruction {
fn from(r#move: Point) -> Self {
let operation = Operation::POINT;
let b = r#move.from;
let c = r#move.to;

View File

@ -9,39 +9,41 @@ use serde::{Deserialize, Serialize};
pub struct Operation(pub u8);
impl Operation {
pub const MOVE: Operation = Operation(0);
pub const POINT: Operation = Operation(0);
pub const CLOSE: Operation = Operation(1);
pub const LOAD_BOOLEAN: Operation = Operation(2);
pub const LOAD_CONSTANT: Operation = Operation(3);
pub const LOAD_LIST: Operation = Operation(4);
pub const LOAD_SELF: Operation = Operation(5);
pub const GET_LOCAL: Operation = Operation(6);
pub const SET_LOCAL: Operation = Operation(7);
pub const ADD: Operation = Operation(8);
pub const SUBTRACT: Operation = Operation(9);
pub const MULTIPLY: Operation = Operation(10);
pub const DIVIDE: Operation = Operation(11);
pub const MODULO: Operation = Operation(12);
pub const TEST: Operation = Operation(13);
pub const TEST_SET: Operation = Operation(14);
pub const EQUAL: Operation = Operation(15);
pub const LESS: Operation = Operation(16);
pub const LESS_EQUAL: Operation = Operation(17);
pub const NEGATE: Operation = Operation(18);
pub const NOT: Operation = Operation(19);
pub const CALL: Operation = Operation(20);
pub const CALL_NATIVE: Operation = Operation(21);
pub const JUMP: Operation = Operation(22);
pub const RETURN: Operation = Operation(23);
pub const LOAD_FUNCTION: Operation = Operation(4);
pub const LOAD_LIST: Operation = Operation(5);
pub const LOAD_SELF: Operation = Operation(6);
pub const GET_LOCAL: Operation = Operation(7);
pub const SET_LOCAL: Operation = Operation(8);
pub const ADD: Operation = Operation(9);
pub const SUBTRACT: Operation = Operation(10);
pub const MULTIPLY: Operation = Operation(11);
pub const DIVIDE: Operation = Operation(12);
pub const MODULO: Operation = Operation(13);
pub const TEST: Operation = Operation(14);
pub const TEST_SET: Operation = Operation(15);
pub const EQUAL: Operation = Operation(16);
pub const LESS: Operation = Operation(17);
pub const LESS_EQUAL: Operation = Operation(18);
pub const NEGATE: Operation = Operation(19);
pub const NOT: Operation = Operation(20);
pub const CALL: Operation = Operation(21);
pub const CALL_NATIVE: Operation = Operation(22);
pub const JUMP: Operation = Operation(23);
pub const RETURN: Operation = Operation(24);
}
impl Operation {
pub fn name(self) -> &'static str {
match self {
Self::MOVE => "MOVE",
pub fn name(&self) -> &'static str {
match *self {
Self::POINT => "MOVE",
Self::CLOSE => "CLOSE",
Self::LOAD_BOOLEAN => "LOAD_BOOLEAN",
Self::LOAD_CONSTANT => "LOAD_CONSTANT",
Self::LOAD_FUNCTION => "LOAD_FUNCTION",
Self::LOAD_LIST => "LOAD_LIST",
Self::LOAD_SELF => "LOAD_SELF",
Self::GET_LOCAL => "GET_LOCAL",

View File

@ -46,7 +46,7 @@ pub fn lex(source: &str) -> Result<Vec<(Token, Span)>, DustError> {
Ok(tokens)
}
/// Low-level tool for lexing a single token at a time.
/// Tool for lexing a single token at a time.
///
/// See the [`lex`] function for an example of how to create and use a Lexer.
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]

View File

@ -47,8 +47,10 @@ pub use crate::lexer::{lex, LexError, Lexer};
pub use crate::native_function::{NativeFunction, NativeFunctionError};
pub use crate::r#type::{EnumType, FunctionType, StructType, Type, TypeConflict};
pub use crate::token::{Token, TokenKind, TokenOwned};
pub use crate::value::{AbstractValue, ConcreteValue, DustString, RangeValue, Value, ValueError};
pub use crate::vm::{run, Vm};
pub use crate::value::{
AbstractList, ConcreteValue, DustString, Function, RangeValue, Value, ValueError,
};
pub use crate::vm::{run, Pointer, Vm};
use std::fmt::Display;

View File

@ -2,16 +2,16 @@ use std::panic;
use smallvec::SmallVec;
use crate::{DustString, NativeFunctionError, Value, Vm};
use crate::{vm::Record, DustString, NativeFunctionError, Value};
pub fn panic(
vm: &Vm,
record: &mut Record,
arguments: SmallVec<[&Value; 4]>,
) -> Result<Option<Value>, NativeFunctionError> {
let mut message: Option<DustString> = None;
for value_ref in arguments {
let string = value_ref.display(vm);
let string = value_ref.display(record);
match message {
Some(ref mut message) => message.push_str(&string),

View File

@ -2,9 +2,12 @@ use std::io::{stdin, stdout, Write};
use smallvec::SmallVec;
use crate::{ConcreteValue, NativeFunctionError, Value, Vm};
use crate::{vm::Record, ConcreteValue, NativeFunctionError, Value};
pub fn read_line(vm: &Vm, _: SmallVec<[&Value; 4]>) -> Result<Option<Value>, NativeFunctionError> {
pub fn read_line(
record: &mut Record,
_: SmallVec<[&Value; 4]>,
) -> Result<Option<Value>, NativeFunctionError> {
let mut buffer = String::new();
match stdin().read_line(&mut buffer) {
@ -17,25 +20,23 @@ pub fn read_line(vm: &Vm, _: SmallVec<[&Value; 4]>) -> Result<Option<Value>, Nat
}
Err(error) => Err(NativeFunctionError::Io {
error: error.kind(),
position: vm.current_position(),
}),
}
}
pub fn write(
vm: &Vm,
record: &mut Record,
arguments: SmallVec<[&Value; 4]>,
) -> Result<Option<Value>, NativeFunctionError> {
let mut stdout = stdout();
for argument in arguments {
let string = argument.display(vm);
let string = argument.display(record);
stdout
.write_all(string.as_bytes())
.map_err(|io_error| NativeFunctionError::Io {
error: io_error.kind(),
position: vm.current_position(),
})?;
}
@ -43,19 +44,18 @@ pub fn write(
}
pub fn write_line(
vm: &Vm,
record: &mut Record,
arguments: SmallVec<[&Value; 4]>,
) -> Result<Option<Value>, NativeFunctionError> {
let mut stdout = stdout();
for argument in arguments {
let string = argument.display(vm);
let string = argument.display(record);
stdout
.write_all(string.as_bytes())
.map_err(|io_error| NativeFunctionError::Io {
error: io_error.kind(),
position: vm.current_position(),
})?;
}
@ -63,7 +63,6 @@ pub fn write_line(
.write(b"\n")
.map_err(|io_error| NativeFunctionError::Io {
error: io_error.kind(),
position: vm.current_position(),
})?;
Ok(None)

View File

@ -31,7 +31,7 @@ pub enum Type {
Range {
r#type: Box<Type>,
},
SelfChunk,
SelfFunction,
String,
Struct(StructType),
Tuple {
@ -194,7 +194,7 @@ impl Display for Type {
}
Type::None => write!(f, "none"),
Type::Range { r#type } => write!(f, "{type} range"),
Type::SelfChunk => write!(f, "self"),
Type::SelfFunction => write!(f, "self"),
Type::String => write!(f, "str"),
Type::Struct(struct_type) => write!(f, "{struct_type}"),
Type::Tuple { fields } => {
@ -257,8 +257,8 @@ impl Ord for Type {
left_type.cmp(right_type)
}
(Type::Range { .. }, _) => Ordering::Greater,
(Type::SelfChunk, Type::SelfChunk) => Ordering::Equal,
(Type::SelfChunk, _) => Ordering::Greater,
(Type::SelfFunction, Type::SelfFunction) => Ordering::Equal,
(Type::SelfFunction, _) => Ordering::Greater,
(Type::String, Type::String) => Ordering::Equal,
(Type::String { .. }, _) => Ordering::Greater,
(Type::Struct(left_struct), Type::Struct(right_struct)) => {

View File

@ -0,0 +1,45 @@
use std::fmt::{self, Display, Formatter};
use crate::{vm::Record, Pointer, Type};
use super::DustString;
#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub struct AbstractList {
pub item_type: Type,
pub item_pointers: Vec<Pointer>,
}
impl AbstractList {
pub fn display(&self, record: &Record) -> DustString {
let mut display = DustString::new();
display.push('[');
for (i, item) in self.item_pointers.iter().enumerate() {
if i > 0 {
display.push_str(", ");
}
let item_display = record.follow_pointer(*item).display(record);
display.push_str(&item_display);
}
display.push(']');
display
}
}
impl Display for AbstractList {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "[")?;
for pointer in &self.item_pointers {
write!(f, "{}", pointer)?;
}
write!(f, "]")
}
}

View File

@ -1,115 +0,0 @@
use std::fmt::{self, Display, Formatter};
use crate::{vm::Pointer, ConcreteValue, DustString, Type, Value, Vm};
#[derive(Debug, PartialEq, PartialOrd)]
pub enum AbstractValue {
FunctionSelf,
List {
item_type: Type,
item_pointers: Vec<Pointer>,
},
}
impl AbstractValue {
pub fn to_value(self) -> Value {
Value::Abstract(self)
}
pub fn to_concrete_owned<'a>(&self, vm: &'a Vm<'a>) -> ConcreteValue {
match self {
AbstractValue::FunctionSelf => ConcreteValue::function(vm.chunk().clone()),
AbstractValue::List { item_pointers, .. } => {
let mut items: Vec<ConcreteValue> = Vec::with_capacity(item_pointers.len());
for pointer in item_pointers {
let item_option = vm.follow_pointer_allow_empty(*pointer);
let item = match item_option {
Some(value) => value.clone().into_concrete_owned(vm),
None => continue,
};
items.push(item);
}
ConcreteValue::List(items)
}
}
}
pub fn display(&self, vm: &Vm) -> DustString {
let mut display = DustString::new();
match self {
AbstractValue::FunctionSelf => display.push_str("self"),
AbstractValue::List {
item_pointers: items,
..
} => {
display.push('[');
for (i, item) in items.iter().enumerate() {
if i > 0 {
display.push_str(", ");
}
let item_display = vm.follow_pointer(*item).display(vm);
display.push_str(&item_display);
}
display.push(']');
}
}
display
}
pub fn r#type(&self) -> Type {
match self {
AbstractValue::FunctionSelf => Type::SelfChunk,
AbstractValue::List { item_type, .. } => Type::List(Box::new(item_type.clone())),
}
}
}
impl Clone for AbstractValue {
fn clone(&self) -> Self {
log::trace!("Cloning abstract value {:?}", self);
match self {
AbstractValue::FunctionSelf => AbstractValue::FunctionSelf,
AbstractValue::List {
item_type: r#type,
item_pointers: items,
} => AbstractValue::List {
item_type: r#type.clone(),
item_pointers: items.clone(),
},
}
}
}
impl Display for AbstractValue {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
AbstractValue::FunctionSelf => write!(f, "self"),
AbstractValue::List {
item_pointers: items,
..
} => {
write!(f, "[")?;
for (i, item) in items.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", item)?;
}
write!(f, "]")
}
}
}
}

View File

@ -3,7 +3,7 @@ use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize};
use smartstring::{LazyCompact, SmartString};
use crate::{Chunk, Type, Value, ValueError};
use crate::{Type, Value, ValueError};
use super::RangeValue;
@ -15,7 +15,6 @@ pub enum ConcreteValue {
Byte(u8),
Character(char),
Float(f64),
Function(Box<Chunk>),
Integer(i64),
List(Vec<ConcreteValue>),
Range(RangeValue),
@ -27,10 +26,6 @@ impl ConcreteValue {
Value::Concrete(self)
}
pub fn function(chunk: Chunk) -> Self {
ConcreteValue::Function(Box::new(chunk))
}
pub fn list<T: Into<Vec<ConcreteValue>>>(into_list: T) -> Self {
ConcreteValue::List(into_list.into())
}
@ -57,7 +52,6 @@ impl ConcreteValue {
ConcreteValue::Byte(_) => Type::Byte,
ConcreteValue::Character(_) => Type::Character,
ConcreteValue::Float(_) => Type::Float,
ConcreteValue::Function(chunk) => Type::function(chunk.r#type().clone()),
ConcreteValue::Integer(_) => Type::Integer,
ConcreteValue::List(list) => {
let item_type = list.first().map_or(Type::Any, |item| item.r#type());
@ -70,28 +64,43 @@ impl ConcreteValue {
}
#[inline(always)]
pub fn add(&self, other: &Self) -> Result<ConcreteValue, ValueError> {
pub fn add(&self, other: &Self) -> ConcreteValue {
use ConcreteValue::*;
let sum = match (self, other) {
(Byte(left), Byte(right)) => ConcreteValue::Byte(left.saturating_add(*right)),
(Character(left), Character(right)) => {
ConcreteValue::string(format!("{}{}", left, right))
}
(Character(left), String(right)) => ConcreteValue::string(format!("{}{}", left, right)),
(Float(left), Float(right)) => ConcreteValue::Float(*left + *right),
(Integer(left), Integer(right)) => ConcreteValue::Integer(left.saturating_add(*right)),
(String(left), String(right)) => ConcreteValue::string(format!("{}{}", left, right)),
(String(left), Character(right)) => ConcreteValue::string(format!("{}{}", left, right)),
_ => {
return Err(ValueError::CannotAdd(
self.clone().to_value(),
other.clone().to_value(),
))
}
};
match (self, other) {
(Byte(left), Byte(right)) => {
let sum = left.saturating_add(*right);
Ok(sum)
Byte(sum)
}
(Character(left), Character(right)) => {
let mut concatenated = DustString::new();
concatenated.push(*left);
concatenated.push(*right);
String(concatenated)
}
(Float(left), Float(right)) => {
let sum = left + right;
Float(sum)
}
(Integer(left), Integer(right)) => {
let sum = left.saturating_add(*right);
Integer(sum)
}
(String(left), Character(_)) => todo!(),
(String(left), String(right)) => todo!(),
_ => panic!(
"{}",
ValueError::CannotAdd(
Value::Concrete(self.clone()),
Value::Concrete(other.clone())
)
),
}
}
pub fn subtract(&self, other: &Self) -> Result<ConcreteValue, ValueError> {
@ -168,18 +177,16 @@ impl ConcreteValue {
Ok(product)
}
pub fn negate(&self) -> Result<ConcreteValue, ValueError> {
pub fn negate(&self) -> ConcreteValue {
use ConcreteValue::*;
let negated = match self {
match self {
Boolean(value) => ConcreteValue::Boolean(!value),
Byte(value) => ConcreteValue::Byte(value.wrapping_neg()),
Float(value) => ConcreteValue::Float(-value),
Integer(value) => ConcreteValue::Integer(value.wrapping_neg()),
_ => return Err(ValueError::CannotNegate(self.clone().to_value())),
};
Ok(negated)
_ => panic!("{}", ValueError::CannotNegate(self.clone().to_value())),
}
}
pub fn not(&self) -> Result<ConcreteValue, ValueError> {
@ -201,7 +208,6 @@ impl ConcreteValue {
(Byte(left), Byte(right)) => ConcreteValue::Boolean(left == right),
(Character(left), Character(right)) => ConcreteValue::Boolean(left == right),
(Float(left), Float(right)) => ConcreteValue::Boolean(left == right),
(Function(left), Function(right)) => ConcreteValue::Boolean(left == right),
(Integer(left), Integer(right)) => ConcreteValue::Boolean(left == right),
(List(left), List(right)) => ConcreteValue::Boolean(left == right),
(Range(left), Range(right)) => ConcreteValue::Boolean(left == right),
@ -226,7 +232,6 @@ impl ConcreteValue {
(Byte(left), Byte(right)) => ConcreteValue::Boolean(left < right),
(Character(left), Character(right)) => ConcreteValue::Boolean(left < right),
(Float(left), Float(right)) => ConcreteValue::Boolean(left < right),
(Function(left), Function(right)) => ConcreteValue::Boolean(left < right),
(Integer(left), Integer(right)) => ConcreteValue::Boolean(left < right),
(List(left), List(right)) => ConcreteValue::Boolean(left < right),
(Range(left), Range(right)) => ConcreteValue::Boolean(left < right),
@ -250,7 +255,6 @@ impl ConcreteValue {
(Byte(left), Byte(right)) => ConcreteValue::Boolean(left <= right),
(Character(left), Character(right)) => ConcreteValue::Boolean(left <= right),
(Float(left), Float(right)) => ConcreteValue::Boolean(left <= right),
(Function(left), Function(right)) => ConcreteValue::Boolean(left <= right),
(Integer(left), Integer(right)) => ConcreteValue::Boolean(left <= right),
(List(left), List(right)) => ConcreteValue::Boolean(left <= right),
(Range(left), Range(right)) => ConcreteValue::Boolean(left <= right),
@ -276,7 +280,6 @@ impl Clone for ConcreteValue {
ConcreteValue::Byte(byte) => ConcreteValue::Byte(*byte),
ConcreteValue::Character(character) => ConcreteValue::Character(*character),
ConcreteValue::Float(float) => ConcreteValue::Float(*float),
ConcreteValue::Function(function) => ConcreteValue::Function(function.clone()),
ConcreteValue::Integer(integer) => ConcreteValue::Integer(*integer),
ConcreteValue::List(list) => ConcreteValue::List(list.clone()),
ConcreteValue::Range(range) => ConcreteValue::Range(*range),
@ -300,7 +303,6 @@ impl Display for ConcreteValue {
Ok(())
}
ConcreteValue::Function(chunk) => write!(f, "{}", chunk.r#type()),
ConcreteValue::Integer(integer) => write!(f, "{integer}"),
ConcreteValue::List(list) => {
write!(f, "[")?;

View File

@ -0,0 +1,26 @@
use std::fmt::{self, Display, Formatter};
use crate::FunctionType;
use super::DustString;
#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub struct Function {
pub name: Option<DustString>,
pub r#type: FunctionType,
pub prototype_index: usize,
}
impl Display for Function {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let mut type_string = self.r#type.to_string();
if let Some(name) = &self.name {
debug_assert!(type_string.starts_with("fn "));
type_string.insert_str(3, name);
}
write!(f, "{type_string}")
}
}

View File

@ -1,32 +1,34 @@
//! Runtime values used by the VM.
mod abstract_value;
mod abstract_list;
mod concrete_value;
mod function;
mod range_value;
pub use abstract_value::AbstractValue;
pub use abstract_list::AbstractList;
pub use concrete_value::{ConcreteValue, DustString};
pub use function::Function;
pub use range_value::RangeValue;
use serde::{Deserialize, Serialize};
use std::fmt::{self, Debug, Display, Formatter};
use crate::{Type, Vm};
use crate::{vm::Record, Type, Vm};
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
pub enum Value {
#[serde(skip)]
Abstract(AbstractValue),
Concrete(ConcreteValue),
#[serde(skip)]
AbstractList(AbstractList),
#[serde(skip)]
Function(Function),
#[serde(skip)]
SelfFunction,
}
impl Value {
pub fn into_concrete_owned<'a>(self, vm: &'a Vm<'a>) -> ConcreteValue {
match self {
Value::Abstract(abstract_value) => abstract_value.to_concrete_owned(vm),
Value::Concrete(concrete_value) => concrete_value,
}
}
pub fn as_boolean(&self) -> Option<&bool> {
if let Value::Concrete(ConcreteValue::Boolean(value)) = self {
Some(value)
@ -35,20 +37,34 @@ impl Value {
}
}
pub fn r#type(&self) -> Type {
match self {
Value::Abstract(abstract_value) => abstract_value.r#type(),
Value::Concrete(concrete_value) => concrete_value.r#type(),
pub fn as_function(&self) -> Option<&Function> {
if let Value::Function(function) = self {
Some(function)
} else {
None
}
}
pub fn add(&self, other: &Value) -> Result<Value, ValueError> {
match (self, other) {
(Value::Concrete(left), Value::Concrete(right)) => left.add(right).map(Value::Concrete),
_ => Err(ValueError::CannotAdd(self.to_owned(), other.to_owned())),
pub fn r#type(&self) -> Type {
match self {
Value::Concrete(concrete_value) => concrete_value.r#type(),
Value::AbstractList(AbstractList { item_type, .. }) => {
Type::List(Box::new(item_type.clone()))
}
Value::Function(Function { r#type, .. }) => Type::Function(Box::new(r#type.clone())),
Value::SelfFunction => Type::SelfFunction,
}
}
pub fn add(&self, other: &Value) -> Value {
let concrete = match (self, other) {
(Value::Concrete(left), Value::Concrete(right)) => left.add(right),
_ => panic!("{}", ValueError::CannotAdd(self.clone(), other.clone())),
};
Value::Concrete(concrete)
}
pub fn subtract(&self, other: &Value) -> Result<Value, ValueError> {
match (self, other) {
(Value::Concrete(left), Value::Concrete(right)) => {
@ -91,11 +107,13 @@ impl Value {
}
}
pub fn negate(&self) -> Result<Value, ValueError> {
match self {
Value::Concrete(concrete_value) => concrete_value.negate().map(Value::Concrete),
_ => Err(ValueError::CannotNegate(self.to_owned())),
}
pub fn negate(&self) -> Value {
let concrete = match self {
Value::Concrete(concrete_value) => concrete_value.negate(),
_ => panic!("{}", ValueError::CannotNegate(self.clone())),
};
Value::Concrete(concrete)
}
pub fn not(&self) -> Result<Value, ValueError> {
@ -132,10 +150,12 @@ impl Value {
}
}
pub fn display(&self, vm: &Vm) -> DustString {
pub fn display(&self, record: &Record) -> DustString {
match self {
Value::Abstract(abstract_value) => abstract_value.display(vm),
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"),
}
}
}
@ -143,8 +163,10 @@ impl Value {
impl Display for Value {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Value::Abstract(abstract_value) => write!(f, "{}", abstract_value),
Value::Concrete(concrete_value) => write!(f, "{}", concrete_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"),
}
}
}

View File

@ -0,0 +1,52 @@
use std::fmt::{self, Debug, Display, Formatter};
use super::FunctionCall;
#[derive(Clone, PartialEq)]
pub struct CallStack {
pub calls: Vec<FunctionCall>,
}
impl CallStack {
pub fn new() -> Self {
CallStack {
calls: Vec::with_capacity(1),
}
}
pub fn with_capacity(capacity: usize) -> Self {
CallStack {
calls: Vec::with_capacity(capacity),
}
}
pub fn is_empty(&self) -> bool {
self.calls.is_empty()
}
pub fn push(&mut self, call: FunctionCall) {
self.calls.push(call);
}
pub fn pop(&mut self) -> Option<FunctionCall> {
self.calls.pop()
}
}
impl Debug for CallStack {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{self}")
}
}
impl Display for CallStack {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
writeln!(f, "-- DUST CALL STACK --")?;
for FunctionCall { function, .. } in &self.calls {
writeln!(f, "{function}")?;
}
Ok(())
}
}

25
dust-lang/src/vm/error.rs Normal file
View File

@ -0,0 +1,25 @@
use std::fmt::{self, Display, Formatter};
use crate::DustString;
use super::call_stack::CallStack;
#[derive(Clone, Debug, PartialEq)]
pub enum VmError {
CallStackUnderflow { thread_name: DustString },
InstructionIndexOutOfBounds { call_stack: CallStack, ip: usize },
MalformedInstruction { instruction: InstructionData },
}
impl Display for VmError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::CallStackUnderflow { thread_name } => {
write!(f, "Call stack underflow in thread {thread_name}")
}
Self::InstructionIndexOutOfBounds { call_stack, ip } => {
write!(f, "Instruction index {} out of bounds\n{call_stack}", ip)
}
}
}
}

View File

@ -1,216 +1,62 @@
//! Virtual machine and errors
mod call_stack;
mod error;
mod record;
mod runner;
mod thread;
use std::fmt::{self, Display, Formatter};
use std::{
fmt::{self, Debug, Display, Formatter},
sync::mpsc,
thread::spawn,
};
use runner::Runner;
pub use error::VmError;
pub use record::Record;
use thread::Thread;
use crate::{compile, instruction::*, Chunk, DustError, Span, Value};
use crate::{compile, Chunk, DustError, Value};
pub fn run(source: &str) -> Result<Option<Value>, DustError> {
let chunk = compile(source)?;
let vm = Vm::new(&chunk, None, None);
let vm = Vm::new(chunk);
Ok(vm.run())
}
/// Dust virtual machine.
///
/// See the [module-level documentation](index.html) for more information.
#[derive(Debug)]
pub struct Vm<'a> {
stack: Vec<Register>,
runners: Vec<Runner>,
chunk: &'a Chunk,
parent: Option<&'a Vm<'a>>,
ip: usize,
last_assigned_register: Option<u8>,
return_value: Option<Value>,
pub struct Vm {
threads: Vec<Thread>,
}
impl<'a> Vm<'a> {
pub fn new(chunk: &'a Chunk, parent: Option<&'a Vm<'a>>, runners: Option<Vec<Runner>>) -> Self {
let stack = vec![Register::Empty; chunk.stack_size()];
let runners = runners.unwrap_or_else(|| {
let mut runners = Vec::with_capacity(chunk.instructions().len());
impl Vm {
pub fn new(chunk: Chunk) -> Self {
let threads = vec![Thread::new(chunk)];
for (instruction, _) in chunk.instructions() {
let runner = Runner::new(*instruction);
debug_assert_eq!(1, threads.capacity());
runners.push(runner);
}
runners
});
Self {
chunk,
runners,
stack,
parent,
ip: 0,
last_assigned_register: None,
return_value: None,
}
}
pub fn chunk(&'a self) -> &'a Chunk {
self.chunk
}
pub fn current_position(&self) -> Span {
let index = self.ip.saturating_sub(1);
let (_, position) = self.chunk.instructions()[index];
position
Self { threads }
}
pub fn run(mut self) -> Option<Value> {
self.execute_next_runner();
self.return_value
}
pub fn execute_next_runner(&mut self) {
assert!(
self.ip < self.runners.len(),
"Runtime Error: IP out of bounds"
);
let runner = self.runners[self.ip];
runner.run(self);
}
pub(crate) fn follow_pointer(&self, pointer: Pointer) -> &Value {
log::trace!("Follow pointer {pointer}");
match pointer {
Pointer::Stack(register_index) => self.open_register(register_index),
Pointer::Constant(constant_index) => self.get_constant(constant_index),
Pointer::ParentStack(register_index) => {
assert!(self.parent.is_some(), "Vm Error: Expected parent");
self.parent.unwrap().open_register(register_index)
}
Pointer::ParentConstant(constant_index) => {
assert!(self.parent.is_some(), "Vm Error: Expected parent");
self.parent.unwrap().get_constant(constant_index)
}
if self.threads.len() == 1 {
return self.threads[0].run();
}
}
pub(crate) fn follow_pointer_allow_empty(&self, pointer: Pointer) -> Option<&Value> {
log::trace!("Follow pointer {pointer}");
let (tx, rx) = mpsc::channel();
match pointer {
Pointer::Stack(register_index) => self.open_register_allow_empty(register_index),
Pointer::Constant(constant_index) => {
let constant = self.get_constant(constant_index);
for mut thread in self.threads {
let tx = tx.clone();
Some(constant)
}
Pointer::ParentStack(register_index) => {
assert!(self.parent.is_some(), "Vm Error: Expected parent");
spawn(move || {
let return_value = thread.run();
self.parent
.unwrap()
.open_register_allow_empty(register_index)
}
Pointer::ParentConstant(constant_index) => {
assert!(self.parent.is_some(), "Vm Error: Expected parent");
let constant = self.parent.unwrap().get_constant(constant_index);
Some(constant)
}
if let Some(value) = return_value {
tx.send(value).unwrap();
}
});
}
}
fn open_register(&self, register_index: u8) -> &Value {
log::trace!("Open register R{register_index}");
let register_index = register_index as usize;
assert!(
register_index < self.stack.len(),
"VM Error: Register index out of bounds"
);
let register = &self.stack[register_index];
match register {
Register::Value(value) => value,
Register::Pointer(pointer) => self.follow_pointer(*pointer),
Register::Empty => panic!("VM Error: Register {register_index} is empty"),
}
}
fn open_register_allow_empty(&self, register_index: u8) -> Option<&Value> {
log::trace!("Open register R{register_index}");
let register_index = register_index as usize;
assert!(
register_index < self.stack.len(),
"VM Error: Register index out of bounds"
);
let register = &self.stack[register_index];
match register {
Register::Value(value) => Some(value),
Register::Pointer(pointer) => Some(self.follow_pointer(*pointer)),
Register::Empty => None,
}
}
/// DRY helper to get a value from an Argument
fn get_argument(&self, index: u8, is_constant: bool) -> &Value {
if is_constant {
self.get_constant(index)
} else {
self.open_register(index)
}
}
fn set_register(&mut self, to_register: u8, register: Register) {
self.last_assigned_register = Some(to_register);
let to_register = to_register as usize;
assert!(
to_register < self.stack.len(),
"VM Error: Register index out of bounds"
);
self.stack[to_register] = register;
}
fn get_constant(&self, constant_index: u8) -> &Value {
let constant_index = constant_index as usize;
let constants = self.chunk.constants();
assert!(
constant_index < constants.len(),
"VM Error: Constant index out of bounds"
);
&constants[constant_index]
}
fn get_local_register(&self, local_index: u8) -> u8 {
let local_index = local_index as usize;
let locals = self.chunk.locals();
assert!(
local_index < locals.len(),
"VM Error: Local index out of bounds"
);
locals[local_index].register_index
rx.into_iter().last()
}
}
@ -235,8 +81,6 @@ impl Display for Register {
pub enum Pointer {
Stack(u8),
Constant(u8),
ParentStack(u8),
ParentConstant(u8),
}
impl Display for Pointer {
@ -244,8 +88,13 @@ impl Display for Pointer {
match self {
Self::Stack(index) => write!(f, "R{}", index),
Self::Constant(index) => write!(f, "C{}", index),
Self::ParentStack(index) => write!(f, "PR{}", index),
Self::ParentConstant(index) => write!(f, "PC{}", index),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct FunctionCall {
prototype_index: usize,
record_index: usize,
return_register: u8,
}

167
dust-lang/src/vm/record.rs Normal file
View File

@ -0,0 +1,167 @@
use std::env::consts::OS;
use smallvec::SmallVec;
use crate::{Local, Span, Value};
use super::{runner::RunAction, Pointer, Register};
pub struct Record {
pub ip: usize,
pub actions: SmallVec<[RunAction; 32]>,
positions: SmallVec<[Span; 32]>,
stack: Vec<Register>,
constants: SmallVec<[Value; 16]>,
locals: SmallVec<[Local; 8]>,
last_assigned_register: Option<u8>,
}
impl Record {
pub fn new(
stack: Vec<Register>,
constants: SmallVec<[Value; 16]>,
locals: SmallVec<[Local; 8]>,
actions: SmallVec<[RunAction; 32]>,
positions: SmallVec<[Span; 32]>,
) -> Self {
Self {
ip: 0,
actions,
positions,
stack,
constants,
locals,
last_assigned_register: None,
}
}
pub fn stack_size(&self) -> usize {
self.stack.len()
}
pub fn current_position(&self) -> Span {
self.positions[self.ip]
}
pub fn last_assigned_register(&self) -> Option<u8> {
self.last_assigned_register
}
pub(crate) fn follow_pointer(&self, pointer: Pointer) -> &Value {
log::trace!("Follow pointer {pointer}");
match pointer {
Pointer::Stack(register_index) => self.open_register(register_index),
Pointer::Constant(constant_index) => self.get_constant(constant_index),
}
}
pub(crate) fn follow_pointer_allow_empty(&self, pointer: Pointer) -> Option<&Value> {
log::trace!("Follow pointer {pointer}");
match pointer {
Pointer::Stack(register_index) => self.open_register_allow_empty(register_index),
Pointer::Constant(constant_index) => {
let constant = self.get_constant(constant_index);
Some(constant)
}
}
}
pub fn get_register(&self, register_index: u8) -> &Register {
log::trace!("Get register R{register_index}");
let register_index = register_index as usize;
assert!(
register_index < self.stack.len(),
"VM Error: Register index out of bounds"
);
&self.stack[register_index]
}
pub fn set_register(&mut self, to_register: u8, register: Register) {
self.last_assigned_register = Some(to_register);
let to_register = to_register as usize;
assert!(
to_register < self.stack.len(),
"VM Error: Register index out of bounds"
);
self.stack[to_register] = register;
}
pub fn open_register(&self, register_index: u8) -> &Value {
log::trace!("Open register R{register_index}");
let register_index = register_index as usize;
assert!(
register_index < self.stack.len(),
"VM Error: Register index out of bounds"
);
let register = &self.stack[register_index];
match register {
Register::Value(value) => value,
Register::Pointer(pointer) => self.follow_pointer(*pointer),
Register::Empty => panic!("VM Error: Register {register_index} is empty"),
}
}
pub fn open_register_allow_empty(&self, register_index: u8) -> Option<&Value> {
log::trace!("Open register R{register_index}");
let register_index = register_index as usize;
assert!(
register_index < self.stack.len(),
"VM Error: Register index out of bounds"
);
let register = &self.stack[register_index];
match register {
Register::Value(value) => Some(value),
Register::Pointer(pointer) => Some(self.follow_pointer(*pointer)),
Register::Empty => None,
}
}
/// DRY helper to get a value from an Argument
pub fn get_argument(&self, index: u8, is_constant: bool) -> &Value {
if is_constant {
self.get_constant(index)
} else {
self.open_register(index)
}
}
pub fn get_constant(&self, constant_index: u8) -> &Value {
let constant_index = constant_index as usize;
assert!(
constant_index < self.constants.len(),
"VM Error: Constant index out of bounds"
);
&self.constants[constant_index]
}
pub fn get_local_register(&self, local_index: u8) -> u8 {
let local_index = local_index as usize;
assert!(
local_index < self.locals.len(),
"VM Error: Local index out of bounds"
);
self.locals[local_index].register_index
}
}

View File

@ -1,33 +1,37 @@
use smallvec::SmallVec;
use crate::{AbstractValue, ConcreteValue, NativeFunction, Type, Value};
use crate::{
instruction::{Close, LoadBoolean, LoadConstant, Point},
AbstractList, ConcreteValue, Instruction, InstructionData, NativeFunction, Type, Value,
};
use super::{Instruction, InstructionData, Pointer, Register, Vm};
use super::{thread::ThreadSignal, Pointer, Record, Register};
#[derive(Clone, Copy, Debug)]
pub struct Runner {
logic: RunnerLogic,
data: InstructionData,
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct RunAction {
pub logic: RunnerLogic,
pub data: InstructionData,
}
impl Runner {
pub fn new(instruction: Instruction) -> Self {
impl From<&Instruction> for RunAction {
fn from(instruction: &Instruction) -> Self {
let (operation, data) = instruction.decode();
let logic = RUNNER_LOGIC_TABLE[operation.0 as usize];
Self { logic, data }
}
pub fn from_parts(logic: RunnerLogic, data: InstructionData) -> Self {
Self { logic, data }
}
pub fn run(self, vm: &mut Vm) {
(self.logic)(vm, self.data);
RunAction { logic, data }
}
}
pub type RunnerLogic = fn(&mut Vm, InstructionData);
impl From<Instruction> for RunAction {
fn from(instruction: Instruction) -> Self {
let (operation, data) = instruction.decode();
let logic = RUNNER_LOGIC_TABLE[operation.0 as usize];
RunAction { logic, data }
}
}
pub type RunnerLogic = fn(InstructionData, &mut Record) -> ThreadSignal;
pub const RUNNER_LOGIC_TABLE: [RunnerLogic; 24] = [
r#move,
@ -56,81 +60,79 @@ pub const RUNNER_LOGIC_TABLE: [RunnerLogic; 24] = [
r#return,
];
pub fn r#move(vm: &mut Vm, instruction_data: InstructionData) {
let InstructionData { b, c, .. } = instruction_data;
let from_register_has_value = vm
.stack
.get(b as usize)
.is_some_and(|register| !matches!(register, Register::Empty));
let register = Register::Pointer(Pointer::Stack(b));
pub fn r#move(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let Point { from, to } = instruction_data.into();
let from_register = record.get_register(from);
let from_register_is_empty = matches!(from_register, Register::Empty);
if from_register_has_value {
vm.set_register(c, register);
if !from_register_is_empty {
let register = Register::Pointer(Pointer::Stack(to));
record.set_register(from, register);
}
vm.ip += 1;
vm.execute_next_runner();
ThreadSignal::Continue
}
pub fn close(vm: &mut Vm, instruction_data: InstructionData) {
let InstructionData { b, c, .. } = instruction_data;
pub fn close(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let Close { from, to } = instruction_data.into();
assert!(b < c, "Runtime Error: Malformed instruction");
assert!(from < to, "Runtime Error: Malformed instruction");
for register_index in b..c {
for register_index in from..to {
assert!(
(register_index as usize) < vm.stack.len(),
(register_index as usize) < record.stack_size(),
"Runtime Error: Register index out of bounds"
);
vm.stack[register_index as usize] = Register::Empty;
record.set_register(register_index, Register::Empty);
}
vm.ip += 1;
vm.execute_next_runner();
ThreadSignal::Continue
}
pub fn load_boolean(vm: &mut Vm, instruction_data: InstructionData) {
let InstructionData { a, b, c, .. } = instruction_data;
let boolean = ConcreteValue::Boolean(b != 0).to_value();
pub fn load_boolean(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let LoadBoolean {
destination,
value,
jump_next,
} = instruction_data.into();
let boolean = Value::Concrete(ConcreteValue::Boolean(value));
let register = Register::Value(boolean);
vm.set_register(a, register);
record.set_register(destination, register);
if c != 0 {
vm.ip += 2;
} else {
vm.ip += 1;
if jump_next {
record.ip += 1;
}
vm.execute_next_runner();
ThreadSignal::Continue
}
pub fn load_constant(vm: &mut Vm, instruction_data: InstructionData) {
let InstructionData { a, b, c, .. } = instruction_data;
let register = Register::Pointer(Pointer::Constant(b));
pub fn load_constant(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let LoadConstant {
destination,
constant_index,
jump_next,
} = instruction_data.into();
let register = Register::Pointer(Pointer::Constant(constant_index));
vm.set_register(a, register);
record.set_register(destination, register);
if c != 0 {
vm.ip += 2;
} else {
vm.ip += 1;
if jump_next {
record.ip += 1;
}
vm.execute_next_runner();
ThreadSignal::Continue
}
pub fn load_list(vm: &mut Vm, instruction_data: InstructionData) {
pub fn load_list(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData { a, b, .. } = instruction_data;
let mut item_pointers = Vec::with_capacity((a - b) as usize);
let stack = vm.stack.as_slice();
let mut item_type = Type::Any;
for register_index in b..a {
match &stack[register_index as usize] {
match record.get_register(register_index) {
Register::Empty => continue,
Register::Value(value) => {
if item_type == Type::Any {
@ -139,7 +141,7 @@ pub fn load_list(vm: &mut Vm, instruction_data: InstructionData) {
}
Register::Pointer(pointer) => {
if item_type == Type::Any {
item_type = vm.follow_pointer(*pointer).r#type();
item_type = record.follow_pointer(*pointer).r#type();
}
}
}
@ -149,55 +151,39 @@ pub fn load_list(vm: &mut Vm, instruction_data: InstructionData) {
item_pointers.push(pointer);
}
let list_value = Value::Abstract(AbstractValue::List {
let list_value = Value::AbstractList(AbstractList {
item_type,
item_pointers,
});
let register = Register::Value(list_value);
vm.set_register(a, register);
vm.ip += 1;
vm.execute_next_runner();
record.set_register(a, register);
}
pub fn load_self(vm: &mut Vm, instruction_data: InstructionData) {
pub fn load_self(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData { a, .. } = instruction_data;
let register = Register::Value(AbstractValue::FunctionSelf.to_value());
let register = Register::Value(Value::SelfFunction);
vm.set_register(a, register);
vm.ip += 1;
vm.execute_next_runner();
record.set_register(a, register);
}
pub fn get_local(vm: &mut Vm, instruction_data: InstructionData) {
pub fn get_local(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData { a, b, .. } = instruction_data;
let local_register_index = vm.get_local_register(b);
let local_register_index = record.get_local_register(b);
let register = Register::Pointer(Pointer::Stack(local_register_index));
vm.set_register(a, register);
vm.ip += 1;
vm.execute_next_runner();
record.set_register(a, register);
}
pub fn set_local(vm: &mut Vm, instruction_data: InstructionData) {
pub fn set_local(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData { b, c, .. } = instruction_data;
let local_register_index = vm.get_local_register(c);
let local_register_index = record.get_local_register(c);
let register = Register::Pointer(Pointer::Stack(b));
vm.set_register(local_register_index, register);
vm.ip += 1;
vm.execute_next_runner();
record.set_register(local_register_index, register);
}
pub fn add(vm: &mut Vm, instruction_data: InstructionData) {
pub fn add(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData {
a,
b,
@ -206,8 +192,8 @@ pub fn add(vm: &mut Vm, instruction_data: InstructionData) {
c_is_constant,
..
} = instruction_data;
let left = vm.get_argument(b, b_is_constant);
let right = vm.get_argument(c, c_is_constant);
let left = record.get_argument(b, b_is_constant);
let right = record.get_argument(c, c_is_constant);
let sum = match (left, right) {
(Value::Concrete(left), Value::Concrete(right)) => match (left, right) {
(ConcreteValue::Integer(left), ConcreteValue::Integer(right)) => {
@ -219,14 +205,10 @@ pub fn add(vm: &mut Vm, instruction_data: InstructionData) {
};
let register = Register::Value(sum);
vm.set_register(a, register);
vm.ip += 1;
vm.execute_next_runner();
record.set_register(a, register);
}
pub fn subtract(vm: &mut Vm, instruction_data: InstructionData) {
pub fn subtract(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData {
a,
b,
@ -235,8 +217,8 @@ pub fn subtract(vm: &mut Vm, instruction_data: InstructionData) {
c_is_constant,
..
} = instruction_data;
let left = vm.get_argument(b, b_is_constant);
let right = vm.get_argument(c, c_is_constant);
let left = record.get_argument(b, b_is_constant);
let right = record.get_argument(c, c_is_constant);
let difference = match (left, right) {
(Value::Concrete(left), Value::Concrete(right)) => match (left, right) {
(ConcreteValue::Integer(left), ConcreteValue::Integer(right)) => {
@ -248,14 +230,10 @@ pub fn subtract(vm: &mut Vm, instruction_data: InstructionData) {
};
let register = Register::Value(difference);
vm.set_register(a, register);
vm.ip += 1;
vm.execute_next_runner();
record.set_register(a, register);
}
pub fn multiply(vm: &mut Vm, instruction_data: InstructionData) {
pub fn multiply(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData {
a,
b,
@ -264,8 +242,8 @@ pub fn multiply(vm: &mut Vm, instruction_data: InstructionData) {
c_is_constant,
..
} = instruction_data;
let left = vm.get_argument(b, b_is_constant);
let right = vm.get_argument(c, c_is_constant);
let left = record.get_argument(b, b_is_constant);
let right = record.get_argument(c, c_is_constant);
let product = match (left, right) {
(Value::Concrete(left), Value::Concrete(right)) => match (left, right) {
(ConcreteValue::Integer(left), ConcreteValue::Integer(right)) => {
@ -277,14 +255,10 @@ pub fn multiply(vm: &mut Vm, instruction_data: InstructionData) {
};
let register = Register::Value(product);
vm.set_register(a, register);
vm.ip += 1;
vm.execute_next_runner();
record.set_register(a, register);
}
pub fn divide(vm: &mut Vm, instruction_data: InstructionData) {
pub fn divide(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData {
a,
b,
@ -293,8 +267,8 @@ pub fn divide(vm: &mut Vm, instruction_data: InstructionData) {
c_is_constant,
..
} = instruction_data;
let left = vm.get_argument(b, b_is_constant);
let right = vm.get_argument(c, c_is_constant);
let left = record.get_argument(b, b_is_constant);
let right = record.get_argument(c, c_is_constant);
let quotient = match (left, right) {
(Value::Concrete(left), Value::Concrete(right)) => match (left, right) {
(ConcreteValue::Integer(left), ConcreteValue::Integer(right)) => {
@ -306,14 +280,10 @@ pub fn divide(vm: &mut Vm, instruction_data: InstructionData) {
};
let register = Register::Value(quotient);
vm.set_register(a, register);
vm.ip += 1;
vm.execute_next_runner();
record.set_register(a, register);
}
pub fn modulo(vm: &mut Vm, instruction_data: InstructionData) {
pub fn modulo(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData {
a,
b,
@ -322,8 +292,8 @@ pub fn modulo(vm: &mut Vm, instruction_data: InstructionData) {
c_is_constant,
..
} = instruction_data;
let left = vm.get_argument(b, b_is_constant);
let right = vm.get_argument(c, c_is_constant);
let left = record.get_argument(b, b_is_constant);
let right = record.get_argument(c, c_is_constant);
let remainder = match (left, right) {
(Value::Concrete(left), Value::Concrete(right)) => match (left, right) {
(ConcreteValue::Integer(left), ConcreteValue::Integer(right)) => {
@ -335,41 +305,31 @@ pub fn modulo(vm: &mut Vm, instruction_data: InstructionData) {
};
let register = Register::Value(remainder);
vm.set_register(a, register);
vm.ip += 1;
vm.execute_next_runner();
record.set_register(a, register);
}
pub fn test(vm: &mut Vm, instruction_data: InstructionData) {
pub fn test(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData {
b,
b_is_constant,
c,
..
} = instruction_data;
let value = vm.get_argument(b, b_is_constant);
let value = record.get_argument(b, b_is_constant);
let boolean = if let Value::Concrete(ConcreteValue::Boolean(boolean)) = value {
*boolean
} else {
panic!(
"VM Error: Expected boolean value for TEST operation at {}",
vm.current_position()
);
panic!("VM Error: Expected boolean value for TEST operation",);
};
let test_value = c != 0;
if boolean == test_value {
vm.ip += 2;
record.ip += 2;
} else {
vm.ip += 1;
}
vm.execute_next_runner();
}
pub fn test_set(vm: &mut Vm, instruction_data: InstructionData) {
pub fn test_set(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData {
a,
b,
@ -377,19 +337,16 @@ pub fn test_set(vm: &mut Vm, instruction_data: InstructionData) {
b_is_constant,
..
} = instruction_data;
let value = vm.get_argument(b, b_is_constant);
let value = record.get_argument(b, b_is_constant);
let boolean = if let Value::Concrete(ConcreteValue::Boolean(boolean)) = value {
*boolean
} else {
panic!(
"VM Error: Expected boolean value for TEST_SET operation at {}",
vm.current_position()
);
panic!("VM Error: Expected boolean value for TEST_SET operation",);
};
let test_value = c != 0;
if boolean == test_value {
vm.ip += 2;
record.ip += 2;
} else {
let pointer = if b_is_constant {
Pointer::Constant(b)
@ -398,15 +355,11 @@ pub fn test_set(vm: &mut Vm, instruction_data: InstructionData) {
};
let register = Register::Pointer(pointer);
vm.set_register(a, register);
vm.ip += 1;
record.set_register(a, register);
}
vm.execute_next_runner();
}
pub fn equal(vm: &mut Vm, instruction_data: InstructionData) {
pub fn equal(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData {
b,
c,
@ -415,20 +368,17 @@ pub fn equal(vm: &mut Vm, instruction_data: InstructionData) {
d,
..
} = instruction_data;
let left = vm.get_argument(b, b_is_constant);
let right = vm.get_argument(c, c_is_constant);
let left = record.get_argument(b, b_is_constant);
let right = record.get_argument(c, c_is_constant);
let is_equal = left == right;
if is_equal == d {
vm.ip += 2;
record.ip += 2;
} else {
vm.ip += 1;
}
vm.execute_next_runner();
}
pub fn less(vm: &mut Vm, instruction_data: InstructionData) {
pub fn less(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData {
b,
c,
@ -437,20 +387,17 @@ pub fn less(vm: &mut Vm, instruction_data: InstructionData) {
d,
..
} = instruction_data;
let left = vm.get_argument(b, b_is_constant);
let right = vm.get_argument(c, c_is_constant);
let left = record.get_argument(b, b_is_constant);
let right = record.get_argument(c, c_is_constant);
let is_less = left < right;
if is_less == d {
vm.ip += 2;
record.ip += 2;
} else {
vm.ip += 1;
}
vm.execute_next_runner();
}
pub fn less_equal(vm: &mut Vm, instruction_data: InstructionData) {
pub fn less_equal(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData {
b,
c,
@ -459,107 +406,77 @@ pub fn less_equal(vm: &mut Vm, instruction_data: InstructionData) {
d,
..
} = instruction_data;
let left = vm.get_argument(b, b_is_constant);
let right = vm.get_argument(c, c_is_constant);
let left = record.get_argument(b, b_is_constant);
let right = record.get_argument(c, c_is_constant);
let is_less_or_equal = left <= right;
if is_less_or_equal == d {
vm.ip += 2;
record.ip += 2;
} else {
vm.ip += 1;
}
vm.execute_next_runner();
}
pub fn negate(vm: &mut Vm, instruction_data: InstructionData) {
pub fn negate(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData {
a,
b,
b_is_constant,
..
} = instruction_data;
let argument = vm.get_argument(b, b_is_constant);
let negated = match argument {
Value::Concrete(value) => match value {
ConcreteValue::Float(float) => ConcreteValue::Float(-float),
ConcreteValue::Integer(integer) => ConcreteValue::Integer(-integer),
_ => panic!("Value Error: Cannot negate value"),
},
Value::Abstract(_) => panic!("VM Error: Cannot negate value"),
};
let register = Register::Value(Value::Concrete(negated));
let argument = record.get_argument(b, b_is_constant);
let negated = argument.negate();
let register = Register::Value(negated);
vm.set_register(a, register);
vm.ip += 1;
vm.execute_next_runner();
record.set_register(a, register);
}
pub fn not(vm: &mut Vm, instruction_data: InstructionData) {
pub fn not(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData {
a,
b,
b_is_constant,
..
} = instruction_data;
let argument = vm.get_argument(b, b_is_constant);
let argument = record.get_argument(b, b_is_constant);
let not = match argument {
Value::Concrete(ConcreteValue::Boolean(boolean)) => ConcreteValue::Boolean(!boolean),
_ => panic!("VM Error: Expected boolean value for NOT operation"),
};
let register = Register::Value(Value::Concrete(not));
vm.set_register(a, register);
vm.ip += 1;
vm.execute_next_runner();
record.set_register(a, register);
}
pub fn jump(vm: &mut Vm, instruction_data: InstructionData) {
pub fn jump(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData { b, c, .. } = instruction_data;
let offset = b as usize;
let is_positive = c != 0;
if is_positive {
vm.ip += offset + 1
record.ip += offset + 1
} else {
vm.ip -= offset
record.ip -= offset
}
vm.execute_next_runner();
}
pub fn call(vm: &mut Vm<'_>, instruction_data: InstructionData) {
let InstructionData {
a,
b,
c,
b_is_constant,
..
} = instruction_data;
let function = vm.get_argument(b, b_is_constant);
let mut function_vm = if let Value::Concrete(ConcreteValue::Function(chunk)) = function {
Vm::new(chunk, Some(vm), None)
} else if let Value::Abstract(AbstractValue::FunctionSelf) = function {
Vm::new(vm.chunk, Some(vm), Some(vm.runners.clone()))
} else {
panic!("VM Error: Expected function")
};
pub fn call(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData { a, b, c, .. } = instruction_data;
let prototype = record.get_prototype(b);
let first_argument_index = a - c;
let mut argument_index = 0;
for argument_register_index in first_argument_index..a {
let target_register_is_empty =
matches!(vm.stack[argument_register_index as usize], Register::Empty);
let target_register_is_empty = matches!(
record.stack[argument_register_index as usize],
Register::Empty
);
if target_register_is_empty {
continue;
}
function_vm.set_register(
function_record.set_register(
argument_index as u8,
Register::Pointer(Pointer::ParentStack(argument_register_index)),
);
@ -567,31 +484,27 @@ pub fn call(vm: &mut Vm<'_>, instruction_data: InstructionData) {
argument_index += 1;
}
let return_value = function_vm.run();
let return_value = function_record.run();
if let Some(concrete_value) = return_value {
let register = Register::Value(concrete_value);
vm.set_register(a, register);
record.set_register(a, register);
}
vm.ip += 1;
vm.execute_next_runner();
}
pub fn call_native(vm: &mut Vm, instruction_data: InstructionData) {
pub fn call_native(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData { a, b, c, .. } = instruction_data;
let first_argument_index = (a - c) as usize;
let argument_range = first_argument_index..a as usize;
let mut arguments: SmallVec<[&Value; 4]> = SmallVec::new();
for register_index in argument_range {
let register = &vm.stack[register_index];
let register = &record.stack[register_index];
let value = match register {
Register::Value(value) => value,
Register::Pointer(pointer) => {
let value_option = vm.follow_pointer_allow_empty(*pointer);
let value_option = record.follow_pointer_allow_empty(*pointer);
match value_option {
Some(value) => value,
@ -605,30 +518,24 @@ pub fn call_native(vm: &mut Vm, instruction_data: InstructionData) {
}
let function = NativeFunction::from(b);
let return_value = function.call(vm, arguments).unwrap();
let return_value = function.call(record.arguments).unwrap();
if let Some(value) = return_value {
let register = Register::Value(value);
vm.set_register(a, register);
record.set_register(a, register);
}
vm.ip += 1;
vm.execute_next_runner();
}
pub fn r#return(vm: &mut Vm, instruction_data: InstructionData) {
pub fn r#return(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let should_return_value = instruction_data.b != 0;
if !should_return_value {
return vm.ip += 1;
}
if !should_return_value {}
if let Some(register_index) = &vm.last_assigned_register {
let return_value = vm.open_register(*register_index).clone();
if let Some(register_index) = &record.last_assigned_register {
let return_value = record.open_register(*register_index).clone();
vm.return_value = Some(return_value);
record.return_value = Some(return_value);
} else {
panic!("Stack underflow");
}
@ -642,7 +549,7 @@ mod tests {
use super::*;
const ALL_OPERATIONS: [(Operation, RunnerLogic); 24] = [
(Operation::MOVE, r#move),
(Operation::POINT, r#move),
(Operation::CLOSE, close),
(Operation::LOAD_BOOLEAN, load_boolean),
(Operation::LOAD_CONSTANT, load_constant),

View File

@ -0,0 +1,93 @@
use crate::{Chunk, Value};
use super::{call_stack::CallStack, record::Record, runner::RunAction, 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(|instruction| RunAction::from(instruction))
.collect();
let record = Record::new(
Vec::with_capacity(stack_size),
constants,
locals,
actions,
positions,
);
for chunk in prototypes {
create_records(chunk, records);
}
records.push(record);
}
pub struct Thread {
call_stack: CallStack,
records: Vec<Record>,
return_register: Option<u8>,
}
impl Thread {
pub fn new(chunk: Chunk) -> Self {
let call_stack = CallStack::new();
let mut records = Vec::with_capacity(chunk.prototypes().len());
create_records(chunk, &mut records);
Thread {
call_stack,
records,
return_register: None,
}
}
pub fn run(&mut self) -> Option<Value> {
assert!(!self.call_stack.is_empty());
let mut record = &mut self.records[0];
loop {
assert!(
record.ip < record.actions.len(),
"{}",
VmError::InstructionIndexOutOfBounds {
call_stack: self.call_stack.clone(),
ip: record.ip,
}
);
let action = record.actions[record.ip];
let signal = (action.logic)(action.data, &mut record);
match signal {
ThreadSignal::Continue => {
record.ip += 1;
}
ThreadSignal::Call(FunctionCall {
record_index,
return_register,
..
}) => {
record = &mut self.records[record_index];
self.return_register = Some(return_register);
}
ThreadSignal::Return(value_option) => {
let outer_call = self.call_stack.pop();
if outer_call.is_none() {
return value_option;
}
}
}
}
}
}
pub enum ThreadSignal {
Continue,
Call(FunctionCall),
Return(Option<Value>),
}