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 compile_end = start_time.elapsed();
|
||||
|
||||
let mut vm = Vm::new(&source, &chunk, None);
|
||||
let vm = Vm::new(&source, &chunk, None);
|
||||
let return_value = vm.run();
|
||||
let run_end = start_time.elapsed();
|
||||
|
||||
match vm.run() {
|
||||
Ok(Some(value)) => {
|
||||
if !mode.no_output {
|
||||
println!("{}", value)
|
||||
}
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(error) => {
|
||||
let dust_error = DustError::runtime(error, &source);
|
||||
let report = dust_error.report();
|
||||
|
||||
eprintln!("{report}");
|
||||
if let Some(value) = return_value {
|
||||
if !mode.no_output {
|
||||
println!("{}", value)
|
||||
}
|
||||
}
|
||||
|
||||
let run_end = start_time.elapsed();
|
||||
|
||||
if mode.time {
|
||||
let compile_time = compile_end.as_micros();
|
||||
let run_time = run_end - compile_end;
|
||||
|
@ -856,12 +856,12 @@ impl<'src> Compiler<'src> {
|
||||
|
||||
let destination = self.next_register();
|
||||
let comparison = match operator {
|
||||
Token::DoubleEqual => Instruction::equal(destination, true, left, right),
|
||||
Token::BangEqual => Instruction::equal(destination, false, left, right),
|
||||
Token::Less => Instruction::less(destination, true, left, right),
|
||||
Token::LessEqual => Instruction::less_equal(destination, true, left, right),
|
||||
Token::Greater => Instruction::less_equal(destination, false, left, right),
|
||||
Token::GreaterEqual => Instruction::less(destination, false, left, right),
|
||||
Token::DoubleEqual => Instruction::equal(true, left, right),
|
||||
Token::BangEqual => Instruction::equal(false, left, right),
|
||||
Token::Less => Instruction::less(true, left, right),
|
||||
Token::LessEqual => Instruction::less_equal(true, left, right),
|
||||
Token::Greater => Instruction::less_equal(false, left, right),
|
||||
Token::GreaterEqual => Instruction::less(false, left, right),
|
||||
_ => {
|
||||
return Err(CompileError::ExpectedTokenMultiple {
|
||||
expected: &[
|
||||
@ -877,8 +877,14 @@ impl<'src> Compiler<'src> {
|
||||
})
|
||||
}
|
||||
};
|
||||
let jump = Instruction::jump(1, true);
|
||||
let load_false = Instruction::load_boolean(destination, false, true);
|
||||
let load_true = Instruction::load_boolean(destination, true, false);
|
||||
|
||||
self.emit_instruction(comparison, Type::Boolean, operator_position);
|
||||
self.emit_instruction(jump, Type::None, operator_position);
|
||||
self.emit_instruction(load_false, Type::Boolean, operator_position);
|
||||
self.emit_instruction(load_true, Type::Boolean, operator_position);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -1132,7 +1138,19 @@ impl<'src> Compiler<'src> {
|
||||
self.advance()?;
|
||||
self.parse_expression()?;
|
||||
|
||||
if let Some((instruction, _, _)) = self.instructions.last() {
|
||||
if matches!(
|
||||
self.get_last_operations(),
|
||||
Some([
|
||||
Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL,
|
||||
Operation::JUMP,
|
||||
Operation::LOAD_BOOLEAN,
|
||||
Operation::LOAD_BOOLEAN
|
||||
]),
|
||||
) {
|
||||
self.instructions.pop();
|
||||
self.instructions.pop();
|
||||
self.instructions.pop();
|
||||
} else if let Some((instruction, _, _)) = self.instructions.last() {
|
||||
let argument = match instruction.as_argument() {
|
||||
Some(argument) => argument,
|
||||
None => {
|
||||
@ -1264,42 +1282,42 @@ impl<'src> Compiler<'src> {
|
||||
|
||||
self.parse_expression()?;
|
||||
|
||||
let (expression_instruction, expression_type, expression_position) =
|
||||
self.instructions.last().unwrap();
|
||||
|
||||
if expression_type != &Type::Boolean {
|
||||
return Err(CompileError::ExpectedFunction {
|
||||
found: self.previous_token.to_owned(),
|
||||
actual_type: expression_type.clone(),
|
||||
position: *expression_position,
|
||||
});
|
||||
}
|
||||
|
||||
let test_argument = match expression_instruction.as_argument() {
|
||||
Some(argument) => argument,
|
||||
None => {
|
||||
return Err(CompileError::ExpectedExpression {
|
||||
found: self.previous_token.to_owned(),
|
||||
position: *expression_position,
|
||||
})
|
||||
}
|
||||
};
|
||||
let test = Instruction::test(test_argument, true);
|
||||
|
||||
self.emit_instruction(test, Type::None, self.current_position);
|
||||
|
||||
if matches!(
|
||||
self.get_last_operations(),
|
||||
Some([
|
||||
Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL,
|
||||
Operation::JUMP,
|
||||
Operation::LOAD_BOOLEAN,
|
||||
Operation::LOAD_BOOLEAN,
|
||||
],)
|
||||
Operation::LOAD_BOOLEAN
|
||||
]),
|
||||
) {
|
||||
self.instructions.pop();
|
||||
self.instructions.pop();
|
||||
self.instructions.pop();
|
||||
} else {
|
||||
let (expression_instruction, expression_type, expression_position) =
|
||||
self.instructions.last().unwrap();
|
||||
|
||||
if expression_type != &Type::Boolean {
|
||||
return Err(CompileError::ExpectedFunction {
|
||||
found: self.previous_token.to_owned(),
|
||||
actual_type: expression_type.clone(),
|
||||
position: *expression_position,
|
||||
});
|
||||
}
|
||||
|
||||
let test_argument = match expression_instruction.as_argument() {
|
||||
Some(argument) => argument,
|
||||
None => {
|
||||
return Err(CompileError::ExpectedExpression {
|
||||
found: self.previous_token.to_owned(),
|
||||
position: *expression_position,
|
||||
})
|
||||
}
|
||||
};
|
||||
let test = Instruction::test(test_argument, true);
|
||||
|
||||
self.emit_instruction(test, Type::None, self.current_position);
|
||||
}
|
||||
|
||||
let block_start = self.instructions.len();
|
||||
|
@ -1,7 +1,6 @@
|
||||
use crate::{Argument, Instruction, Operation};
|
||||
|
||||
pub struct Equal {
|
||||
pub destination: u8,
|
||||
pub value: bool,
|
||||
pub left: Argument,
|
||||
pub right: Argument,
|
||||
@ -9,27 +8,20 @@ pub struct Equal {
|
||||
|
||||
impl From<&Instruction> for Equal {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
let destination = instruction.a_field();
|
||||
let value = instruction.d_field();
|
||||
let (left, right) = instruction.b_and_c_as_arguments();
|
||||
|
||||
Equal {
|
||||
destination,
|
||||
value,
|
||||
left,
|
||||
right,
|
||||
}
|
||||
Equal { value, left, right }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Equal> for Instruction {
|
||||
fn from(equal: Equal) -> Self {
|
||||
let operation = Operation::EQUAL;
|
||||
let a = equal.destination;
|
||||
let (b, b_is_constant) = equal.left.as_index_and_constant_flag();
|
||||
let (c, c_is_constant) = equal.right.as_index_and_constant_flag();
|
||||
let d = equal.value;
|
||||
|
||||
Instruction::new(operation, a, b, c, b_is_constant, c_is_constant, d)
|
||||
Instruction::new(operation, 0, b, c, b_is_constant, c_is_constant, d)
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
use crate::{Argument, Instruction, Operation};
|
||||
|
||||
pub struct Less {
|
||||
pub destination: u8,
|
||||
pub value: bool,
|
||||
pub left: Argument,
|
||||
pub right: Argument,
|
||||
@ -9,27 +8,20 @@ pub struct Less {
|
||||
|
||||
impl From<&Instruction> for Less {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
let destination = instruction.a_field();
|
||||
let value = instruction.d_field();
|
||||
let (left, right) = instruction.b_and_c_as_arguments();
|
||||
|
||||
Less {
|
||||
destination,
|
||||
value,
|
||||
left,
|
||||
right,
|
||||
}
|
||||
Less { value, left, right }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Less> for Instruction {
|
||||
fn from(less: Less) -> Self {
|
||||
let operation = Operation::LESS;
|
||||
let a = less.destination;
|
||||
let (b, b_is_constant) = less.left.as_index_and_constant_flag();
|
||||
let (c, c_is_constant) = less.right.as_index_and_constant_flag();
|
||||
let d = less.value;
|
||||
|
||||
Instruction::new(operation, a, b, c, b_is_constant, c_is_constant, d)
|
||||
Instruction::new(operation, 0, b, c, b_is_constant, c_is_constant, d)
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
use crate::{Argument, Instruction, Operation};
|
||||
|
||||
pub struct LessEqual {
|
||||
pub destination: u8,
|
||||
pub value: bool,
|
||||
pub left: Argument,
|
||||
pub right: Argument,
|
||||
@ -9,27 +8,20 @@ pub struct LessEqual {
|
||||
|
||||
impl From<&Instruction> for LessEqual {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
let destination = instruction.a_field();
|
||||
let value = instruction.d_field();
|
||||
let (left, right) = instruction.b_and_c_as_arguments();
|
||||
|
||||
LessEqual {
|
||||
destination,
|
||||
value,
|
||||
left,
|
||||
right,
|
||||
}
|
||||
LessEqual { value, left, right }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LessEqual> for Instruction {
|
||||
fn from(less_equal: LessEqual) -> Self {
|
||||
let operation = Operation::LESS_EQUAL;
|
||||
let a = less_equal.destination;
|
||||
let (b, b_options) = less_equal.left.as_index_and_constant_flag();
|
||||
let (c, c_options) = less_equal.right.as_index_and_constant_flag();
|
||||
let d = less_equal.value;
|
||||
|
||||
Instruction::new(operation, a, b, c, b_options, c_options, d)
|
||||
Instruction::new(operation, 0, b, c, b_options, c_options, d)
|
||||
}
|
||||
}
|
||||
|
@ -152,7 +152,7 @@ use crate::NativeFunction;
|
||||
#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct Instruction(u32);
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct InstructionData {
|
||||
pub a: u8,
|
||||
pub b: u8,
|
||||
@ -343,36 +343,16 @@ impl Instruction {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn equal(destination: u8, value: bool, left: Argument, right: Argument) -> Instruction {
|
||||
Instruction::from(Equal {
|
||||
destination,
|
||||
value,
|
||||
left,
|
||||
right,
|
||||
})
|
||||
pub fn equal(value: bool, left: Argument, right: Argument) -> Instruction {
|
||||
Instruction::from(Equal { value, left, right })
|
||||
}
|
||||
|
||||
pub fn less(destination: u8, value: bool, left: Argument, right: Argument) -> Instruction {
|
||||
Instruction::from(Less {
|
||||
destination,
|
||||
value,
|
||||
left,
|
||||
right,
|
||||
})
|
||||
pub fn less(value: bool, left: Argument, right: Argument) -> Instruction {
|
||||
Instruction::from(Less { value, left, right })
|
||||
}
|
||||
|
||||
pub fn less_equal(
|
||||
destination: u8,
|
||||
value: bool,
|
||||
left: Argument,
|
||||
right: Argument,
|
||||
) -> Instruction {
|
||||
Instruction::from(LessEqual {
|
||||
destination,
|
||||
value,
|
||||
left,
|
||||
right,
|
||||
})
|
||||
pub fn less_equal(value: bool, left: Argument, right: Argument) -> Instruction {
|
||||
Instruction::from(LessEqual { value, left, right })
|
||||
}
|
||||
|
||||
pub fn negate(destination: u8, argument: Argument) -> Instruction {
|
||||
@ -661,37 +641,22 @@ impl Instruction {
|
||||
format!("if {bang}{argument} {{ JUMP +1 }} else {{ R{destination} = {argument} }}")
|
||||
}
|
||||
Operation::EQUAL => {
|
||||
let Equal {
|
||||
destination,
|
||||
value,
|
||||
left,
|
||||
right,
|
||||
} = Equal::from(self);
|
||||
let Equal { value, left, right } = Equal::from(self);
|
||||
let comparison_symbol = if value { "==" } else { "!=" };
|
||||
|
||||
format!("R{destination} = {left} {comparison_symbol} {right}")
|
||||
format!("if {left} {comparison_symbol} {right} {{ JUMP +1 }}")
|
||||
}
|
||||
Operation::LESS => {
|
||||
let Less {
|
||||
destination,
|
||||
value,
|
||||
left,
|
||||
right,
|
||||
} = Less::from(self);
|
||||
let Less { value, left, right } = Less::from(self);
|
||||
let comparison_symbol = if value { "<" } else { ">=" };
|
||||
|
||||
format!("R{destination} = {left} {comparison_symbol} {right}")
|
||||
format!("if {left} {comparison_symbol} {right} {{ JUMP +1 }}")
|
||||
}
|
||||
Operation::LESS_EQUAL => {
|
||||
let LessEqual {
|
||||
destination,
|
||||
value,
|
||||
left,
|
||||
right,
|
||||
} = LessEqual::from(self);
|
||||
let LessEqual { value, left, right } = LessEqual::from(self);
|
||||
let comparison_symbol = if value { "<=" } else { ">" };
|
||||
|
||||
format!("R{destination} = {left} {comparison_symbol} {right}")
|
||||
format!("if {left} {comparison_symbol} {right} {{ JUMP +1 }}")
|
||||
}
|
||||
Operation::NEGATE => {
|
||||
let Negate {
|
||||
|
@ -17,23 +17,23 @@ impl AbstractValue {
|
||||
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 {
|
||||
AbstractValue::FunctionSelf => Ok(ConcreteValue::function(vm.chunk().clone())),
|
||||
AbstractValue::FunctionSelf => ConcreteValue::function(vm.chunk().clone()),
|
||||
AbstractValue::List { item_pointers, .. } => {
|
||||
let mut items: Vec<ConcreteValue> = Vec::with_capacity(item_pointers.len());
|
||||
|
||||
for pointer in item_pointers {
|
||||
let item_option = vm.follow_pointer_allow_empty(*pointer)?;
|
||||
let item_option = vm.follow_pointer_allow_empty(*pointer);
|
||||
let item = match item_option {
|
||||
Some(value_ref) => value_ref.into_concrete_owned(vm)?,
|
||||
Some(value_ref) => value_ref.into_concrete_owned(vm),
|
||||
None => continue,
|
||||
};
|
||||
|
||||
items.push(item);
|
||||
}
|
||||
|
||||
Ok(ConcreteValue::List(items))
|
||||
ConcreteValue::List(items)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -54,7 +54,7 @@ impl AbstractValue {
|
||||
display.push_str(", ");
|
||||
}
|
||||
|
||||
let item_display = vm.follow_pointer(*item)?.display(vm)?;
|
||||
let item_display = vm.follow_pointer(*item).display(vm)?;
|
||||
|
||||
display.push_str(&item_display);
|
||||
}
|
||||
|
@ -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 {
|
||||
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 {
|
||||
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