1
0

Optimize; Revert to branch-style comparisons for performance

This commit is contained in:
Jeff 2024-12-14 00:45:49 -05:00
parent 1777ad298b
commit 9ae923febd
11 changed files with 1076 additions and 1243 deletions

View File

@ -199,24 +199,15 @@ fn main() {
let chunk = compiler.finish(None, None); let chunk = compiler.finish(None, None);
let compile_end = start_time.elapsed(); 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() { if let Some(value) = return_value {
Ok(Some(value)) => {
if !mode.no_output { if !mode.no_output {
println!("{}", value) println!("{}", value)
} }
} }
Ok(None) => {}
Err(error) => {
let dust_error = DustError::runtime(error, &source);
let report = dust_error.report();
eprintln!("{report}");
}
}
let run_end = start_time.elapsed();
if mode.time { if mode.time {
let compile_time = compile_end.as_micros(); let compile_time = compile_end.as_micros();

View File

@ -856,12 +856,12 @@ impl<'src> Compiler<'src> {
let destination = self.next_register(); let destination = self.next_register();
let comparison = match operator { let comparison = match operator {
Token::DoubleEqual => Instruction::equal(destination, true, left, right), Token::DoubleEqual => Instruction::equal(true, left, right),
Token::BangEqual => Instruction::equal(destination, false, left, right), Token::BangEqual => Instruction::equal(false, left, right),
Token::Less => Instruction::less(destination, true, left, right), Token::Less => Instruction::less(true, left, right),
Token::LessEqual => Instruction::less_equal(destination, true, left, right), Token::LessEqual => Instruction::less_equal(true, left, right),
Token::Greater => Instruction::less_equal(destination, false, left, right), Token::Greater => Instruction::less_equal(false, left, right),
Token::GreaterEqual => Instruction::less(destination, false, left, right), Token::GreaterEqual => Instruction::less(false, left, right),
_ => { _ => {
return Err(CompileError::ExpectedTokenMultiple { return Err(CompileError::ExpectedTokenMultiple {
expected: &[ 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(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(()) Ok(())
} }
@ -1132,7 +1138,19 @@ impl<'src> Compiler<'src> {
self.advance()?; self.advance()?;
self.parse_expression()?; 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() { let argument = match instruction.as_argument() {
Some(argument) => argument, Some(argument) => argument,
None => { None => {
@ -1264,6 +1282,19 @@ impl<'src> Compiler<'src> {
self.parse_expression()?; self.parse_expression()?;
if matches!(
self.get_last_operations(),
Some([
Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL,
Operation::JUMP,
Operation::LOAD_BOOLEAN,
Operation::LOAD_BOOLEAN
]),
) {
self.instructions.pop();
self.instructions.pop();
self.instructions.pop();
} else {
let (expression_instruction, expression_type, expression_position) = let (expression_instruction, expression_type, expression_position) =
self.instructions.last().unwrap(); self.instructions.last().unwrap();
@ -1287,19 +1318,6 @@ impl<'src> Compiler<'src> {
let test = Instruction::test(test_argument, true); let test = Instruction::test(test_argument, true);
self.emit_instruction(test, Type::None, self.current_position); 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,
],)
) {
self.instructions.pop();
self.instructions.pop();
self.instructions.pop();
} }
let block_start = self.instructions.len(); let block_start = self.instructions.len();

View File

@ -1,7 +1,6 @@
use crate::{Argument, Instruction, Operation}; use crate::{Argument, Instruction, Operation};
pub struct Equal { pub struct Equal {
pub destination: u8,
pub value: bool, pub value: bool,
pub left: Argument, pub left: Argument,
pub right: Argument, pub right: Argument,
@ -9,27 +8,20 @@ pub struct Equal {
impl From<&Instruction> for Equal { impl From<&Instruction> for Equal {
fn from(instruction: &Instruction) -> Self { fn from(instruction: &Instruction) -> Self {
let destination = instruction.a_field();
let value = instruction.d_field(); let value = instruction.d_field();
let (left, right) = instruction.b_and_c_as_arguments(); let (left, right) = instruction.b_and_c_as_arguments();
Equal { Equal { value, left, right }
destination,
value,
left,
right,
}
} }
} }
impl From<Equal> for Instruction { impl From<Equal> for Instruction {
fn from(equal: Equal) -> Self { fn from(equal: Equal) -> Self {
let operation = Operation::EQUAL; let operation = Operation::EQUAL;
let a = equal.destination;
let (b, b_is_constant) = equal.left.as_index_and_constant_flag(); 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 (c, c_is_constant) = equal.right.as_index_and_constant_flag();
let d = equal.value; 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)
} }
} }

View File

@ -1,7 +1,6 @@
use crate::{Argument, Instruction, Operation}; use crate::{Argument, Instruction, Operation};
pub struct Less { pub struct Less {
pub destination: u8,
pub value: bool, pub value: bool,
pub left: Argument, pub left: Argument,
pub right: Argument, pub right: Argument,
@ -9,27 +8,20 @@ pub struct Less {
impl From<&Instruction> for Less { impl From<&Instruction> for Less {
fn from(instruction: &Instruction) -> Self { fn from(instruction: &Instruction) -> Self {
let destination = instruction.a_field();
let value = instruction.d_field(); let value = instruction.d_field();
let (left, right) = instruction.b_and_c_as_arguments(); let (left, right) = instruction.b_and_c_as_arguments();
Less { Less { value, left, right }
destination,
value,
left,
right,
}
} }
} }
impl From<Less> for Instruction { impl From<Less> for Instruction {
fn from(less: Less) -> Self { fn from(less: Less) -> Self {
let operation = Operation::LESS; let operation = Operation::LESS;
let a = less.destination;
let (b, b_is_constant) = less.left.as_index_and_constant_flag(); 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 (c, c_is_constant) = less.right.as_index_and_constant_flag();
let d = less.value; 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)
} }
} }

View File

@ -1,7 +1,6 @@
use crate::{Argument, Instruction, Operation}; use crate::{Argument, Instruction, Operation};
pub struct LessEqual { pub struct LessEqual {
pub destination: u8,
pub value: bool, pub value: bool,
pub left: Argument, pub left: Argument,
pub right: Argument, pub right: Argument,
@ -9,27 +8,20 @@ pub struct LessEqual {
impl From<&Instruction> for LessEqual { impl From<&Instruction> for LessEqual {
fn from(instruction: &Instruction) -> Self { fn from(instruction: &Instruction) -> Self {
let destination = instruction.a_field();
let value = instruction.d_field(); let value = instruction.d_field();
let (left, right) = instruction.b_and_c_as_arguments(); let (left, right) = instruction.b_and_c_as_arguments();
LessEqual { LessEqual { value, left, right }
destination,
value,
left,
right,
}
} }
} }
impl From<LessEqual> for Instruction { impl From<LessEqual> for Instruction {
fn from(less_equal: LessEqual) -> Self { fn from(less_equal: LessEqual) -> Self {
let operation = Operation::LESS_EQUAL; let operation = Operation::LESS_EQUAL;
let a = less_equal.destination;
let (b, b_options) = less_equal.left.as_index_and_constant_flag(); 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 (c, c_options) = less_equal.right.as_index_and_constant_flag();
let d = less_equal.value; 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)
} }
} }

View File

@ -152,7 +152,7 @@ use crate::NativeFunction;
#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] #[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Instruction(u32); 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 struct InstructionData {
pub a: u8, pub a: u8,
pub b: u8, pub b: u8,
@ -343,36 +343,16 @@ impl Instruction {
}) })
} }
pub fn equal(destination: u8, value: bool, left: Argument, right: Argument) -> Instruction { pub fn equal(value: bool, left: Argument, right: Argument) -> Instruction {
Instruction::from(Equal { Instruction::from(Equal { value, left, right })
destination,
value,
left,
right,
})
} }
pub fn less(destination: u8, value: bool, left: Argument, right: Argument) -> Instruction { pub fn less(value: bool, left: Argument, right: Argument) -> Instruction {
Instruction::from(Less { Instruction::from(Less { value, left, right })
destination,
value,
left,
right,
})
} }
pub fn less_equal( pub fn less_equal(value: bool, left: Argument, right: Argument) -> Instruction {
destination: u8, Instruction::from(LessEqual { value, left, right })
value: bool,
left: Argument,
right: Argument,
) -> Instruction {
Instruction::from(LessEqual {
destination,
value,
left,
right,
})
} }
pub fn negate(destination: u8, argument: Argument) -> Instruction { pub fn negate(destination: u8, argument: Argument) -> Instruction {
@ -661,37 +641,22 @@ impl Instruction {
format!("if {bang}{argument} {{ JUMP +1 }} else {{ R{destination} = {argument} }}") format!("if {bang}{argument} {{ JUMP +1 }} else {{ R{destination} = {argument} }}")
} }
Operation::EQUAL => { Operation::EQUAL => {
let Equal { let Equal { value, left, right } = Equal::from(self);
destination,
value,
left,
right,
} = Equal::from(self);
let comparison_symbol = if value { "==" } else { "!=" }; let comparison_symbol = if value { "==" } else { "!=" };
format!("R{destination} = {left} {comparison_symbol} {right}") format!("if {left} {comparison_symbol} {right} {{ JUMP +1 }}")
} }
Operation::LESS => { Operation::LESS => {
let Less { let Less { value, left, right } = Less::from(self);
destination,
value,
left,
right,
} = Less::from(self);
let comparison_symbol = if value { "<" } else { ">=" }; let comparison_symbol = if value { "<" } else { ">=" };
format!("R{destination} = {left} {comparison_symbol} {right}") format!("if {left} {comparison_symbol} {right} {{ JUMP +1 }}")
} }
Operation::LESS_EQUAL => { Operation::LESS_EQUAL => {
let LessEqual { let LessEqual { value, left, right } = LessEqual::from(self);
destination,
value,
left,
right,
} = LessEqual::from(self);
let comparison_symbol = if value { "<=" } else { ">" }; let comparison_symbol = if value { "<=" } else { ">" };
format!("R{destination} = {left} {comparison_symbol} {right}") format!("if {left} {comparison_symbol} {right} {{ JUMP +1 }}")
} }
Operation::NEGATE => { Operation::NEGATE => {
let Negate { let Negate {

View File

@ -17,23 +17,23 @@ impl AbstractValue {
ValueRef::Abstract(self) ValueRef::Abstract(self)
} }
pub fn to_concrete_owned(&self, vm: &Vm) -> Result<ConcreteValue, VmError> { pub fn to_concrete_owned(&self, vm: &Vm) -> ConcreteValue {
match self { match self {
AbstractValue::FunctionSelf => Ok(ConcreteValue::function(vm.chunk().clone())), AbstractValue::FunctionSelf => ConcreteValue::function(vm.chunk().clone()),
AbstractValue::List { item_pointers, .. } => { AbstractValue::List { item_pointers, .. } => {
let mut items: Vec<ConcreteValue> = Vec::with_capacity(item_pointers.len()); let mut items: Vec<ConcreteValue> = Vec::with_capacity(item_pointers.len());
for pointer in item_pointers { 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 { 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, None => continue,
}; };
items.push(item); items.push(item);
} }
Ok(ConcreteValue::List(items)) ConcreteValue::List(items)
} }
} }
} }
@ -54,7 +54,7 @@ impl AbstractValue {
display.push_str(", "); 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); display.push_str(&item_display);
} }

View File

@ -25,10 +25,10 @@ impl Value {
} }
} }
pub fn into_concrete_owned(self, vm: &Vm) -> Result<ConcreteValue, VmError> { pub fn into_concrete_owned(self, vm: &Vm) -> ConcreteValue {
match self { match self {
Value::Abstract(abstract_value) => abstract_value.to_concrete_owned(vm), 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<ConcreteValue, VmError> { pub fn into_concrete_owned(self, vm: &Vm) -> ConcreteValue {
match self { match self {
ValueRef::Abstract(abstract_value) => abstract_value.to_concrete_owned(vm), 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(),
} }
} }

File diff suppressed because it is too large Load Diff

462
dust-lang/src/vm/mod.rs Normal file
View File

@ -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<Option<ConcreteValue>, 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<Register>,
chunk: &'a Chunk,
parent: Option<&'a Vm<'a>>,
ip: usize,
last_assigned_register: Option<u8>,
source: &'a str,
return_value: Option<ConcreteValue>,
}
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<ConcreteValue> {
let runners = self
.chunk
.instructions()
.iter()
.map(|(instruction, _)| {
let (operation, data) = instruction.decode();
let runner = RUNNERS[operation.0 as usize];
(runner, data)
})
.collect::<Vec<(Runner, InstructionData)>>();
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<ValueRef> {
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<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];
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"
);
}
}
}

528
dust-lang/src/vm/runners.rs Normal file
View File

@ -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");
}
}