diff --git a/dust-lang/Cargo.toml b/dust-lang/Cargo.toml index 0481312..2090f1a 100644 --- a/dust-lang/Cargo.toml +++ b/dust-lang/Cargo.toml @@ -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 } diff --git a/dust-lang/src/chunk/disassembler.rs b/dust-lang/src/chunk/disassembler.rs index c5edceb..595e0bd 100644 --- a/dust-lang/src/chunk/disassembler.rs +++ b/dust-lang/src/chunk/disassembler.rs @@ -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::>().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) } } diff --git a/dust-lang/src/chunk/mod.rs b/dust-lang/src/chunk/mod.rs index 703033e..5fdc673 100644 --- a/dust-lang/src/chunk/mod.rs +++ b/dust-lang/src/chunk/mod.rs @@ -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, 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, stack_size: usize, } impl Chunk { - pub fn new( + pub(crate) fn new( name: Option, r#type: FunctionType, - instructions: SmallVec<[(Instruction, Span); 32]>, - constants: SmallVec<[Value; 16]>, - locals: SmallVec<[Local; 8]>, + instructions: impl Into>, + positions: impl Into>, + constants: impl Into>, + locals: impl Into>, + prototypes: impl Into>, 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, r#type: FunctionType, - instructions: impl Into>, + instructions: impl Into>, + positions: impl Into>, constants: impl Into>, locals: impl Into>, + prototypes: Vec, ) -> 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, + FunctionType, + SmallVec<[Instruction; 32]>, + SmallVec<[Span; 32]>, + SmallVec<[Value; 16]>, + SmallVec<[Local; 8]>, + Vec, + 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 { + &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 } } diff --git a/dust-lang/src/compiler/mod.rs b/dust-lang/src/compiler/mod.rs index 6062e6d..cf8dcb4 100644 --- a/dust-lang/src/compiler/mod.rs +++ b/dust-lang/src/compiler/mod.rs @@ -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 { /// See the [`compile`] function an example of how to create and use a Compiler. #[derive(Debug)] pub struct Compiler<'src> { - self_name: Option, - 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, + + /// Return type of the function being compiled. This is assigned to the chunk when + /// [`Compiler::finish`] is called. + return_type: Option, + + /// 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, + + /// 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, + /// 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::>(); + .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)); diff --git a/dust-lang/src/instruction/call.rs b/dust-lang/src/instruction/call.rs index 28dd473..979aedd 100644 --- a/dust-lang/src/instruction/call.rs +++ b/dust-lang/src/instruction/call.rs @@ -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 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) } } diff --git a/dust-lang/src/instruction/close.rs b/dust-lang/src/instruction/close.rs index ef33740..ff485a9 100644 --- a/dust-lang/src/instruction/close.rs +++ b/dust-lang/src/instruction/close.rs @@ -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 for Close { + fn from(instruction: InstructionData) -> Self { + Close { + from: instruction.b, + to: instruction.c, + } + } +} + impl From for Instruction { fn from(close: Close) -> Self { let operation = Operation::CLOSE; diff --git a/dust-lang/src/instruction/load_boolean.rs b/dust-lang/src/instruction/load_boolean.rs index 9937624..64c19b9 100644 --- a/dust-lang/src/instruction/load_boolean.rs +++ b/dust-lang/src/instruction/load_boolean.rs @@ -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 for LoadBoolean { + fn from(instruction: InstructionData) -> Self { + let destination = instruction.a; + let value = instruction.b != 0; + let jump_next = instruction.c != 0; LoadBoolean { destination, diff --git a/dust-lang/src/instruction/load_constant.rs b/dust-lang/src/instruction/load_constant.rs index b9bf47f..7c8598b 100644 --- a/dust-lang/src/instruction/load_constant.rs +++ b/dust-lang/src/instruction/load_constant.rs @@ -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 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 for Instruction { fn from(load_constant: LoadConstant) -> Self { let operation = Operation::LOAD_CONSTANT; @@ -30,3 +48,21 @@ impl From 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(()) + } + } +} diff --git a/dust-lang/src/instruction/load_function.rs b/dust-lang/src/instruction/load_function.rs new file mode 100644 index 0000000..99d6804 --- /dev/null +++ b/dust-lang/src/instruction/load_function.rs @@ -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 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) + } +} diff --git a/dust-lang/src/instruction/mod.rs b/dust-lang/src/instruction/mod.rs index d660411..3ff7257 100644 --- a/dust-lang/src/instruction/mod.rs +++ b/dust-lang/src/instruction/mod.rs @@ -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})") } } } diff --git a/dust-lang/src/instruction/move.rs b/dust-lang/src/instruction/move.rs index 4c92c25..887f94d 100644 --- a/dust-lang/src/instruction/move.rs +++ b/dust-lang/src/instruction/move.rs @@ -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 for Instruction { - fn from(r#move: Move) -> Self { - let operation = Operation::MOVE; +impl From 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 for Instruction { + fn from(r#move: Point) -> Self { + let operation = Operation::POINT; let b = r#move.from; let c = r#move.to; diff --git a/dust-lang/src/instruction/operation.rs b/dust-lang/src/instruction/operation.rs index c8dfc25..de4c541 100644 --- a/dust-lang/src/instruction/operation.rs +++ b/dust-lang/src/instruction/operation.rs @@ -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", diff --git a/dust-lang/src/lexer.rs b/dust-lang/src/lexer.rs index c66015c..02e7b07 100644 --- a/dust-lang/src/lexer.rs +++ b/dust-lang/src/lexer.rs @@ -46,7 +46,7 @@ pub fn lex(source: &str) -> Result, 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)] diff --git a/dust-lang/src/lib.rs b/dust-lang/src/lib.rs index 0133434..f1e8dbd 100644 --- a/dust-lang/src/lib.rs +++ b/dust-lang/src/lib.rs @@ -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; diff --git a/dust-lang/src/native_function/assertion.rs b/dust-lang/src/native_function/assertion.rs index 9077f63..3e73a8a 100644 --- a/dust-lang/src/native_function/assertion.rs +++ b/dust-lang/src/native_function/assertion.rs @@ -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, NativeFunctionError> { let mut message: Option = 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), diff --git a/dust-lang/src/native_function/io.rs b/dust-lang/src/native_function/io.rs index 41a1b16..153ceb8 100644 --- a/dust-lang/src/native_function/io.rs +++ b/dust-lang/src/native_function/io.rs @@ -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, NativeFunctionError> { +pub fn read_line( + record: &mut Record, + _: SmallVec<[&Value; 4]>, +) -> Result, 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, 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, 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, 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) diff --git a/dust-lang/src/type.rs b/dust-lang/src/type.rs index 86d6ec3..0b75e53 100644 --- a/dust-lang/src/type.rs +++ b/dust-lang/src/type.rs @@ -31,7 +31,7 @@ pub enum Type { Range { r#type: Box, }, - 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)) => { diff --git a/dust-lang/src/value/abstract_list.rs b/dust-lang/src/value/abstract_list.rs new file mode 100644 index 0000000..1b168f1 --- /dev/null +++ b/dust-lang/src/value/abstract_list.rs @@ -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, +} + +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, "]") + } +} diff --git a/dust-lang/src/value/abstract_value.rs b/dust-lang/src/value/abstract_value.rs deleted file mode 100644 index f4e626a..0000000 --- a/dust-lang/src/value/abstract_value.rs +++ /dev/null @@ -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, - }, -} - -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 = 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, "]") - } - } - } -} diff --git a/dust-lang/src/value/concrete_value.rs b/dust-lang/src/value/concrete_value.rs index 58bf1c1..2abdf63 100644 --- a/dust-lang/src/value/concrete_value.rs +++ b/dust-lang/src/value/concrete_value.rs @@ -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), Integer(i64), List(Vec), 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>>(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 { + 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 { @@ -168,18 +177,16 @@ impl ConcreteValue { Ok(product) } - pub fn negate(&self) -> Result { + 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 { @@ -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, "[")?; diff --git a/dust-lang/src/value/function.rs b/dust-lang/src/value/function.rs new file mode 100644 index 0000000..7e0ee11 --- /dev/null +++ b/dust-lang/src/value/function.rs @@ -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, + 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}") + } +} diff --git a/dust-lang/src/value/mod.rs b/dust-lang/src/value/mod.rs index 61f94e7..70e21b7 100644 --- a/dust-lang/src/value/mod.rs +++ b/dust-lang/src/value/mod.rs @@ -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 { - 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 { match (self, other) { (Value::Concrete(left), Value::Concrete(right)) => { @@ -91,11 +107,13 @@ impl Value { } } - pub fn negate(&self) -> Result { - 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 { @@ -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"), } } } diff --git a/dust-lang/src/vm/call_stack.rs b/dust-lang/src/vm/call_stack.rs new file mode 100644 index 0000000..367ed8e --- /dev/null +++ b/dust-lang/src/vm/call_stack.rs @@ -0,0 +1,52 @@ +use std::fmt::{self, Debug, Display, Formatter}; + +use super::FunctionCall; + +#[derive(Clone, PartialEq)] +pub struct CallStack { + pub calls: Vec, +} + +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 { + 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(()) + } +} diff --git a/dust-lang/src/vm/error.rs b/dust-lang/src/vm/error.rs new file mode 100644 index 0000000..77d69ae --- /dev/null +++ b/dust-lang/src/vm/error.rs @@ -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) + } + } + } +} diff --git a/dust-lang/src/vm/mod.rs b/dust-lang/src/vm/mod.rs index 0c58928..969fb8c 100644 --- a/dust-lang/src/vm/mod.rs +++ b/dust-lang/src/vm/mod.rs @@ -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, 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, - - runners: Vec, - chunk: &'a Chunk, - parent: Option<&'a Vm<'a>>, - - ip: usize, - last_assigned_register: Option, - return_value: Option, +pub struct Vm { + threads: Vec, } -impl<'a> Vm<'a> { - pub fn new(chunk: &'a Chunk, parent: Option<&'a Vm<'a>>, runners: Option>) -> 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 { - 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, +} diff --git a/dust-lang/src/vm/record.rs b/dust-lang/src/vm/record.rs new file mode 100644 index 0000000..a85bd97 --- /dev/null +++ b/dust-lang/src/vm/record.rs @@ -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, + constants: SmallVec<[Value; 16]>, + locals: SmallVec<[Local; 8]>, + + last_assigned_register: Option, +} + +impl Record { + pub fn new( + stack: Vec, + 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 { + 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 + } +} diff --git a/dust-lang/src/vm/runner.rs b/dust-lang/src/vm/runner.rs index 49332f6..175e680 100644 --- a/dust-lang/src/vm/runner.rs +++ b/dust-lang/src/vm/runner.rs @@ -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 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), diff --git a/dust-lang/src/vm/thread.rs b/dust-lang/src/vm/thread.rs new file mode 100644 index 0000000..7ee3f2b --- /dev/null +++ b/dust-lang/src/vm/thread.rs @@ -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) { + 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, + return_register: Option, +} + +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 { + 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), +}