1
0

Optimize VM; Add two new register layouts

This commit is contained in:
Jeff 2025-01-21 19:53:58 -05:00
parent 9cf873bd39
commit 121d2aae7b
34 changed files with 668 additions and 182 deletions

View File

@ -22,7 +22,7 @@ smartstring = { version = "1.0.1", features = [
], default-features = false }
tracing = "0.1.41"
crossbeam-channel = "0.5.14"
smallvec = { version = "1.13.2", features = ["serde"] }
smallvec = { version = "1.13.2", features = ["const_generics", "serde"] }
[dev-dependencies]
criterion = { version = "0.3.4", features = ["html_reports"] }

View File

@ -9,9 +9,7 @@ const SOURCE: &str = r#"
while i < 1_000 {
i += 1
spawn(
fn () { random_int(0, 10); }
)
spawn(fn () { random_int(0, 10); })
}
"#;

View File

@ -67,6 +67,10 @@ pub enum CompileError {
},
// Type errors
ArgumentTypeConflict {
conflict: TypeConflict,
position: Span,
},
CannotAddType {
argument_type: Type,
position: Span,
@ -180,6 +184,7 @@ impl AnnotatedError for CompileError {
fn description(&self) -> &'static str {
match self {
Self::ArgumentTypeConflict { .. } => "Argument type conflict",
Self::CannotAddArguments { .. } => "Cannot add these types",
Self::CannotAddType { .. } => "Cannot add to this type",
Self::ComparisonChain { .. } => "Cannot chain comparison operations",

View File

@ -36,7 +36,9 @@ use crate::{
Chunk, DustError, DustString, FunctionType, Instruction, Lexer, Local, NativeFunction, Operand,
Operation, Scope, Span, Token, TokenKind, Type,
chunk::ConstantTable,
instruction::{CallNative, Close, GetLocal, Jump, LoadList, Move, Return, SetLocal, TypeCode},
instruction::{
Call, CallNative, Close, GetLocal, Jump, LoadList, Move, Return, SetLocal, TypeCode,
},
};
/// Compiles the input and returns a chunk.
@ -340,6 +342,30 @@ impl<'src> Compiler<'src> {
.unwrap_or(self.minimum_register)
}
fn next_pointer_register(&self) -> u16 {
self.instructions
.iter()
.rev()
.find_map(|(instruction, _, _)| match instruction.operation() {
Operation::CALL => {
let Call { destination, .. } = Call::from(instruction);
Some(destination + 1)
}
Operation::CALL_NATIVE => {
let CallNative {
first_argument,
argument_count,
..
} = CallNative::from(instruction);
Some(first_argument + argument_count as u16)
}
_ => None,
})
.unwrap_or(self.minimum_register)
}
fn advance(&mut self) -> Result<(), CompileError> {
if self.is_eof() {
return Ok(());
@ -1567,46 +1593,65 @@ impl<'src> Compiler<'src> {
}
fn parse_call_native(&mut self, function: NativeFunction) -> Result<(), CompileError> {
todo!();
let start = self.current_position.0;
let function_type = function.r#type();
let mut first_argument = None;
let mut argument_count = 0;
// let start = self.previous_position.0;
// let start_register = self.next_register();
self.expect(Token::LeftParenthesis)?;
// self.expect(Token::LeftParenthesis)?;
while !self.allow(Token::RightParenthesis)? {
self.parse_expression()?;
// while !self.allow(Token::RightParenthesis)? {
// let expected_register = self.next_register();
let expected_argument_type = function_type
.value_parameters
.get(argument_count as usize)
.map(|(_, r#type)| r#type)
.unwrap_or(&Type::None);
let (_, actual_argument_type, position) = self.instructions.last().unwrap().clone();
// self.parse_expression()?;
expected_argument_type
.check(&actual_argument_type)
.map_err(|conflict| CompileError::ArgumentTypeConflict { conflict, position })?;
// let actual_register = self.next_register() - 1;
// let registers_to_close = actual_register - expected_register;
if argument_count == 0 {
let argument = self
.instructions
.last()
.map(|(instruction, _, _)| instruction)
.unwrap()
.a_field();
// if registers_to_close > 0 {
// let close = Instruction::from(Close {
// from: expected_register,
// to: actual_register,
// });
first_argument = Some(argument);
}
// self.emit_instruction(close, Type::None, self.current_position);
// }
argument_count += 1;
// self.allow(Token::Comma)?;
// }
self.allow(Token::Comma)?;
}
// let end = self.previous_position.1;
// let destination = self.next_register();
// let argument_count = destination - start_register;
// let return_type = function.r#type().return_type;
// let call_native = Instruction::from(CallNative {
// destination,
// function,
// argument_count,
// });
let end = self.previous_position.1;
let destination = match function.r#type().return_type {
Type::Boolean => self.next_boolean_register(),
Type::Byte => self.next_byte_register(),
Type::Character => self.next_character_register(),
Type::Float => self.next_float_register(),
Type::Integer => self.next_integer_register(),
Type::String => self.next_string_register(),
Type::None => 0,
_ => todo!(),
};
let return_type = function.r#type().return_type;
let call_native = Instruction::from(CallNative {
destination,
function,
first_argument: first_argument.unwrap_or(0),
argument_count,
});
// self.emit_instruction(call_native, return_type, Span(start, end));
self.emit_instruction(call_native, return_type, Span(start, end));
// Ok(())
Ok(())
}
fn parse_semicolon(&mut self) -> Result<(), CompileError> {

View File

@ -1,6 +1,6 @@
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operand, Operation, TypeCode};
use super::{Instruction, Operand, Operation, TwoOperandLayout, TypeCode};
pub struct Add {
pub destination: u16,
@ -36,7 +36,7 @@ impl From<Add> for Instruction {
let b_type = add.left_type;
let c_type = add.right_type;
InstructionBuilder {
TwoOperandLayout {
operation,
a_field,
b_field,

View File

@ -2,7 +2,7 @@ use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
use super::TwoOperandLayout;
pub struct Call {
pub destination: u16,
@ -11,8 +11,8 @@ pub struct Call {
pub is_recursive: bool,
}
impl From<Instruction> for Call {
fn from(instruction: Instruction) -> Self {
impl From<&Instruction> for Call {
fn from(instruction: &Instruction) -> Self {
let destination = instruction.a_field();
let function_register = instruction.b_field();
let argument_count = instruction.c_field();
@ -34,7 +34,7 @@ impl From<Call> for Instruction {
let c_field = call.argument_count;
let d_field = call.is_recursive;
InstructionBuilder {
TwoOperandLayout {
operation: Operation::CALL,
a_field,
b_field,

View File

@ -1,24 +1,28 @@
use std::fmt::Display;
use crate::{Instruction, NativeFunction, Operation};
use crate::{Instruction, NativeFunction, Operation, Type};
use super::InstructionBuilder;
use super::ZeroOperandLayout;
pub struct CallNative {
pub destination: u16,
pub function: NativeFunction,
pub argument_count: u16,
pub first_argument: u16,
pub argument_count: u8,
}
impl From<Instruction> for CallNative {
fn from(instruction: Instruction) -> Self {
impl From<&Instruction> for CallNative {
fn from(instruction: &Instruction) -> Self {
let destination = instruction.a_field();
let function = NativeFunction::from(instruction.b_field());
let first_argument = instruction.c_field();
let argument_count = instruction.e_field();
CallNative {
destination,
function,
argument_count: instruction.c_field(),
first_argument,
argument_count,
}
}
}
@ -28,14 +32,15 @@ impl From<CallNative> for Instruction {
let operation = Operation::CALL_NATIVE;
let a_field = call_native.destination;
let b_field = call_native.function as u16;
let c_field = call_native.argument_count;
let c_field = call_native.first_argument;
let e_field = call_native.argument_count;
InstructionBuilder {
ZeroOperandLayout {
operation,
a_field,
b_field,
c_field,
..Default::default()
e_field,
}
.build()
}
@ -46,21 +51,58 @@ impl Display for CallNative {
let CallNative {
destination,
function,
first_argument,
argument_count,
} = self;
let arguments_start = destination.saturating_sub(*argument_count);
let arguments_end = arguments_start + argument_count;
let function_type = function.r#type();
if function.returns_value() {
write!(f, "R{destination} = ")?;
match function_type.return_type {
Type::Boolean => write!(f, "R_BOOL_{destination} = ")?,
Type::Byte => write!(f, "R_BYTE_{destination} = ")?,
Type::Character => write!(f, "R_CHAR_{destination} = ")?,
Type::Float => write!(f, "R_FLOAT_{destination} = ")?,
Type::Integer => write!(f, "R_INT_{destination} = ")?,
Type::String => write!(f, "R_STR_{destination} = ")?,
Type::None => {}
_ => todo!(),
}
}
match argument_count {
0 => {
write!(f, "{function}()")
if *argument_count == 0 {
write!(f, "{function}()")
} else {
let last_argument = first_argument + (*argument_count as u16).saturating_sub(1);
let arguments = *first_argument..=last_argument;
let parameter_types = function_type
.value_parameters
.iter()
.map(|(_, r#type)| r#type);
write!(f, "{function}(")?;
let mut is_first = true;
for (argument, parameter_type) in arguments.zip(parameter_types) {
if is_first {
is_first = false;
} else {
write!(f, ", ")?;
}
match parameter_type {
Type::Boolean => write!(f, "R_BOOL_{argument}")?,
Type::Byte => write!(f, "R_BYTE_{argument}")?,
Type::Character => write!(f, "R_CHAR_{argument}")?,
Type::Float => write!(f, "R_FLOAT_{argument}")?,
Type::Integer => write!(f, "R_INT_{argument}")?,
Type::String => write!(f, "R_STR_{argument}")?,
Type::None => {}
_ => todo!(),
}
}
1 => write!(f, "{function}(R{arguments_start})"),
_ => write!(f, "{function}(R{arguments_start}..R{arguments_end})"),
write!(f, ")")
}
}
}

View File

@ -2,7 +2,7 @@ use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
use super::TwoOperandLayout;
pub struct Close {
pub from: u16,
@ -24,7 +24,7 @@ impl From<Close> for Instruction {
let b_field = close.from;
let c_field = close.to;
InstructionBuilder {
TwoOperandLayout {
operation,
b_field,
c_field,

View File

@ -1,6 +1,6 @@
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operand, Operation, TypeCode};
use super::{Instruction, Operand, Operation, TwoOperandLayout, TypeCode};
pub struct Divide {
pub destination: u16,
@ -36,7 +36,7 @@ impl From<Divide> for Instruction {
let b_type = divide.left_type;
let c_type = divide.right_type;
InstructionBuilder {
TwoOperandLayout {
operation,
a_field,
b_field,

View File

@ -1,6 +1,6 @@
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operand, Operation, TypeCode};
use super::{Instruction, Operand, Operation, TwoOperandLayout, TypeCode};
pub struct Equal {
pub comparator: bool,
@ -36,7 +36,7 @@ impl From<Equal> for Instruction {
let b_type = equal_bool.left_type;
let c_type = equal_bool.right_type;
InstructionBuilder {
TwoOperandLayout {
operation,
b_field,
c_field,

View File

@ -2,7 +2,7 @@ use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::{InstructionBuilder, TypeCode};
use super::{TwoOperandLayout, TypeCode};
pub struct GetLocal {
pub destination: u16,
@ -31,7 +31,7 @@ impl From<GetLocal> for Instruction {
let b_field = get_local.local_index;
let b_type = get_local.r#type;
InstructionBuilder {
TwoOperandLayout {
operation,
a_field,
b_field,

View File

@ -2,7 +2,7 @@ use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
use super::TwoOperandLayout;
pub struct Jump {
pub offset: u16,
@ -24,7 +24,7 @@ impl From<Jump> for Instruction {
let b_field = jump.offset;
let c_field = jump.is_positive as u16;
InstructionBuilder {
TwoOperandLayout {
operation,
b_field,
c_field,

View File

@ -1,6 +1,6 @@
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operand, Operation, TypeCode};
use super::{Instruction, Operand, Operation, TwoOperandLayout, TypeCode};
pub struct Less {
pub comparator: bool,
@ -36,7 +36,7 @@ impl From<Less> for Instruction {
let b_type = less.left_type;
let c_type = less.right_type;
InstructionBuilder {
TwoOperandLayout {
operation,
b_field,
c_field,

View File

@ -1,6 +1,6 @@
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operand, Operation, TypeCode};
use super::{Instruction, Operand, Operation, TwoOperandLayout, TypeCode};
pub struct LessEqual {
pub comparator: bool,
@ -36,7 +36,7 @@ impl From<LessEqual> for Instruction {
let b_type = less_equal_byte.left_type;
let c_type = less_equal_byte.right_type;
InstructionBuilder {
TwoOperandLayout {
operation,
b_field,
c_field,

View File

@ -2,7 +2,7 @@ use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::{InstructionBuilder, TypeCode};
use super::{TwoOperandLayout, TypeCode};
pub struct LoadConstant {
pub destination: u16,
@ -29,7 +29,7 @@ impl From<Instruction> for LoadConstant {
impl From<LoadConstant> for Instruction {
fn from(load_constant: LoadConstant) -> Self {
InstructionBuilder {
TwoOperandLayout {
operation: Operation::LOAD_CONSTANT,
a_field: load_constant.destination,
b_type: load_constant.type_code,

View File

@ -1,6 +1,6 @@
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operation};
use super::{Instruction, TwoOperandLayout, Operation};
pub struct LoadFunction {
pub destination: u16,
@ -24,7 +24,7 @@ impl From<Instruction> for LoadFunction {
impl From<LoadFunction> for Instruction {
fn from(load_function: LoadFunction) -> Self {
InstructionBuilder {
TwoOperandLayout {
operation: Operation::LOAD_FUNCTION,
a_field: load_function.destination,
b_field: load_function.prototype_index,

View File

@ -2,7 +2,7 @@ use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::{InstructionBuilder, TypeCode};
use super::{TwoOperandLayout, TypeCode};
pub struct LoadInline {
pub destination: u16,
@ -33,7 +33,7 @@ impl From<LoadInline> for Instruction {
let c_field = load_boolean.boolean as u16;
let d_field = load_boolean.jump_next;
InstructionBuilder {
TwoOperandLayout {
operation,
a_field,
b_field,

View File

@ -2,7 +2,7 @@ use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
use super::TwoOperandLayout;
pub struct LoadList {
pub destination: u16,
@ -26,7 +26,7 @@ impl From<Instruction> for LoadList {
impl From<LoadList> for Instruction {
fn from(load_list: LoadList) -> Self {
InstructionBuilder {
TwoOperandLayout {
operation: Operation::LOAD_LIST,
a_field: load_list.destination,
b_field: load_list.start_register,

View File

@ -2,7 +2,7 @@ use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
use super::TwoOperandLayout;
pub struct LoadSelf {
pub destination: u16,
@ -23,7 +23,7 @@ impl From<Instruction> for LoadSelf {
impl From<LoadSelf> for Instruction {
fn from(load_self: LoadSelf) -> Self {
InstructionBuilder {
TwoOperandLayout {
operation: Operation::LOAD_SELF,
a_field: load_self.destination,
c_field: load_self.jump_next as u16,

View File

@ -1,8 +1,17 @@
//! The Dust instruction set.
//!
//! Each instruction is 64 bits and uses up to seven distinct fields.
//! Each instruction is 64 bits and uses a layout that divides the bits into fields.
//!
//! # Layout
//! # Layouts
//!
//! ## Seven-Field Layout
//!
//! Used for most instructions, this layout has A, B and C fields for a destination and two
//! operands, including basic type codes and constant flags for the operands and an additional
//! boolean in the D field.
//!
//! Only shorter type codes (up to 3 bits) can fit in this layout. These type codes are relegated to
//! Dust's basic types: `bool`, `byte`, `char`, `float`, `int` and `str`.
//!
//! Bits | Description
//! ----- | -----------
@ -10,15 +19,50 @@
//! 7 | Flag indicating if the B field is a constant
//! 8 | Flag indicating if the C field is a constant
//! 9 | D field (boolean)
//! 10-12 | B field type
//! 13-15 | C field type
//! 10-12 | B field type (basic type)
//! 13-15 | C field type (basic type)
//! 16-31 | A field (unsigned 16-bit integer)
//! 32-47 | B field (unsigned 16-bit integer)
//! 48-63 | C field (unsigned 16-bit integer)
//!
//! **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.
//! ## Six-Field Layout
//!
//! Instructions that have only one operand and/or need to support all of Dust's types use this
//! layout.
//!
//! Bits | Description
//! ----- | -----------
//! 0-6 | Operation
//! 7 | Flag indicating if the B field is a constant
//! 8 | Flag indicating if the C field is a constant
//! 9 | D field (boolean)
//! 10-15 | B field type (any type)
//! 16-31 | A field (unsigned 16-bit integer)
//! 32-47 | B field (unsigned 16-bit integer)
//! 48-63 | C field (unsigned 16-bit integer)
//!
//! ## Five-Field Layout
//!
//! If the instruction's types can be inferred from the operation, the type codes can be omitted and
//! an extra byte can be used for the E field.
//!
//! Bits | Description
//! ----- | -----------
//! 0-6 | Operation
//! 7-14 | E field (unsigned 8-bit integer)
//! 15 | Unused
//! 16-31 | A field (unsigned 16-bit integer)
//! 32-47 | B field (unsigned 16-bit integer)
//! 48-63 | C field (unsigned 16-bit integer)
//!
//! **Be careful when working with instructions.**
//!
//! Do not read instruction fields by calling methods on the instruction directly, except for the
//! `operation` method. These methods read bits directly from the underlying `u64`, ignoring the
//! layout. Instead, to avoid mistakes and persist future changes, call `operation`, then convert
//! the instruction to a struct for that specific operation. In performance-critical code, convert
//! the instruction to a layout struct and cache it, being careful to interpret the fields
//! correctly.
//!
//! # Examples
//!
@ -155,7 +199,38 @@ pub use type_code::TypeCode;
use crate::NativeFunction;
#[derive(Clone, Copy, PartialEq, PartialOrd)]
pub struct InstructionBuilder {
pub struct ZeroOperandLayout {
pub operation: Operation,
pub a_field: u16,
pub b_field: u16,
pub c_field: u16,
pub e_field: u8,
}
impl ZeroOperandLayout {
pub fn build(self) -> Instruction {
let bits = ((self.operation.0 as u64) << 57)
| ((self.e_field as u64) << 47)
| ((self.a_field as u64) << 32)
| ((self.b_field as u64) << 16)
| (self.c_field as u64);
Instruction(bits)
}
}
#[derive(Clone, Copy, PartialEq, PartialOrd)]
pub struct OneOperandLayout {
pub operation: Operation,
pub a_field: u16,
pub b_field: u16,
pub c_field: u16,
pub d_field: bool,
pub r#type: TypeCode,
}
#[derive(Clone, Copy, PartialEq, PartialOrd)]
pub struct TwoOperandLayout {
pub operation: Operation,
pub a_field: u16,
pub b_field: u16,
@ -167,9 +242,9 @@ pub struct InstructionBuilder {
pub c_type: TypeCode,
}
impl From<&Instruction> for InstructionBuilder {
impl From<&Instruction> for TwoOperandLayout {
fn from(instruction: &Instruction) -> Self {
InstructionBuilder {
TwoOperandLayout {
operation: instruction.operation(),
a_field: instruction.a_field(),
b_field: instruction.b_field(),
@ -183,7 +258,7 @@ impl From<&Instruction> for InstructionBuilder {
}
}
impl InstructionBuilder {
impl TwoOperandLayout {
pub fn build(self) -> Instruction {
let bits = ((self.operation.0 as u64) << 57)
| ((self.b_is_constant as u64) << 56)
@ -199,9 +274,9 @@ impl InstructionBuilder {
}
}
impl Default for InstructionBuilder {
impl Default for TwoOperandLayout {
fn default() -> Self {
InstructionBuilder {
TwoOperandLayout {
operation: Operation::MOVE,
a_field: 0,
b_field: 0,
@ -215,13 +290,13 @@ impl Default for InstructionBuilder {
}
}
impl Debug for InstructionBuilder {
impl Debug for TwoOperandLayout {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{self}")
}
}
impl Display for InstructionBuilder {
impl Display for TwoOperandLayout {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.build())
}
@ -264,6 +339,10 @@ impl Instruction {
TypeCode(byte)
}
pub fn e_field(&self) -> u8 {
(self.0 >> 47) as u8
}
pub fn a_field(&self) -> u16 {
((self.0 >> 32) & 0xFFFF) as u16
}
@ -554,11 +633,13 @@ impl Instruction {
pub fn call_native(
destination: u16,
function: NativeFunction,
argument_count: u16,
first_argument: u16,
argument_count: u8,
) -> Instruction {
Instruction::from(CallNative {
destination,
function,
first_argument,
argument_count,
})
}
@ -778,8 +859,8 @@ impl Instruction {
Operation::LESS_EQUAL => LessEqual::from(*self).to_string(),
Operation::TEST => Test::from(*self).to_string(),
Operation::TEST_SET => TestSet::from(*self).to_string(),
Operation::CALL => Call::from(*self).to_string(),
Operation::CALL_NATIVE => CallNative::from(*self).to_string(),
Operation::CALL => Call::from(self).to_string(),
Operation::CALL_NATIVE => CallNative::from(self).to_string(),
Operation::JUMP => Jump::from(*self).to_string(),
Operation::RETURN => Return::from(*self).to_string(),
@ -959,4 +1040,11 @@ mod tests {
assert_eq!(TypeCode::CHARACTER, instruction.c_type());
}
#[test]
fn decond_e_field() {
let instruction = Instruction::call_native(42, NativeFunction::ReadLine, 4, 255);
assert_eq!(255, instruction.e_field());
}
}

View File

@ -1,6 +1,6 @@
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operand, Operation, TypeCode};
use super::{Instruction, Operand, Operation, TwoOperandLayout, TypeCode};
pub struct Modulo {
pub destination: u16,
@ -36,7 +36,7 @@ impl From<Modulo> for Instruction {
let b_type = modulo.left_type;
let c_type = modulo.right_type;
InstructionBuilder {
TwoOperandLayout {
operation,
a_field,
b_field,

View File

@ -2,7 +2,7 @@ use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::{InstructionBuilder, TypeCode};
use super::{TwoOperandLayout, TypeCode};
pub struct Move {
pub from: u16,
@ -27,7 +27,7 @@ impl From<Move> for Instruction {
let b_field = r#move.to;
let b_type = r#move.type_code;
InstructionBuilder {
TwoOperandLayout {
operation,
a_field,
b_field,

View File

@ -1,6 +1,6 @@
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operand, Operation, TypeCode};
use super::{Instruction, Operand, Operation, TwoOperandLayout, TypeCode};
pub struct Multiply {
pub destination: u16,
@ -36,7 +36,7 @@ impl From<Multiply> for Instruction {
let b_type = multiply.left_type;
let c_type = multiply.right_type;
InstructionBuilder {
TwoOperandLayout {
operation,
a_field,
b_field,

View File

@ -1,6 +1,6 @@
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operand, Operation, TypeCode};
use super::{Instruction, Operand, Operation, TwoOperandLayout, TypeCode};
pub struct Negate {
pub destination: u16,
@ -29,7 +29,7 @@ impl From<Negate> for Instruction {
let (b_field, b_is_constant) = negate.argument.as_index_and_constant_flag();
let b_type = negate.argument_type;
InstructionBuilder {
TwoOperandLayout {
operation,
a_field,
b_field,

View File

@ -2,7 +2,7 @@ use std::fmt::Display;
use crate::{Instruction, Operand, Operation};
use super::InstructionBuilder;
use super::TwoOperandLayout;
pub struct Not {
pub destination: u16,
@ -27,7 +27,7 @@ impl From<Not> for Instruction {
let a_field = not.destination;
let (b_field, b_is_constant) = not.argument.as_index_and_constant_flag();
InstructionBuilder {
TwoOperandLayout {
operation,
a_field,
b_field,

View File

@ -2,7 +2,7 @@ use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::{InstructionBuilder, TypeCode};
use super::{TwoOperandLayout, TypeCode};
pub struct Return {
pub should_return_value: bool,
@ -31,7 +31,7 @@ impl From<Return> for Instruction {
let b_type = r#return.return_type;
let c_field = r#return.return_register;
InstructionBuilder {
TwoOperandLayout {
operation,
b_field,
b_type,

View File

@ -2,7 +2,7 @@ use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
use super::TwoOperandLayout;
pub struct SetLocal {
pub register_index: u16,
@ -27,7 +27,7 @@ impl From<SetLocal> for Instruction {
let b_field = set_local.register_index;
let c_field = set_local.local_index;
InstructionBuilder {
TwoOperandLayout {
operation,
b_field,
c_field,

View File

@ -1,6 +1,6 @@
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operand, Operation, TypeCode};
use super::{Instruction, Operand, Operation, TwoOperandLayout, TypeCode};
pub struct Subtract {
pub destination: u16,
@ -36,7 +36,7 @@ impl From<Subtract> for Instruction {
let b_type = subtract.left_type;
let c_type = subtract.right_type;
InstructionBuilder {
TwoOperandLayout {
operation,
a_field,
b_field,

View File

@ -2,7 +2,7 @@ use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
use super::TwoOperandLayout;
pub struct Test {
pub operand_register: u16,
@ -26,7 +26,7 @@ impl From<Test> for Instruction {
let b_field = test.operand_register;
let c_field = test.test_value as u16;
InstructionBuilder {
TwoOperandLayout {
operation: Operation::TEST,
b_field,
c_field,

View File

@ -2,7 +2,7 @@ use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operand, Operation};
use super::InstructionBuilder;
use super::TwoOperandLayout;
pub struct TestSet {
pub destination: u16,
@ -31,7 +31,7 @@ impl From<TestSet> for Instruction {
let (b_field, b_is_constant) = test_set.argument.as_index_and_constant_flag();
let c_field = test_set.test_value as u16;
InstructionBuilder {
TwoOperandLayout {
operation,
a_field,
b_field,

View File

@ -9,7 +9,7 @@ use tracing::trace;
use crate::{
Instruction, Operation, Type, Value,
instruction::{InstructionBuilder, Jump, TypeCode},
instruction::{Jump, TwoOperandLayout, TypeCode},
};
use super::{Pointer, Register, thread::ThreadData};
@ -43,14 +43,15 @@ impl ActionSequence {
if !is_positive {
let action = Action {
logic: loop_optimized,
optimal_logic: None,
data: ActionData {
name: "LOOP_OPTIMIZED",
instruction: InstructionBuilder::from(instruction),
instruction: TwoOperandLayout::from(instruction),
integer_pointers: [ptr::null_mut(); 3],
boolean_register_pointers: [ptr::null_mut(); 2],
integer_register_pointers: [ptr::null_mut(); 2],
runs: 0,
condensed_actions: loop_actions.take().unwrap(),
loop_actions: loop_actions.take().unwrap(),
},
};
@ -82,32 +83,34 @@ impl ActionSequence {
#[derive(Clone)]
pub struct Action {
pub logic: ActionLogic,
pub optimal_logic: Option<fn(&mut ActionData, &mut usize)>,
pub data: ActionData,
}
impl From<&Instruction> for Action {
fn from(instruction: &Instruction) -> Self {
let builder = InstructionBuilder::from(instruction);
let builder = TwoOperandLayout::from(instruction);
let operation = builder.operation;
let logic = match operation {
Operation::MOVE => r#move,
Operation::CLOSE => close,
Operation::LOAD_INLINE => load_inline,
Operation::LOAD_CONSTANT => load_constant,
Operation::LOAD_LIST => load_list,
Operation::LOAD_FUNCTION => load_function,
Operation::LOAD_SELF => load_self,
Operation::GET_LOCAL => get_local,
Operation::SET_LOCAL => set_local,
Operation::ADD => add,
Operation::LESS => less,
Operation::JUMP => jump,
Operation::RETURN => r#return,
unknown => unknown.panic_from_unknown_code(),
};
let (logic, optimal_logic): (ActionLogic, Option<fn(&mut ActionData, &mut usize)>) =
match operation {
Operation::MOVE => (r#move, Some(move_optimal)),
Operation::LOAD_INLINE => (load_inline, None),
Operation::LOAD_CONSTANT => (load_constant, None),
Operation::LOAD_LIST => (load_list, None),
Operation::LOAD_FUNCTION => (load_function, None),
Operation::LOAD_SELF => (load_self, None),
Operation::GET_LOCAL => (get_local, None),
Operation::SET_LOCAL => (set_local, None),
Operation::ADD => (add, Some(add_optimal)),
Operation::LESS => (less, Some(less_optimal)),
Operation::JUMP => (jump, Some(jump_optimal)),
Operation::RETURN => (r#return, None),
unknown => unknown.panic_from_unknown_code(),
};
Action {
logic,
optimal_logic,
data: ActionData {
name: operation.name(),
instruction: builder,
@ -115,7 +118,7 @@ impl From<&Instruction> for Action {
boolean_register_pointers: [ptr::null_mut(); 2],
integer_register_pointers: [ptr::null_mut(); 2],
runs: 0,
condensed_actions: Vec::new(),
loop_actions: Vec::new(),
},
}
}
@ -136,13 +139,13 @@ impl Debug for Action {
#[derive(Debug, Clone)]
pub struct ActionData {
pub name: &'static str,
pub instruction: InstructionBuilder,
pub instruction: TwoOperandLayout,
pub boolean_register_pointers: [*mut Register<bool>; 2],
pub integer_register_pointers: [*mut Register<i64>; 2],
pub integer_pointers: [*mut i64; 3],
pub runs: usize,
pub condensed_actions: Vec<Action>,
pub loop_actions: Vec<Action>,
}
pub type ActionLogic = fn(&mut ThreadData, &mut ActionData);
@ -151,25 +154,25 @@ fn loop_optimized(thread_data: &mut ThreadData, action_data: &mut ActionData) {
let mut local_ip = 0;
loop {
if local_ip >= action_data.condensed_actions.len() {
if local_ip >= action_data.loop_actions.len() {
break;
}
let action = &mut action_data.condensed_actions[local_ip];
let action = &mut action_data.loop_actions[local_ip];
local_ip += 1;
if action.data.runs == 0 {
trace!("Condensed action initial: {}", action.data.name);
trace!("Action: {} Optimizing", action.data.name);
(action.logic)(thread_data, &mut action.data);
continue;
}
trace!("Condensed action optimized: {}", action.data.name);
trace!("Action: {} Optimized", action.data.name);
match action.data.name {
"LESS" => unsafe {
"LESS_INT" => unsafe {
asm!(
"cmp {0}, {1}",
"jns 2f",
@ -205,12 +208,94 @@ fn loop_optimized(thread_data: &mut ThreadData, action_data: &mut ActionData) {
} else {
local_ip -= (offset + 1) as usize;
}
// unsafe {
// asm!(
// "cmp {0}, 0",
// "je 2f",
// "add {1}, {2}",
// "jmp 3f",
// "2:",
// "sub {1}, {3}",
// "3:",
// in(reg) is_positive as i64,
// inout(reg) local_ip,
// in(reg) offset as i64,
// in(reg) (offset + 1) as i64,
// )
// }
}
_ => todo!(),
};
}
}
const OPTIMAL_LOGIC: [fn(&mut ActionData, &mut usize); 3] =
[less_optimal, add_optimal, move_optimal];
fn less_optimal(action_data: &mut ActionData, local_ip: &mut usize) {
unsafe {
asm!(
"cmp {0}, {1}",
"jns 2f",
"add {2}, 1",
"2:",
in(reg) *action_data.integer_pointers[0],
in(reg) *action_data.integer_pointers[1],
inout(reg) *local_ip,
)
}
}
fn add_optimal(action_data: &mut ActionData, _: &mut usize) {
unsafe {
asm!(
"add {0}, {1}",
inout(reg) *action_data.integer_pointers[1] => *action_data.integer_pointers[0],
in(reg) *action_data.integer_pointers[2],
)
}
}
fn move_optimal(action_data: &mut ActionData, _: &mut usize) {
unsafe {
asm!(
"mov {0}, {1}",
out(reg) action_data.integer_register_pointers[0],
in(reg) action_data.integer_register_pointers[1],
)
}
}
fn jump_optimal(action_data: &mut ActionData, local_ip: &mut usize) {
let Jump {
offset,
is_positive,
} = Jump::from(action_data.instruction.build());
if is_positive {
*local_ip += offset as usize;
} else {
*local_ip -= (offset + 1) as usize;
}
// unsafe {
// asm!(
// "cmp {0}, 0",
// "je 2f",
// "add {1}, {2}",
// "jmp 3f",
// "2:",
// "sub {1}, {3}",
// "3:",
// in(reg) is_positive as i64,
// inout(reg) *local_ip,
// in(reg) offset as i64,
// in(reg) (offset + 1) as i64,
// )
// }
}
fn r#move(thread_data: &mut ThreadData, action_data: &mut ActionData) {
let ActionData { instruction, .. } = action_data;
let current_frame = thread_data.current_frame_mut();
@ -272,7 +357,7 @@ fn load_inline(thread_data: &mut ThreadData, action_data: &mut ActionData) {
*old_register = new_register;
if instruction.c_field != 0 {
current_frame.instruction_pointer += 1;
current_frame.ip += 1;
}
}
@ -299,7 +384,7 @@ fn load_constant(thread_data: &mut ThreadData, action_data: &mut ActionData) {
};
if instruction.c_field != 0 {
current_frame.instruction_pointer += 1;
current_frame.ip += 1;
}
}
@ -471,7 +556,7 @@ fn less(thread_data: &mut ThreadData, action_data: &mut ActionData) {
if *runs > 0 {
unsafe {
if *pointers[0] < *pointers[1] {
current_frame.instruction_pointer += 1;
current_frame.ip += 1;
}
}
} else {
@ -538,7 +623,7 @@ fn less(thread_data: &mut ThreadData, action_data: &mut ActionData) {
};
if is_less {
current_frame.instruction_pointer += 1;
current_frame.ip += 1;
}
pointers[0] = left_pointer;
@ -554,9 +639,9 @@ fn jump(thread_data: &mut ThreadData, action_data: &mut ActionData) {
let is_positive = instruction.c_field != 0;
if is_positive {
current_frame.instruction_pointer += offset;
current_frame.ip += offset;
} else {
current_frame.instruction_pointer -= offset + 1;
current_frame.ip -= offset + 1;
}
action_data.runs += 1;

View File

@ -12,7 +12,7 @@ pub struct CallFrame {
pub prototype: Arc<Chunk>,
pub registers: RegisterTable,
pub return_register: u16,
pub instruction_pointer: usize,
pub ip: usize,
}
impl CallFrame {
@ -20,7 +20,7 @@ impl CallFrame {
Self {
prototype,
return_register,
instruction_pointer: 0,
ip: 0,
registers: RegisterTable::new(),
}
}
@ -35,7 +35,7 @@ impl Display for CallFrame {
.name
.as_ref()
.unwrap_or(&DustString::from("anonymous")),
self.instruction_pointer,
self.ip,
)
}
}

View File

@ -4,7 +4,7 @@ use std::slice::SliceIndex;
use smallvec::{SmallVec, smallvec};
use tracing::trace;
use crate::DustString;
use crate::{AbstractList, DustString};
use super::Pointer;
@ -33,25 +33,38 @@ impl<T: Clone> Register<T> {
}
}
const BOOLEAN_REGISTER_COUNT: usize = 64;
const BYTE_REGISTER_COUNT: usize = 64;
const CHARACTER_REGISTER_COUNT: usize = 64;
const FLOAT_REGISTER_COUNT: usize = 64;
const INTEGER_REGISTER_COUNT: usize = 64;
const STRING_REGISTER_COUNT: usize = 64;
const LIST_REGISTER_COUNT: usize = 16;
const POINTER_REGISTER_COUNT: usize = 256;
#[derive(Debug)]
pub struct RegisterTable {
booleans: SmallVec<[Register<bool>; 64]>,
bytes: SmallVec<[Register<u8>; 64]>,
characters: SmallVec<[Register<char>; 64]>,
floats: SmallVec<[Register<f64>; 64]>,
integers: SmallVec<[Register<i64>; 64]>,
strings: SmallVec<[Register<DustString>; 64]>,
booleans: SmallVec<[Register<bool>; BOOLEAN_REGISTER_COUNT]>,
bytes: SmallVec<[Register<u8>; BYTE_REGISTER_COUNT]>,
characters: SmallVec<[Register<char>; CHARACTER_REGISTER_COUNT]>,
floats: SmallVec<[Register<f64>; FLOAT_REGISTER_COUNT]>,
integers: SmallVec<[Register<i64>; INTEGER_REGISTER_COUNT]>,
strings: SmallVec<[Register<DustString>; STRING_REGISTER_COUNT]>,
lists: SmallVec<[Register<AbstractList>; LIST_REGISTER_COUNT]>,
pointers: SmallVec<[Register<Pointer>; POINTER_REGISTER_COUNT]>,
}
impl RegisterTable {
pub fn new() -> Self {
Self {
booleans: smallvec![Register::Empty; 64],
bytes: smallvec![Register::Empty; 64],
characters: smallvec![Register::Empty; 64],
floats: smallvec![Register::Empty; 64],
integers: smallvec![Register::Empty; 64],
strings: smallvec![Register::Empty; 64],
booleans: smallvec![Register::Empty; BOOLEAN_REGISTER_COUNT],
bytes: smallvec![Register::Empty; BYTE_REGISTER_COUNT],
characters: smallvec![Register::Empty; CHARACTER_REGISTER_COUNT],
floats: smallvec![Register::Empty; FLOAT_REGISTER_COUNT],
integers: smallvec![Register::Empty; INTEGER_REGISTER_COUNT],
strings: smallvec![Register::Empty; STRING_REGISTER_COUNT],
lists: smallvec![Register::Empty; LIST_REGISTER_COUNT],
pointers: smallvec![Register::Empty; POINTER_REGISTER_COUNT],
}
}
@ -75,6 +88,16 @@ impl RegisterTable {
}
}
pub fn set_boolean(&mut self, index: u16, value: bool) {
trace!("Set R_BOOL_{index} to value {value}");
let index = index as usize;
self.booleans[index] = Register::Value(value);
Self::handle_growth(&mut self.booleans);
}
pub fn get_byte(&self, index: u16) -> &Register<u8> {
let index = index as usize;
@ -95,6 +118,16 @@ impl RegisterTable {
}
}
pub fn set_byte(&mut self, index: u16, value: u8) {
trace!("Set R_BYTE_{index} to value {value}");
let index = index as usize;
self.bytes[index] = Register::Value(value);
Self::handle_growth(&mut self.bytes);
}
pub fn get_character(&self, index: u16) -> &Register<char> {
let index = index as usize;
@ -115,6 +148,16 @@ impl RegisterTable {
}
}
pub fn set_character(&mut self, index: u16, value: char) {
trace!("Set R_CHAR_{index} to value {value}");
let index = index as usize;
self.characters[index] = Register::Value(value);
Self::handle_growth(&mut self.characters);
}
pub fn get_float(&self, index: u16) -> &Register<f64> {
let index = index as usize;
@ -135,6 +178,16 @@ impl RegisterTable {
}
}
pub fn set_float(&mut self, index: u16, value: f64) {
trace!("Set R_FLOAT_{index} to value {value}");
let index = index as usize;
self.floats[index] = Register::Value(value);
Self::handle_growth(&mut self.floats);
}
pub fn get_integer(&self, index: u16) -> &Register<i64> {
let index = index as usize;
@ -164,14 +217,6 @@ impl RegisterTable {
}
}
pub fn set_integer(&mut self, index: u16, value: i64) {
trace!("Set R_INT_{index} to value {value}");
let index = index as usize;
self.integers[index] = Register::Value(value);
}
pub fn get_many_integer_mut<I, const N: usize>(
&mut self,
indices: [I; N],
@ -186,6 +231,16 @@ impl RegisterTable {
}
}
pub fn set_integer(&mut self, index: u16, value: i64) {
trace!("Set R_INT_{index} to value {value}");
let index = index as usize;
self.integers[index] = Register::Value(value);
Self::handle_growth(&mut self.integers);
}
pub fn get_string(&self, index: u16) -> &Register<DustString> {
let index = index as usize;
@ -205,6 +260,26 @@ impl RegisterTable {
unsafe { self.strings.get_mut(index).unwrap_unchecked() }
}
}
pub fn set_string(&mut self, index: u16, value: DustString) {
trace!("Set R_STR_{index} to value {value}");
let index = index as usize;
self.strings[index] = Register::Value(value);
Self::handle_growth(&mut self.strings);
}
fn handle_growth<T: Clone, const REGISTER_COUNT: usize>(
registers: &mut SmallVec<[Register<T>; REGISTER_COUNT]>,
) {
if REGISTER_COUNT >= registers.len() {
let new_length = registers.len() + REGISTER_COUNT;
registers.resize(new_length, Register::Empty);
}
}
}
impl Default for RegisterTable {

View File

@ -3,7 +3,13 @@ use std::{sync::Arc, thread::JoinHandle};
use smallvec::SmallVec;
use tracing::{info, span, trace};
use crate::{Chunk, DustString, Value, vm::action::ActionSequence};
use crate::{
Chunk, DustString, TypeCode, Value,
vm::{
Register,
action::{ActionData, ActionSequence},
},
};
use super::{Action, CallFrame};
@ -36,7 +42,7 @@ impl Thread {
let mut thread_data = ThreadData {
stack: call_stack,
return_value: None,
spawned_threads: Vec::with_capacity(0),
spawned_threads: Vec::new(),
};
let mut action_sequence = ActionSequence::new(&self.chunk.instructions);
@ -44,12 +50,154 @@ impl Thread {
loop {
let current_frame = thread_data.current_frame_mut();
let current_action = action_sequence.get_mut(current_frame.instruction_pointer);
current_frame.instruction_pointer += 1;
let current_action = action_sequence.get_mut(current_frame.ip);
current_frame.ip += 1;
trace!("Action: {}", current_action);
(current_action.logic)(&mut thread_data, &mut current_action.data);
// (current_action.logic)(&mut thread_data, &mut current_action.data);
match current_action.data.name {
"LOAD_CONSTANT" => {
let ActionData {
instruction,
integer_pointers,
integer_register_pointers,
runs,
..
} = &mut current_action.data;
let r#type = instruction.b_type;
if *runs > 0 {
match r#type {
TypeCode::INTEGER => unsafe {
*integer_register_pointers[0] =
Register::Value(*integer_pointers[0]);
},
unknown => unknown.panic_from_unknown_code(),
}
continue;
}
let current_frame = thread_data.current_frame_mut();
let destination = instruction.a_field;
let constant_index = instruction.b_field;
match r#type {
TypeCode::INTEGER => {
let mut value = *current_frame
.prototype
.constants
.get_integer(constant_index)
.unwrap();
let new_register = Register::Value(value);
let old_register = current_frame.registers.get_integer_mut(destination);
*old_register = new_register;
integer_pointers[0] = &mut value;
integer_register_pointers[0] = old_register;
}
unknown => unknown.panic_from_unknown_code(),
};
if instruction.c_field != 0 {
current_frame.ip += 1;
}
*runs += 1;
}
"LESS_INT" => {
let ActionData {
instruction,
integer_pointers,
integer_register_pointers,
runs,
..
} = &mut current_action.data;
let r#type = instruction.b_type;
if *runs > 0 {
unsafe {
// if *pointers[0] < *pointers[1] {
// current_frame.ip += 1;
// }
}
} else {
let (is_less, left_pointer, right_pointer) =
match (instruction.b_is_constant, instruction.c_is_constant) {
(true, true) => {
let left = current_frame
.prototype
.constants
.get_integer(instruction.b_field)
.unwrap();
let right = current_frame
.prototype
.constants
.get_integer(instruction.c_field)
.unwrap();
let is_less = left < right;
(
is_less,
Box::into_raw(Box::new(*left)),
Box::into_raw(Box::new(*right)),
)
}
(true, false) => {
let left = *current_frame
.prototype
.constants
.get_integer(instruction.b_field)
.unwrap();
let right = current_frame
.registers
.get_integer_mut(instruction.c_field)
.expect_value_mut();
let is_less = left < *right;
(is_less, Box::into_raw(Box::new(left)), right as *mut i64)
}
(false, true) => {
let right = *current_frame
.prototype
.constants
.get_integer(instruction.c_field)
.unwrap();
let left = current_frame
.registers
.get_integer_mut(instruction.b_field)
.expect_value_mut();
let is_less = *left < right;
(is_less, left as *mut i64, Box::into_raw(Box::new(right)))
}
(false, false) => {
let [left, right] =
current_frame.registers.get_many_integer_mut([
instruction.b_field as usize,
instruction.c_field as usize,
]);
let left = left.expect_value_mut();
let right = right.expect_value_mut();
let is_less = *left < *right;
(is_less, left as *mut i64, right as *mut i64)
}
};
if is_less {
current_frame.ip += 1;
}
integer_pointers[0] = left_pointer;
integer_pointers[1] = right_pointer;
*runs += 1;
}
}
_ => todo!(),
}
if let Some(value_option) = thread_data.return_value {
return value_option;