1
0

Use a lookup table instead of matching operation codes in the VM

This commit is contained in:
Jeff 2024-12-11 08:39:48 -05:00
parent 20f451fe6c
commit 395f0af213
5 changed files with 689 additions and 519 deletions

View File

@ -17,7 +17,7 @@ use std::io::Write;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use smallvec::SmallVec; use smallvec::SmallVec;
use crate::{ConcreteValue, DustString, FunctionType, Instruction, Span, Type}; use crate::{ConcreteValue, DustString, FunctionType, Instruction, Span};
/// In-memory representation of a Dust program or function. /// In-memory representation of a Dust program or function.
/// ///

View File

@ -154,7 +154,6 @@ pub struct Instruction(u32);
#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] #[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct InstructionData { pub struct InstructionData {
pub operation: Operation,
pub a: u8, pub a: u8,
pub b: u8, pub b: u8,
pub c: u8, pub c: u8,
@ -226,16 +225,18 @@ impl Instruction {
self.0 = (self.0 & 0xFF00FFFF) | ((bits as u32) << 24); self.0 = (self.0 & 0xFF00FFFF) | ((bits as u32) << 24);
} }
pub fn decode(self) -> InstructionData { pub fn decode(self) -> (Operation, InstructionData) {
(
self.operation(),
InstructionData { InstructionData {
operation: self.operation(),
a: self.a_field(), a: self.a_field(),
b: self.b_field(), b: self.b_field(),
c: self.c_field(), c: self.c_field(),
b_is_constant: self.b_is_constant(), b_is_constant: self.b_is_constant(),
c_is_constant: self.c_is_constant(), c_is_constant: self.c_is_constant(),
d: self.d_field(), d: self.d_field(),
} },
)
} }
pub fn r#move(from: u8, to: u8) -> Instruction { pub fn r#move(from: u8, to: u8) -> Instruction {
@ -498,7 +499,6 @@ impl Instruction {
Operation::LoadBoolean Operation::LoadBoolean
| Operation::LoadConstant | Operation::LoadConstant
| Operation::LoadList | Operation::LoadList
| Operation::LoadMap
| Operation::LoadSelf | Operation::LoadSelf
| Operation::GetLocal | Operation::GetLocal
| Operation::Add | Operation::Add
@ -506,7 +506,6 @@ impl Instruction {
| Operation::Multiply | Operation::Multiply
| Operation::Divide | Operation::Divide
| Operation::Modulo | Operation::Modulo
| Operation::Power
| Operation::Negate | Operation::Negate
| Operation::Not | Operation::Not
| Operation::Equal | Operation::Equal

View File

@ -9,27 +9,25 @@ pub const CLOSE_BYTE: u8 = 1;
pub const LOAD_BOOLEAN_BYTE: u8 = 2; pub const LOAD_BOOLEAN_BYTE: u8 = 2;
pub const LOAD_CONSTANT_BYTE: u8 = 3; pub const LOAD_CONSTANT_BYTE: u8 = 3;
pub const LOAD_LIST_BYTE: u8 = 4; pub const LOAD_LIST_BYTE: u8 = 4;
pub const LOAD_MAP_BYTE: u8 = 5; pub const LOAD_SELF_BYTE: u8 = 5;
pub const LOAD_SELF_BYTE: u8 = 6; pub const GET_LOCAL_BYTE: u8 = 6;
pub const GET_LOCAL_BYTE: u8 = 7; pub const SET_LOCAL_BYTE: u8 = 7;
pub const SET_LOCAL_BYTE: u8 = 8; pub const ADD_BYTE: u8 = 8;
pub const ADD_BYTE: u8 = 9; pub const SUBTRACT_BYTE: u8 = 9;
pub const SUBTRACT_BYTE: u8 = 10; pub const MULTIPLY_BYTE: u8 = 10;
pub const MULTIPLY_BYTE: u8 = 11; pub const DIVIDE_BYTE: u8 = 11;
pub const DIVIDE_BYTE: u8 = 12; pub const MODULO_BYTE: u8 = 12;
pub const MODULO_BYTE: u8 = 13; pub const TEST_BYTE: u8 = 13;
pub const POWER_BYTE: u8 = 14; pub const TEST_SET_BYTE: u8 = 14;
pub const TEST_BYTE: u8 = 15; pub const EQUAL_BYTE: u8 = 15;
pub const TEST_SET_BYTE: u8 = 16; pub const LESS_BYTE: u8 = 16;
pub const EQUAL_BYTE: u8 = 17; pub const LESS_EQUAL_BYTE: u8 = 17;
pub const LESS_BYTE: u8 = 18; pub const NEGATE_BYTE: u8 = 18;
pub const LESS_EQUAL_BYTE: u8 = 19; pub const NOT_BYTE: u8 = 19;
pub const NEGATE_BYTE: u8 = 20; pub const CALL_BYTE: u8 = 20;
pub const NOT_BYTE: u8 = 21; pub const CALL_NATIVE_BYTE: u8 = 21;
pub const CALL_BYTE: u8 = 22; pub const JUMP_BYTE: u8 = 22;
pub const CALL_NATIVE_BYTE: u8 = 23; pub const RETURN_BYTE: u8 = 23;
pub const JUMP_BYTE: u8 = 24;
pub const RETURN_BYTE: u8 = 25;
/// Part of an [Instruction][crate::Instruction] that is encoded as a single byte. /// Part of an [Instruction][crate::Instruction] that is encoded as a single byte.
#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] #[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
@ -40,7 +38,6 @@ pub enum Operation {
LoadBoolean = LOAD_BOOLEAN_BYTE, LoadBoolean = LOAD_BOOLEAN_BYTE,
LoadConstant = LOAD_CONSTANT_BYTE, LoadConstant = LOAD_CONSTANT_BYTE,
LoadList = LOAD_LIST_BYTE, LoadList = LOAD_LIST_BYTE,
LoadMap = LOAD_MAP_BYTE,
LoadSelf = LOAD_SELF_BYTE, LoadSelf = LOAD_SELF_BYTE,
GetLocal = GET_LOCAL_BYTE, GetLocal = GET_LOCAL_BYTE,
SetLocal = SET_LOCAL_BYTE, SetLocal = SET_LOCAL_BYTE,
@ -49,7 +46,6 @@ pub enum Operation {
Multiply = MULTIPLY_BYTE, Multiply = MULTIPLY_BYTE,
Divide = DIVIDE_BYTE, Divide = DIVIDE_BYTE,
Modulo = MODULO_BYTE, Modulo = MODULO_BYTE,
Power = POWER_BYTE,
Test = TEST_BYTE, Test = TEST_BYTE,
TestSet = TEST_SET_BYTE, TestSet = TEST_SET_BYTE,
Equal = EQUAL_BYTE, Equal = EQUAL_BYTE,
@ -71,7 +67,6 @@ impl From<u8> for Operation {
LOAD_BOOLEAN_BYTE => Self::LoadBoolean, LOAD_BOOLEAN_BYTE => Self::LoadBoolean,
LOAD_CONSTANT_BYTE => Self::LoadConstant, LOAD_CONSTANT_BYTE => Self::LoadConstant,
LOAD_LIST_BYTE => Self::LoadList, LOAD_LIST_BYTE => Self::LoadList,
LOAD_MAP_BYTE => Self::LoadMap,
LOAD_SELF_BYTE => Self::LoadSelf, LOAD_SELF_BYTE => Self::LoadSelf,
GET_LOCAL_BYTE => Self::GetLocal, GET_LOCAL_BYTE => Self::GetLocal,
SET_LOCAL_BYTE => Self::SetLocal, SET_LOCAL_BYTE => Self::SetLocal,
@ -80,7 +75,6 @@ impl From<u8> for Operation {
MULTIPLY_BYTE => Self::Multiply, MULTIPLY_BYTE => Self::Multiply,
DIVIDE_BYTE => Self::Divide, DIVIDE_BYTE => Self::Divide,
MODULO_BYTE => Self::Modulo, MODULO_BYTE => Self::Modulo,
POWER_BYTE => Self::Power,
TEST_BYTE => Self::Test, TEST_BYTE => Self::Test,
TEST_SET_BYTE => Self::TestSet, TEST_SET_BYTE => Self::TestSet,
EQUAL_BYTE => Self::Equal, EQUAL_BYTE => Self::Equal,
@ -111,7 +105,6 @@ impl Operation {
Self::LoadBoolean => "LOAD_BOOLEAN", Self::LoadBoolean => "LOAD_BOOLEAN",
Self::LoadConstant => "LOAD_CONSTANT", Self::LoadConstant => "LOAD_CONSTANT",
Self::LoadList => "LOAD_LIST", Self::LoadList => "LOAD_LIST",
Self::LoadMap => "LOAD_MAP",
Self::LoadSelf => "LOAD_SELF", Self::LoadSelf => "LOAD_SELF",
Self::GetLocal => "GET_LOCAL", Self::GetLocal => "GET_LOCAL",
Self::SetLocal => "SET_LOCAL", Self::SetLocal => "SET_LOCAL",
@ -120,7 +113,6 @@ impl Operation {
Self::Multiply => "MULTIPLY", Self::Multiply => "MULTIPLY",
Self::Divide => "DIVIDE", Self::Divide => "DIVIDE",
Self::Modulo => "MODULO", Self::Modulo => "MODULO",
Self::Power => "POWER",
Self::Test => "TEST", Self::Test => "TEST",
Self::TestSet => "TEST_SET", Self::TestSet => "TEST_SET",
Self::Equal => "EQUAL", Self::Equal => "EQUAL",
@ -152,13 +144,12 @@ impl Display for Operation {
mod tests { mod tests {
use super::*; use super::*;
const ALL_OPERATIONS: [Operation; 26] = [ const ALL_OPERATIONS: [Operation; 24] = [
Operation::Move, Operation::Move,
Operation::Close, Operation::Close,
Operation::LoadBoolean, Operation::LoadBoolean,
Operation::LoadConstant, Operation::LoadConstant,
Operation::LoadList, Operation::LoadList,
Operation::LoadMap,
Operation::LoadSelf, Operation::LoadSelf,
Operation::GetLocal, Operation::GetLocal,
Operation::SetLocal, Operation::SetLocal,
@ -167,7 +158,6 @@ mod tests {
Operation::Multiply, Operation::Multiply,
Operation::Divide, Operation::Divide,
Operation::Modulo, Operation::Modulo,
Operation::Power,
Operation::Test, Operation::Test,
Operation::TestSet, Operation::TestSet,
Operation::Equal, Operation::Equal,

View File

@ -154,7 +154,7 @@ impl ValueRef<'_> {
} }
#[inline(always)] #[inline(always)]
pub fn less_than(&self, other: ValueRef) -> Result<Value, ValueError> { pub fn less(&self, other: ValueRef) -> Result<Value, ValueError> {
match (self, other) { match (self, other) {
(ValueRef::Concrete(left), ValueRef::Concrete(right)) => { (ValueRef::Concrete(left), ValueRef::Concrete(right)) => {
left.less_than(right).map(Value::Concrete) left.less_than(right).map(Value::Concrete)
@ -163,7 +163,7 @@ impl ValueRef<'_> {
} }
} }
pub fn less_than_or_equal(&self, other: ValueRef) -> Result<Value, ValueError> { pub fn less_equal(&self, other: ValueRef) -> Result<Value, ValueError> {
match (self, other) { match (self, other) {
(ValueRef::Concrete(left), ValueRef::Concrete(right)) => { (ValueRef::Concrete(left), ValueRef::Concrete(right)) => {
left.less_than_or_equal(right).map(Value::Concrete) left.less_than_or_equal(right).map(Value::Concrete)

View File

@ -8,7 +8,7 @@ use smallvec::SmallVec;
use crate::{ use crate::{
compile, instruction::*, AbstractValue, AnnotatedError, Chunk, ConcreteValue, DustError, compile, instruction::*, AbstractValue, AnnotatedError, Chunk, ConcreteValue, DustError,
Instruction, NativeFunctionError, Operation, Span, Value, ValueError, ValueRef, Instruction, NativeFunction, NativeFunctionError, Operation, Span, Value, ValueError, ValueRef,
}; };
pub fn run(source: &str) -> Result<Option<ConcreteValue>, DustError> { pub fn run(source: &str) -> Result<Option<ConcreteValue>, DustError> {
@ -18,6 +18,34 @@ pub fn run(source: &str) -> Result<Option<ConcreteValue>, DustError> {
vm.run().map_err(|error| DustError::runtime(error, source)) vm.run().map_err(|error| DustError::runtime(error, source))
} }
type Runner = fn(&mut Vm, InstructionData) -> Result<(), VmError>;
const RUNNERS: [Runner; 23] = [
Vm::r#move,
Vm::close,
Vm::load_boolean,
Vm::load_constant,
Vm::load_list,
Vm::load_self,
Vm::get_local,
Vm::set_local,
Vm::add,
Vm::subtract,
Vm::multiply,
Vm::divide,
Vm::modulo,
Vm::test,
Vm::test_set,
Vm::equal,
Vm::less,
Vm::less_equal,
Vm::negate,
Vm::not,
Vm::call,
Vm::call_native,
Vm::jump,
];
/// Dust virtual machine. /// Dust virtual machine.
/// ///
/// See the [module-level documentation](index.html) for more information. /// See the [module-level documentation](index.html) for more information.
@ -62,262 +90,320 @@ impl<'a> Vm<'a> {
position position
} }
pub fn run(&mut self) -> Result<Option<ConcreteValue>, VmError> { fn r#move<'b, 'c>(
loop { vm: &'b mut Vm<'c>,
let instruction = self.read(); instruction_data: InstructionData,
) -> Result<(), VmError> {
let InstructionData { b, c, .. } = instruction_data;
let from_register_has_value = vm
.stack
.get(b as usize)
.is_some_and(|register| !matches!(register, Register::Empty));
let register = Register::Pointer(Pointer::Stack(b));
if from_register_has_value {
vm.set_register(c, register)?;
}
Ok(())
}
fn close<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> {
let InstructionData { b, c, .. } = instruction_data;
if vm.stack.len() < c as usize {
return Err(VmError::StackUnderflow {
position: vm.current_position(),
});
}
for register_index in b..c {
vm.stack[register_index as usize] = Register::Empty;
}
Ok(())
}
fn load_boolean<'b, 'c>(
vm: &'b mut Vm<'c>,
instruction_data: InstructionData,
) -> Result<(), VmError> {
let InstructionData { a, b, c, .. } = instruction_data;
let boolean = ConcreteValue::Boolean(b != 0).to_value();
let register = Register::Value(boolean);
vm.set_register(a, register)?;
if c != 0 {
vm.jump_instructions(1, true);
}
Ok(())
}
fn load_constant<'b, 'c>(
vm: &'b mut Vm<'c>,
instruction_data: InstructionData,
) -> Result<(), VmError> {
let InstructionData { a, b, c, .. } = instruction_data;
let register = Register::Pointer(Pointer::Constant(b));
vm.set_register(a, register)?;
if c != 0 {
vm.jump_instructions(1, true);
}
Ok(())
}
fn load_list<'b, 'c>(
vm: &'b mut Vm<'c>,
instruction_data: InstructionData,
) -> Result<(), VmError> {
let InstructionData { a, b, .. } = instruction_data;
let mut item_pointers = Vec::new();
let stack = vm.stack.as_slice();
for register_index in b..a {
if let Register::Empty = stack[register_index as usize] {
continue;
}
let pointer = Pointer::Stack(register_index);
item_pointers.push(pointer);
}
let list_value = AbstractValue::List { item_pointers }.to_value();
let register = Register::Value(list_value);
vm.set_register(a, register)
}
fn load_self<'b, 'c>(
vm: &'b mut Vm<'c>,
instruction_data: InstructionData,
) -> Result<(), VmError> {
let InstructionData { a, .. } = instruction_data;
let register = Register::Value(AbstractValue::FunctionSelf.to_value());
vm.set_register(a, register)
}
fn get_local<'b, 'c>(
vm: &'b mut Vm<'c>,
instruction_data: InstructionData,
) -> Result<(), VmError> {
let InstructionData { a, b, .. } = instruction_data;
let local_register_index = vm.get_local_register(b)?;
let register = Register::Pointer(Pointer::Stack(local_register_index));
vm.set_register(a, register)
}
fn set_local<'b, 'c>(
vm: &'b mut Vm<'c>,
instruction_data: InstructionData,
) -> Result<(), VmError> {
let InstructionData { b, c, .. } = instruction_data;
let local_register_index = vm.get_local_register(c)?;
let register = Register::Pointer(Pointer::Stack(b));
vm.set_register(local_register_index, register)
}
fn add<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> {
let InstructionData { let InstructionData {
operation,
a, a,
b, b,
c, c,
b_is_constant, b_is_constant,
c_is_constant, c_is_constant,
d, ..
} = instruction.decode(); } = instruction_data;
let left = vm.get_argument(b, b_is_constant)?;
log::info!( let right = vm.get_argument(c, c_is_constant)?;
"{} | {} | {} | {}",
self.ip - 1,
self.current_position(),
instruction.operation(),
instruction.disassembly_info()
);
match operation {
Operation::Move => {
let Move { from, to } = Move::from(&instruction);
let from_register_has_value = self
.stack
.get(from as usize)
.is_some_and(|register| !matches!(register, Register::Empty));
let register = Register::Pointer(Pointer::Stack(from));
if from_register_has_value {
self.set_register(to, register)?;
}
}
Operation::Close => {
let Close { from, to } = Close::from(&instruction);
if self.stack.len() < to as usize {
return Err(VmError::StackUnderflow {
position: self.current_position(),
});
}
for register_index in from..to {
self.stack[register_index as usize] = Register::Empty;
}
}
Operation::LoadBoolean => {
let LoadBoolean {
destination,
value,
jump_next,
} = LoadBoolean::from(&instruction);
let boolean = ConcreteValue::Boolean(value).to_value();
let register = Register::Value(boolean);
self.set_register(destination, register)?;
if jump_next {
self.jump(1, true);
}
}
Operation::LoadConstant => {
let register = Register::Pointer(Pointer::Constant(b));
self.set_register(a, register)?;
if c != 0 {
self.jump(1, true);
}
}
Operation::LoadList => {
let LoadList {
destination,
start_register,
} = LoadList::from(&instruction);
let mut pointers = Vec::new();
for register in start_register..destination {
if let Some(Register::Empty) = self.stack.get(register as usize) {
continue;
}
let pointer = Pointer::Stack(register);
pointers.push(pointer);
}
let register = Register::Value(
AbstractValue::List {
item_pointers: pointers,
}
.to_value(),
);
self.set_register(destination, register)?;
}
Operation::LoadSelf => {
let LoadSelf { destination } = LoadSelf::from(&instruction);
let register = Register::Value(AbstractValue::FunctionSelf.to_value());
self.set_register(destination, register)?;
}
Operation::GetLocal => {
let GetLocal {
destination,
local_index,
} = GetLocal::from(&instruction);
let local_register = self.get_local_register(local_index)?;
let register = Register::Pointer(Pointer::Stack(local_register));
self.set_register(destination, register)?;
}
Operation::SetLocal => {
let SetLocal {
register_index,
local_index,
} = SetLocal::from(&instruction);
let local_register_index = self.get_local_register(local_index)?;
let register = Register::Pointer(Pointer::Stack(register_index));
self.set_register(local_register_index, register)?;
}
Operation::Add => {
let left = if b_is_constant {
self.get_constant(b).to_value_ref()
} else {
self.open_register(b)?
};
let right = if c_is_constant {
self.get_constant(c).to_value_ref()
} else {
self.open_register(c)?
};
let sum_result = left.add(right); let sum_result = left.add(right);
let sum = match sum_result { let sum = match sum_result {
Ok(sum) => sum, Ok(sum) => sum,
Err(error) => { Err(error) => {
return Err(VmError::Value { return Err(VmError::Value {
error, error,
position: self.current_position(), position: vm.current_position(),
}); });
} }
}; };
let register = Register::Value(sum);
self.set_register(a, Register::Value(sum))?; vm.set_register(a, register)
} }
Operation::Subtract => {
let left = self.get_argument(b, b_is_constant)?; fn subtract<'b, 'c>(
let right = self.get_argument(c, c_is_constant)?; vm: &'b mut Vm<'c>,
instruction_data: InstructionData,
) -> Result<(), VmError> {
let InstructionData {
a,
b,
c,
b_is_constant,
c_is_constant,
..
} = instruction_data;
let left = vm.get_argument(b, b_is_constant)?;
let right = vm.get_argument(c, c_is_constant)?;
let subtraction_result = left.subtract(right); let subtraction_result = left.subtract(right);
let difference = match subtraction_result { let difference = match subtraction_result {
Ok(difference) => difference, Ok(difference) => difference,
Err(error) => { Err(error) => {
return Err(VmError::Value { return Err(VmError::Value {
error, error,
position: self.current_position(), position: vm.current_position(),
}); });
} }
}; };
let register = Register::Value(difference);
self.set_register(a, Register::Value(difference))?; vm.set_register(a, register)
} }
Operation::Multiply => {
let left = self.get_argument(b, b_is_constant)?; fn multiply<'b, 'c>(
let right = self.get_argument(c, c_is_constant)?; vm: &'b mut Vm<'c>,
instruction_data: InstructionData,
) -> Result<(), VmError> {
let InstructionData {
a,
b,
c,
b_is_constant,
c_is_constant,
..
} = instruction_data;
let left = vm.get_argument(b, b_is_constant)?;
let right = vm.get_argument(c, c_is_constant)?;
let multiplication_result = left.multiply(right); let multiplication_result = left.multiply(right);
let product = match multiplication_result { let product = match multiplication_result {
Ok(product) => product, Ok(product) => product,
Err(error) => { Err(error) => {
return Err(VmError::Value { return Err(VmError::Value {
error, error,
position: self.current_position(), position: vm.current_position(),
}); });
} }
}; };
let register = Register::Value(product);
self.set_register(a, Register::Value(product))?; vm.set_register(a, register)
} }
Operation::Divide => {
let Divide { fn divide<'b, 'c>(
destination, vm: &'b mut Vm<'c>,
left, instruction_data: InstructionData,
right, ) -> Result<(), VmError> {
} = Divide::from(&instruction); let InstructionData {
let left = self.get_argument(b, b_is_constant)?; a,
let right = self.get_argument(c, c_is_constant)?; b,
c,
b_is_constant,
c_is_constant,
..
} = instruction_data;
let left = vm.get_argument(b, b_is_constant)?;
let right = vm.get_argument(c, c_is_constant)?;
let division_result = left.divide(right); let division_result = left.divide(right);
let quotient = match division_result { let quotient = match division_result {
Ok(quotient) => quotient, Ok(quotient) => quotient,
Err(error) => { Err(error) => {
return Err(VmError::Value { return Err(VmError::Value {
error, error,
position: self.current_position(), position: vm.current_position(),
}); });
} }
}; };
let register = Register::Value(quotient);
self.set_register(destination, Register::Value(quotient))?; vm.set_register(a, register)
} }
Operation::Modulo => {
let Modulo { fn modulo<'b, 'c>(
destination, vm: &'b mut Vm<'c>,
left, instruction_data: InstructionData,
right, ) -> Result<(), VmError> {
} = Modulo::from(&instruction); let InstructionData {
let left = self.get_argument(b, b_is_constant)?; a,
let right = self.get_argument(c, c_is_constant)?; b,
c,
b_is_constant,
c_is_constant,
..
} = instruction_data;
let left = vm.get_argument(b, b_is_constant)?;
let right = vm.get_argument(c, c_is_constant)?;
let modulo_result = left.modulo(right); let modulo_result = left.modulo(right);
let remainder = match modulo_result { let remainder = match modulo_result {
Ok(remainder) => remainder, Ok(remainder) => remainder,
Err(error) => { Err(error) => {
return Err(VmError::Value { return Err(VmError::Value {
error, error,
position: self.current_position(), position: vm.current_position(),
}); });
} }
}; };
let register = Register::Value(remainder);
self.set_register(destination, Register::Value(remainder))?; vm.set_register(a, register)
} }
Operation::Test => {
let value = if b_is_constant { fn test<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> {
self.get_constant(b).to_value_ref() let InstructionData {
} else { b,
self.open_register(b)? b_is_constant,
}; c,
let boolean = if let ValueRef::Concrete(ConcreteValue::Boolean(boolean)) = value ..
{ } = instruction_data;
let value = vm.get_argument(b, b_is_constant)?;
let boolean = if let ValueRef::Concrete(ConcreteValue::Boolean(boolean)) = value {
*boolean *boolean
} else { } else {
return Err(VmError::ExpectedBoolean { panic!(
found: value.to_owned(), "VM Error: Expected boolean value for TEST operation at {}",
position: self.current_position(), vm.current_position()
}); );
};
if boolean == (c != 0) {
self.jump(1, true);
}
}
Operation::TestSet => {
let value = self.get_argument(b, b_is_constant)?;
let boolean = if let ValueRef::Concrete(ConcreteValue::Boolean(boolean)) = value
{
*boolean
} else {
return Err(VmError::ExpectedBoolean {
found: value.to_owned(),
position: self.current_position(),
});
}; };
let test_value = c != 0; let test_value = c != 0;
if boolean == test_value { if boolean == test_value {
self.jump(1, true); vm.jump_instructions(1, true);
}
Ok(())
}
fn test_set<'b, 'c>(
vm: &'b mut Vm<'c>,
instruction_data: InstructionData,
) -> Result<(), VmError> {
let InstructionData {
a,
b,
c,
b_is_constant,
..
} = instruction_data;
let value = vm.get_argument(b, b_is_constant)?;
let boolean = if let ValueRef::Concrete(ConcreteValue::Boolean(boolean)) = value {
*boolean
} else {
panic!(
"VM Error: Expected boolean value for TEST_SET operation at {}",
vm.current_position()
);
};
let test_value = c != 0;
if boolean == test_value {
vm.jump_instructions(1, true);
} else { } else {
let pointer = if b_is_constant { let pointer = if b_is_constant {
Pointer::Constant(b) Pointer::Constant(b)
@ -326,148 +412,179 @@ impl<'a> Vm<'a> {
}; };
let register = Register::Pointer(pointer); let register = Register::Pointer(pointer);
self.set_register(a, register)?; vm.set_register(a, register)?;
} }
Ok(())
} }
Operation::Equal => {
let left = self.get_argument(b, b_is_constant)?; fn equal<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> {
let right = self.get_argument(c, c_is_constant)?; let InstructionData {
a,
b,
c,
b_is_constant,
c_is_constant,
d,
..
} = instruction_data;
let left = vm.get_argument(b, b_is_constant)?;
let right = vm.get_argument(c, c_is_constant)?;
let equal_result = left.equal(right).map_err(|error| VmError::Value { let equal_result = left.equal(right).map_err(|error| VmError::Value {
error, error,
position: self.current_position(), position: vm.current_position(),
})?; })?;
let is_equal = let is_equal = if let Value::Concrete(ConcreteValue::Boolean(is_equal)) = equal_result {
if let Value::Concrete(ConcreteValue::Boolean(boolean)) = equal_result { is_equal
boolean
} else { } else {
return Err(VmError::ExpectedBoolean { panic!(
found: equal_result, "VM Error: Expected boolean value for EQUAL operation at {}",
position: self.current_position(), vm.current_position()
}); );
}; };
let comparison = is_equal == d; let comparison = is_equal == d;
let register = let register = Register::Value(Value::Concrete(ConcreteValue::Boolean(comparison)));
Register::Value(Value::Concrete(ConcreteValue::Boolean(comparison)));
self.set_register(a, register)?; vm.set_register(a, register)
} }
Operation::Less => {
let left = if b_is_constant { fn less<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> {
self.get_constant(b).to_value_ref() let InstructionData {
} else { a,
self.open_register(b)? b,
}; c,
let right = if c_is_constant { b_is_constant,
self.get_constant(c).to_value_ref() c_is_constant,
} else { d,
self.open_register(c)? ..
}; } = instruction_data;
let less_result = left.less_than(right); let left = vm.get_argument(b, b_is_constant)?;
let less_than_value = match less_result { let right = vm.get_argument(c, c_is_constant)?;
Ok(value) => value, let less_result = left.less(right).map_err(|error| VmError::Value {
Err(error) => {
return Err(VmError::Value {
error, error,
position: self.current_position(), position: vm.current_position(),
}); })?;
} let is_less = if let Value::Concrete(ConcreteValue::Boolean(is_less)) = less_result {
is_less
} else {
panic!(
"VM Error: Expected boolean value for LESS operation at {}",
vm.current_position()
);
}; };
let is_less_than = match less_than_value { let comparison = is_less == d;
Value::Concrete(ConcreteValue::Boolean(boolean)) => boolean, let register = Register::Value(Value::Concrete(ConcreteValue::Boolean(comparison)));
_ => {
return Err(VmError::ExpectedBoolean {
found: less_than_value,
position: self.current_position(),
});
}
};
let comparison = is_less_than == d;
let register =
Register::Value(Value::Concrete(ConcreteValue::Boolean(comparison)));
self.set_register(a, register)?; vm.set_register(a, register)
} }
Operation::LessEqual => {
let left = if b_is_constant { fn less_equal<'b, 'c>(
self.get_constant(b).to_value_ref() vm: &'b mut Vm<'c>,
} else { instruction_data: InstructionData,
self.open_register(b)? ) -> Result<(), VmError> {
}; let InstructionData {
let right = if c_is_constant { a,
self.get_constant(c).to_value_ref() b,
} else { c,
self.open_register(c)? b_is_constant,
}; c_is_constant,
let less_or_equal_result = left.less_than_or_equal(right); d,
let less_or_equal_value = match less_or_equal_result { ..
Ok(value) => value, } = instruction_data;
Err(error) => { let left = vm.get_argument(b, b_is_constant)?;
return Err(VmError::Value { let right = vm.get_argument(c, c_is_constant)?;
let less_or_equal_result = left.less_equal(right).map_err(|error| VmError::Value {
error, error,
position: self.current_position(), position: vm.current_position(),
}); })?;
} let is_less_or_equal = if let Value::Concrete(ConcreteValue::Boolean(is_less_or_equal)) =
less_or_equal_result
{
is_less_or_equal
} else {
panic!(
"VM Error: Expected boolean value for LESS_EQUAl operation at {}",
vm.current_position()
);
}; };
let is_less_than_or_equal = match less_or_equal_value { let comparison = is_less_or_equal == d;
Value::Concrete(ConcreteValue::Boolean(boolean)) => boolean, let register = Register::Value(Value::Concrete(ConcreteValue::Boolean(comparison)));
_ => {
return Err(VmError::ExpectedBoolean {
found: less_or_equal_value,
position: self.current_position(),
});
}
};
let comparison = is_less_than_or_equal == d;
let register =
Register::Value(Value::Concrete(ConcreteValue::Boolean(comparison)));
self.set_register(a, register)?; vm.set_register(a, register)
} }
Operation::Negate => {
let value = self.get_argument(b, b_is_constant)?; fn negate<'b, 'c>(
vm: &'b mut Vm<'c>,
instruction_data: InstructionData,
) -> Result<(), VmError> {
let InstructionData {
a,
b,
b_is_constant,
..
} = instruction_data;
let value = vm.get_argument(b, b_is_constant)?;
let negated = value.negate().map_err(|error| VmError::Value { let negated = value.negate().map_err(|error| VmError::Value {
error, error,
position: self.current_position(), position: vm.current_position(),
})?; })?;
let register = Register::Value(negated); let register = Register::Value(negated);
self.set_register(a, register)?; vm.set_register(a, register)
} }
Operation::Not => {
let value = self.get_argument(b, b_is_constant)?; fn not<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> {
let InstructionData {
a,
b,
b_is_constant,
..
} = instruction_data;
let value = vm.get_argument(b, b_is_constant)?;
let not = value.not().map_err(|error| VmError::Value { let not = value.not().map_err(|error| VmError::Value {
error, error,
position: self.current_position(), position: vm.current_position(),
})?; })?;
let register = Register::Value(not); let register = Register::Value(not);
self.set_register(a, register)?; vm.set_register(a, register)
} }
Operation::Jump => {
self.jump(b as usize, c != 0); fn jump<'b, 'c>(vm: &mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> {
let InstructionData { b, c, .. } = instruction_data;
let is_positive = c != 0;
vm.jump_instructions(b as usize, is_positive);
Ok(())
} }
Operation::Call => {
let function = self.get_argument(b, b_is_constant)?; fn call<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> {
let chunk = if let ValueRef::Concrete(ConcreteValue::Function(chunk)) = function 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 chunk
} else if let ValueRef::Abstract(AbstractValue::FunctionSelf) = function { } else if let ValueRef::Abstract(AbstractValue::FunctionSelf) = function {
self.chunk vm.chunk
} else { } else {
return Err(VmError::ExpectedFunction { return Err(VmError::ExpectedFunction {
found: function.into_concrete_owned(self)?, found: function.into_concrete_owned(vm)?,
position: self.current_position(), position: vm.current_position(),
}); });
}; };
let mut function_vm = Vm::new(self.source, chunk, Some(self)); let mut function_vm = Vm::new(vm.source, chunk, Some(vm));
let first_argument_index = a - c; let first_argument_index = a - c;
let mut argument_index = 0; let mut argument_index = 0;
for argument_register_index in first_argument_index..a { for argument_register_index in first_argument_index..a {
let target_register_is_empty = matches!( let target_register_is_empty =
self.stack[argument_register_index as usize], matches!(vm.stack[argument_register_index as usize], Register::Empty);
Register::Empty
);
if target_register_is_empty { if target_register_is_empty {
continue; continue;
@ -486,25 +603,27 @@ impl<'a> Vm<'a> {
if let Some(concrete_value) = return_value { if let Some(concrete_value) = return_value {
let register = Register::Value(concrete_value.to_value()); let register = Register::Value(concrete_value.to_value());
self.set_register(a, register)?; vm.set_register(a, register)?;
} }
Ok(())
} }
Operation::CallNative => {
let CallNative { fn call_native<'b, 'c>(
destination, vm: &'b mut Vm<'c>,
function, instruction_data: InstructionData,
argument_count, ) -> Result<(), VmError> {
} = CallNative::from(&instruction); let InstructionData { a, b, c, .. } = instruction_data;
let first_argument_index = (destination - argument_count) as usize; let first_argument_index = (a - c) as usize;
let argument_range = first_argument_index..destination as usize; let argument_range = first_argument_index..a as usize;
let mut arguments: SmallVec<[ValueRef; 4]> = SmallVec::new(); let mut arguments: SmallVec<[ValueRef; 4]> = SmallVec::new();
for register_index in argument_range { for register_index in argument_range {
let register = &self.stack[register_index]; let register = &vm.stack[register_index];
let value = match register { let value = match register {
Register::Value(value) => value.to_ref(), Register::Value(value) => value.to_ref(),
Register::Pointer(pointer) => { Register::Pointer(pointer) => {
let value_option = self.follow_pointer_allow_empty(*pointer)?; let value_option = vm.follow_pointer_allow_empty(*pointer)?;
match value_option { match value_option {
Some(value) => value, Some(value) => value,
@ -517,7 +636,8 @@ impl<'a> Vm<'a> {
arguments.push(value); arguments.push(value);
} }
let call_result = function.call(self, arguments); let function = NativeFunction::from(b);
let call_result = function.call(vm, arguments);
let return_value = match call_result { let return_value = match call_result {
Ok(value_option) => value_option, Ok(value_option) => value_option,
Err(error) => return Err(VmError::NativeFunction(error)), Err(error) => return Err(VmError::NativeFunction(error)),
@ -526,21 +646,35 @@ impl<'a> Vm<'a> {
if let Some(value) = return_value { if let Some(value) = return_value {
let register = Register::Value(value); let register = Register::Value(value);
self.set_register(destination, register)?; vm.set_register(a, register)?;
} }
Ok(())
} }
Operation::Return => {
let Return { pub fn run(&mut self) -> Result<Option<ConcreteValue>, VmError> {
should_return_value, loop {
} = Return::from(&instruction); let instruction = self.read();
let (operation, instruction_data) = instruction.decode();
log::info!(
"{} | {} | {} | {}",
self.ip - 1,
self.current_position(),
instruction.operation(),
instruction.disassembly_info()
);
if let Operation::Return = operation {
let should_return_value = instruction_data.b != 0;
if !should_return_value { if !should_return_value {
return Ok(None); return Ok(None);
} }
return if let Some(register_index) = self.last_assigned_register { return if let Some(register_index) = &self.last_assigned_register {
let return_value = self let return_value = self
.open_register(register_index)? .open_register(*register_index)?
.into_concrete_owned(self)?; .into_concrete_owned(self)?;
Ok(Some(return_value)) Ok(Some(return_value))
@ -549,8 +683,10 @@ impl<'a> Vm<'a> {
position: self.current_position(), position: self.current_position(),
}) })
}; };
} } else {
_ => unreachable!(), let runner = RUNNERS[operation as usize];
runner(self, instruction_data).unwrap();
} }
} }
} }
@ -659,7 +795,7 @@ impl<'a> Vm<'a> {
} }
/// DRY helper for handling JUMP instructions /// DRY helper for handling JUMP instructions
fn jump(&mut self, offset: usize, is_positive: bool) { fn jump_instructions(&mut self, offset: usize, is_positive: bool) {
log::trace!( log::trace!(
"Jumping {}", "Jumping {}",
if is_positive { if is_positive {
@ -901,3 +1037,48 @@ impl AnnotatedError for VmError {
todo!() todo!()
} }
} }
#[cfg(test)]
mod tests {
use std::any::Any;
use super::*;
const ALL_OPERATIONS: [(Operation, Runner); 23] = [
(Operation::Move, Vm::r#move),
(Operation::Close, Vm::close),
(Operation::LoadBoolean, Vm::load_boolean),
(Operation::LoadConstant, Vm::load_constant),
(Operation::LoadList, Vm::load_list),
(Operation::LoadSelf, Vm::load_self),
(Operation::GetLocal, Vm::get_local),
(Operation::SetLocal, Vm::set_local),
(Operation::Add, Vm::add),
(Operation::Subtract, Vm::subtract),
(Operation::Multiply, Vm::multiply),
(Operation::Divide, Vm::divide),
(Operation::Modulo, Vm::modulo),
(Operation::Test, Vm::test),
(Operation::TestSet, Vm::test_set),
(Operation::Equal, Vm::equal),
(Operation::Less, Vm::less),
(Operation::LessEqual, Vm::less_equal),
(Operation::Negate, Vm::negate),
(Operation::Not, Vm::not),
(Operation::Call, Vm::call),
(Operation::CallNative, Vm::call_native),
(Operation::Jump, Vm::jump),
];
#[test]
fn operations_map_to_the_correct_runner() {
for (operation, expected_runner) in ALL_OPERATIONS {
let actual_runner = RUNNERS[operation as usize];
assert_eq!(
expected_runner, actual_runner,
"{operation} runner is incorrect"
);
}
}
}