From 9ae923febd24b35db4e54fa33f068b22d88e97dd Mon Sep 17 00:00:00 2001 From: Jeff Date: Sat, 14 Dec 2024 00:45:49 -0500 Subject: [PATCH] Optimize; Revert to branch-style comparisons for performance --- dust-cli/src/main.rs | 21 +- dust-lang/src/compiler/mod.rs | 84 +- dust-lang/src/instruction/equal.rs | 12 +- dust-lang/src/instruction/less.rs | 12 +- dust-lang/src/instruction/less_equal.rs | 12 +- dust-lang/src/instruction/mod.rs | 61 +- dust-lang/src/value/abstract_value.rs | 12 +- dust-lang/src/value/mod.rs | 8 +- dust-lang/src/vm.rs | 1107 ----------------------- dust-lang/src/vm/mod.rs | 462 ++++++++++ dust-lang/src/vm/runners.rs | 528 +++++++++++ 11 files changed, 1076 insertions(+), 1243 deletions(-) delete mode 100644 dust-lang/src/vm.rs create mode 100644 dust-lang/src/vm/mod.rs create mode 100644 dust-lang/src/vm/runners.rs diff --git a/dust-cli/src/main.rs b/dust-cli/src/main.rs index 35c9796..0cceb84 100644 --- a/dust-cli/src/main.rs +++ b/dust-cli/src/main.rs @@ -199,25 +199,16 @@ fn main() { let chunk = compiler.finish(None, None); let compile_end = start_time.elapsed(); - let mut vm = Vm::new(&source, &chunk, None); + let vm = Vm::new(&source, &chunk, None); + let return_value = vm.run(); + let run_end = start_time.elapsed(); - match vm.run() { - Ok(Some(value)) => { - if !mode.no_output { - println!("{}", value) - } - } - Ok(None) => {} - Err(error) => { - let dust_error = DustError::runtime(error, &source); - let report = dust_error.report(); - - eprintln!("{report}"); + if let Some(value) = return_value { + if !mode.no_output { + println!("{}", value) } } - let run_end = start_time.elapsed(); - if mode.time { let compile_time = compile_end.as_micros(); let run_time = run_end - compile_end; diff --git a/dust-lang/src/compiler/mod.rs b/dust-lang/src/compiler/mod.rs index 5b3dea8..e249fb2 100644 --- a/dust-lang/src/compiler/mod.rs +++ b/dust-lang/src/compiler/mod.rs @@ -856,12 +856,12 @@ impl<'src> Compiler<'src> { let destination = self.next_register(); let comparison = match operator { - Token::DoubleEqual => Instruction::equal(destination, true, left, right), - Token::BangEqual => Instruction::equal(destination, false, left, right), - Token::Less => Instruction::less(destination, true, left, right), - Token::LessEqual => Instruction::less_equal(destination, true, left, right), - Token::Greater => Instruction::less_equal(destination, false, left, right), - Token::GreaterEqual => Instruction::less(destination, false, left, right), + Token::DoubleEqual => Instruction::equal(true, left, right), + Token::BangEqual => Instruction::equal(false, left, right), + Token::Less => Instruction::less(true, left, right), + Token::LessEqual => Instruction::less_equal(true, left, right), + Token::Greater => Instruction::less_equal(false, left, right), + Token::GreaterEqual => Instruction::less(false, left, right), _ => { return Err(CompileError::ExpectedTokenMultiple { expected: &[ @@ -877,8 +877,14 @@ impl<'src> Compiler<'src> { }) } }; + let jump = Instruction::jump(1, true); + let load_false = Instruction::load_boolean(destination, false, true); + let load_true = Instruction::load_boolean(destination, true, false); self.emit_instruction(comparison, Type::Boolean, operator_position); + self.emit_instruction(jump, Type::None, operator_position); + self.emit_instruction(load_false, Type::Boolean, operator_position); + self.emit_instruction(load_true, Type::Boolean, operator_position); Ok(()) } @@ -1132,7 +1138,19 @@ impl<'src> Compiler<'src> { self.advance()?; self.parse_expression()?; - if let Some((instruction, _, _)) = self.instructions.last() { + if matches!( + self.get_last_operations(), + Some([ + Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL, + Operation::JUMP, + Operation::LOAD_BOOLEAN, + Operation::LOAD_BOOLEAN + ]), + ) { + self.instructions.pop(); + self.instructions.pop(); + self.instructions.pop(); + } else if let Some((instruction, _, _)) = self.instructions.last() { let argument = match instruction.as_argument() { Some(argument) => argument, None => { @@ -1264,42 +1282,42 @@ impl<'src> Compiler<'src> { self.parse_expression()?; - let (expression_instruction, expression_type, expression_position) = - self.instructions.last().unwrap(); - - if expression_type != &Type::Boolean { - return Err(CompileError::ExpectedFunction { - found: self.previous_token.to_owned(), - actual_type: expression_type.clone(), - position: *expression_position, - }); - } - - let test_argument = match expression_instruction.as_argument() { - Some(argument) => argument, - None => { - return Err(CompileError::ExpectedExpression { - found: self.previous_token.to_owned(), - position: *expression_position, - }) - } - }; - let test = Instruction::test(test_argument, true); - - self.emit_instruction(test, Type::None, self.current_position); - if matches!( self.get_last_operations(), Some([ Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL, Operation::JUMP, Operation::LOAD_BOOLEAN, - Operation::LOAD_BOOLEAN, - ],) + Operation::LOAD_BOOLEAN + ]), ) { self.instructions.pop(); self.instructions.pop(); self.instructions.pop(); + } else { + let (expression_instruction, expression_type, expression_position) = + self.instructions.last().unwrap(); + + if expression_type != &Type::Boolean { + return Err(CompileError::ExpectedFunction { + found: self.previous_token.to_owned(), + actual_type: expression_type.clone(), + position: *expression_position, + }); + } + + let test_argument = match expression_instruction.as_argument() { + Some(argument) => argument, + None => { + return Err(CompileError::ExpectedExpression { + found: self.previous_token.to_owned(), + position: *expression_position, + }) + } + }; + let test = Instruction::test(test_argument, true); + + self.emit_instruction(test, Type::None, self.current_position); } let block_start = self.instructions.len(); diff --git a/dust-lang/src/instruction/equal.rs b/dust-lang/src/instruction/equal.rs index 6091236..ebf5717 100644 --- a/dust-lang/src/instruction/equal.rs +++ b/dust-lang/src/instruction/equal.rs @@ -1,7 +1,6 @@ use crate::{Argument, Instruction, Operation}; pub struct Equal { - pub destination: u8, pub value: bool, pub left: Argument, pub right: Argument, @@ -9,27 +8,20 @@ pub struct Equal { impl From<&Instruction> for Equal { fn from(instruction: &Instruction) -> Self { - let destination = instruction.a_field(); let value = instruction.d_field(); let (left, right) = instruction.b_and_c_as_arguments(); - Equal { - destination, - value, - left, - right, - } + Equal { value, left, right } } } impl From for Instruction { fn from(equal: Equal) -> Self { let operation = Operation::EQUAL; - let a = equal.destination; let (b, b_is_constant) = equal.left.as_index_and_constant_flag(); let (c, c_is_constant) = equal.right.as_index_and_constant_flag(); let d = equal.value; - Instruction::new(operation, a, b, c, b_is_constant, c_is_constant, d) + Instruction::new(operation, 0, b, c, b_is_constant, c_is_constant, d) } } diff --git a/dust-lang/src/instruction/less.rs b/dust-lang/src/instruction/less.rs index 6e87ed3..3efcba8 100644 --- a/dust-lang/src/instruction/less.rs +++ b/dust-lang/src/instruction/less.rs @@ -1,7 +1,6 @@ use crate::{Argument, Instruction, Operation}; pub struct Less { - pub destination: u8, pub value: bool, pub left: Argument, pub right: Argument, @@ -9,27 +8,20 @@ pub struct Less { impl From<&Instruction> for Less { fn from(instruction: &Instruction) -> Self { - let destination = instruction.a_field(); let value = instruction.d_field(); let (left, right) = instruction.b_and_c_as_arguments(); - Less { - destination, - value, - left, - right, - } + Less { value, left, right } } } impl From for Instruction { fn from(less: Less) -> Self { let operation = Operation::LESS; - let a = less.destination; let (b, b_is_constant) = less.left.as_index_and_constant_flag(); let (c, c_is_constant) = less.right.as_index_and_constant_flag(); let d = less.value; - Instruction::new(operation, a, b, c, b_is_constant, c_is_constant, d) + Instruction::new(operation, 0, b, c, b_is_constant, c_is_constant, d) } } diff --git a/dust-lang/src/instruction/less_equal.rs b/dust-lang/src/instruction/less_equal.rs index 2ccd0cc..c2b929b 100644 --- a/dust-lang/src/instruction/less_equal.rs +++ b/dust-lang/src/instruction/less_equal.rs @@ -1,7 +1,6 @@ use crate::{Argument, Instruction, Operation}; pub struct LessEqual { - pub destination: u8, pub value: bool, pub left: Argument, pub right: Argument, @@ -9,27 +8,20 @@ pub struct LessEqual { impl From<&Instruction> for LessEqual { fn from(instruction: &Instruction) -> Self { - let destination = instruction.a_field(); let value = instruction.d_field(); let (left, right) = instruction.b_and_c_as_arguments(); - LessEqual { - destination, - value, - left, - right, - } + LessEqual { value, left, right } } } impl From for Instruction { fn from(less_equal: LessEqual) -> Self { let operation = Operation::LESS_EQUAL; - let a = less_equal.destination; let (b, b_options) = less_equal.left.as_index_and_constant_flag(); let (c, c_options) = less_equal.right.as_index_and_constant_flag(); let d = less_equal.value; - Instruction::new(operation, a, b, c, b_options, c_options, d) + Instruction::new(operation, 0, b, c, b_options, c_options, d) } } diff --git a/dust-lang/src/instruction/mod.rs b/dust-lang/src/instruction/mod.rs index 59f08f5..d660411 100644 --- a/dust-lang/src/instruction/mod.rs +++ b/dust-lang/src/instruction/mod.rs @@ -152,7 +152,7 @@ use crate::NativeFunction; #[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] pub struct Instruction(u32); -#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] pub struct InstructionData { pub a: u8, pub b: u8, @@ -343,36 +343,16 @@ impl Instruction { }) } - pub fn equal(destination: u8, value: bool, left: Argument, right: Argument) -> Instruction { - Instruction::from(Equal { - destination, - value, - left, - right, - }) + pub fn equal(value: bool, left: Argument, right: Argument) -> Instruction { + Instruction::from(Equal { value, left, right }) } - pub fn less(destination: u8, value: bool, left: Argument, right: Argument) -> Instruction { - Instruction::from(Less { - destination, - value, - left, - right, - }) + pub fn less(value: bool, left: Argument, right: Argument) -> Instruction { + Instruction::from(Less { value, left, right }) } - pub fn less_equal( - destination: u8, - value: bool, - left: Argument, - right: Argument, - ) -> Instruction { - Instruction::from(LessEqual { - destination, - value, - left, - right, - }) + pub fn less_equal(value: bool, left: Argument, right: Argument) -> Instruction { + Instruction::from(LessEqual { value, left, right }) } pub fn negate(destination: u8, argument: Argument) -> Instruction { @@ -661,37 +641,22 @@ impl Instruction { format!("if {bang}{argument} {{ JUMP +1 }} else {{ R{destination} = {argument} }}") } Operation::EQUAL => { - let Equal { - destination, - value, - left, - right, - } = Equal::from(self); + let Equal { value, left, right } = Equal::from(self); let comparison_symbol = if value { "==" } else { "!=" }; - format!("R{destination} = {left} {comparison_symbol} {right}") + format!("if {left} {comparison_symbol} {right} {{ JUMP +1 }}") } Operation::LESS => { - let Less { - destination, - value, - left, - right, - } = Less::from(self); + let Less { value, left, right } = Less::from(self); let comparison_symbol = if value { "<" } else { ">=" }; - format!("R{destination} = {left} {comparison_symbol} {right}") + format!("if {left} {comparison_symbol} {right} {{ JUMP +1 }}") } Operation::LESS_EQUAL => { - let LessEqual { - destination, - value, - left, - right, - } = LessEqual::from(self); + let LessEqual { value, left, right } = LessEqual::from(self); let comparison_symbol = if value { "<=" } else { ">" }; - format!("R{destination} = {left} {comparison_symbol} {right}") + format!("if {left} {comparison_symbol} {right} {{ JUMP +1 }}") } Operation::NEGATE => { let Negate { diff --git a/dust-lang/src/value/abstract_value.rs b/dust-lang/src/value/abstract_value.rs index c51fa57..0407fb5 100644 --- a/dust-lang/src/value/abstract_value.rs +++ b/dust-lang/src/value/abstract_value.rs @@ -17,23 +17,23 @@ impl AbstractValue { ValueRef::Abstract(self) } - pub fn to_concrete_owned(&self, vm: &Vm) -> Result { + pub fn to_concrete_owned(&self, vm: &Vm) -> ConcreteValue { match self { - AbstractValue::FunctionSelf => Ok(ConcreteValue::function(vm.chunk().clone())), + 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_option = vm.follow_pointer_allow_empty(*pointer); let item = match item_option { - Some(value_ref) => value_ref.into_concrete_owned(vm)?, + Some(value_ref) => value_ref.into_concrete_owned(vm), None => continue, }; items.push(item); } - Ok(ConcreteValue::List(items)) + ConcreteValue::List(items) } } } @@ -54,7 +54,7 @@ impl AbstractValue { display.push_str(", "); } - let item_display = vm.follow_pointer(*item)?.display(vm)?; + let item_display = vm.follow_pointer(*item).display(vm)?; display.push_str(&item_display); } diff --git a/dust-lang/src/value/mod.rs b/dust-lang/src/value/mod.rs index 8e8204a..d155fcb 100644 --- a/dust-lang/src/value/mod.rs +++ b/dust-lang/src/value/mod.rs @@ -25,10 +25,10 @@ impl Value { } } - pub fn into_concrete_owned(self, vm: &Vm) -> Result { + pub fn into_concrete_owned(self, vm: &Vm) -> ConcreteValue { match self { Value::Abstract(abstract_value) => abstract_value.to_concrete_owned(vm), - Value::Concrete(concrete_value) => Ok(concrete_value), + Value::Concrete(concrete_value) => concrete_value, } } @@ -64,10 +64,10 @@ impl ValueRef<'_> { } } - pub fn into_concrete_owned(self, vm: &Vm) -> Result { + pub fn into_concrete_owned(self, vm: &Vm) -> ConcreteValue { match self { ValueRef::Abstract(abstract_value) => abstract_value.to_concrete_owned(vm), - ValueRef::Concrete(concrete_value) => Ok(concrete_value.clone()), + ValueRef::Concrete(concrete_value) => concrete_value.clone(), } } diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs deleted file mode 100644 index 2b6bfa8..0000000 --- a/dust-lang/src/vm.rs +++ /dev/null @@ -1,1107 +0,0 @@ -//! Virtual machine and errors -use std::{ - fmt::{self, Display, Formatter}, - io, -}; - -use smallvec::SmallVec; - -use crate::{ - compile, instruction::*, AbstractValue, AnnotatedError, Chunk, ConcreteValue, DustError, - Instruction, NativeFunction, NativeFunctionError, Operation, Span, Value, ValueError, ValueRef, -}; - -pub fn run(source: &str) -> Result, DustError> { - let chunk = compile(source)?; - let mut vm = Vm::new(source, &chunk, None); - - vm.run().map_err(|error| DustError::runtime(error, source)) -} - -type Runner = fn(&mut Vm, InstructionData) -> Result<(), VmError>; - -const RUNNERS: [Runner; 23] = [ - Vm::r#move, - Vm::close, - Vm::load_boolean, - Vm::load_constant, - Vm::load_list, - Vm::load_self, - Vm::get_local, - Vm::set_local, - Vm::add, - Vm::subtract, - Vm::multiply, - Vm::divide, - Vm::modulo, - Vm::test, - Vm::test_set, - Vm::equal, - Vm::less, - Vm::less_equal, - Vm::negate, - Vm::not, - Vm::call, - Vm::call_native, - Vm::jump, -]; - -/// Dust virtual machine. -/// -/// See the [module-level documentation](index.html) for more information. -#[derive(Debug)] -pub struct Vm<'a> { - stack: Vec, - - chunk: &'a Chunk, - parent: Option<&'a Vm<'a>>, - - ip: usize, - last_assigned_register: Option, - source: &'a str, -} - -impl<'a> Vm<'a> { - pub fn new(source: &'a str, chunk: &'a Chunk, parent: Option<&'a Vm<'a>>) -> Self { - let stack = vec![Register::Empty; chunk.stack_size()]; - - Self { - chunk, - stack, - parent, - ip: 0, - last_assigned_register: None, - source, - } - } - - pub fn chunk(&self) -> &Chunk { - self.chunk - } - - pub fn source(&self) -> &'a str { - self.source - } - - pub fn current_position(&self) -> Span { - let index = self.ip.saturating_sub(1); - let (_, position) = self.chunk.instructions()[index]; - - position - } - - #[allow(clippy::needless_lifetimes)] - fn r#move<'b, 'c>( - vm: &'b mut Vm<'c>, - instruction_data: InstructionData, - ) -> Result<(), VmError> { - 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)); - - if from_register_has_value { - vm.set_register(c, register)?; - } - - Ok(()) - } - - #[allow(clippy::needless_lifetimes)] - fn close<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> { - let InstructionData { b, c, .. } = instruction_data; - - if vm.stack.len() < c as usize { - return Err(VmError::StackUnderflow { - position: vm.current_position(), - }); - } - - for register_index in b..c { - vm.stack[register_index as usize] = Register::Empty; - } - - Ok(()) - } - - #[allow(clippy::needless_lifetimes)] - fn load_boolean<'b, 'c>( - vm: &'b mut Vm<'c>, - instruction_data: InstructionData, - ) -> Result<(), VmError> { - let InstructionData { a, b, c, .. } = instruction_data; - let boolean = ConcreteValue::Boolean(b != 0).to_value(); - let register = Register::Value(boolean); - - vm.set_register(a, register)?; - - if c != 0 { - vm.jump_instructions(1, true); - } - - Ok(()) - } - - #[allow(clippy::needless_lifetimes)] - fn load_constant<'b, 'c>( - vm: &'b mut Vm<'c>, - instruction_data: InstructionData, - ) -> Result<(), VmError> { - let InstructionData { a, b, c, .. } = instruction_data; - let register = Register::Pointer(Pointer::Constant(b)); - - vm.set_register(a, register)?; - - if c != 0 { - vm.jump_instructions(1, true); - } - - Ok(()) - } - - #[allow(clippy::needless_lifetimes)] - fn load_list<'b, 'c>( - vm: &'b mut Vm<'c>, - instruction_data: InstructionData, - ) -> Result<(), VmError> { - let InstructionData { a, b, .. } = instruction_data; - let mut item_pointers = Vec::new(); - let stack = vm.stack.as_slice(); - - for register_index in b..a { - if let Register::Empty = stack[register_index as usize] { - continue; - } - - let pointer = Pointer::Stack(register_index); - - item_pointers.push(pointer); - } - - let list_value = AbstractValue::List { item_pointers }.to_value(); - let register = Register::Value(list_value); - - vm.set_register(a, register) - } - - #[allow(clippy::needless_lifetimes)] - fn load_self<'b, 'c>( - vm: &'b mut Vm<'c>, - instruction_data: InstructionData, - ) -> Result<(), VmError> { - let InstructionData { a, .. } = instruction_data; - let register = Register::Value(AbstractValue::FunctionSelf.to_value()); - - vm.set_register(a, register) - } - - #[allow(clippy::needless_lifetimes)] - fn get_local<'b, 'c>( - vm: &'b mut Vm<'c>, - instruction_data: InstructionData, - ) -> Result<(), VmError> { - let InstructionData { a, b, .. } = instruction_data; - let local_register_index = vm.get_local_register(b)?; - let register = Register::Pointer(Pointer::Stack(local_register_index)); - - vm.set_register(a, register) - } - - #[allow(clippy::needless_lifetimes)] - fn set_local<'b, 'c>( - vm: &'b mut Vm<'c>, - instruction_data: InstructionData, - ) -> Result<(), VmError> { - let InstructionData { b, c, .. } = instruction_data; - let local_register_index = vm.get_local_register(c)?; - let register = Register::Pointer(Pointer::Stack(b)); - - vm.set_register(local_register_index, register) - } - - #[allow(clippy::needless_lifetimes)] - fn add<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> { - let InstructionData { - a, - b, - c, - b_is_constant, - c_is_constant, - .. - } = instruction_data; - let left = vm.get_argument(b, b_is_constant)?; - let right = vm.get_argument(c, c_is_constant)?; - let sum_result = left.add(right); - let sum = match sum_result { - Ok(sum) => sum, - Err(error) => { - return Err(VmError::Value { - error, - position: vm.current_position(), - }); - } - }; - let register = Register::Value(sum); - - vm.set_register(a, register) - } - - #[allow(clippy::needless_lifetimes)] - fn subtract<'b, 'c>( - vm: &'b mut Vm<'c>, - instruction_data: InstructionData, - ) -> Result<(), VmError> { - let InstructionData { - a, - b, - c, - b_is_constant, - c_is_constant, - .. - } = instruction_data; - let left = vm.get_argument(b, b_is_constant)?; - let right = vm.get_argument(c, c_is_constant)?; - let subtraction_result = left.subtract(right); - let difference = match subtraction_result { - Ok(difference) => difference, - Err(error) => { - return Err(VmError::Value { - error, - position: vm.current_position(), - }); - } - }; - let register = Register::Value(difference); - - vm.set_register(a, register) - } - - #[allow(clippy::needless_lifetimes)] - fn multiply<'b, 'c>( - vm: &'b mut Vm<'c>, - instruction_data: InstructionData, - ) -> Result<(), VmError> { - let InstructionData { - a, - b, - c, - b_is_constant, - c_is_constant, - .. - } = instruction_data; - let left = vm.get_argument(b, b_is_constant)?; - let right = vm.get_argument(c, c_is_constant)?; - let multiplication_result = left.multiply(right); - let product = match multiplication_result { - Ok(product) => product, - Err(error) => { - return Err(VmError::Value { - error, - position: vm.current_position(), - }); - } - }; - let register = Register::Value(product); - - vm.set_register(a, register) - } - - #[allow(clippy::needless_lifetimes)] - fn divide<'b, 'c>( - vm: &'b mut Vm<'c>, - instruction_data: InstructionData, - ) -> Result<(), VmError> { - let InstructionData { - a, - b, - c, - b_is_constant, - c_is_constant, - .. - } = instruction_data; - let left = vm.get_argument(b, b_is_constant)?; - let right = vm.get_argument(c, c_is_constant)?; - let division_result = left.divide(right); - let quotient = match division_result { - Ok(quotient) => quotient, - Err(error) => { - return Err(VmError::Value { - error, - position: vm.current_position(), - }); - } - }; - let register = Register::Value(quotient); - - vm.set_register(a, register) - } - - #[allow(clippy::needless_lifetimes)] - fn modulo<'b, 'c>( - vm: &'b mut Vm<'c>, - instruction_data: InstructionData, - ) -> Result<(), VmError> { - let InstructionData { - a, - b, - c, - b_is_constant, - c_is_constant, - .. - } = instruction_data; - let left = vm.get_argument(b, b_is_constant)?; - let right = vm.get_argument(c, c_is_constant)?; - let modulo_result = left.modulo(right); - let remainder = match modulo_result { - Ok(remainder) => remainder, - Err(error) => { - return Err(VmError::Value { - error, - position: vm.current_position(), - }); - } - }; - let register = Register::Value(remainder); - - vm.set_register(a, register) - } - - #[allow(clippy::needless_lifetimes)] - fn test<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> { - let InstructionData { - b, - b_is_constant, - c, - .. - } = instruction_data; - let value = vm.get_argument(b, b_is_constant)?; - let boolean = if let ValueRef::Concrete(ConcreteValue::Boolean(boolean)) = value { - *boolean - } else { - panic!( - "VM Error: Expected boolean value for TEST operation at {}", - vm.current_position() - ); - }; - let test_value = c != 0; - - if boolean == test_value { - vm.jump_instructions(1, true); - } - - Ok(()) - } - - #[allow(clippy::needless_lifetimes)] - fn test_set<'b, 'c>( - vm: &'b mut Vm<'c>, - instruction_data: InstructionData, - ) -> Result<(), VmError> { - let InstructionData { - a, - b, - c, - b_is_constant, - .. - } = instruction_data; - let value = vm.get_argument(b, b_is_constant)?; - let boolean = if let ValueRef::Concrete(ConcreteValue::Boolean(boolean)) = value { - *boolean - } else { - panic!( - "VM Error: Expected boolean value for TEST_SET operation at {}", - vm.current_position() - ); - }; - let test_value = c != 0; - - if boolean == test_value { - vm.jump_instructions(1, true); - } else { - let pointer = if b_is_constant { - Pointer::Constant(b) - } else { - Pointer::Stack(b) - }; - let register = Register::Pointer(pointer); - - vm.set_register(a, register)?; - } - - Ok(()) - } - - #[allow(clippy::needless_lifetimes)] - fn equal<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> { - let InstructionData { - a, - b, - c, - b_is_constant, - c_is_constant, - d, - .. - } = instruction_data; - let left = vm.get_argument(b, b_is_constant)?; - let right = vm.get_argument(c, c_is_constant)?; - let equal_result = left.equal(right).map_err(|error| VmError::Value { - error, - position: vm.current_position(), - })?; - let is_equal = if let Value::Concrete(ConcreteValue::Boolean(is_equal)) = equal_result { - is_equal - } else { - panic!( - "VM Error: Expected boolean value for EQUAL operation at {}", - vm.current_position() - ); - }; - let comparison = is_equal == d; - let register = Register::Value(Value::Concrete(ConcreteValue::Boolean(comparison))); - - vm.set_register(a, register) - } - - #[allow(clippy::needless_lifetimes)] - fn less<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> { - let InstructionData { - a, - b, - c, - b_is_constant, - c_is_constant, - d, - .. - } = instruction_data; - let left = vm.get_argument(b, b_is_constant)?; - let right = vm.get_argument(c, c_is_constant)?; - let less_result = left.less(right).map_err(|error| VmError::Value { - error, - position: vm.current_position(), - })?; - let is_less = if let Value::Concrete(ConcreteValue::Boolean(is_less)) = less_result { - is_less - } else { - panic!( - "VM Error: Expected boolean value for LESS operation at {}", - vm.current_position() - ); - }; - let comparison = is_less == d; - let register = Register::Value(Value::Concrete(ConcreteValue::Boolean(comparison))); - - vm.set_register(a, register) - } - - #[allow(clippy::needless_lifetimes)] - fn less_equal<'b, 'c>( - vm: &'b mut Vm<'c>, - instruction_data: InstructionData, - ) -> Result<(), VmError> { - let InstructionData { - a, - b, - c, - b_is_constant, - c_is_constant, - d, - .. - } = instruction_data; - let left = vm.get_argument(b, b_is_constant)?; - let right = vm.get_argument(c, c_is_constant)?; - let less_or_equal_result = left.less_equal(right).map_err(|error| VmError::Value { - error, - position: vm.current_position(), - })?; - let is_less_or_equal = if let Value::Concrete(ConcreteValue::Boolean(is_less_or_equal)) = - less_or_equal_result - { - is_less_or_equal - } else { - panic!( - "VM Error: Expected boolean value for LESS_EQUAl operation at {}", - vm.current_position() - ); - }; - let comparison = is_less_or_equal == d; - let register = Register::Value(Value::Concrete(ConcreteValue::Boolean(comparison))); - - vm.set_register(a, register) - } - - #[allow(clippy::needless_lifetimes)] - fn negate<'b, 'c>( - vm: &'b mut Vm<'c>, - instruction_data: InstructionData, - ) -> Result<(), VmError> { - let InstructionData { - a, - b, - b_is_constant, - .. - } = instruction_data; - let value = vm.get_argument(b, b_is_constant)?; - let negated = value.negate().map_err(|error| VmError::Value { - error, - position: vm.current_position(), - })?; - let register = Register::Value(negated); - - vm.set_register(a, register) - } - - #[allow(clippy::needless_lifetimes)] - fn not<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> { - let InstructionData { - a, - b, - b_is_constant, - .. - } = instruction_data; - let value = vm.get_argument(b, b_is_constant)?; - let not = value.not().map_err(|error| VmError::Value { - error, - position: vm.current_position(), - })?; - let register = Register::Value(not); - - vm.set_register(a, register) - } - - #[allow(clippy::needless_lifetimes)] - fn jump<'b, 'c>(vm: &mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> { - let InstructionData { b, c, .. } = instruction_data; - let is_positive = c != 0; - - vm.jump_instructions(b as usize, is_positive); - - Ok(()) - } - - #[allow(clippy::needless_lifetimes)] - fn call<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> { - let InstructionData { - a, - b, - c, - b_is_constant, - .. - } = instruction_data; - let function = vm.get_argument(b, b_is_constant)?; - let chunk = if let ValueRef::Concrete(ConcreteValue::Function(chunk)) = function { - chunk - } else if let ValueRef::Abstract(AbstractValue::FunctionSelf) = function { - vm.chunk - } else { - return Err(VmError::ExpectedFunction { - found: function.into_concrete_owned(vm)?, - position: vm.current_position(), - }); - }; - let mut function_vm = Vm::new(vm.source, chunk, Some(vm)); - 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); - - if target_register_is_empty { - continue; - } - - function_vm.set_register( - argument_index as u8, - Register::Pointer(Pointer::ParentStack(argument_register_index)), - )?; - - argument_index += 1; - } - - let return_value = function_vm.run()?; - - if let Some(concrete_value) = return_value { - let register = Register::Value(concrete_value.to_value()); - - vm.set_register(a, register)?; - } - - Ok(()) - } - - #[allow(clippy::needless_lifetimes)] - fn call_native<'b, 'c>( - vm: &'b mut Vm<'c>, - instruction_data: InstructionData, - ) -> Result<(), VmError> { - 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<[ValueRef; 4]> = SmallVec::new(); - - for register_index in argument_range { - let register = &vm.stack[register_index]; - let value = match register { - Register::Value(value) => value.to_ref(), - Register::Pointer(pointer) => { - let value_option = vm.follow_pointer_allow_empty(*pointer)?; - - match value_option { - Some(value) => value, - None => continue, - } - } - Register::Empty => continue, - }; - - arguments.push(value); - } - - let function = NativeFunction::from(b); - let call_result = function.call(vm, arguments); - let return_value = match call_result { - Ok(value_option) => value_option, - Err(error) => return Err(VmError::NativeFunction(error)), - }; - - if let Some(value) = return_value { - let register = Register::Value(value); - - vm.set_register(a, register)?; - } - - Ok(()) - } - - pub fn run(&mut self) -> Result, VmError> { - loop { - let instruction = self.read(); - let (operation, instruction_data) = instruction.decode(); - - log::info!( - "{} | {} | {} | {}", - self.ip - 1, - self.current_position(), - instruction.operation(), - instruction.disassembly_info() - ); - - if let Operation::RETURN = operation { - let should_return_value = instruction_data.b != 0; - - if !should_return_value { - return Ok(None); - } - - return if let Some(register_index) = &self.last_assigned_register { - let return_value = self - .open_register(*register_index)? - .into_concrete_owned(self)?; - - Ok(Some(return_value)) - } else { - Err(VmError::StackUnderflow { - position: self.current_position(), - }) - }; - } else { - let runner = RUNNERS[operation.0 as usize]; - - runner(self, instruction_data).unwrap(); - } - } - } - - pub(crate) fn follow_pointer(&self, pointer: Pointer) -> Result { - match pointer { - Pointer::Stack(register_index) => self.open_register(register_index), - Pointer::Constant(constant_index) => { - let constant = self.get_constant(constant_index); - - Ok(ValueRef::Concrete(constant)) - } - Pointer::ParentStack(register_index) => { - let parent = self - .parent - .as_ref() - .ok_or_else(|| VmError::ExpectedParent { - position: self.current_position(), - })?; - - parent.open_register(register_index) - } - Pointer::ParentConstant(constant_index) => { - let parent = self - .parent - .as_ref() - .ok_or_else(|| VmError::ExpectedParent { - position: self.current_position(), - })?; - let constant = parent.get_constant(constant_index); - - Ok(ValueRef::Concrete(constant)) - } - } - } - - pub(crate) fn follow_pointer_allow_empty( - &self, - pointer: Pointer, - ) -> Result, VmError> { - match pointer { - Pointer::Stack(register_index) => self.open_register_allow_empty(register_index), - Pointer::Constant(constant_index) => { - let constant = self.get_constant(constant_index); - - Ok(Some(ValueRef::Concrete(constant))) - } - Pointer::ParentStack(register_index) => { - let parent = self - .parent - .as_ref() - .ok_or_else(|| VmError::ExpectedParent { - position: self.current_position(), - })?; - - parent.open_register_allow_empty(register_index) - } - Pointer::ParentConstant(constant_index) => { - let parent = self - .parent - .as_ref() - .ok_or_else(|| VmError::ExpectedParent { - position: self.current_position(), - })?; - let constant = parent.get_constant(constant_index); - - Ok(Some(ValueRef::Concrete(constant))) - } - } - } - - fn open_register(&self, register_index: u8) -> Result { - let register_index = register_index as usize; - let stack = self.stack.as_slice(); - - if register_index < stack.len() { - let register = &stack[register_index]; - - return match register { - Register::Value(value) => Ok(value.to_ref()), - Register::Pointer(pointer) => self.follow_pointer(*pointer), - Register::Empty => panic!("VM Error: Register {register_index} is empty"), - }; - } - - panic!("VM Error: Register index out of bounds"); - } - - fn open_register_allow_empty(&self, register_index: u8) -> Result, VmError> { - let register_index = register_index as usize; - let register = - self.stack - .get(register_index) - .ok_or_else(|| VmError::RegisterIndexOutOfBounds { - index: register_index, - position: self.current_position(), - })?; - - log::trace!("Open R{register_index} to {register}"); - - match register { - Register::Value(value) => Ok(Some(value.to_ref())), - Register::Pointer(pointer) => self.follow_pointer(*pointer).map(Some), - Register::Empty => Ok(None), - } - } - - /// DRY helper for handling JUMP instructions - fn jump_instructions(&mut self, offset: usize, is_positive: bool) { - log::trace!( - "Jumping {}", - if is_positive { - format!("+{}", offset) - } else { - format!("-{}", offset) - } - ); - - if is_positive { - self.ip += offset - } else { - self.ip -= offset + 1 - } - } - - /// DRY helper to get a value from an Argument - fn get_argument(&self, index: u8, is_constant: bool) -> Result { - if is_constant { - Ok(self.get_constant(index).to_value_ref()) - } else { - Ok(self.open_register(index)?) - } - } - - fn set_register(&mut self, to_register: u8, register: Register) -> Result<(), VmError> { - self.last_assigned_register = Some(to_register); - let to_register = to_register as usize; - let stack = self.stack.as_mut_slice(); - - if to_register < stack.len() { - stack[to_register] = register; - - return Ok(()); - } - - panic!("VM Error: Register index out of bounds"); - } - - fn get_constant(&self, constant_index: u8) -> &ConcreteValue { - let constant_index = constant_index as usize; - let constants = self.chunk.constants().as_slice(); - - if constant_index < constants.len() { - return &constants[constant_index]; - } - - panic!("VM Error: Constant index out of bounds"); - } - - fn get_local_register(&self, local_index: u8) -> Result { - let local_index = local_index as usize; - let locals = self.chunk.locals().as_slice(); - - if local_index < locals.len() { - let register_index = locals[local_index].register_index; - - return Ok(register_index); - } - - panic!("VM Error: Local index out of bounds"); - } - - fn read(&mut self) -> Instruction { - let instructions = self.chunk.instructions().as_slice(); - - if self.ip < instructions.len() { - let (instruction, _) = instructions[self.ip]; - - self.ip += 1; - - return instruction; - } - - panic!("VM Error: Instruction pointer out of bounds"); - } -} - -#[derive(Clone, Debug, PartialEq)] -pub enum Register { - Empty, - Value(Value), - Pointer(Pointer), -} - -impl Display for Register { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Empty => write!(f, "empty"), - Self::Value(value) => write!(f, "{}", value), - Self::Pointer(pointer) => write!(f, "{}", pointer), - } - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] -pub enum Pointer { - Stack(u8), - Constant(u8), - ParentStack(u8), - ParentConstant(u8), -} - -impl Display for Pointer { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - 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 enum VmError { - // Stack errors - StackOverflow { - position: Span, - }, - StackUnderflow { - position: Span, - }, - - // Register errors - EmptyRegister { - index: usize, - position: Span, - }, - ExpectedConcreteValue { - found: AbstractValue, - position: Span, - }, - ExpectedValue { - found: Register, - position: Span, - }, - RegisterIndexOutOfBounds { - index: usize, - position: Span, - }, - - // Local errors - UndefinedLocal { - local_index: u8, - position: Span, - }, - - // Execution errors - ExpectedBoolean { - found: Value, - position: Span, - }, - ExpectedFunction { - found: ConcreteValue, - position: Span, - }, - ExpectedParent { - position: Span, - }, - ValueDisplay { - error: io::ErrorKind, - position: Span, - }, - - // Chunk errors - ConstantIndexOutOfBounds { - index: usize, - position: Span, - }, - InstructionIndexOutOfBounds { - index: usize, - position: Span, - }, - LocalIndexOutOfBounds { - index: usize, - position: Span, - }, - - // Wrappers for foreign errors - NativeFunction(NativeFunctionError), - Value { - error: ValueError, - position: Span, - }, -} - -impl AnnotatedError for VmError { - fn title() -> &'static str { - "Runtime Error" - } - - fn description(&self) -> &'static str { - match self { - Self::ConstantIndexOutOfBounds { .. } => "Constant index out of bounds", - Self::EmptyRegister { .. } => "Empty register", - Self::ExpectedBoolean { .. } => "Expected boolean", - Self::ExpectedConcreteValue { .. } => "Expected concrete value", - Self::ExpectedFunction { .. } => "Expected function", - Self::ExpectedParent { .. } => "Expected parent", - Self::ExpectedValue { .. } => "Expected value", - Self::InstructionIndexOutOfBounds { .. } => "Instruction index out of bounds", - Self::LocalIndexOutOfBounds { .. } => "Local index out of bounds", - Self::NativeFunction(error) => error.description(), - Self::RegisterIndexOutOfBounds { .. } => "Register index out of bounds", - Self::StackOverflow { .. } => "Stack overflow", - Self::StackUnderflow { .. } => "Stack underflow", - Self::UndefinedLocal { .. } => "Undefined local", - Self::Value { .. } => "Value error", - Self::ValueDisplay { .. } => "Value display error", - } - } - - fn detail_snippets(&self) -> SmallVec<[(String, Span); 2]> { - match self { - VmError::StackOverflow { position } => todo!(), - VmError::StackUnderflow { position } => todo!(), - VmError::EmptyRegister { index, position } => todo!(), - VmError::ExpectedConcreteValue { found, position } => todo!(), - VmError::ExpectedValue { found, position } => todo!(), - VmError::RegisterIndexOutOfBounds { index, position } => todo!(), - VmError::UndefinedLocal { - local_index, - position, - } => todo!(), - VmError::ExpectedBoolean { found, position } => todo!(), - VmError::ExpectedFunction { found, position } => todo!(), - VmError::ExpectedParent { position } => todo!(), - VmError::ValueDisplay { error, position } => todo!(), - VmError::ConstantIndexOutOfBounds { index, position } => todo!(), - VmError::InstructionIndexOutOfBounds { index, position } => todo!(), - VmError::LocalIndexOutOfBounds { index, position } => todo!(), - VmError::NativeFunction(native_function_error) => todo!(), - VmError::Value { error, position } => todo!(), - } - } - - fn help_snippets(&self) -> SmallVec<[(String, Span); 2]> { - todo!() - } -} - -#[cfg(test)] -mod tests { - use std::any::Any; - - use super::*; - - const ALL_OPERATIONS: [(Operation, Runner); 23] = [ - (Operation::MOVE, Vm::r#move), - (Operation::CLOSE, Vm::close), - (Operation::LOAD_BOOLEAN, Vm::load_boolean), - (Operation::LOAD_CONSTANT, Vm::load_constant), - (Operation::LOAD_LIST, Vm::load_list), - (Operation::LOAD_SELF, Vm::load_self), - (Operation::GET_LOCAL, Vm::get_local), - (Operation::SET_LOCAL, Vm::set_local), - (Operation::ADD, Vm::add), - (Operation::SUBTRACT, Vm::subtract), - (Operation::MULTIPLY, Vm::multiply), - (Operation::DIVIDE, Vm::divide), - (Operation::MODULO, Vm::modulo), - (Operation::TEST, Vm::test), - (Operation::TEST_SET, Vm::test_set), - (Operation::EQUAL, Vm::equal), - (Operation::LESS, Vm::less), - (Operation::LESS_EQUAL, Vm::less_equal), - (Operation::NEGATE, Vm::negate), - (Operation::NOT, Vm::not), - (Operation::CALL, Vm::call), - (Operation::CALL_NATIVE, Vm::call_native), - (Operation::JUMP, Vm::jump), - ]; - - #[test] - fn operations_map_to_the_correct_runner() { - for (operation, expected_runner) in ALL_OPERATIONS { - let actual_runner = RUNNERS[operation.0 as usize]; - - assert_eq!( - expected_runner, actual_runner, - "{operation} runner is incorrect" - ); - } - } -} diff --git a/dust-lang/src/vm/mod.rs b/dust-lang/src/vm/mod.rs new file mode 100644 index 0000000..d3b0ae0 --- /dev/null +++ b/dust-lang/src/vm/mod.rs @@ -0,0 +1,462 @@ +//! Virtual machine and errors +mod runners; + +use std::{ + fmt::{self, Display, Formatter}, + io, +}; + +use runners::{Runner, RUNNERS}; +use smallvec::SmallVec; + +use crate::{ + compile, instruction::*, AbstractValue, AnnotatedError, Chunk, ConcreteValue, DustError, + NativeFunctionError, Span, Value, ValueError, ValueRef, +}; + +pub fn run(source: &str) -> Result, DustError> { + let chunk = compile(source)?; + let vm = Vm::new(source, &chunk, None); + + Ok(vm.run()) +} + +/// Dust virtual machine. +/// +/// See the [module-level documentation](index.html) for more information. +#[derive(Debug)] +pub struct Vm<'a> { + stack: Vec, + + chunk: &'a Chunk, + parent: Option<&'a Vm<'a>>, + + ip: usize, + last_assigned_register: Option, + source: &'a str, + return_value: Option, +} + +impl<'a> Vm<'a> { + pub fn new(source: &'a str, chunk: &'a Chunk, parent: Option<&'a Vm<'a>>) -> Self { + let stack = vec![Register::Empty; chunk.stack_size()]; + + Self { + chunk, + stack, + parent, + ip: 0, + last_assigned_register: None, + source, + return_value: None, + } + } + + pub fn chunk(&self) -> &Chunk { + self.chunk + } + + pub fn source(&self) -> &'a str { + self.source + } + + pub fn current_position(&self) -> Span { + let index = self.ip.saturating_sub(1); + let (_, position) = self.chunk.instructions()[index]; + + position + } + + pub fn run(mut self) -> Option { + let runners = self + .chunk + .instructions() + .iter() + .map(|(instruction, _)| { + let (operation, data) = instruction.decode(); + let runner = RUNNERS[operation.0 as usize]; + + (runner, data) + }) + .collect::>(); + + while self.ip < runners.len() && self.return_value.is_none() { + let (runner, data) = runners[self.ip]; + + self.ip += 1; + + runner(&mut self, data); + } + + self.return_value.take() + } + + pub(crate) fn follow_pointer(&self, pointer: Pointer) -> ValueRef { + log::trace!("Follow pointer {pointer}"); + + match pointer { + Pointer::Stack(register_index) => self.open_register(register_index), + Pointer::Constant(constant_index) => { + let constant = self.get_constant(constant_index); + + ValueRef::Concrete(constant) + } + 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) + .to_value_ref() + } + } + } + + pub(crate) fn follow_pointer_allow_empty(&self, pointer: Pointer) -> Option { + 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(ValueRef::Concrete(constant)) + } + Pointer::ParentStack(register_index) => { + assert!(self.parent.is_some(), "Vm Error: Expected parent"); + + 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) + .to_value_ref(); + + Some(constant) + } + } + } + + fn open_register(&self, register_index: u8) -> ValueRef { + 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.to_ref(), + 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 { + 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]; + + log::trace!("Open R{register_index} to {register}"); + + match register { + Register::Value(value) => Some(value.to_ref()), + Register::Pointer(pointer) => Some(self.follow_pointer(*pointer)), + Register::Empty => None, + } + } + + /// DRY helper for handling JUMP instructions + fn jump_instructions(&mut self, offset: usize, is_positive: bool) { + log::trace!( + "Jumping {}", + if is_positive { + format!("+{}", offset) + } else { + format!("-{}", offset) + } + ); + + if is_positive { + self.ip += offset + } else { + self.ip -= offset + 1 + } + } + + /// DRY helper to get a value from an Argument + fn get_argument(&self, index: u8, is_constant: bool) -> ValueRef { + if is_constant { + self.get_constant(index).to_value_ref() + } 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; + let stack = self.stack.as_mut_slice(); + + assert!( + to_register < stack.len(), + "VM Error: Register index out of bounds" + ); + + stack[to_register] = register; + } + + fn get_constant(&self, constant_index: u8) -> &ConcreteValue { + let constant_index = constant_index as usize; + let constants = self.chunk.constants().as_slice(); + + 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().as_slice(); + + assert!( + local_index < locals.len(), + "VM Error: Local index out of bounds" + ); + + locals[local_index].register_index + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Register { + Empty, + Value(Value), + Pointer(Pointer), +} + +impl Display for Register { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Self::Empty => write!(f, "empty"), + Self::Value(value) => write!(f, "{}", value), + Self::Pointer(pointer) => write!(f, "{}", pointer), + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub enum Pointer { + Stack(u8), + Constant(u8), + ParentStack(u8), + ParentConstant(u8), +} + +impl Display for Pointer { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + 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 enum VmError { + // Stack errors + StackOverflow { + position: Span, + }, + StackUnderflow { + position: Span, + }, + + // Register errors + EmptyRegister { + index: usize, + position: Span, + }, + ExpectedConcreteValue { + found: AbstractValue, + position: Span, + }, + ExpectedValue { + found: Register, + position: Span, + }, + RegisterIndexOutOfBounds { + index: usize, + position: Span, + }, + + // Local errors + UndefinedLocal { + local_index: u8, + position: Span, + }, + + // Execution errors + ExpectedBoolean { + found: Value, + position: Span, + }, + ExpectedFunction { + found: ConcreteValue, + position: Span, + }, + ExpectedParent { + position: Span, + }, + ValueDisplay { + error: io::ErrorKind, + position: Span, + }, + + // Chunk errors + ConstantIndexOutOfBounds { + index: usize, + position: Span, + }, + InstructionIndexOutOfBounds { + index: usize, + position: Span, + }, + LocalIndexOutOfBounds { + index: usize, + position: Span, + }, + + // Wrappers for foreign errors + NativeFunction(NativeFunctionError), + Value { + error: ValueError, + position: Span, + }, +} + +impl AnnotatedError for VmError { + fn title() -> &'static str { + "Runtime Error" + } + + fn description(&self) -> &'static str { + match self { + Self::ConstantIndexOutOfBounds { .. } => "Constant index out of bounds", + Self::EmptyRegister { .. } => "Empty register", + Self::ExpectedBoolean { .. } => "Expected boolean", + Self::ExpectedConcreteValue { .. } => "Expected concrete value", + Self::ExpectedFunction { .. } => "Expected function", + Self::ExpectedParent { .. } => "Expected parent", + Self::ExpectedValue { .. } => "Expected value", + Self::InstructionIndexOutOfBounds { .. } => "Instruction index out of bounds", + Self::LocalIndexOutOfBounds { .. } => "Local index out of bounds", + Self::NativeFunction(error) => error.description(), + Self::RegisterIndexOutOfBounds { .. } => "Register index out of bounds", + Self::StackOverflow { .. } => "Stack overflow", + Self::StackUnderflow { .. } => "Stack underflow", + Self::UndefinedLocal { .. } => "Undefined local", + Self::Value { .. } => "Value error", + Self::ValueDisplay { .. } => "Value display error", + } + } + + fn detail_snippets(&self) -> SmallVec<[(String, Span); 2]> { + match self { + VmError::StackOverflow { position } => todo!(), + VmError::StackUnderflow { position } => todo!(), + VmError::EmptyRegister { index, position } => todo!(), + VmError::ExpectedConcreteValue { found, position } => todo!(), + VmError::ExpectedValue { found, position } => todo!(), + VmError::RegisterIndexOutOfBounds { index, position } => todo!(), + VmError::UndefinedLocal { + local_index, + position, + } => todo!(), + VmError::ExpectedBoolean { found, position } => todo!(), + VmError::ExpectedFunction { found, position } => todo!(), + VmError::ExpectedParent { position } => todo!(), + VmError::ValueDisplay { error, position } => todo!(), + VmError::ConstantIndexOutOfBounds { index, position } => todo!(), + VmError::InstructionIndexOutOfBounds { index, position } => todo!(), + VmError::LocalIndexOutOfBounds { index, position } => todo!(), + VmError::NativeFunction(native_function_error) => todo!(), + VmError::Value { error, position } => todo!(), + } + } + + fn help_snippets(&self) -> SmallVec<[(String, Span); 2]> { + todo!() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const ALL_OPERATIONS: [(Operation, Runner); 24] = [ + (Operation::MOVE, runners::r#move), + (Operation::CLOSE, runners::close), + (Operation::LOAD_BOOLEAN, runners::load_boolean), + (Operation::LOAD_CONSTANT, runners::load_constant), + (Operation::LOAD_LIST, runners::load_list), + (Operation::LOAD_SELF, runners::load_self), + (Operation::GET_LOCAL, runners::get_local), + (Operation::SET_LOCAL, runners::set_local), + (Operation::ADD, runners::add), + (Operation::SUBTRACT, runners::subtract), + (Operation::MULTIPLY, runners::multiply), + (Operation::DIVIDE, runners::divide), + (Operation::MODULO, runners::modulo), + (Operation::TEST, runners::test), + (Operation::TEST_SET, runners::test_set), + (Operation::EQUAL, runners::equal), + (Operation::LESS, runners::less), + (Operation::LESS_EQUAL, runners::less_equal), + (Operation::NEGATE, runners::negate), + (Operation::NOT, runners::not), + (Operation::CALL, runners::call), + (Operation::CALL_NATIVE, runners::call_native), + (Operation::JUMP, runners::jump), + (Operation::RETURN, runners::r#return), + ]; + + #[test] + fn operations_map_to_the_correct_runner() { + for (operation, expected_runner) in ALL_OPERATIONS { + let actual_runner = RUNNERS[operation.0 as usize]; + + assert_eq!( + expected_runner, actual_runner, + "{operation} runner is incorrect" + ); + } + } +} diff --git a/dust-lang/src/vm/runners.rs b/dust-lang/src/vm/runners.rs new file mode 100644 index 0000000..a2c7dff --- /dev/null +++ b/dust-lang/src/vm/runners.rs @@ -0,0 +1,528 @@ +use smallvec::SmallVec; + +use crate::{AbstractValue, ConcreteValue, NativeFunction, Value, ValueRef}; + +use super::{InstructionData, Pointer, Register, Vm}; + +pub type Runner = fn(&mut Vm, InstructionData); + +pub const RUNNERS: [Runner; 24] = [ + r#move, + close, + load_boolean, + load_constant, + load_list, + load_self, + get_local, + set_local, + add, + subtract, + multiply, + divide, + modulo, + test, + test_set, + equal, + less, + less_equal, + negate, + not, + call, + call_native, + jump, + r#return, +]; + +#[allow(clippy::needless_lifetimes)] +pub fn r#move<'b, 'c>(vm: &'b mut Vm<'c>, 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)); + + if from_register_has_value { + vm.set_register(c, register); + } +} + +#[allow(clippy::needless_lifetimes)] +pub fn close<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) { + let InstructionData { b, c, .. } = instruction_data; + + assert!(b < c, "Runtime Error: Malformed instruction"); + + for register_index in b..c { + assert!( + (register_index as usize) < vm.stack.len(), + "Runtime Error: Register index out of bounds" + ); + + vm.stack[register_index as usize] = Register::Empty; + } +} + +#[allow(clippy::needless_lifetimes)] +pub fn load_boolean<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) { + let InstructionData { a, b, c, .. } = instruction_data; + let boolean = ConcreteValue::Boolean(b != 0).to_value(); + let register = Register::Value(boolean); + + vm.set_register(a, register); + + if c != 0 { + vm.jump_instructions(1, true); + } +} + +#[allow(clippy::needless_lifetimes)] +pub fn load_constant<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) { + let InstructionData { a, b, c, .. } = instruction_data; + let register = Register::Pointer(Pointer::Constant(b)); + + vm.set_register(a, register); + + if c != 0 { + vm.jump_instructions(1, true); + } +} + +#[allow(clippy::needless_lifetimes)] +pub fn load_list<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) { + let InstructionData { a, b, .. } = instruction_data; + let mut item_pointers = Vec::new(); + let stack = vm.stack.as_slice(); + + for register_index in b..a { + if let Register::Empty = stack[register_index as usize] { + continue; + } + + let pointer = Pointer::Stack(register_index); + + item_pointers.push(pointer); + } + + let list_value = AbstractValue::List { item_pointers }.to_value(); + let register = Register::Value(list_value); + + vm.set_register(a, register) +} + +#[allow(clippy::needless_lifetimes)] +pub fn load_self<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) { + let InstructionData { a, .. } = instruction_data; + let register = Register::Value(AbstractValue::FunctionSelf.to_value()); + + vm.set_register(a, register) +} + +#[allow(clippy::needless_lifetimes)] +pub fn get_local<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) { + let InstructionData { a, b, .. } = instruction_data; + let local_register_index = vm.get_local_register(b); + let register = Register::Pointer(Pointer::Stack(local_register_index)); + + vm.set_register(a, register) +} + +#[allow(clippy::needless_lifetimes)] +pub fn set_local<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) { + let InstructionData { b, c, .. } = instruction_data; + let local_register_index = vm.get_local_register(c); + let register = Register::Pointer(Pointer::Stack(b)); + + vm.set_register(local_register_index, register) +} + +#[allow(clippy::needless_lifetimes)] +pub fn add<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) { + let InstructionData { + a, + b, + c, + b_is_constant, + c_is_constant, + .. + } = instruction_data; + let left = vm.get_argument(b, b_is_constant); + let right = vm.get_argument(c, c_is_constant); + let sum = match (left, right) { + (ValueRef::Concrete(left), ValueRef::Concrete(right)) => match (left, right) { + (ConcreteValue::Integer(left), ConcreteValue::Integer(right)) => { + ConcreteValue::Integer(left + right).to_value() + } + _ => panic!("Value Error: Cannot add values"), + }, + _ => panic!("Value Error: Cannot add values {left} and {right}"), + }; + let register = Register::Value(sum); + + vm.set_register(a, register) +} + +#[allow(clippy::needless_lifetimes)] +pub fn subtract<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) { + let InstructionData { + a, + b, + c, + b_is_constant, + c_is_constant, + .. + } = instruction_data; + let left = vm.get_argument(b, b_is_constant); + let right = vm.get_argument(c, c_is_constant); + let difference = match (left, right) { + (ValueRef::Concrete(left), ValueRef::Concrete(right)) => match (left, right) { + (ConcreteValue::Integer(left), ConcreteValue::Integer(right)) => { + ConcreteValue::Integer(left - right).to_value() + } + _ => panic!("Value Error: Cannot subtract values {left} and {right}"), + }, + _ => panic!("Value Error: Cannot subtract values {left} and {right}"), + }; + let register = Register::Value(difference); + + vm.set_register(a, register) +} + +#[allow(clippy::needless_lifetimes)] +pub fn multiply<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) { + let InstructionData { + a, + b, + c, + b_is_constant, + c_is_constant, + .. + } = instruction_data; + let left = vm.get_argument(b, b_is_constant); + let right = vm.get_argument(c, c_is_constant); + let product = match (left, right) { + (ValueRef::Concrete(left), ValueRef::Concrete(right)) => match (left, right) { + (ConcreteValue::Integer(left), ConcreteValue::Integer(right)) => { + ConcreteValue::Integer(left * right).to_value() + } + _ => panic!("Value Error: Cannot multiply values"), + }, + _ => panic!("Value Error: Cannot multiply values"), + }; + let register = Register::Value(product); + + vm.set_register(a, register) +} + +#[allow(clippy::needless_lifetimes)] +pub fn divide<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) { + let InstructionData { + a, + b, + c, + b_is_constant, + c_is_constant, + .. + } = instruction_data; + let left = vm.get_argument(b, b_is_constant); + let right = vm.get_argument(c, c_is_constant); + let quotient = match (left, right) { + (ValueRef::Concrete(left), ValueRef::Concrete(right)) => match (left, right) { + (ConcreteValue::Integer(left), ConcreteValue::Integer(right)) => { + ConcreteValue::Integer(left / right).to_value() + } + _ => panic!("Value Error: Cannot divide values"), + }, + _ => panic!("Value Error: Cannot divide values"), + }; + let register = Register::Value(quotient); + + vm.set_register(a, register) +} + +#[allow(clippy::needless_lifetimes)] +pub fn modulo<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) { + let InstructionData { + a, + b, + c, + b_is_constant, + c_is_constant, + .. + } = instruction_data; + let left = vm.get_argument(b, b_is_constant); + let right = vm.get_argument(c, c_is_constant); + let remainder = match (left, right) { + (ValueRef::Concrete(left), ValueRef::Concrete(right)) => match (left, right) { + (ConcreteValue::Integer(left), ConcreteValue::Integer(right)) => { + ConcreteValue::Integer(left % right).to_value() + } + _ => panic!("Value Error: Cannot modulo values"), + }, + _ => panic!("Value Error: Cannot modulo values"), + }; + let register = Register::Value(remainder); + + vm.set_register(a, register) +} + +#[allow(clippy::needless_lifetimes)] +pub fn test<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) { + let InstructionData { + b, + b_is_constant, + c, + .. + } = instruction_data; + let value = vm.get_argument(b, b_is_constant); + let boolean = if let ValueRef::Concrete(ConcreteValue::Boolean(boolean)) = value { + *boolean + } else { + panic!( + "VM Error: Expected boolean value for TEST operation at {}", + vm.current_position() + ); + }; + let test_value = c != 0; + + if boolean == test_value { + vm.jump_instructions(1, true); + } +} + +#[allow(clippy::needless_lifetimes)] +pub fn test_set<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) { + let InstructionData { + a, + b, + c, + b_is_constant, + .. + } = instruction_data; + let value = vm.get_argument(b, b_is_constant); + let boolean = if let ValueRef::Concrete(ConcreteValue::Boolean(boolean)) = value { + *boolean + } else { + panic!( + "VM Error: Expected boolean value for TEST_SET operation at {}", + vm.current_position() + ); + }; + let test_value = c != 0; + + if boolean == test_value { + vm.jump_instructions(1, true); + } else { + let pointer = if b_is_constant { + Pointer::Constant(b) + } else { + Pointer::Stack(b) + }; + let register = Register::Pointer(pointer); + + vm.set_register(a, register); + } +} + +#[allow(clippy::needless_lifetimes)] +pub fn equal<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) { + let InstructionData { + b, + c, + b_is_constant, + c_is_constant, + d, + .. + } = instruction_data; + let left = vm.get_argument(b, b_is_constant); + let right = vm.get_argument(c, c_is_constant); + let is_equal = left == right; + + if is_equal == d { + vm.jump_instructions(1, true); + } +} + +#[allow(clippy::needless_lifetimes)] +pub fn less<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) { + let InstructionData { + b, + c, + b_is_constant, + c_is_constant, + d, + .. + } = instruction_data; + let left = vm.get_argument(b, b_is_constant); + let right = vm.get_argument(c, c_is_constant); + let is_less = left < right; + + if is_less == d { + vm.jump_instructions(1, true); + } +} + +#[allow(clippy::needless_lifetimes)] +pub fn less_equal<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) { + let InstructionData { + b, + c, + b_is_constant, + c_is_constant, + d, + .. + } = instruction_data; + let left = vm.get_argument(b, b_is_constant); + let right = vm.get_argument(c, c_is_constant); + let is_less_or_equal = left <= right; + + if is_less_or_equal == d { + vm.jump_instructions(1, true); + } +} + +#[allow(clippy::needless_lifetimes)] +pub fn negate<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) { + let InstructionData { + a, + b, + b_is_constant, + .. + } = instruction_data; + let argument = vm.get_argument(b, b_is_constant); + let negated = match argument { + ValueRef::Concrete(value) => match value { + ConcreteValue::Float(float) => ConcreteValue::Float(-float), + ConcreteValue::Integer(integer) => ConcreteValue::Integer(-integer), + _ => panic!("Value Error: Cannot negate value"), + }, + ValueRef::Abstract(_) => panic!("VM Error: Cannot negate value"), + }; + let register = Register::Value(Value::Concrete(negated)); + + vm.set_register(a, register) +} + +#[allow(clippy::needless_lifetimes)] +pub fn not<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) { + let InstructionData { + a, + b, + b_is_constant, + .. + } = instruction_data; + let argument = vm.get_argument(b, b_is_constant); + let not = match argument { + ValueRef::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) +} + +#[allow(clippy::needless_lifetimes)] +pub fn jump<'c>(vm: &mut Vm<'c>, instruction_data: InstructionData) { + let InstructionData { b, c, .. } = instruction_data; + let is_positive = c != 0; + + vm.jump_instructions(b as usize, is_positive); +} + +#[allow(clippy::needless_lifetimes)] +pub fn call<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) { + let InstructionData { + a, + b, + c, + b_is_constant, + .. + } = instruction_data; + let function = vm.get_argument(b, b_is_constant); + let chunk = if let ValueRef::Concrete(ConcreteValue::Function(chunk)) = function { + chunk + } else if let ValueRef::Abstract(AbstractValue::FunctionSelf) = function { + vm.chunk + } else { + panic!("VM Error: Expected function") + }; + let mut function_vm = Vm::new(vm.source, chunk, Some(vm)); + 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); + + if target_register_is_empty { + continue; + } + + function_vm.set_register( + argument_index as u8, + Register::Pointer(Pointer::ParentStack(argument_register_index)), + ); + + argument_index += 1; + } + + let return_value = function_vm.run(); + + if let Some(concrete_value) = return_value { + let register = Register::Value(concrete_value.to_value()); + + vm.set_register(a, register); + } +} + +#[allow(clippy::needless_lifetimes)] +pub fn call_native<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) { + 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<[ValueRef; 4]> = SmallVec::new(); + + for register_index in argument_range { + let register = &vm.stack[register_index]; + let value = match register { + Register::Value(value) => value.to_ref(), + Register::Pointer(pointer) => { + let value_option = vm.follow_pointer_allow_empty(*pointer); + + match value_option { + Some(value) => value, + None => continue, + } + } + Register::Empty => continue, + }; + + arguments.push(value); + } + + let function = NativeFunction::from(b); + let return_value = function.call(vm, arguments).unwrap(); + + if let Some(value) = return_value { + let register = Register::Value(value); + + vm.set_register(a, register); + } +} + +#[allow(clippy::needless_lifetimes)] +pub fn r#return<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) { + let should_return_value = instruction_data.b != 0; + + if !should_return_value { + return; + } + + if let Some(register_index) = &vm.last_assigned_register { + let return_value = vm.open_register(*register_index).into_concrete_owned(vm); + + vm.return_value = Some(return_value); + } else { + panic!("Stack underflow"); + } +}