1
0

Continue rewrite of instructions and operations

This commit is contained in:
Jeff 2024-12-09 07:01:07 -05:00
parent cc069df7ee
commit a9e867aaab
40 changed files with 957 additions and 999 deletions

View File

@ -137,20 +137,23 @@ The compiler always checks types on the fly, so there is no need for a separate
### Instructions
Dust's virtual machine is register-based and uses 64-bit instructions, which encode nine pieces of
Dust's virtual machine is register-based and uses 64-bit instructions, which encode ten pieces of
information:
Bit | Description
----- | -----------
0-8 | The operation code.
9 | Boolean flag indicating whether the second argument is a constant
10 | Boolean flag indicating whether the third argument is a constant
11 | Boolean flag indicating whether the first argument is a local
12 | Boolean flag indicating whether the second argument is a local
13 | Boolean flag indicating whether the third argument is a local
17-32 | First argument, usually the destination register or local where a value is stored
33-48 | Second argument, a register, local, constant or boolean flag
49-63 | Third argument, a register, local, constant or boolean flag
0-5 | Operation code
6-8 | Unused, reserved in case more operation codes are needed
9 | Flag indicating that A is a local
10 | Flag indicating that B is a constant
11 | Flag indicating that B is a local
12 | Flag indicating that C is a constant
13 | Flag indicating that C is a local
14 | D Argument (boolean value)
15-16 | Unused
17-32 | A argument (unsigned 16-bit integer)
33-48 | B argument (unsigned 16-bit integer)
49-63 | C argument (unsigned 16-bit integer)
Because the instructions are 64 bits, the maximum number of registers is 2^16, which is more than
enough, even for programs that are very large. This also means that chunks can store up to 2^16

View File

@ -20,8 +20,7 @@ pub struct Chunk {
name: Option<String>,
r#type: FunctionType,
instructions: SmallVec<[Instruction; 32]>,
positions: SmallVec<[Span; 32]>,
instructions: SmallVec<[(Instruction, Span); 32]>,
constants: SmallVec<[ConcreteValue; 16]>,
locals: SmallVec<[Local; 8]>,
}
@ -31,7 +30,6 @@ impl Chunk {
Self {
name,
instructions: SmallVec::new(),
positions: SmallVec::new(),
constants: SmallVec::new(),
locals: SmallVec::new(),
r#type: FunctionType {
@ -45,8 +43,7 @@ impl Chunk {
pub fn with_data(
name: Option<String>,
r#type: FunctionType,
instructions: SmallVec<[Instruction; 32]>,
positions: SmallVec<[Span; 32]>,
instructions: SmallVec<[(Instruction, Span); 32]>,
constants: SmallVec<[ConcreteValue; 16]>,
locals: SmallVec<[Local; 8]>,
) -> Self {
@ -54,7 +51,6 @@ impl Chunk {
name,
r#type,
instructions,
positions,
constants,
locals,
}
@ -80,14 +76,10 @@ impl Chunk {
&self.constants
}
pub fn instructions(&self) -> &SmallVec<[Instruction; 32]> {
pub fn instructions(&self) -> &SmallVec<[(Instruction, Span); 32]> {
&self.instructions
}
pub fn positions(&self) -> &SmallVec<[Span; 32]> {
&self.positions
}
pub fn locals(&self) -> &SmallVec<[Local; 8]> {
&self.locals
}
@ -96,9 +88,9 @@ impl Chunk {
self.instructions()
.iter()
.rev()
.find_map(|instruction| {
.find_map(|(instruction, _)| {
if instruction.yields_value() {
Some(instruction.a() as usize + 1)
Some(instruction.a as usize + 1)
} else {
None
}
@ -145,10 +137,10 @@ impl PartialEq for Chunk {
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Local {
/// The index of the identifier in the constants table.
pub identifier_index: u16,
pub identifier_index: u8,
/// The expected type of the local's value.
pub r#type: Type,
/// Stack index where the local's value is stored.
pub register_index: u8,
/// Whether the local is mutable.
pub is_mutable: bool,
@ -159,11 +151,11 @@ pub struct Local {
impl Local {
/// Creates a new Local instance.
pub fn new(identifier_index: u16, r#type: Type, mutable: bool, scope: Scope) -> Self {
pub fn new(identifier_index: u8, register_index: u8, is_mutable: bool, scope: Scope) -> Self {
Self {
identifier_index,
r#type,
is_mutable: mutable,
register_index,
is_mutable,
scope,
}
}

View File

@ -4,6 +4,8 @@
//! - [`compile`] borrows a string and returns a chunk, handling the entire compilation process and
//! turning any resulting [`ComplileError`] into a [`DustError`].
//! - [`Compiler`] uses a lexer to get tokens and assembles a chunk.
mod optimize;
use std::{
fmt::{self, Display, Formatter},
mem::replace,
@ -11,16 +13,17 @@ use std::{
};
use colored::Colorize;
use optimize::{optimize_control_flow, optimize_set_local};
use smallvec::{smallvec, SmallVec};
use crate::{
instruction::{
Call, CallNative, Close, DefineLocal, GetLocal, Jump, LoadBoolean, LoadConstant, LoadList,
LoadSelf, Move, Negate, Not, Return, SetLocal, Test,
Call, CallNative, Close, GetLocal, Jump, LoadConstant, LoadList, LoadSelf, Move, Negate,
Not, Return, SetLocal, Test,
},
optimize_control_flow, optimize_set_local, AnnotatedError, Argument, Chunk, ConcreteValue,
Destination, DustError, DustString, FunctionType, Instruction, LexError, Lexer, Local,
NativeFunction, Operation, Scope, Span, Token, TokenKind, TokenOwned, Type, TypeConflict,
AnnotatedError, Argument, Chunk, ConcreteValue, DustError, DustString, FunctionType,
Instruction, LexError, Lexer, Local, NativeFunction, Operation, Scope, Span, Token, TokenKind,
TokenOwned, Type, TypeConflict,
};
/// Compiles the input and returns a chunk.
@ -32,7 +35,7 @@ use crate::{
/// let source = "40 + 2 == 42";
/// let chunk = compile(source).unwrap();
///
/// assert_eq!(chunk.len(), 6);
/// assert_eq!(chunk.len(), 3);
/// ```
pub fn compile(source: &str) -> Result<Chunk, DustError> {
let lexer = Lexer::new(source);
@ -55,7 +58,7 @@ pub struct Compiler<'src> {
self_name: Option<DustString>,
instructions: SmallVec<[(Instruction, Type, Span); 32]>,
constants: SmallVec<[ConcreteValue; 16]>,
locals: SmallVec<[Local; 8]>,
locals: SmallVec<[(Local, Type); 8]>,
lexer: Lexer<'src>,
@ -65,7 +68,7 @@ pub struct Compiler<'src> {
previous_position: Span,
return_type: Option<Type>,
minimum_register: u16,
minimum_register: u8,
block_index: u8,
current_scope: Scope,
}
@ -99,8 +102,8 @@ impl<'src> Compiler<'src> {
pub fn finish(
self,
type_parameters: Option<SmallVec<[u16; 4]>>,
value_parameters: Option<SmallVec<[(u16, Type); 4]>>,
type_parameters: Option<SmallVec<[u8; 4]>>,
value_parameters: Option<SmallVec<[(u8, Type); 4]>>,
) -> Chunk {
log::info!("End chunk");
@ -109,20 +112,14 @@ impl<'src> Compiler<'src> {
value_parameters,
return_type: self.return_type.unwrap_or(Type::None),
};
let (instructions, positions) = self
let instructions = self
.instructions
.into_iter()
.map(|(instruction, _, position)| (instruction, position))
.unzip();
.collect();
let locals = self.locals.into_iter().map(|(local, _)| local).collect();
Chunk::with_data(
self.self_name,
r#type,
instructions,
positions,
self.constants,
self.locals,
)
Chunk::with_data(self.self_name, r#type, instructions, self.constants, locals)
}
pub fn compile(&mut self) -> Result<(), CompileError> {
@ -143,13 +140,13 @@ impl<'src> Compiler<'src> {
matches!(self.current_token, Token::Eof)
}
fn next_register(&self) -> u16 {
fn next_register(&self) -> u8 {
self.instructions
.iter()
.rev()
.find_map(|(instruction, _, _)| {
if instruction.yields_value() {
Some(instruction.a() + 1)
Some(instruction.a + 1)
} else {
None
}
@ -176,7 +173,7 @@ impl<'src> Compiler<'src> {
Ok(())
}
fn get_local(&self, index: u16) -> Result<&Local, CompileError> {
fn get_local(&self, index: u8) -> Result<&(Local, Type), CompileError> {
self.locals
.get(index as usize)
.ok_or(CompileError::UndeclaredVariable {
@ -185,12 +182,12 @@ impl<'src> Compiler<'src> {
})
}
fn get_local_index(&self, identifier_text: &str) -> Result<u16, CompileError> {
fn get_local_index(&self, identifier_text: &str) -> Result<u8, CompileError> {
self.locals
.iter()
.enumerate()
.rev()
.find_map(|(index, local)| {
.find_map(|(index, (local, _))| {
let constant = self.constants.get(local.identifier_index as usize)?;
let identifier = if let ConcreteValue::String(identifier) = constant {
identifier
@ -199,7 +196,7 @@ impl<'src> Compiler<'src> {
};
if identifier == identifier_text {
Some(index as u16)
Some(index as u8)
} else {
None
}
@ -213,39 +210,44 @@ impl<'src> Compiler<'src> {
fn declare_local(
&mut self,
identifier: &str,
register_index: u8,
r#type: Type,
is_mutable: bool,
scope: Scope,
) -> (u16, u16) {
) -> (u8, u8) {
log::info!("Declaring local {identifier}");
let identifier = ConcreteValue::string(identifier);
let identifier_index = self.push_or_get_constant(identifier);
let local_index = self.locals.len() as u16;
let local_index = self.locals.len() as u8;
self.locals
.push(Local::new(identifier_index, r#type, is_mutable, scope));
self.locals.push((
Local::new(identifier_index, register_index, is_mutable, scope),
r#type,
));
(local_index, identifier_index)
}
fn get_identifier(&self, local_index: u16) -> Option<String> {
self.locals.get(local_index as usize).and_then(|local| {
fn get_identifier(&self, local_index: u8) -> Option<String> {
self.locals
.get(local_index as usize)
.and_then(|(local, _)| {
self.constants
.get(local.identifier_index as usize)
.map(|value| value.to_string())
})
}
fn push_or_get_constant(&mut self, value: ConcreteValue) -> u16 {
fn push_or_get_constant(&mut self, value: ConcreteValue) -> u8 {
if let Some(index) = self
.constants
.iter()
.position(|constant| constant == &value)
{
index as u16
index as u8
} else {
let index = self.constants.len() as u16;
let index = self.constants.len() as u8;
self.constants.push(value);
@ -325,17 +327,30 @@ impl<'src> Compiler<'src> {
.unwrap_or(Type::None)
}
fn get_register_type(&self, register_index: u16) -> Result<Type, CompileError> {
fn get_register_type(&self, register_index: u8) -> Result<Type, CompileError> {
if let Some((_, r#type)) = self
.locals
.iter()
.find(|(local, _)| local.register_index == register_index)
{
return Ok(r#type.clone());
}
for (instruction, r#type, _) in &self.instructions {
if instruction.a() == register_index {
if let Operation::LoadList = instruction.operation() {
if !instruction.yields_value() {
continue;
}
let operation = instruction.operation();
if let Operation::LoadList = operation {
let LoadList { start_register, .. } = LoadList::from(instruction);
let item_type = self.get_register_type(start_register)?;
return Ok(Type::List(Box::new(item_type)));
}
if let Operation::LoadSelf = instruction.operation() {
if let Operation::LoadSelf = operation {
return Ok(Type::SelfChunk);
}
@ -343,7 +358,6 @@ impl<'src> Compiler<'src> {
return Ok(r#type.clone());
}
}
}
Err(CompileError::CannotResolveRegisterType {
register_index: register_index as usize,
@ -391,14 +405,10 @@ impl<'src> Compiler<'src> {
) -> Result<(), CompileError> {
let r#type = constant.r#type();
let constant_index = self.push_or_get_constant(constant);
let destination = Destination::Register(self.next_register());
let instruction = Instruction::from(LoadConstant {
destination,
constant_index,
jump_next: false,
});
let destination = self.next_register();
let load_constant = Instruction::load_constant(destination, constant_index, false);
self.emit_instruction(instruction, r#type, position);
self.emit_instruction(load_constant, r#type, position);
Ok(())
}
@ -410,14 +420,10 @@ impl<'src> Compiler<'src> {
self.advance()?;
let boolean = text.parse::<bool>().unwrap();
let destination = Destination::Register(self.next_register());
let instruction = Instruction::from(LoadBoolean {
destination,
value: boolean,
jump_next: false,
});
let destination = self.next_register();
let load_boolean = Instruction::load_boolean(destination, boolean, false);
self.emit_instruction(instruction, Type::Boolean, position);
self.emit_instruction(load_boolean, Type::Boolean, position);
Ok(())
} else {
@ -576,7 +582,7 @@ impl<'src> Compiler<'src> {
))
}
let destination = Destination::Register(self.next_register());
let destination = self.next_register();
let instruction = match operator.kind() {
TokenKind::Bang => Instruction::from(Not {
destination,
@ -624,8 +630,13 @@ impl<'src> Compiler<'src> {
position: self.previous_position,
})?;
let (left, push_back_left) = self.handle_binary_argument(&left_instruction)?;
let left_is_mutable_local = if let Argument::Local(local_index) = left {
self.get_local(local_index)?.is_mutable
let left_is_mutable_local = if let Operation::GetLocal = left_instruction.operation() {
let GetLocal { local_index, .. } = GetLocal::from(&left_instruction);
self.locals
.get(local_index as usize)
.map(|(local, _)| local.is_mutable)
.unwrap_or(false)
} else {
false
};
@ -742,12 +753,11 @@ impl<'src> Compiler<'src> {
let destination = if is_assignment {
match left {
Argument::Register(register) => Destination::Register(register),
Argument::Local(local_index) => Destination::Local(local_index),
Argument::Constant(_) => Destination::Register(self.next_register()),
Argument::Register(register) => register,
Argument::Constant(_) => self.next_register(),
}
} else {
Destination::Register(self.next_register())
self.next_register()
};
let instruction = match operator {
Token::Plus | Token::PlusEqual => Instruction::add(destination, left, right),
@ -823,13 +833,14 @@ impl<'src> Compiler<'src> {
.push((right_instruction, right_type, right_position));
}
let destination = self.next_register();
let comparison = match operator {
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),
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),
_ => {
return Err(CompileError::ExpectedTokenMultiple {
expected: &[
@ -845,26 +856,8 @@ impl<'src> Compiler<'src> {
})
}
};
let destination = Destination::Register(self.next_register());
let jump = Instruction::from(Jump {
offset: 1,
is_positive: true,
});
let load_true = Instruction::from(LoadBoolean {
destination,
value: true,
jump_next: true,
});
let load_false = Instruction::from(LoadBoolean {
destination,
value: false,
jump_next: false,
});
self.emit_instruction(comparison, Type::None, operator_position);
self.emit_instruction(jump, Type::None, operator_position);
self.emit_instruction(load_true, Type::Boolean, operator_position);
self.emit_instruction(load_false, Type::Boolean, operator_position);
self.emit_instruction(comparison, Type::Boolean, operator_position);
Ok(())
}
@ -893,10 +886,8 @@ impl<'src> Compiler<'src> {
});
}
if let Some([Operation::Test, Operation::Jump]) = self.get_last_operations() {}
let (argument, push_back) = self.handle_binary_argument(&left_instruction)?;
let is_local = matches!(argument, Argument::Local(_));
let is_local = left_instruction.operation() == Operation::GetLocal;
if push_back || is_local {
self.instructions
@ -929,7 +920,7 @@ impl<'src> Compiler<'src> {
if is_logic_chain {
let expression_length = self.instructions.len() - jump_index - 1;
jump_distance += expression_length as u16;
jump_distance += expression_length as u8;
let jump = Instruction::jump(jump_distance, true);
self.instructions
@ -957,7 +948,7 @@ impl<'src> Compiler<'src> {
} else if let Some(native_function) = NativeFunction::from_str(identifier) {
return self.parse_native_call(native_function);
} else if self.self_name.as_deref() == Some(identifier) {
let destination = Destination::Register(self.next_register());
let destination = self.next_register();
let load_self = Instruction::from(LoadSelf { destination });
self.emit_instruction(load_self, Type::SelfChunk, start_position);
@ -970,7 +961,9 @@ impl<'src> Compiler<'src> {
});
};
let local = self.get_local(local_index)?;
let (local, r#type) = self
.get_local(local_index)
.map(|(local, r#type)| (local, r#type.clone()))?;
let is_mutable = local.is_mutable;
if !self.current_scope.contains(&local.scope) {
@ -999,17 +992,16 @@ impl<'src> Compiler<'src> {
});
self.emit_instruction(set_local, Type::None, start_position);
optimize_set_local(&mut self.instructions);
optimize_set_local(self);
return Ok(());
}
let destination = Destination::Register(self.next_register());
let destination = self.next_register();
let get_local = Instruction::from(GetLocal {
destination,
local_index,
});
let r#type = self.get_local(local_index)?.r#type.clone();
self.emit_instruction(get_local, r#type, self.previous_position);
@ -1083,7 +1075,7 @@ impl<'src> Compiler<'src> {
self.allow(Token::Comma)?;
}
let destination = Destination::Register(self.next_register());
let destination = self.next_register();
let end = self.previous_position.1;
let load_list = Instruction::from(LoadList {
destination,
@ -1099,22 +1091,18 @@ impl<'src> Compiler<'src> {
self.advance()?;
self.parse_expression()?;
if matches!(
self.get_last_operations(),
Some([
Operation::Equal | Operation::Less | Operation::LessEqual,
Operation::Jump,
Operation::LoadBoolean,
Operation::LoadBoolean,
])
) {
self.instructions.pop();
self.instructions.pop();
self.instructions.pop();
} else if let Some((instruction, _, _)) = self.instructions.last() {
let test_register = instruction.a();
if let Some((instruction, _, _)) = self.instructions.last() {
let argument = match instruction.destination_as_argument() {
Some(argument) => argument,
None => {
return Err(CompileError::ExpectedExpression {
found: self.previous_token.to_owned(),
position: self.previous_position,
});
}
};
let test = Instruction::from(Test {
argument: Argument::Register(test_register),
argument,
test_value: true,
});
@ -1135,7 +1123,7 @@ impl<'src> Compiler<'src> {
}
let if_block_end = self.instructions.len();
let mut if_block_distance = (if_block_end - if_block_start) as u16;
let mut if_block_distance = (if_block_end - if_block_start) as u8;
let if_block_type = self.get_last_instruction_type();
let if_last_register = self.next_register().saturating_sub(1);
@ -1162,7 +1150,7 @@ impl<'src> Compiler<'src> {
};
let else_block_end = self.instructions.len();
let else_block_distance = (else_block_end - if_block_end) as u16;
let else_block_distance = (else_block_end - if_block_end) as u8;
let else_block_type = self.get_last_instruction_type();
if let Err(conflict) = if_block_type.check(&else_block_type) {
@ -1178,7 +1166,7 @@ impl<'src> Compiler<'src> {
if let Some(skippable) =
self.get_last_jumpable_mut_between(1, if_block_distance as usize)
{
skippable.set_c_to_boolean(true);
skippable.c = true as u8;
} else {
if_block_distance += 1;
let jump = Instruction::from(Jump {
@ -1230,7 +1218,7 @@ impl<'src> Compiler<'src> {
fn parse_while(&mut self) -> Result<(), CompileError> {
self.advance()?;
let expression_start = self.instructions.len() as u16;
let expression_start = self.instructions.len() as u8;
self.parse_expression()?;
@ -1252,8 +1240,8 @@ impl<'src> Compiler<'src> {
self.parse_block()?;
let block_end = self.instructions.len() as u16;
let jump_distance = block_end - block_start as u16 + 1;
let block_end = self.instructions.len() as u8;
let jump_distance = block_end - block_start as u8 + 1;
let jump = Instruction::from(Jump {
offset: jump_distance,
is_positive: true,
@ -1303,7 +1291,7 @@ impl<'src> Compiler<'src> {
let argument_count = destination - start_register;
let return_type = function.r#type().return_type;
let call_native = Instruction::from(CallNative {
destination: Destination::Register(destination),
destination,
function,
argument_count,
});
@ -1417,21 +1405,20 @@ impl<'src> Compiler<'src> {
self.expect(Token::Equal)?;
self.parse_expression()?;
let register = self.next_register() - 1;
let register_index = self.next_register() - 1;
let r#type = if let Some(r#type) = explicit_type {
r#type
} else {
self.get_register_type(register)?
self.get_register_type(register_index)?
};
let (local_index, _) =
self.declare_local(identifier, r#type, is_mutable, self.current_scope);
let define_local = Instruction::from(DefineLocal {
local_index,
register,
is_mutable,
});
self.emit_instruction(define_local, Type::None, position);
self.declare_local(
identifier,
register_index,
r#type,
is_mutable,
self.current_scope,
);
Ok(())
}
@ -1453,7 +1440,7 @@ impl<'src> Compiler<'src> {
function_compiler.expect(Token::LeftParenthesis)?;
let mut value_parameters: Option<SmallVec<[(u16, Type); 4]>> = None;
let mut value_parameters: Option<SmallVec<[(u8, Type); 4]>> = None;
while !function_compiler.allow(Token::RightParenthesis)? {
let is_mutable = function_compiler.allow(Token::Mut)?;
@ -1478,8 +1465,10 @@ impl<'src> Compiler<'src> {
function_compiler.advance()?;
let local_register_index = function_compiler.next_register();
let (_, identifier_index) = function_compiler.declare_local(
parameter,
local_register_index,
r#type.clone(),
is_mutable,
function_compiler.current_scope,
@ -1526,7 +1515,7 @@ impl<'src> Compiler<'src> {
let function =
ConcreteValue::function(function_compiler.finish(None, value_parameters.clone()));
let constant_index = self.push_or_get_constant(function);
let register = self.next_register();
let destination = self.next_register();
let function_type = FunctionType {
type_parameters: None,
value_parameters,
@ -1536,30 +1525,23 @@ impl<'src> Compiler<'src> {
if let Some((identifier, position)) = identifier_info {
let (local_index, _) = self.declare_local(
identifier,
destination,
Type::function(function_type.clone()),
false,
self.current_scope,
);
let load_constant = Instruction::from(LoadConstant {
destination: Destination::Register(register),
constant_index,
jump_next: false,
});
let define_local = Instruction::from(DefineLocal {
local_index,
register,
is_mutable: false,
});
let load_constant = Instruction::load_constant(destination, constant_index, false);
let set_local = Instruction::set_local(destination, local_index);
self.emit_instruction(
load_constant,
Type::function(function_type),
Span(function_start, function_end),
);
self.emit_instruction(define_local, Type::None, position);
self.emit_instruction(set_local, Type::None, position);
} else {
let load_constant = Instruction::from(LoadConstant {
destination: Destination::Register(register),
destination,
constant_index,
jump_next: false,
});
@ -1637,9 +1619,9 @@ impl<'src> Compiler<'src> {
}
let end = self.current_position.1;
let register = self.next_register();
let destination = self.next_register();
let call = Instruction::from(Call {
destination: Destination::Register(register),
destination,
function,
argument_count,
});

View File

@ -2,7 +2,7 @@
use smallvec::SmallVec;
use crate::{instruction::SetLocal, Instruction, Operation, Span, Type};
use crate::{instruction::SetLocal, CompileError, Compiler, Instruction, Operation, Span, Type};
fn get_last_operations<const COUNT: usize>(
instructions: &[(Instruction, Type, Span)],
@ -58,13 +58,12 @@ pub fn optimize_control_flow(instructions: &mut [(Instruction, Type, Span)]) {
let first_loader = &mut instructions.iter_mut().nth_back(1).unwrap().0;
first_loader.set_c_to_boolean(true);
first_loader.c = true as u8;
let first_loader_register = first_loader.a();
let first_loader_destination = first_loader.a;
let second_loader = &mut instructions.last_mut().unwrap().0;
let second_loader_new = *second_loader.clone().set_a(first_loader_register);
*second_loader = second_loader_new;
second_loader.a = first_loader_destination;
}
/// Optimizes a math instruction followed by a SetLocal instruction.
@ -85,9 +84,9 @@ pub fn optimize_control_flow(instructions: &mut [(Instruction, Type, Span)]) {
/// The instructions must be in the following order:
/// - `Add`, `Subtract`, `Multiply`, `Divide` or `Modulo`
/// - `SetLocal`
pub fn optimize_set_local(instructions: &mut SmallVec<[(Instruction, Type, Span); 32]>) {
pub fn optimize_set_local(compiler: &mut Compiler) -> Result<(), CompileError> {
if !matches!(
get_last_operations(instructions),
compiler.get_last_operations(),
Some([
Operation::Add
| Operation::Subtract
@ -97,17 +96,17 @@ pub fn optimize_set_local(instructions: &mut SmallVec<[(Instruction, Type, Span)
Operation::SetLocal,
])
) {
return;
return Ok(());
}
log::debug!("Condensing math and SetLocal to math instruction");
let set_local = SetLocal::from(&instructions.pop().unwrap().0);
let math_instruction = instructions.last_mut().unwrap().0;
let math_instruction_new = *math_instruction
.clone()
.set_a(set_local.local_index)
.set_a_is_local(true);
let set_local = SetLocal::from(&compiler.instructions.pop().unwrap().0);
let (local, _) = compiler.get_local(set_local.local_index)?;
let local_register_index = local.register_index;
let math_instruction = &mut compiler.instructions.last_mut().unwrap().0;
instructions.last_mut().unwrap().0 = math_instruction_new;
math_instruction.a = local_register_index;
Ok(())
}

View File

@ -61,8 +61,8 @@ const CONSTANT_HEADER: [&str; 4] = [
const LOCAL_HEADER: [&str; 4] = [
"Locals",
"------",
" i SCOPE MUTABLE TYPE IDENTIFIER ",
"--- ------- ------- ---------------- ----------------",
" i IDENTIFIER REGISTER SCOPE MUTABLE",
"--- ---------------- -------- ------- -------",
];
/// Builder that constructs a human-readable representation of a chunk.
@ -209,13 +209,7 @@ impl<'a> Disassembler<'a> {
self.push_header(line);
}
for (index, (instruction, position)) in self
.chunk
.instructions()
.iter()
.zip(self.chunk.positions().iter())
.enumerate()
{
for (index, (instruction, position)) in self.chunk.instructions().iter().enumerate() {
let position = position.to_string();
let operation = instruction.operation().to_string();
let info = instruction.disassembly_info();
@ -235,7 +229,7 @@ impl<'a> Disassembler<'a> {
index,
Local {
identifier_index,
r#type,
register_index,
scope,
is_mutable,
},
@ -247,10 +241,10 @@ impl<'a> Disassembler<'a> {
.get(*identifier_index as usize)
.map(|value| value.to_string())
.unwrap_or_else(|| "unknown".to_string());
let type_display = r#type.to_string();
let register_display = format!("R{register_index}");
let scope = scope.to_string();
let local_display = format!(
"{index:^3} {scope:^7} {is_mutable:^7} {type_display:^16} {identifier_display:^16}"
"{index:^3} {identifier_display:^16} {register_display:^8} {scope:^7} {is_mutable:^7}"
);
self.push_details(&local_display);

View File

@ -1,14 +1,12 @@
//! Top-level Dust errors with source code annotations.
//! Top-level error for the Dust language API that can create detailed reports with source code
//! annotations.
use std::fmt::{self, Display, Formatter};
use annotate_snippets::{Level, Renderer, Snippet};
use crate::{CompileError, Span, VmError};
/// A top-level error that can occur during the execution of Dust code.
///
/// This error can display nicely formatted messages with source code annotations.
/// A top-level error that can occur during the interpretation of Dust code.
#[derive(Debug, PartialEq)]
pub enum DustError<'src> {
Compile {
@ -30,7 +28,7 @@ impl<'src> DustError<'src> {
DustError::Runtime { error, source }
}
pub fn create_report(&self) -> String {
pub fn report(&self) -> String {
let (position, title, description, details) = self.error_data();
let label = format!("{}: {}", title, description);
let details = details.unwrap_or_else(|| "While parsing this code".to_string());
@ -74,7 +72,7 @@ impl<'src> DustError<'src> {
impl Display for DustError<'_> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.create_report())
write!(f, "{}", self.report())
}
}

View File

@ -1,14 +1,14 @@
use crate::{Argument, Destination, Instruction, Operation};
use crate::{Argument, Instruction, Operation};
pub struct Add {
pub destination: Destination,
pub destination: u8,
pub left: Argument,
pub right: Argument,
}
impl From<&Instruction> for Add {
fn from(instruction: &Instruction) -> Self {
let destination = instruction.a_as_destination();
let destination = instruction.a;
let (left, right) = instruction.b_and_c_as_arguments();
Add {
@ -21,16 +21,12 @@ impl From<&Instruction> for Add {
impl From<Add> for Instruction {
fn from(add: Add) -> Self {
let (a, a_options) = add.destination.as_index_and_a_options();
let operation = Operation::Add;
let a = add.destination;
let (b, b_options) = add.left.as_index_and_b_options();
let (c, c_options) = add.right.as_index_and_c_options();
let metadata = operation as u8 | b_options.bits() | c_options.bits();
Instruction {
operation: Operation::ADD,
options: a_options | b_options | c_options,
a,
b,
c,
}
Instruction { metadata, a, b, c }
}
}

View File

@ -1,32 +1,32 @@
use crate::{Argument, Destination, Instruction, Operation};
use crate::{Argument, Instruction, Operation};
pub struct Call {
pub destination: Destination,
pub destination: u8,
pub function: Argument,
pub argument_count: u16,
pub argument_count: u8,
}
impl From<&Instruction> for Call {
fn from(instruction: &Instruction) -> Self {
let destination = instruction.a;
let function = instruction.b_as_argument();
let argument_count = instruction.c;
Call {
destination: instruction.a_as_destination(),
function: instruction.b_as_argument(),
argument_count: instruction.c,
destination,
function,
argument_count,
}
}
}
impl From<Call> for Instruction {
fn from(call: Call) -> Self {
let (a, a_options) = call.destination.as_index_and_a_options();
let a = call.destination;
let (b, b_options) = call.function.as_index_and_b_options();
let c = call.argument_count;
let metadata = Operation::Call as u8 | b_options.bits();
Instruction {
operation: Operation::CALL,
options: a_options | b_options,
a,
b,
c: call.argument_count,
}
Instruction { metadata, a, b, c }
}
}

View File

@ -1,14 +1,14 @@
use crate::{Destination, Instruction, NativeFunction, Operation};
use crate::{Instruction, NativeFunction, Operation};
pub struct CallNative {
pub destination: Destination,
pub destination: u8,
pub function: NativeFunction,
pub argument_count: u16,
pub argument_count: u8,
}
impl From<&Instruction> for CallNative {
fn from(instruction: &Instruction) -> Self {
let destination = instruction.a_as_destination();
let destination = instruction.a;
let function = NativeFunction::from(instruction.b);
CallNative {
@ -21,15 +21,11 @@ impl From<&Instruction> for CallNative {
impl From<CallNative> for Instruction {
fn from(call_native: CallNative) -> Self {
let (a, a_options) = call_native.destination.as_index_and_a_options();
let b = call_native.function as u16;
let metadata = Operation::CallNative as u8;
let a = call_native.destination;
let b = call_native.function as u8;
let c = call_native.argument_count;
Instruction {
operation: Operation::CALL_NATIVE,
options: a_options,
a,
b,
c: call_native.argument_count,
}
Instruction { metadata, a, b, c }
}
}

View File

@ -1,10 +1,8 @@
use crate::{Instruction, Operation};
use super::InstructionOptions;
pub struct Close {
pub from: u16,
pub to: u16,
pub from: u8,
pub to: u8,
}
impl From<&Instruction> for Close {
@ -18,12 +16,9 @@ impl From<&Instruction> for Close {
impl From<Close> for Instruction {
fn from(close: Close) -> Self {
Instruction {
operation: Operation::CLOSE,
options: InstructionOptions::empty(),
a: 0,
b: close.from,
c: close.to,
}
let metadata = Operation::Close as u8;
let (a, b, c) = (0, close.from, close.to);
Instruction { metadata, a, b, c }
}
}

View File

@ -1,14 +1,14 @@
use crate::{Argument, Destination, Instruction, Operation};
use crate::{Argument, Instruction, Operation};
pub struct Divide {
pub destination: Destination,
pub destination: u8,
pub left: Argument,
pub right: Argument,
}
impl From<&Instruction> for Divide {
fn from(instruction: &Instruction) -> Self {
let destination = instruction.a_as_destination();
let destination = instruction.a;
let (left, right) = instruction.b_and_c_as_arguments();
Divide {
@ -21,16 +21,11 @@ impl From<&Instruction> for Divide {
impl From<Divide> for Instruction {
fn from(divide: Divide) -> Self {
let (a, a_options) = divide.destination.as_index_and_a_options();
let a = divide.destination;
let (b, b_options) = divide.left.as_index_and_b_options();
let (c, c_options) = divide.right.as_index_and_c_options();
let metadata = Operation::Divide as u8 | b_options.bits() | c_options.bits();
Instruction {
operation: Operation::DIVIDE,
options: a_options | b_options | c_options,
a,
b,
c,
}
Instruction { metadata, a, b, c }
}
}

View File

@ -1,9 +1,9 @@
use crate::{Argument, Destination, Instruction, Operation};
use crate::{Argument, Instruction, Operation};
use super::InstructionOptions;
pub struct Equal {
pub destination: Destination,
pub destination: u8,
pub value: bool,
pub left: Argument,
pub right: Argument,
@ -11,8 +11,8 @@ pub struct Equal {
impl From<&Instruction> for Equal {
fn from(instruction: &Instruction) -> Self {
let destination = instruction.a_as_destination();
let value = instruction.options.d();
let destination = instruction.a;
let value = instruction.d();
let (left, right) = instruction.b_and_c_as_arguments();
Equal {
@ -26,7 +26,7 @@ impl From<&Instruction> for Equal {
impl From<Equal> for Instruction {
fn from(equal: Equal) -> Self {
let (a, a_options) = equal.destination.as_index_and_a_options();
let a = equal.destination;
let (b, b_options) = equal.left.as_index_and_b_options();
let (c, c_options) = equal.right.as_index_and_c_options();
let d_options = if equal.value {
@ -34,13 +34,9 @@ impl From<Equal> for Instruction {
} else {
InstructionOptions::empty()
};
let metadata =
Operation::Equal as u8 | b_options.bits() | c_options.bits() | d_options.bits();
Instruction {
operation: Operation::EQUAL,
options: a_options | b_options | c_options | d_options,
a,
b,
c,
}
Instruction { metadata, a, b, c }
}
}

View File

@ -1,31 +1,29 @@
use crate::{Destination, Instruction, Operation};
use crate::{Instruction, Operation};
pub struct GetLocal {
pub destination: Destination,
pub local_index: u16,
pub destination: u8,
pub local_index: u8,
}
impl From<&Instruction> for GetLocal {
fn from(instruction: &Instruction) -> Self {
let destination = instruction.a_as_destination();
let destination = instruction.a;
let local_index = instruction.b;
GetLocal {
destination,
local_index: instruction.b,
local_index,
}
}
}
impl From<GetLocal> for Instruction {
fn from(get_local: GetLocal) -> Self {
let (a, a_options) = get_local.destination.as_index_and_a_options();
let a = get_local.destination;
let b = get_local.local_index;
let c = 0;
let metadata = Operation::GetLocal as u8;
Instruction {
operation: Operation::GET_LOCAL,
options: a_options,
a,
b: get_local.local_index,
c: 0,
}
Instruction { metadata, a, b, c }
}
}

View File

@ -1,9 +1,7 @@
use crate::{Instruction, Operation};
use super::InstructionOptions;
pub struct Jump {
pub offset: u16,
pub offset: u8,
pub is_positive: bool,
}
@ -18,12 +16,11 @@ impl From<&Instruction> for Jump {
impl From<Jump> for Instruction {
fn from(jump: Jump) -> Self {
Instruction {
operation: Operation::JUMP,
options: InstructionOptions::empty(),
a: 0,
b: jump.offset,
c: jump.is_positive as u16,
}
let metadata = Operation::Jump as u8;
let a = 0;
let b = jump.offset;
let c = jump.is_positive as u8;
Instruction { metadata, a, b, c }
}
}

View File

@ -1,9 +1,9 @@
use crate::{Argument, Destination, Instruction, Operation};
use crate::{Argument, Instruction, Operation};
use super::InstructionOptions;
pub struct Less {
pub destination: Destination,
pub destination: u8,
pub value: bool,
pub left: Argument,
pub right: Argument,
@ -11,8 +11,8 @@ pub struct Less {
impl From<&Instruction> for Less {
fn from(instruction: &Instruction) -> Self {
let destination = instruction.a_as_destination();
let value = instruction.options.d();
let destination = instruction.a;
let value = instruction.d();
let (left, right) = instruction.b_and_c_as_arguments();
Less {
@ -26,7 +26,7 @@ impl From<&Instruction> for Less {
impl From<Less> for Instruction {
fn from(less: Less) -> Self {
let (a, a_options) = less.destination.as_index_and_a_options();
let a = less.destination;
let (b, b_options) = less.left.as_index_and_b_options();
let (c, c_options) = less.right.as_index_and_c_options();
let d_options = if less.value {
@ -34,13 +34,9 @@ impl From<Less> for Instruction {
} else {
InstructionOptions::empty()
};
let metadata =
Operation::Less as u8 | b_options.bits() | c_options.bits() | d_options.bits();
Instruction {
operation: Operation::LESS,
options: a_options | b_options | c_options | d_options,
a,
b,
c,
}
Instruction { metadata, a, b, c }
}
}

View File

@ -1,9 +1,9 @@
use crate::{Argument, Destination, Instruction, Operation};
use crate::{Argument, Instruction, Operation};
use super::InstructionOptions;
pub struct LessEqual {
pub destination: Destination,
pub destination: u8,
pub value: bool,
pub left: Argument,
pub right: Argument,
@ -11,8 +11,8 @@ pub struct LessEqual {
impl From<&Instruction> for LessEqual {
fn from(instruction: &Instruction) -> Self {
let destination = instruction.a_as_destination();
let value = instruction.options.d();
let destination = instruction.a;
let value = instruction.d();
let (left, right) = instruction.b_and_c_as_arguments();
LessEqual {
@ -26,7 +26,7 @@ impl From<&Instruction> for LessEqual {
impl From<LessEqual> for Instruction {
fn from(less_equal: LessEqual) -> Self {
let (a, a_options) = less_equal.destination.as_index_and_a_options();
let a = less_equal.destination;
let (b, b_options) = less_equal.left.as_index_and_b_options();
let (c, c_options) = less_equal.right.as_index_and_c_options();
let d_options = if less_equal.value {
@ -34,13 +34,9 @@ impl From<LessEqual> for Instruction {
} else {
InstructionOptions::empty()
};
let metadata =
Operation::LessEqual as u8 | b_options.bits() | c_options.bits() | d_options.bits();
Instruction {
operation: Operation::LESS_EQUAL,
options: a_options | b_options | c_options | d_options,
a,
b,
c,
}
Instruction { metadata, a, b, c }
}
}

View File

@ -1,14 +1,14 @@
use crate::{Destination, Instruction, Operation};
use crate::{Instruction, Operation};
pub struct LoadBoolean {
pub destination: Destination,
pub destination: u8,
pub value: bool,
pub jump_next: bool,
}
impl From<&Instruction> for LoadBoolean {
fn from(instruction: &Instruction) -> Self {
let destination = instruction.a_as_destination();
let destination = instruction.a;
let value = instruction.b != 0;
let jump_next = instruction.c != 0;
@ -22,16 +22,11 @@ impl From<&Instruction> for LoadBoolean {
impl From<LoadBoolean> for Instruction {
fn from(load_boolean: LoadBoolean) -> Self {
let (a, options) = load_boolean.destination.as_index_and_a_options();
let b = load_boolean.value as u16;
let c = load_boolean.jump_next as u16;
let metadata = Operation::LoadBoolean as u8;
let a = load_boolean.destination;
let b = load_boolean.value as u8;
let c = load_boolean.jump_next as u8;
Instruction {
operation: Operation::LOAD_BOOLEAN,
options,
a,
b,
c,
}
Instruction { metadata, a, b, c }
}
}

View File

@ -1,14 +1,14 @@
use crate::{Destination, Instruction, Operation};
use crate::{Instruction, Operation};
pub struct LoadConstant {
pub destination: Destination,
pub constant_index: u16,
pub destination: u8,
pub constant_index: u8,
pub jump_next: bool,
}
impl From<&Instruction> for LoadConstant {
fn from(instruction: &Instruction) -> Self {
let destination = instruction.a_as_destination();
let destination = instruction.a;
let constant_index = instruction.b;
let jump_next = instruction.c != 0;
@ -22,16 +22,11 @@ impl From<&Instruction> for LoadConstant {
impl From<LoadConstant> for Instruction {
fn from(load_constant: LoadConstant) -> Self {
let (a, options) = load_constant.destination.as_index_and_a_options();
let metadata = Operation::LoadConstant as u8;
let a = load_constant.destination;
let b = load_constant.constant_index;
let c = load_constant.jump_next as u16;
let c = load_constant.jump_next as u8;
Instruction {
operation: Operation::LOAD_CONSTANT,
options,
a,
b,
c,
}
Instruction { metadata, a, b, c }
}
}

View File

@ -1,13 +1,13 @@
use crate::{Destination, Instruction, Operation};
use crate::{Instruction, Operation};
pub struct LoadList {
pub destination: Destination,
pub start_register: u16,
pub destination: u8,
pub start_register: u8,
}
impl From<&Instruction> for LoadList {
fn from(instruction: &Instruction) -> Self {
let destination = instruction.a_as_destination();
let destination = instruction.a;
let start_register = instruction.b;
LoadList {
@ -19,15 +19,11 @@ impl From<&Instruction> for LoadList {
impl From<LoadList> for Instruction {
fn from(load_list: LoadList) -> Self {
let (a, options) = load_list.destination.as_index_and_a_options();
let metadata = Operation::LoadList as u8;
let a = load_list.destination;
let b = load_list.start_register;
let c = 0;
Instruction {
operation: Operation::LOAD_LIST,
options,
a,
b,
c: 0,
}
Instruction { metadata, a, b, c }
}
}

View File

@ -1,12 +1,12 @@
use crate::{Destination, Instruction, Operation};
use crate::{Instruction, Operation};
pub struct LoadSelf {
pub destination: Destination,
pub destination: u8,
}
impl From<&Instruction> for LoadSelf {
fn from(instruction: &Instruction) -> Self {
let destination = instruction.a_as_destination();
let destination = instruction.a;
LoadSelf { destination }
}
@ -14,14 +14,11 @@ impl From<&Instruction> for LoadSelf {
impl From<LoadSelf> for Instruction {
fn from(load_self: LoadSelf) -> Self {
let (a, options) = load_self.destination.as_index_and_a_options();
let metadata = Operation::LoadSelf as u8;
let a = load_self.destination;
let b = 0;
let c = 0;
Instruction {
operation: Operation::LOAD_SELF,
options,
a,
b: 0,
c: 0,
}
Instruction { metadata, a, b, c }
}
}

View File

@ -1,31 +1,32 @@
//! An operation and its arguments for the Dust virtual machine.
//! Instructions for the Dust virtual machine.
//!
//! Each instruction is 64 bits and holds ten distinct fields:
//! Each instruction is 32 bits and uses up to seven distinct fields:
//!
//! Bit | Description
//! ----- | -----------
//! 0-8 | Operation code
//! 9 | Flag for whether A is a local
//! 10 | Flag for whether B argument is a constant
//! 11 | Flag for whether C argument is a local
//! 12 | Flag for whether B argument is a constant
//! 13 | Flag for whether C argument is a local
//! 14 | D Argument
//! 15-16 | Unused
//! 17-32 | A argument
//! 33-48 | B argument
//! 49-63 | C argument
//! 0-4 | Operation code
//! 5 | Flag indicating if the B argument is a constant
//! 6 | Flag indicating if the C argument is a constant
//! 7 | D field (boolean)
//! 8-15 | A field (unsigned 8-bit integer)
//! 16-23 | B field (unsigned 8-bit integer)
//! 24-31 | C field (unsigned 8-bit integer)
//!
//! Be careful when working with instructions directly. When modifying an instruction, be sure to
//! account for the fact that setting the A, B, or C arguments to 0 will have no effect. It is
//! usually best to remove instructions and insert new ones in their place instead of mutating them.
//! **Be careful when working with instructions directly**. When modifying an instruction's fields,
//! you may also need to modify its flags. It is usually best to remove instructions and insert new
//! ones in their place instead of mutating them.
//!
//! # Examples
//!
//! ## Creating Instructions
//!
//! For each operation, there are two ways to create an instruction:
//!
//! - Use the associated function on `Instruction`
//! - Use the corresponding struct and call `Instruction::from`
//!
//! # Examples
//! Both produce the same result, but the first is more concise. The structs are more useful when
//! reading instructions, as shown below.
//!
//! ```
//! # use dust_lang::instruction::{Instruction, Move};
@ -35,24 +36,63 @@
//! assert_eq!(move_1, move_2);
//! ```
//!
//! Use the `Destination` and `Argument` enums to create instructions. This is easier to read and
//! enforces consistency in how the `Instruction` methods are called.
//! Use the [`Argument`][] type when creating instructions. In addition to being easy to read and
//! write, this ensures that the instruction has the correct flags to represent the arguments.
//!
//! ```
//! # use dust_lang::instruction::{Instruction, Add, Destination, Argument};
//! # use dust_lang::instruction::{Instruction, Add, u8, Argument};
//! let add_1 = Instruction::add(
//! Destination::Register(0),
//! u8::Register(0),
//! Argument::Local(1),
//! Argument::Constant(2)
//! );
//! let add_2 = Instruction::from(Add {
//! destination: Destination::Register(0),
//! destination: u8::Register(0),
//! left: Argument::Local(1),
//! right: Argument::Constant(2),
//! });
//!
//! assert_eq!(add_1, add_2);
//! ```
//!
//! ## Reading Instructions
//!
//! To read an instruction, check its `operation` field, then convert the instruction to the struct
//! that corresponds to that operation. Like the example above, this removes the burden of dealing
//! with the options and handles the process of reading the options and the instruction's fields
//! into `u8` or `Argument` enums when appropriate.
//!
//! ```
//! # use dust_lang::instruction::{Instruction, Add, u8, Argument, Operation};
//! # let mystery_instruction = Instruction::add(
//! u8::Local(1),
//! Argument::Local(1),
//! Argument::Constant(2)
//! );
//!
//! // Let's read an instruction and see if it performs addition-assignment, like in one of the
//! // following examples:
//! // - `a += 2`
//! // - `a = a + 2`
//! // - `a = 2 + a`
//!
//! let operation = mystery_instruction.operation();
//!
//! match operation {
//! Operation::ADD => {
//! let Add { destination, left, right } = Add::from(&mystery_instruction);
//! let is_add_assign =
//! left == Argument::Register(destination)
//! || right == Argument::Register(destination);
//!
//! assert!(is_add_assign);
//! }
//! // Handle other operations...
//! _ => {
//! panic!("Unknown operation code: {operation}");
//! }
//! }
//! ```
mod add;
mod call;
mod call_native;
@ -117,23 +157,32 @@ use crate::NativeFunction;
/// See the [module-level documentation](index.html) for more information.
#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Instruction {
operation: Operation,
options: InstructionOptions,
a: u16,
b: u16,
c: u16,
pub metadata: u8,
pub a: u8,
pub b: u8,
pub c: u8,
}
impl Instruction {
pub fn r#move(from: u16, to: u16) -> Instruction {
pub fn operation(&self) -> Operation {
let operation_bits = self.metadata & 0b0001_1111;
Operation::from(operation_bits)
}
pub fn d(&self) -> bool {
self.metadata & 0b1000_0000 != 0
}
pub fn r#move(from: u8, to: u8) -> Instruction {
Instruction::from(Move { from, to })
}
pub fn close(from: u16, to: u16) -> Instruction {
pub fn close(from: u8, to: u8) -> Instruction {
Instruction::from(Close { from, to })
}
pub fn load_boolean(destination: Destination, value: bool, jump_next: bool) -> Instruction {
pub fn load_boolean(destination: u8, value: bool, jump_next: bool) -> Instruction {
Instruction::from(LoadBoolean {
destination,
value,
@ -141,11 +190,7 @@ impl Instruction {
})
}
pub fn load_constant(
destination: Destination,
constant_index: u16,
jump_next: bool,
) -> Instruction {
pub fn load_constant(destination: u8, constant_index: u8, jump_next: bool) -> Instruction {
Instruction::from(LoadConstant {
destination,
constant_index,
@ -153,32 +198,32 @@ impl Instruction {
})
}
pub fn load_list(destination: Destination, start_register: u16) -> Instruction {
pub fn load_list(destination: u8, start_register: u8) -> Instruction {
Instruction::from(LoadList {
destination,
start_register,
})
}
pub fn load_self(destination: Destination) -> Instruction {
pub fn load_self(destination: u8) -> Instruction {
Instruction::from(LoadSelf { destination })
}
pub fn get_local(destination: Destination, local_index: u16) -> Instruction {
pub fn get_local(destination: u8, local_index: u8) -> Instruction {
Instruction::from(GetLocal {
destination,
local_index,
})
}
pub fn set_local(register: u16, local_index: u16) -> Instruction {
pub fn set_local(register: u8, local_index: u8) -> Instruction {
Instruction::from(SetLocal {
local_index,
register_index: register,
})
}
pub fn add(destination: Destination, left: Argument, right: Argument) -> Instruction {
pub fn add(destination: u8, left: Argument, right: Argument) -> Instruction {
Instruction::from(Add {
destination,
left,
@ -186,7 +231,7 @@ impl Instruction {
})
}
pub fn subtract(destination: Destination, left: Argument, right: Argument) -> Instruction {
pub fn subtract(destination: u8, left: Argument, right: Argument) -> Instruction {
Instruction::from(Subtract {
destination,
left,
@ -194,7 +239,7 @@ impl Instruction {
})
}
pub fn multiply(destination: Destination, left: Argument, right: Argument) -> Instruction {
pub fn multiply(destination: u8, left: Argument, right: Argument) -> Instruction {
Instruction::from(Multiply {
destination,
left,
@ -202,7 +247,7 @@ impl Instruction {
})
}
pub fn divide(destination: Destination, left: Argument, right: Argument) -> Instruction {
pub fn divide(destination: u8, left: Argument, right: Argument) -> Instruction {
Instruction::from(Divide {
destination,
left,
@ -210,7 +255,7 @@ impl Instruction {
})
}
pub fn modulo(destination: Destination, left: Argument, right: Argument) -> Instruction {
pub fn modulo(destination: u8, left: Argument, right: Argument) -> Instruction {
Instruction::from(Modulo {
destination,
left,
@ -225,7 +270,7 @@ impl Instruction {
})
}
pub fn test_set(destination: Destination, argument: Argument, value: bool) -> Instruction {
pub fn test_set(destination: u8, argument: Argument, value: bool) -> Instruction {
Instruction::from(TestSet {
destination,
argument,
@ -233,12 +278,7 @@ impl Instruction {
})
}
pub fn equal(
destination: Destination,
value: bool,
left: Argument,
right: Argument,
) -> Instruction {
pub fn equal(destination: u8, value: bool, left: Argument, right: Argument) -> Instruction {
Instruction::from(Equal {
destination,
value,
@ -247,12 +287,7 @@ impl Instruction {
})
}
pub fn less(
destination: Destination,
value: bool,
left: Argument,
right: Argument,
) -> Instruction {
pub fn less(destination: u8, value: bool, left: Argument, right: Argument) -> Instruction {
Instruction::from(Less {
destination,
value,
@ -262,7 +297,7 @@ impl Instruction {
}
pub fn less_equal(
destination: Destination,
destination: u8,
value: bool,
left: Argument,
right: Argument,
@ -275,28 +310,28 @@ impl Instruction {
})
}
pub fn negate(destination: Destination, argument: Argument) -> Instruction {
pub fn negate(destination: u8, argument: Argument) -> Instruction {
Instruction::from(Negate {
destination,
argument,
})
}
pub fn not(destination: Destination, argument: Argument) -> Instruction {
pub fn not(destination: u8, argument: Argument) -> Instruction {
Instruction::from(Not {
destination,
argument,
})
}
pub fn jump(offset: u16, is_positive: bool) -> Instruction {
pub fn jump(offset: u8, is_positive: bool) -> Instruction {
Instruction::from(Jump {
offset,
is_positive,
})
}
pub fn call(destination: Destination, function: Argument, argument_count: u16) -> Instruction {
pub fn call(destination: u8, function: Argument, argument_count: u8) -> Instruction {
Instruction::from(Call {
destination,
function,
@ -305,9 +340,9 @@ impl Instruction {
}
pub fn call_native(
destination: Destination,
destination: u8,
function: NativeFunction,
argument_count: u16,
argument_count: u8,
) -> Instruction {
Instruction::from(CallNative {
destination,
@ -323,55 +358,46 @@ impl Instruction {
}
pub fn destination_as_argument(&self) -> Option<Argument> {
match self.operation {
Operation::LOAD_CONSTANT => Some(Argument::Constant(self.b)),
Operation::GET_LOCAL => Some(Argument::Local(self.b)),
Operation::LOAD_BOOLEAN
| Operation::LOAD_LIST
| Operation::LOAD_SELF
| Operation::ADD
| Operation::SUBTRACT
| Operation::MULTIPLY
| Operation::DIVIDE
| Operation::MODULO
| Operation::NEGATE
| Operation::NOT
| Operation::CALL
| Operation::CALL_NATIVE => Some(Argument::Register(self.a)),
match self.operation() {
Operation::LoadConstant => Some(Argument::Constant(self.b)),
Operation::LoadBoolean
| Operation::LoadList
| Operation::LoadSelf
| Operation::GetLocal
| Operation::Add
| Operation::Subtract
| Operation::Multiply
| Operation::Divide
| Operation::Modulo
| Operation::Negate
| Operation::Not
| Operation::Call
| Operation::CallNative => Some(Argument::Register(self.a)),
_ => None,
}
}
pub fn a_as_destination(&self) -> Destination {
if self.options.a_is_local() {
Destination::Local(self.a)
} else {
Destination::Register(self.a)
}
}
pub fn b_as_argument(&self) -> Argument {
if self.options.b_is_constant() {
let b_is_constant = self.metadata & 0b0010_0000 != 0;
if b_is_constant {
Argument::Constant(self.b)
} else if self.options.b_is_local() {
Argument::Local(self.b)
} else {
Argument::Register(self.b)
}
}
pub fn b_and_c_as_arguments(&self) -> (Argument, Argument) {
let left = if self.options.b_is_constant() {
let b_is_constant = self.metadata & 0b0010_0000 != 0;
let c_is_constant = self.metadata & 0b0100_0000 != 0;
let left = if b_is_constant {
Argument::Constant(self.b)
} else if self.options.b_is_local() {
Argument::Local(self.b)
} else {
Argument::Register(self.b)
};
let right = if self.options.c_is_constant() {
let right = if c_is_constant {
Argument::Constant(self.c)
} else if self.options.c_is_local() {
Argument::Local(self.c)
} else {
Argument::Register(self.c)
};
@ -380,58 +406,53 @@ impl Instruction {
}
pub fn yields_value(&self) -> bool {
match self.operation {
Operation::LOAD_BOOLEAN
| Operation::LOAD_CONSTANT
| Operation::LOAD_LIST
| Operation::LOAD_SELF
| Operation::GET_LOCAL
| Operation::ADD
| Operation::SUBTRACT
| Operation::MULTIPLY
| Operation::DIVIDE
| Operation::MODULO
| Operation::NEGATE
| Operation::NOT
| Operation::CALL => true,
Operation::MOVE
| Operation::CLOSE
| Operation::SET_LOCAL
| Operation::EQUAL
| Operation::LESS
| Operation::LESS_EQUAL
| Operation::TEST
| Operation::TEST_SET
| Operation::JUMP
| Operation::RETURN => false,
Operation::CALL_NATIVE => {
match self.operation() {
Operation::LoadBoolean
| Operation::LoadConstant
| Operation::LoadList
| Operation::LoadMap
| Operation::LoadSelf
| Operation::GetLocal
| Operation::Add
| Operation::Subtract
| Operation::Multiply
| Operation::Divide
| Operation::Modulo
| Operation::Power
| Operation::Negate
| Operation::Not
| Operation::Equal
| Operation::Less
| Operation::LessEqual
| Operation::Call => true,
Operation::CallNative => {
let function = NativeFunction::from(self.b);
function.returns_value()
}
_ => {
if cfg!(debug_assertions) {
panic!("Unknown operation {}", self.operation);
} else {
false
}
}
Operation::Move
| Operation::Close
| Operation::SetLocal
| Operation::Test
| Operation::TestSet
| Operation::Jump
| Operation::Return => false,
}
}
pub fn disassembly_info(&self) -> String {
match self.operation {
Operation::MOVE => {
match self.operation() {
Operation::Move => {
let Move { from, to } = Move::from(self);
format!("R{to} = R{from}")
}
Operation::CLOSE => {
Operation::Close => {
let Close { from, to } = Close::from(self);
format!("R{from}..R{to}")
}
Operation::LOAD_BOOLEAN => {
Operation::LoadBoolean => {
let LoadBoolean {
destination,
value,
@ -439,12 +460,12 @@ impl Instruction {
} = LoadBoolean::from(self);
if jump_next {
format!("{destination} = {value} && JUMP +1")
format!("R{destination} = {value} && JUMP +1")
} else {
format!("{destination} = {value}")
format!("R{destination} = {value}")
}
}
Operation::LOAD_CONSTANT => {
Operation::LoadConstant => {
let LoadConstant {
destination,
constant_index,
@ -452,87 +473,87 @@ impl Instruction {
} = LoadConstant::from(self);
if jump_next {
format!("{destination} = C{constant_index} JUMP +1")
format!("R{destination} = C{constant_index} JUMP +1")
} else {
format!("{destination} = C{constant_index}")
format!("R{destination} = C{constant_index}")
}
}
Operation::LOAD_LIST => {
Operation::LoadList => {
let LoadList {
destination,
start_register,
} = LoadList::from(self);
let end_register = destination.index().saturating_sub(1);
let end_register = destination.saturating_sub(1);
format!("{destination} = [R{start_register}..=R{end_register}]",)
format!("R{destination} = [R{start_register}..=R{end_register}]",)
}
Operation::LOAD_SELF => {
Operation::LoadSelf => {
let LoadSelf { destination } = LoadSelf::from(self);
format!("{destination} = self")
format!("R{destination} = self")
}
Operation::GET_LOCAL => {
Operation::GetLocal => {
let GetLocal {
destination,
local_index,
} = GetLocal::from(self);
format!("{destination} = L{local_index}")
format!("R{destination} = L{local_index}")
}
Operation::SET_LOCAL => {
Operation::SetLocal => {
let SetLocal {
register_index: register,
register_index,
local_index,
} = SetLocal::from(self);
format!("L{local_index} = R{register}")
format!("L{local_index} = R{register_index}")
}
Operation::ADD => {
Operation::Add => {
let Add {
destination,
left,
right,
} = Add::from(self);
format!("{destination} = {left} + {right}")
format!("R{destination} = {left} + {right}")
}
Operation::SUBTRACT => {
Operation::Subtract => {
let Subtract {
destination,
left,
right,
} = Subtract::from(self);
format!("{destination} = {left} - {right}")
format!("R{destination} = {left} - {right}")
}
Operation::MULTIPLY => {
Operation::Multiply => {
let Multiply {
destination,
left,
right,
} = Multiply::from(self);
format!("{destination} = {left} * {right}")
format!("R{destination} = {left} * {right}")
}
Operation::DIVIDE => {
Operation::Divide => {
let Divide {
destination,
left,
right,
} = Divide::from(self);
format!("{destination} = {left} / {right}")
format!("R{destination} = {left} / {right}")
}
Operation::MODULO => {
Operation::Modulo => {
let Modulo {
destination,
left,
right,
} = Modulo::from(self);
format!("{destination} = {left} % {right}")
format!("R{destination} = {left} % {right}")
}
Operation::TEST => {
Operation::Test => {
let Test {
argument,
test_value: value,
@ -541,7 +562,7 @@ impl Instruction {
format!("if {bang}{argument} {{ JUMP +1 }}",)
}
Operation::TEST_SET => {
Operation::TestSet => {
let TestSet {
destination,
argument,
@ -549,9 +570,9 @@ impl Instruction {
} = TestSet::from(self);
let bang = if value { "" } else { "!" };
format!("if {bang}{argument} {{ JUMP +1 }} else {{ {destination} = {argument} }}")
format!("if {bang}{argument} {{ JUMP +1 }} else {{ R{destination} = {argument} }}")
}
Operation::EQUAL => {
Operation::Equal => {
let Equal {
destination,
value,
@ -560,9 +581,9 @@ impl Instruction {
} = Equal::from(self);
let comparison_symbol = if value { "==" } else { "!=" };
format!("{destination} = {left} {comparison_symbol} {right}")
format!("R{destination} = {left} {comparison_symbol} {right}")
}
Operation::LESS => {
Operation::Less => {
let Less {
destination,
value,
@ -571,9 +592,9 @@ impl Instruction {
} = Less::from(self);
let comparison_symbol = if value { "<" } else { ">=" };
format!("{destination} {left} {comparison_symbol} {right}")
format!("R{destination} {left} {comparison_symbol} {right}")
}
Operation::LESS_EQUAL => {
Operation::LessEqual => {
let LessEqual {
destination,
value,
@ -582,25 +603,25 @@ impl Instruction {
} = LessEqual::from(self);
let comparison_symbol = if value { "<=" } else { ">" };
format!("{destination} {left} {comparison_symbol} {right}")
format!("R{destination} {left} {comparison_symbol} {right}")
}
Operation::NEGATE => {
Operation::Negate => {
let Negate {
destination,
argument,
} = Negate::from(self);
format!("{destination} = -{argument}")
format!("R{destination} = -{argument}")
}
Operation::NOT => {
Operation::Not => {
let Not {
destination,
argument,
} = Not::from(self);
format!("{destination} = !{argument}")
format!("R{destination} = !{argument}")
}
Operation::JUMP => {
Operation::Jump => {
let Jump {
offset,
is_positive,
@ -612,33 +633,33 @@ impl Instruction {
format!("JUMP -{offset}")
}
}
Operation::CALL => {
Operation::Call => {
let Call {
destination,
function,
argument_count,
} = Call::from(self);
let arguments_start = destination.index().saturating_sub(argument_count);
let arguments_start = destination.saturating_sub(argument_count);
let arguments_end = arguments_start + argument_count;
format!("{destination} = {function}(R{arguments_start}..R{arguments_end})")
format!("R{destination} = {function}(R{arguments_start}..R{arguments_end})")
}
Operation::CALL_NATIVE => {
Operation::CallNative => {
let CallNative {
destination,
function,
argument_count,
} = CallNative::from(self);
let arguments_start = destination.index().saturating_sub(argument_count);
let arguments_start = destination.saturating_sub(argument_count);
let arguments_end = arguments_start + argument_count;
if function.returns_value() {
format!("{destination} = {function}(R{arguments_start}..R{arguments_end})")
format!("R{destination} = {function}(R{arguments_start}..R{arguments_end})")
} else {
format!("{function}(R{arguments_start}..R{arguments_end})")
}
}
Operation::RETURN => {
Operation::Return => {
let Return {
should_return_value,
} = Return::from(self);
@ -651,7 +672,7 @@ impl Instruction {
}
_ => {
if cfg!(debug_assertions) {
panic!("Unknown operation {}", self.operation);
panic!("Unknown operation {}", self.operation());
} else {
"RETURN".to_string()
}
@ -662,61 +683,20 @@ impl Instruction {
impl Debug for Instruction {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{} {}", self.operation, self.disassembly_info())
}
}
#[derive(Debug, Clone, Copy)]
pub enum Destination {
Local(u16),
Register(u16),
}
impl Destination {
pub fn index(&self) -> u16 {
match self {
Destination::Local(index) => *index,
Destination::Register(index) => *index,
}
}
pub fn is_local(&self) -> bool {
matches!(self, Destination::Local(_))
}
pub fn is_register(&self) -> bool {
matches!(self, Destination::Register(_))
}
pub fn as_index_and_a_options(&self) -> (u16, InstructionOptions) {
match self {
Destination::Local(index) => (*index, InstructionOptions::A_IS_LOCAL),
Destination::Register(index) => (*index, InstructionOptions::empty()),
}
}
}
impl Display for Destination {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Destination::Local(index) => write!(f, "L{index}"),
Destination::Register(index) => write!(f, "R{index}"),
}
write!(f, "{} {}", self.operation(), self.disassembly_info())
}
}
#[derive(Debug, Clone, Copy)]
pub enum Argument {
Constant(u16),
Local(u16),
Register(u16),
Constant(u8),
Register(u8),
}
impl Argument {
pub fn index(&self) -> u16 {
pub fn index(&self) -> u8 {
match self {
Argument::Constant(index) => *index,
Argument::Local(index) => *index,
Argument::Register(index) => *index,
}
}
@ -725,26 +705,20 @@ impl Argument {
matches!(self, Argument::Constant(_))
}
pub fn is_local(&self) -> bool {
matches!(self, Argument::Local(_))
}
pub fn is_register(&self) -> bool {
matches!(self, Argument::Register(_))
}
pub fn as_index_and_b_options(&self) -> (u16, InstructionOptions) {
pub fn as_index_and_b_options(&self) -> (u8, InstructionOptions) {
match self {
Argument::Constant(index) => (*index, InstructionOptions::B_IS_CONSTANT),
Argument::Local(index) => (*index, InstructionOptions::B_IS_LOCAL),
Argument::Register(index) => (*index, InstructionOptions::empty()),
}
}
pub fn as_index_and_c_options(&self) -> (u16, InstructionOptions) {
pub fn as_index_and_c_options(&self) -> (u8, InstructionOptions) {
match self {
Argument::Constant(index) => (*index, InstructionOptions::C_IS_CONSTANT),
Argument::Local(index) => (*index, InstructionOptions::C_IS_LOCAL),
Argument::Register(index) => (*index, InstructionOptions::empty()),
}
}
@ -754,8 +728,27 @@ impl Display for Argument {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Argument::Constant(index) => write!(f, "C{index}"),
Argument::Local(index) => write!(f, "L{index}"),
Argument::Register(index) => write!(f, "R{index}"),
}
}
}
#[cfg(test)]
mod tests {
use std::mem::offset_of;
use super::*;
#[test]
fn instruction_is_8_bytes() {
assert_eq!(size_of::<Instruction>(), 8);
}
#[test]
fn instruction_layout() {
assert_eq!(offset_of!(Instruction, a), 0);
assert_eq!(offset_of!(Instruction, b), 1);
assert_eq!(offset_of!(Instruction, c), 2);
assert_eq!(offset_of!(Instruction, metadata), 3);
}
}

View File

@ -1,14 +1,14 @@
use crate::{Argument, Destination, Instruction, Operation};
use crate::{Argument, Instruction, Operation};
pub struct Modulo {
pub destination: Destination,
pub destination: u8,
pub left: Argument,
pub right: Argument,
}
impl From<&Instruction> for Modulo {
fn from(instruction: &Instruction) -> Self {
let destination = instruction.a_as_destination();
let destination = instruction.a;
let (left, right) = instruction.b_and_c_as_arguments();
Modulo {
@ -21,16 +21,11 @@ impl From<&Instruction> for Modulo {
impl From<Modulo> for Instruction {
fn from(modulo: Modulo) -> Self {
let (a, a_options) = modulo.destination.as_index_and_a_options();
let a = modulo.destination;
let (b, b_options) = modulo.left.as_index_and_b_options();
let (c, c_options) = modulo.right.as_index_and_c_options();
let metadata = Operation::Modulo as u8 | b_options.bits() | c_options.bits();
Instruction {
operation: Operation::MODULO,
options: a_options | b_options | c_options,
a,
b,
c,
}
Instruction { metadata, a, b, c }
}
}

View File

@ -1,10 +1,8 @@
use crate::{Instruction, Operation};
use super::InstructionOptions;
pub struct Move {
pub from: u16,
pub to: u16,
pub from: u8,
pub to: u8,
}
impl From<&Instruction> for Move {
@ -18,12 +16,11 @@ impl From<&Instruction> for Move {
impl From<Move> for Instruction {
fn from(r#move: Move) -> Self {
Instruction {
operation: Operation::MOVE,
options: InstructionOptions::empty(),
a: 0,
b: r#move.from,
c: r#move.to,
}
let metadata = Operation::Move as u8;
let a = 0;
let b = r#move.from;
let c = r#move.to;
Instruction { metadata, a, b, c }
}
}

View File

@ -1,14 +1,14 @@
use crate::{Argument, Destination, Instruction, Operation};
use crate::{Argument, Instruction, Operation};
pub struct Multiply {
pub destination: Destination,
pub destination: u8,
pub left: Argument,
pub right: Argument,
}
impl From<&Instruction> for Multiply {
fn from(instruction: &Instruction) -> Self {
let destination = instruction.a_as_destination();
let destination = instruction.a;
let (left, right) = instruction.b_and_c_as_arguments();
Multiply {
@ -21,16 +21,11 @@ impl From<&Instruction> for Multiply {
impl From<Multiply> for Instruction {
fn from(multiply: Multiply) -> Self {
let (a, a_options) = multiply.destination.as_index_and_a_options();
let a = multiply.destination;
let (b, b_options) = multiply.left.as_index_and_b_options();
let (c, c_options) = multiply.right.as_index_and_c_options();
let metadata = Operation::Multiply as u8 | b_options.bits() | c_options.bits();
Instruction {
operation: Operation::MULTIPLY,
options: a_options | b_options | c_options,
a,
b,
c,
}
Instruction { metadata, a, b, c }
}
}

View File

@ -1,13 +1,13 @@
use crate::{Argument, Destination, Instruction, Operation};
use crate::{Argument, Instruction, Operation};
pub struct Negate {
pub destination: Destination,
pub destination: u8,
pub argument: Argument,
}
impl From<&Instruction> for Negate {
fn from(instruction: &Instruction) -> Self {
let destination = instruction.a_as_destination();
let destination = instruction.a;
let argument = instruction.b_as_argument();
Negate {
@ -19,15 +19,11 @@ impl From<&Instruction> for Negate {
impl From<Negate> for Instruction {
fn from(negate: Negate) -> Self {
let (a, a_options) = negate.destination.as_index_and_a_options();
let a = negate.destination;
let (b, b_options) = negate.argument.as_index_and_b_options();
let c = 0;
let metadata = Operation::Negate as u8 | b_options.bits();
Instruction {
operation: Operation::NEGATE,
options: a_options | b_options,
a,
b,
c: 0,
}
Instruction { metadata, a, b, c }
}
}

View File

@ -1,13 +1,13 @@
use crate::{Argument, Destination, Instruction, Operation};
use crate::{Argument, Instruction, Operation};
pub struct Not {
pub destination: Destination,
pub destination: u8,
pub argument: Argument,
}
impl From<&Instruction> for Not {
fn from(instruction: &Instruction) -> Self {
let destination = instruction.a_as_destination();
let destination = instruction.a;
let argument = instruction.b_as_argument();
Not {
@ -19,15 +19,11 @@ impl From<&Instruction> for Not {
impl From<Not> for Instruction {
fn from(not: Not) -> Self {
let (a, a_options) = not.destination.as_index_and_a_options();
let a = not.destination;
let (b, b_options) = not.argument.as_index_and_b_options();
let c = 0;
let metadata = Operation::Not as u8 | b_options.bits();
Instruction {
operation: Operation::NOT,
options: a_options | b_options,
a,
b,
c: 0,
}
Instruction { metadata, a, b, c }
}
}

View File

@ -4,75 +4,141 @@ use std::fmt::{self, Debug, Display, Formatter};
use serde::{Deserialize, Serialize};
pub const MOVE_BYTE: u8 = 0;
pub const CLOSE_BYTE: u8 = 1;
pub const LOAD_BOOLEAN_BYTE: u8 = 2;
pub const LOAD_CONSTANT_BYTE: u8 = 3;
pub const LOAD_LIST_BYTE: u8 = 4;
pub const LOAD_MAP_BYTE: u8 = 5;
pub const LOAD_SELF_BYTE: u8 = 6;
pub const GET_LOCAL_BYTE: u8 = 7;
pub const SET_LOCAL_BYTE: u8 = 8;
pub const ADD_BYTE: u8 = 9;
pub const SUBTRACT_BYTE: u8 = 10;
pub const MULTIPLY_BYTE: u8 = 11;
pub const DIVIDE_BYTE: u8 = 12;
pub const MODULO_BYTE: u8 = 13;
pub const POWER_BYTE: u8 = 14;
pub const TEST_BYTE: u8 = 15;
pub const TEST_SET_BYTE: u8 = 16;
pub const EQUAL_BYTE: u8 = 17;
pub const LESS_BYTE: u8 = 18;
pub const LESS_EQUAL_BYTE: u8 = 19;
pub const NEGATE_BYTE: u8 = 20;
pub const NOT_BYTE: u8 = 21;
pub const CALL_BYTE: u8 = 22;
pub const CALL_NATIVE_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.
#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Operation(u8);
#[repr(u8)]
pub enum Operation {
Move = MOVE_BYTE,
Close = CLOSE_BYTE,
LoadBoolean = LOAD_BOOLEAN_BYTE,
LoadConstant = LOAD_CONSTANT_BYTE,
LoadList = LOAD_LIST_BYTE,
LoadMap = LOAD_MAP_BYTE,
LoadSelf = LOAD_SELF_BYTE,
GetLocal = GET_LOCAL_BYTE,
SetLocal = SET_LOCAL_BYTE,
Add = ADD_BYTE,
Subtract = SUBTRACT_BYTE,
Multiply = MULTIPLY_BYTE,
Divide = DIVIDE_BYTE,
Modulo = MODULO_BYTE,
Power = POWER_BYTE,
Test = TEST_BYTE,
TestSet = TEST_SET_BYTE,
Equal = EQUAL_BYTE,
Less = LESS_BYTE,
LessEqual = LESS_EQUAL_BYTE,
Negate = NEGATE_BYTE,
Not = NOT_BYTE,
Call = CALL_BYTE,
CallNative = CALL_NATIVE_BYTE,
Jump = JUMP_BYTE,
Return = RETURN_BYTE,
}
impl Operation {
pub const MOVE: Operation = Operation(0);
pub const CLOSE: Operation = Operation(1);
pub const LOAD_BOOLEAN: Operation = Operation(2);
pub const LOAD_CONSTANT: Operation = Operation(3);
pub const LOAD_LIST: Operation = Operation(4);
pub const LOAD_SELF: Operation = Operation(5);
pub const GET_LOCAL: Operation = Operation(6);
pub const SET_LOCAL: Operation = Operation(7);
pub const ADD: Operation = Operation(8);
pub const SUBTRACT: Operation = Operation(9);
pub const MULTIPLY: Operation = Operation(10);
pub const DIVIDE: Operation = Operation(11);
pub const MODULO: Operation = Operation(12);
pub const TEST: Operation = Operation(13);
pub const TEST_SET: Operation = Operation(14);
pub const EQUAL: Operation = Operation(15);
pub const LESS: Operation = Operation(16);
pub const LESS_EQUAL: Operation = Operation(17);
pub const NEGATE: Operation = Operation(18);
pub const NOT: Operation = Operation(19);
pub const CALL: Operation = Operation(20);
pub const CALL_NATIVE: Operation = Operation(21);
pub const JUMP: Operation = Operation(22);
pub const RETURN: Operation = Operation(23);
pub fn name(self) -> &'static str {
match self {
Self::MOVE => "MOVE",
Self::CLOSE => "CLOSE",
Self::LOAD_BOOLEAN => "LOAD_BOOLEAN",
Self::LOAD_CONSTANT => "LOAD_CONSTANT",
Self::LOAD_LIST => "LOAD_LIST",
Self::LOAD_SELF => "LOAD_SELF",
Self::GET_LOCAL => "GET_LOCAL",
Self::SET_LOCAL => "SET_LOCAL",
Self::ADD => "ADD",
Self::SUBTRACT => "SUBTRACT",
Self::MULTIPLY => "MULTIPLY",
Self::DIVIDE => "DIVIDE",
Self::MODULO => "MODULO",
Self::TEST => "TEST",
Self::TEST_SET => "TEST_SET",
Self::EQUAL => "EQUAL",
Self::LESS => "LESS",
Self::LESS_EQUAL => "LESS_EQUAL",
Self::NEGATE => "NEGATE",
Self::NOT => "NOT",
Self::CALL => "CALL",
Self::CALL_NATIVE => "CALL_NATIVE",
Self::JUMP => "JUMP",
Self::RETURN => "RETURN",
impl From<u8> for Operation {
fn from(byte: u8) -> Self {
match byte {
MOVE_BYTE => Self::Move,
CLOSE_BYTE => Self::Close,
LOAD_BOOLEAN_BYTE => Self::LoadBoolean,
LOAD_CONSTANT_BYTE => Self::LoadConstant,
LOAD_LIST_BYTE => Self::LoadList,
LOAD_MAP_BYTE => Self::LoadMap,
LOAD_SELF_BYTE => Self::LoadSelf,
GET_LOCAL_BYTE => Self::GetLocal,
SET_LOCAL_BYTE => Self::SetLocal,
ADD_BYTE => Self::Add,
SUBTRACT_BYTE => Self::Subtract,
MULTIPLY_BYTE => Self::Multiply,
DIVIDE_BYTE => Self::Divide,
MODULO_BYTE => Self::Modulo,
POWER_BYTE => Self::Power,
TEST_BYTE => Self::Test,
TEST_SET_BYTE => Self::TestSet,
EQUAL_BYTE => Self::Equal,
LESS_BYTE => Self::Less,
LESS_EQUAL_BYTE => Self::LessEqual,
NEGATE_BYTE => Self::Negate,
NOT_BYTE => Self::Not,
CALL_BYTE => Self::Call,
CALL_NATIVE_BYTE => Self::CallNative,
JUMP_BYTE => Self::Jump,
RETURN_BYTE => Self::Return,
_ => {
if cfg!(debug_assertions) {
panic!("Unknown operation code {}", self.0);
panic!("Invalid operation byte: {}", byte)
} else {
"RETURN"
Self::Return
}
}
}
}
}
impl Operation {
pub fn name(self) -> &'static str {
match self {
Self::Move => "MOVE",
Self::Close => "CLOSE",
Self::LoadBoolean => "LOAD_BOOLEAN",
Self::LoadConstant => "LOAD_CONSTANT",
Self::LoadList => "LOAD_LIST",
Self::LoadMap => "LOAD_MAP",
Self::LoadSelf => "LOAD_SELF",
Self::GetLocal => "GET_LOCAL",
Self::SetLocal => "SET_LOCAL",
Self::Add => "ADD",
Self::Subtract => "SUBTRACT",
Self::Multiply => "MULTIPLY",
Self::Divide => "DIVIDE",
Self::Modulo => "MODULO",
Self::Power => "POWER",
Self::Test => "TEST",
Self::TestSet => "TEST_SET",
Self::Equal => "EQUAL",
Self::Less => "LESS",
Self::LessEqual => "LESS_EQUAL",
Self::Negate => "NEGATE",
Self::Not => "NOT",
Self::Call => "CALL",
Self::CallNative => "CALL_NATIVE",
Self::Jump => "JUMP",
Self::Return => "RETURN",
}
}
}
impl Debug for Operation {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "Operation({})", self.name())
write!(f, "{}", self.name())
}
}
@ -81,3 +147,56 @@ impl Display for Operation {
write!(f, "{}", self.name())
}
}
#[cfg(test)]
mod tests {
use super::*;
const ALL_OPERATIONS: [Operation; 26] = [
Operation::Move,
Operation::Close,
Operation::LoadBoolean,
Operation::LoadConstant,
Operation::LoadList,
Operation::LoadMap,
Operation::LoadSelf,
Operation::GetLocal,
Operation::SetLocal,
Operation::Add,
Operation::Subtract,
Operation::Multiply,
Operation::Divide,
Operation::Modulo,
Operation::Power,
Operation::Test,
Operation::TestSet,
Operation::Equal,
Operation::Less,
Operation::LessEqual,
Operation::Negate,
Operation::Not,
Operation::Call,
Operation::CallNative,
Operation::Jump,
Operation::Return,
];
#[test]
fn operations_are_unique() {
for (i, operation) in ALL_OPERATIONS.into_iter().enumerate() {
assert_eq!(i, operation as usize);
}
}
#[test]
fn operation_uses_five_bits() {
for operation in ALL_OPERATIONS {
assert_eq!(operation as u8 & 0b1110_0000, 0);
}
}
#[test]
fn operation_is_one_byte() {
assert_eq!(size_of::<Operation>(), 1);
}
}

View File

@ -1,42 +1,33 @@
//! Byte that uses its bits as boolean flags to represent information about an instruction's
//! arguments. Additionally, one bit is used as the instruction's `D` field.
//!
//! See the [instruction documentation](crate::instruction) for more information.
use bitflags::bitflags;
use serde::{Deserialize, Serialize};
bitflags! {
/// Byte that uses its bits as boolean flags to represent an instruction's options and D field.
///
/// See the [instruction documentation](crate::instruction) for more information.
#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct InstructionOptions: u8 {
const A_IS_LOCAL = 0b00000001;
const B_IS_CONSTANT = 0b0010_0000;
const B_IS_CONSTANT = 0b00000010;
const B_IS_LOCAL = 0b00000100;
const C_IS_CONSTANT = 0b0100_0000;
const C_IS_CONSTANT = 0b00001000;
const C_IS_LOCAL = 0b00010000;
const D_IS_TRUE = 0b00100000;
const D_IS_TRUE = 0b1000_0000;
}
}
impl InstructionOptions {
pub fn a_is_local(self) -> bool {
self.contains(Self::A_IS_LOCAL)
}
pub fn b_is_constant(self) -> bool {
self.contains(Self::B_IS_CONSTANT)
}
pub fn b_is_local(self) -> bool {
self.contains(Self::B_IS_LOCAL)
}
pub fn c_is_constant(self) -> bool {
self.contains(Self::C_IS_CONSTANT)
}
pub fn c_is_local(self) -> bool {
self.contains(Self::C_IS_LOCAL)
}
pub fn d(self) -> bool {
self.contains(Self::D_IS_TRUE)
}

View File

@ -1,7 +1,5 @@
use crate::{Instruction, Operation};
use super::InstructionOptions;
pub struct Return {
pub should_return_value: bool,
}
@ -18,14 +16,11 @@ impl From<&Instruction> for Return {
impl From<Return> for Instruction {
fn from(r#return: Return) -> Self {
let b = r#return.should_return_value as u16;
let metadata = Operation::Return as u8;
let a = 0;
let b = r#return.should_return_value as u8;
let c = 0;
Instruction {
operation: Operation::RETURN,
options: InstructionOptions::empty(),
a: 0,
b,
c: 0,
}
Instruction { metadata, a, b, c }
}
}

View File

@ -1,10 +1,8 @@
use crate::{Instruction, Operation};
use super::InstructionOptions;
pub struct SetLocal {
pub register_index: u16,
pub local_index: u16,
pub register_index: u8,
pub local_index: u8,
}
impl From<&Instruction> for SetLocal {
@ -21,15 +19,11 @@ impl From<&Instruction> for SetLocal {
impl From<SetLocal> for Instruction {
fn from(set_local: SetLocal) -> Self {
let metadata = Operation::SetLocal as u8;
let a = 0;
let b = set_local.register_index;
let c = set_local.local_index;
Instruction {
operation: Operation::SET_LOCAL,
options: InstructionOptions::empty(),
a: 0,
b,
c,
}
Instruction { metadata, a, b, c }
}
}

View File

@ -1,14 +1,14 @@
use crate::{Argument, Destination, Instruction, Operation};
use crate::{Argument, Instruction, Operation};
pub struct Subtract {
pub destination: Destination,
pub destination: u8,
pub left: Argument,
pub right: Argument,
}
impl From<&Instruction> for Subtract {
fn from(instruction: &Instruction) -> Self {
let destination = instruction.a_as_destination();
let destination = instruction.a;
let (left, right) = instruction.b_and_c_as_arguments();
Subtract {
@ -21,16 +21,11 @@ impl From<&Instruction> for Subtract {
impl From<Subtract> for Instruction {
fn from(subtract: Subtract) -> Self {
let (a, a_options) = subtract.destination.as_index_and_a_options();
let a = subtract.destination;
let (b, b_options) = subtract.left.as_index_and_b_options();
let (c, c_options) = subtract.right.as_index_and_c_options();
let metadata = Operation::Subtract as u8 | b_options.bits() | c_options.bits();
Instruction {
operation: Operation::SUBTRACT,
options: a_options | b_options | c_options,
a,
b,
c,
}
Instruction { metadata, a, b, c }
}
}

View File

@ -19,15 +19,11 @@ impl From<&Instruction> for Test {
impl From<Test> for Instruction {
fn from(test: Test) -> Self {
let a = 0;
let (b, options) = test.argument.as_index_and_b_options();
let c = test.test_value as u16;
let c = test.test_value as u8;
let metadata = Operation::Test as u8 | options.bits();
Instruction {
operation: Operation::TEST,
options,
a: 0,
b,
c,
}
Instruction { metadata, a, b, c }
}
}

View File

@ -1,14 +1,14 @@
use crate::{Argument, Destination, Instruction, Operation};
use crate::{Argument, Instruction, Operation};
pub struct TestSet {
pub destination: Destination,
pub destination: u8,
pub argument: Argument,
pub test_value: bool,
}
impl From<&Instruction> for TestSet {
fn from(instruction: &Instruction) -> Self {
let destination = instruction.a_as_destination();
let destination = instruction.a;
let argument = instruction.b_as_argument();
let test_value = instruction.c != 0;
@ -22,16 +22,11 @@ impl From<&Instruction> for TestSet {
impl From<TestSet> for Instruction {
fn from(test_set: TestSet) -> Self {
let (a, a_options) = test_set.destination.as_index_and_a_options();
let a = test_set.destination;
let (b, b_options) = test_set.argument.as_index_and_b_options();
let c = test_set.test_value as u16;
let c = test_set.test_value as u8;
let metadata = Operation::TestSet as u8 | b_options.bits();
Instruction {
operation: Operation::TEST_SET,
options: a_options | b_options,
a,
b,
c,
}
Instruction { metadata, a, b, c }
}
}

View File

@ -35,7 +35,6 @@ pub mod dust_error;
pub mod instruction;
pub mod lexer;
pub mod native_function;
pub mod optimize;
pub mod scope;
pub mod token;
pub mod r#type;
@ -46,10 +45,9 @@ pub use crate::chunk::{Chunk, Local};
pub use crate::compiler::{compile, CompileError, Compiler};
pub use crate::disassembler::Disassembler;
pub use crate::dust_error::{AnnotatedError, DustError};
pub use crate::instruction::{Argument, Destination, Instruction, Operation};
pub use crate::instruction::{Argument, Instruction, Operation};
pub use crate::lexer::{lex, LexError, Lexer};
pub use crate::native_function::{NativeFunction, NativeFunctionError};
pub use crate::optimize::{optimize_control_flow, optimize_set_local};
pub use crate::r#type::{EnumType, FunctionType, StructType, Type, TypeConflict};
pub use crate::scope::Scope;
pub use crate::token::{write_token_list, Token, TokenKind, TokenOwned};

View File

@ -3,7 +3,7 @@ use std::io::{self, stdout, Write};
use crate::{ConcreteValue, Instruction, NativeFunctionError, Value, Vm, VmError};
pub fn panic<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<Value>, VmError> {
let argument_count = instruction.c();
let argument_count = instruction.c;
let message = if argument_count == 0 {
None
} else {
@ -34,7 +34,7 @@ pub fn panic<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<Valu
}
pub fn to_string<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<Value>, VmError> {
let argument_count = instruction.c();
let argument_count = instruction.c;
if argument_count != 1 {
return Err(VmError::NativeFunction(
@ -63,7 +63,7 @@ pub fn to_string<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<
}
pub fn read_line<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<Value>, VmError> {
let argument_count = instruction.c();
let argument_count = instruction.c;
if argument_count != 0 {
return Err(VmError::NativeFunction(
@ -91,8 +91,8 @@ pub fn read_line<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<
}
pub fn write<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<Value>, VmError> {
let to_register = instruction.a();
let argument_count = instruction.c();
let to_register = instruction.a;
let argument_count = instruction.c;
let mut stdout = stdout();
let map_err = |io_error: io::Error| {
VmError::NativeFunction(NativeFunctionError::Io {
@ -120,8 +120,8 @@ pub fn write<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<Valu
}
pub fn write_line<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<Value>, VmError> {
let to_register = instruction.a();
let argument_count = instruction.c();
let to_register = instruction.a;
let argument_count = instruction.c;
let mut stdout = stdout();
let map_err = |io_error: io::Error| {
VmError::NativeFunction(NativeFunctionError::Io {

View File

@ -75,8 +75,8 @@ macro_rules! define_native_function {
}
}
impl From<u16> for NativeFunction {
fn from(bytes: u16) -> Self {
impl From<u8> for NativeFunction {
fn from(bytes: u8) -> Self {
match bytes {
$(
$bytes => NativeFunction::$name,

View File

@ -278,13 +278,13 @@ impl Ord for Type {
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct FunctionType {
pub type_parameters: Option<SmallVec<[u16; 4]>>,
pub value_parameters: Option<SmallVec<[(u16, Type); 4]>>,
pub type_parameters: Option<SmallVec<[u8; 4]>>,
pub value_parameters: Option<SmallVec<[(u8, Type); 4]>>,
pub return_type: Type,
}
impl FunctionType {
pub fn new<T: Into<SmallVec<[u16; 4]>>, U: Into<SmallVec<[(u16, Type); 4]>>>(
pub fn new<T: Into<SmallVec<[u8; 4]>>, U: Into<SmallVec<[(u8, Type); 4]>>>(
type_parameters: Option<T>,
value_parameters: Option<U>,
return_type: Type,

View File

@ -8,8 +8,7 @@ use smallvec::{smallvec, SmallVec};
use crate::{
compile, instruction::*, AbstractValue, AnnotatedError, Argument, Chunk, ConcreteValue,
Destination, DustError, Instruction, NativeFunctionError, Operation, Span, Value, ValueError,
ValueRef,
DustError, Instruction, NativeFunctionError, Operation, Span, Value, ValueError, ValueRef,
};
pub fn run(source: &str) -> Result<Option<ConcreteValue>, DustError> {
@ -28,10 +27,9 @@ pub struct Vm<'a> {
chunk: &'a Chunk,
parent: Option<&'a Vm<'a>>,
local_definitions: SmallVec<[Option<u16>; 16]>,
ip: usize,
last_assigned_register: Option<u16>,
last_assigned_register: Option<u8>,
source: &'a str,
}
@ -41,7 +39,6 @@ impl<'a> Vm<'a> {
chunk,
stack: smallvec![Register::Empty; chunk.stack_size()],
parent,
local_definitions: smallvec![None; chunk.locals().len()],
ip: 0,
last_assigned_register: None,
source,
@ -53,7 +50,10 @@ impl<'a> Vm<'a> {
}
pub fn current_position(&self) -> Span {
self.chunk.positions()[self.ip.saturating_sub(1)]
let index = self.ip.saturating_sub(1);
let (_, position) = self.chunk.instructions()[index];
position
}
pub fn run(&mut self) -> Result<Option<ConcreteValue>, VmError> {
@ -100,11 +100,10 @@ impl<'a> Vm<'a> {
value,
jump_next,
} = LoadBoolean::from(&instruction);
let register_index = self.get_destination(destination)?;
let boolean = ConcreteValue::Boolean(value).to_value();
let register = Register::Value(boolean);
self.set_register(register_index, register)?;
self.set_register(destination, register)?;
if jump_next {
self.jump(1, true);
@ -116,10 +115,9 @@ impl<'a> Vm<'a> {
constant_index,
jump_next,
} = LoadConstant::from(&instruction);
let register_index = self.get_destination(destination)?;
let register = Register::Pointer(Pointer::Constant(constant_index));
self.set_register(register_index, register)?;
self.set_register(destination, register)?;
if jump_next {
self.jump(1, true);
@ -130,10 +128,9 @@ impl<'a> Vm<'a> {
destination,
start_register,
} = LoadList::from(&instruction);
let register_index = self.get_destination(destination)?;
let mut pointers = Vec::new();
for register in start_register..register_index {
for register in start_register..destination {
if let Some(Register::Empty) = self.stack.get(register as usize) {
continue;
}
@ -146,47 +143,33 @@ impl<'a> Vm<'a> {
let register =
Register::Value(AbstractValue::List { items: pointers }.to_value());
self.set_register(register_index, register)?;
self.set_register(destination, register)?;
}
Operation::LoadSelf => {
let LoadSelf { destination } = LoadSelf::from(&instruction);
let register_index = self.get_destination(destination)?;
let register = Register::Value(AbstractValue::FunctionSelf.to_value());
self.set_register(register_index, register)?;
}
Operation::DefineLocal => {
let DefineLocal {
register,
local_index,
is_mutable,
} = DefineLocal::from(&instruction);
self.local_definitions[local_index as usize] = Some(register);
self.set_register(destination, register)?;
}
Operation::GetLocal => {
let GetLocal {
destination,
local_index,
} = GetLocal::from(&instruction);
let register_index = self.get_destination(destination)?;
let local_register = self.local_definitions[local_index as usize].ok_or(
VmError::UndefinedLocal {
local_index,
position: self.current_position(),
},
)?;
let local_register = self.get_local_register(local_index)?;
let register = Register::Pointer(Pointer::Stack(local_register));
self.set_register(register_index, register)?;
self.set_register(destination, register)?;
}
Operation::SetLocal => {
let SetLocal {
register_index: register,
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.local_definitions[local_index as usize] = Some(register);
self.set_register(local_register_index, register);
}
Operation::Add => {
let Add {
@ -194,26 +177,20 @@ impl<'a> Vm<'a> {
left,
right,
} = Add::from(&instruction);
let register_index = self.get_destination(destination)?;
let left = self.get_argument(left)?;
let right = self.get_argument(right)?;
let sum_result = left.add(right);
let sum = match sum_result {
Ok(sum) => sum,
Err(error) => {
return Err(VmError::Value {
error,
position: self.current_position(),
});
}
};
assert!(
sum_result.is_ok(),
"VM Error: {}",
DustError::runtime(
VmError::Value {
error: sum_result.unwrap_err(),
position: self.current_position()
},
self.source
)
);
let sum = sum_result.unwrap();
self.set_register(register_index, Register::Value(sum))?;
self.set_register(destination, Register::Value(sum))?;
}
Operation::Subtract => {
let Subtract {
@ -221,15 +198,20 @@ impl<'a> Vm<'a> {
left,
right,
} = Subtract::from(&instruction);
let register_index = self.get_destination(destination)?;
let left = self.get_argument(left)?;
let right = self.get_argument(right)?;
let difference = left.subtract(right).map_err(|error| VmError::Value {
let subtraction_result = left.subtract(right);
let difference = match subtraction_result {
Ok(difference) => difference,
Err(error) => {
return Err(VmError::Value {
error,
position: self.current_position(),
})?;
});
}
};
self.set_register(register_index, Register::Value(difference))?;
self.set_register(destination, Register::Value(difference))?;
}
Operation::Multiply => {
let Multiply {
@ -237,15 +219,20 @@ impl<'a> Vm<'a> {
left,
right,
} = Multiply::from(&instruction);
let register_index = self.get_destination(destination)?;
let left = self.get_argument(left)?;
let right = self.get_argument(right)?;
let product = left.multiply(right).map_err(|error| VmError::Value {
let multiplication_result = left.multiply(right);
let product = match multiplication_result {
Ok(product) => product,
Err(error) => {
return Err(VmError::Value {
error,
position: self.current_position(),
})?;
});
}
};
self.set_register(register_index, Register::Value(product))?;
self.set_register(destination, Register::Value(product))?;
}
Operation::Divide => {
let Divide {
@ -253,15 +240,20 @@ impl<'a> Vm<'a> {
left,
right,
} = Divide::from(&instruction);
let register_index = self.get_destination(destination)?;
let left = self.get_argument(left)?;
let right = self.get_argument(right)?;
let quotient = left.divide(right).map_err(|error| VmError::Value {
let division_result = left.divide(right);
let quotient = match division_result {
Ok(quotient) => quotient,
Err(error) => {
return Err(VmError::Value {
error,
position: self.current_position(),
})?;
});
}
};
self.set_register(register_index, Register::Value(quotient))?;
self.set_register(destination, Register::Value(quotient))?;
}
Operation::Modulo => {
let Modulo {
@ -269,15 +261,20 @@ impl<'a> Vm<'a> {
left,
right,
} = Modulo::from(&instruction);
let register_index = self.get_destination(destination)?;
let left = self.get_argument(left)?;
let right = self.get_argument(right)?;
let remainder = left.modulo(right).map_err(|error| VmError::Value {
let modulo_result = left.modulo(right);
let remainder = match modulo_result {
Ok(remainder) => remainder,
Err(error) => {
return Err(VmError::Value {
error,
position: self.current_position(),
})?;
});
}
};
self.set_register(register_index, Register::Value(remainder))?;
self.set_register(destination, Register::Value(remainder))?;
}
Operation::Test => {
let Test {
@ -305,7 +302,6 @@ impl<'a> Vm<'a> {
argument,
test_value,
} = TestSet::from(&instruction);
let register_index = self.get_destination(destination)?;
let value = self.get_argument(argument)?;
let boolean = if let ValueRef::Concrete(ConcreteValue::Boolean(boolean)) = value
{
@ -322,24 +318,20 @@ impl<'a> Vm<'a> {
} else {
let pointer = match argument {
Argument::Constant(constant_index) => Pointer::Constant(constant_index),
Argument::Local(local_index) => {
let register_index = self.local_definitions[local_index as usize]
.ok_or(VmError::UndefinedLocal {
local_index,
position: self.current_position(),
})?;
Pointer::Stack(register_index)
}
Argument::Register(register_index) => Pointer::Stack(register_index),
};
let register = Register::Pointer(pointer);
self.set_register(register_index, register)?;
self.set_register(destination, register)?;
}
}
Operation::Equal => {
let Equal { value, left, right } = Equal::from(&instruction);
let Equal {
destination,
value,
left,
right,
} = Equal::from(&instruction);
let left = self.get_argument(left)?;
let right = self.get_argument(right)?;
let equal_result = left.equal(right).map_err(|error| VmError::Value {
@ -355,64 +347,79 @@ impl<'a> Vm<'a> {
position: self.current_position(),
});
};
let comparison = is_equal == value;
let register =
Register::Value(Value::Concrete(ConcreteValue::Boolean(comparison)));
if is_equal == value {
self.jump(1, true);
}
self.set_register(destination, register)?;
}
Operation::Less => {
let Less { value, left, right } = Less::from(&instruction);
let Less {
destination,
value,
left,
right,
} = Less::from(&instruction);
let left = self.get_argument(left)?;
let right = self.get_argument(right)?;
let less_result = left.less_than(right);
assert!(
less_result.is_ok(),
"VM Error: {}\nPosition: {}",
less_result.unwrap_err(),
self.current_position()
);
let less_than_value = less_result.unwrap();
let less_than_boolean = less_than_value.as_boolean();
assert!(
less_than_boolean.is_some(),
"VM Error: Expected a boolean\nPosition: {}",
self.current_position()
);
let is_less_than = less_than_boolean.unwrap();
if is_less_than == &value {
self.jump(1, true);
}
}
Operation::LessEqual => {
let LessEqual { value, left, right } = LessEqual::from(&instruction);
let left = self.get_argument(left)?;
let right = self.get_argument(right)?;
let less_or_equal_result =
left.less_than_or_equal(right)
.map_err(|error| VmError::Value {
let less_than_value = match less_result {
Ok(value) => value,
Err(error) => {
return Err(VmError::Value {
error,
position: self.current_position(),
})?;
let is_less_than_or_equal =
if let Value::Concrete(ConcreteValue::Boolean(boolean)) =
less_or_equal_result
{
boolean
} else {
});
}
};
let is_less_than = match less_than_value {
Value::Concrete(ConcreteValue::Boolean(boolean)) => boolean,
_ => {
return Err(VmError::ExpectedBoolean {
found: less_or_equal_result,
found: less_than_value,
position: self.current_position(),
});
};
if is_less_than_or_equal == value {
self.jump(1, true);
}
};
let comparison = is_less_than == value;
let register =
Register::Value(Value::Concrete(ConcreteValue::Boolean(comparison)));
self.set_register(destination, register)?;
}
Operation::LessEqual => {
let LessEqual {
destination,
value,
left,
right,
} = LessEqual::from(&instruction);
let left = self.get_argument(left)?;
let right = self.get_argument(right)?;
let less_or_equal_result = left.less_than_or_equal(right);
let less_or_equal_value = match less_or_equal_result {
Ok(value) => value,
Err(error) => {
return Err(VmError::Value {
error,
position: self.current_position(),
});
}
};
let is_less_than_or_equal = match less_or_equal_value {
Value::Concrete(ConcreteValue::Boolean(boolean)) => boolean,
_ => {
return Err(VmError::ExpectedBoolean {
found: less_or_equal_value,
position: self.current_position(),
});
}
};
let comparison = is_less_than_or_equal == value;
let register =
Register::Value(Value::Concrete(ConcreteValue::Boolean(comparison)));
self.set_register(destination, register)?;
}
Operation::Negate => {
let Negate {
@ -424,10 +431,9 @@ impl<'a> Vm<'a> {
error,
position: self.current_position(),
})?;
let register_index = self.get_destination(destination)?;
let register = Register::Value(negated);
self.set_register(register_index, register)?;
self.set_register(destination, register)?;
}
Operation::Not => {
let Not {
@ -439,10 +445,9 @@ impl<'a> Vm<'a> {
error,
position: self.current_position(),
})?;
let register_index = self.get_destination(destination)?;
let register = Register::Value(not);
self.set_register(register_index, register)?;
self.set_register(destination, register)?;
}
Operation::Jump => {
let Jump {
@ -458,7 +463,6 @@ impl<'a> Vm<'a> {
function,
argument_count,
} = Call::from(&instruction);
let register_index = self.get_destination(destination)?;
let function = self.get_argument(function)?;
let chunk = if let ValueRef::Concrete(ConcreteValue::Function(chunk)) = function
{
@ -472,17 +476,15 @@ impl<'a> Vm<'a> {
});
};
let mut function_vm = Vm::new(self.source, chunk, Some(self));
let first_argument_index = register_index - argument_count;
let first_argument_index = destination - argument_count;
for (argument_index, argument_register_index) in
(first_argument_index..register_index).enumerate()
(first_argument_index..destination).enumerate()
{
function_vm.set_register(
argument_index as u16,
argument_index as u8,
Register::Pointer(Pointer::ParentStack(argument_register_index)),
)?;
function_vm.local_definitions[argument_index] = Some(argument_index as u16);
}
let return_value = function_vm.run()?;
@ -490,7 +492,7 @@ impl<'a> Vm<'a> {
if let Some(concrete_value) = return_value {
let register = Register::Value(concrete_value.to_value());
self.set_register(register_index, register)?;
self.set_register(destination, register)?;
}
}
Operation::CallNative => {
@ -502,10 +504,9 @@ impl<'a> Vm<'a> {
let return_value = function.call(self, instruction)?;
if let Some(value) = return_value {
let register_index = self.get_destination(destination)?;
let register = Register::Value(value);
self.set_register(register_index, register)?;
self.set_register(destination, register)?;
}
}
Operation::Return => {
@ -529,6 +530,7 @@ impl<'a> Vm<'a> {
})
};
}
_ => unreachable!(),
}
}
}
@ -565,7 +567,7 @@ impl<'a> Vm<'a> {
}
}
fn open_register(&self, register_index: u16) -> Result<ValueRef, VmError> {
fn open_register(&self, register_index: u8) -> Result<ValueRef, VmError> {
let register_index = register_index as usize;
assert!(
@ -589,7 +591,7 @@ impl<'a> Vm<'a> {
pub(crate) fn open_register_allow_empty(
&self,
register_index: u16,
register_index: u8,
) -> Result<Option<ValueRef>, VmError> {
let register_index = register_index as usize;
let register =
@ -628,16 +630,6 @@ impl<'a> Vm<'a> {
self.ip = new_ip;
}
/// DRY helper to get a register index from a Destination
fn get_destination(&self, destination: Destination) -> Result<u16, VmError> {
let index = match destination {
Destination::Register(register_index) => register_index,
Destination::Local(local_index) => self.get_local_register(local_index)?,
};
Ok(index)
}
/// DRY helper to get a value from an Argument
fn get_argument(&self, argument: Argument) -> Result<ValueRef, VmError> {
let value_ref = match argument {
@ -645,18 +637,13 @@ impl<'a> Vm<'a> {
ValueRef::Concrete(self.get_constant(constant_index))
}
Argument::Register(register_index) => self.open_register(register_index)?,
Argument::Local(local_index) => {
let register_index = self.get_local_register(local_index)?;
self.open_register(register_index)?
}
};
Ok(value_ref)
}
#[inline(always)]
fn set_register(&mut self, to_register: u16, register: Register) -> Result<(), VmError> {
fn set_register(&mut self, to_register: u8, register: Register) -> Result<(), VmError> {
self.last_assigned_register = Some(to_register);
let to_register = to_register as usize;
@ -672,7 +659,7 @@ impl<'a> Vm<'a> {
}
#[inline(always)]
fn get_constant(&self, constant_index: u16) -> &ConcreteValue {
fn get_constant(&self, constant_index: u8) -> &ConcreteValue {
let constant_index = constant_index as usize;
let constants = self.chunk.constants();
@ -685,18 +672,18 @@ impl<'a> Vm<'a> {
}
#[inline(always)]
fn get_local_register(&self, local_index: u16) -> Result<u16, VmError> {
fn get_local_register(&self, local_index: u8) -> Result<u8, VmError> {
let local_index = local_index as usize;
let locals = self.chunk.locals();
assert!(
local_index < self.local_definitions.len(),
local_index < locals.len(),
"VM Error: Local index out of bounds"
);
self.local_definitions[local_index].ok_or_else(|| VmError::UndefinedLocal {
local_index: local_index as u16,
position: self.current_position(),
})
let register_index = locals[local_index].register_index;
Ok(register_index)
}
#[inline(always)]
@ -715,7 +702,7 @@ impl<'a> Vm<'a> {
)
);
let instruction = instructions[self.ip];
let (instruction, _) = instructions[self.ip];
self.ip += 1;
@ -742,10 +729,10 @@ impl Display for Register {
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum Pointer {
Stack(u16),
Constant(u16),
ParentStack(u16),
ParentConstant(u16),
Stack(u8),
Constant(u8),
ParentStack(u8),
ParentConstant(u8),
}
impl Display for Pointer {
@ -789,7 +776,7 @@ pub enum VmError {
// Local errors
UndefinedLocal {
local_index: u16,
local_index: u8,
position: Span,
},

View File

@ -6,7 +6,7 @@ fn add_assign_expects_mutable_variable() {
assert_eq!(
compile(source),
Err(CreateReport::Compile {
Err(DustError::Compile {
error: CompileError::ExpectedMutableVariable {
found: Token::Integer("1").to_owned(),
position: Span(0, 1)
@ -22,7 +22,7 @@ fn divide_assign_expects_mutable_variable() {
assert_eq!(
compile(source),
Err(CreateReport::Compile {
Err(DustError::Compile {
error: CompileError::ExpectedMutableVariable {
found: Token::Integer("1").to_owned(),
position: Span(0, 1)
@ -38,7 +38,7 @@ fn multiply_assign_expects_mutable_variable() {
assert_eq!(
compile(source),
Err(CreateReport::Compile {
Err(DustError::Compile {
error: CompileError::ExpectedMutableVariable {
found: Token::Integer("1").to_owned(),
position: Span(0, 1)
@ -54,7 +54,7 @@ fn subtract_assign_expects_mutable_variable() {
assert_eq!(
compile(source),
Err(CreateReport::Compile {
Err(DustError::Compile {
error: CompileError::ExpectedMutableVariable {
found: Token::Integer("1").to_owned(),
position: Span(0, 1)
@ -70,7 +70,7 @@ fn modulo_assign_expects_mutable_variable() {
assert_eq!(
compile(source),
Err(CreateReport::Compile {
Err(DustError::Compile {
error: CompileError::ExpectedMutableVariable {
found: Token::Integer("1").to_owned(),
position: Span(0, 1)

View File

@ -11,7 +11,7 @@ fn constant() {
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Box::new(Type::Integer)
return_type: Type::Integer
},
vec![
(