Optimize; Revert to branch-style comparisons for performance
This commit is contained in:
parent
1777ad298b
commit
9ae923febd
@ -199,25 +199,16 @@ 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();
|
||||||
let run_time = run_end - compile_end;
|
let run_time = run_end - compile_end;
|
||||||
|
@ -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,42 +1282,42 @@ impl<'src> Compiler<'src> {
|
|||||||
|
|
||||||
self.parse_expression()?;
|
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!(
|
if matches!(
|
||||||
self.get_last_operations(),
|
self.get_last_operations(),
|
||||||
Some([
|
Some([
|
||||||
Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL,
|
Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL,
|
||||||
Operation::JUMP,
|
Operation::JUMP,
|
||||||
Operation::LOAD_BOOLEAN,
|
Operation::LOAD_BOOLEAN,
|
||||||
Operation::LOAD_BOOLEAN,
|
Operation::LOAD_BOOLEAN
|
||||||
],)
|
]),
|
||||||
) {
|
) {
|
||||||
self.instructions.pop();
|
self.instructions.pop();
|
||||||
self.instructions.pop();
|
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();
|
let block_start = self.instructions.len();
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1107
dust-lang/src/vm.rs
1107
dust-lang/src/vm.rs
File diff suppressed because it is too large
Load Diff
462
dust-lang/src/vm/mod.rs
Normal file
462
dust-lang/src/vm/mod.rs
Normal 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
528
dust-lang/src/vm/runners.rs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user