1
0

Experiment with more VM optimizations

This commit is contained in:
Jeff 2025-02-11 16:55:54 -05:00
parent 6a61947476
commit 69ef1b3b06
3 changed files with 238 additions and 315 deletions

View File

@ -1,13 +1,14 @@
use std::fmt::{self, Display, Formatter}; use std::{
arch::asm,
use tracing::{Level, span, trace}; fmt::{self, Display, Formatter},
};
use crate::{ use crate::{
AbstractList, ConcreteValue, DustString, Instruction, Operation, Value, AbstractList, ConcreteValue, DustString, Instruction, Operation, Value,
instruction::{InstructionFields, Jump, TypeCode}, instruction::{InstructionFields, Jump, TypeCode},
}; };
use super::{Pointer, Register, thread::Thread}; use super::{Pointer, Register, call_frame::PointerCache, thread::Thread};
#[derive(Debug)] #[derive(Debug)]
pub struct ActionSequence { pub struct ActionSequence {
@ -28,15 +29,12 @@ impl ActionSequence {
} = Jump::from(instruction); } = Jump::from(instruction);
if !is_positive { if !is_positive {
let mut loop_instructions = Vec::new();
let mut previous = instruction; let mut previous = instruction;
loop_instructions actions.push(Action::optimized(instruction));
.push((InstructionFields::from(instruction), PointerCache::new()));
while let Some(instruction) = instructions.next() { while let Some(instruction) = instructions.next() {
loop_instructions actions.push(Action::optimized(instruction));
.push((InstructionFields::from(instruction), PointerCache::new()));
if matches!( if matches!(
instruction.operation(), instruction.operation(),
@ -56,22 +54,11 @@ impl ActionSequence {
previous = instruction; previous = instruction;
} }
loop_instructions.reverse();
let loop_action = Action {
logic: ACTION_LOGIC_TABLE[0],
instruction: InstructionFields::default(),
optimized_logic: Some(optimized_loop),
loop_instructions: Some(loop_instructions),
};
actions.push(loop_action);
continue; continue;
} }
} }
let action = Action::from(instruction); let action = Action::unoptimized(instruction);
actions.push(action); actions.push(action);
} }
@ -91,7 +78,7 @@ impl Display for ActionSequence {
write!(f, "[")?; write!(f, "[")?;
for (index, action) in self.actions.iter().enumerate() { for (index, action) in self.actions.iter().enumerate() {
write!(f, "{}", action)?; write!(f, "{}", action.name)?;
if index < self.actions.len() - 1 { if index < self.actions.len() - 1 {
write!(f, ", ")?; write!(f, ", ")?;
@ -102,77 +89,71 @@ impl Display for ActionSequence {
} }
} }
#[derive(Clone, Debug)] #[derive(Clone, Copy, Debug)]
pub struct Action { pub struct Action {
pub name: &'static str,
pub logic: ActionLogic, pub logic: ActionLogic,
pub instruction: InstructionFields, pub instruction: InstructionFields,
pub optimized_logic: Option<OptimizedActionLogic>,
pub loop_instructions: Option<Vec<(InstructionFields, PointerCache)>>,
} }
impl From<&Instruction> for Action { impl Action {
fn from(instruction: &Instruction) -> Self { fn optimized(instruction: &Instruction) -> Self {
let operation = instruction.operation();
let logic = ACTION_LOGIC_TABLE[operation.0 as usize];
let instruction = InstructionFields::from(instruction); let instruction = InstructionFields::from(instruction);
let (name, logic): (&'static str, ActionLogic) = match (
instruction.operation,
instruction.b_type,
instruction.c_type,
) {
(Operation::ADD, TypeCode::INTEGER, TypeCode::INTEGER) => {
("ADD_INTEGERS", add_integers_optimized)
}
(Operation::LESS, TypeCode::INTEGER, TypeCode::INTEGER) => {
("LESS_INTEGERS", less_integers_optimized)
}
(Operation::JUMP, _, _) => ("JUMP", jump),
_ => todo!(),
};
Action { Action {
name,
logic, logic,
instruction, instruction,
optimized_logic: None,
loop_instructions: None,
} }
} }
}
impl Display for Action { fn unoptimized(instruction: &Instruction) -> Self {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { let instruction = InstructionFields::from(instruction);
if let Some(loop_instructions) = &self.loop_instructions { let logic = match instruction.operation {
write!(f, "LOOP(")?; Operation::POINT => point,
Operation::CLOSE => close,
Operation::LOAD_ENCODED => load_encoded,
Operation::LOAD_CONSTANT => load_constant,
Operation::LOAD_LIST => load_list,
Operation::LOAD_FUNCTION => load_function,
Operation::LOAD_SELF => load_self,
Operation::ADD => add,
Operation::SUBTRACT => subtract,
Operation::MULTIPLY => multiply,
Operation::DIVIDE => divide,
Operation::MODULO => modulo,
Operation::EQUAL => equal,
Operation::LESS => less,
Operation::LESS_EQUAL => less_equal,
Operation::TEST => test,
Operation::TEST_SET => test_set,
Operation::RETURN => r#return,
_ => todo!(),
};
for (index, (instruction, _)) in loop_instructions.iter().enumerate() { Action {
write!(f, "{}", instruction.operation)?; name: instruction.operation.name(),
logic,
if index < loop_instructions.len() - 1 { instruction,
write!(f, ", ")?;
}
}
write!(f, ")")
} else {
write!(f, "{}", self.instruction.operation)
} }
} }
} }
pub type ActionLogic = fn(InstructionFields, &mut Thread); pub type ActionLogic = fn(InstructionFields, &mut Thread);
pub type OptimizedActionLogic = fn(Vec<(InstructionFields, PointerCache)>, &mut Thread);
pub const ACTION_LOGIC_TABLE: [ActionLogic; 23] = [
point,
close,
load_encoded,
load_constant,
load_function,
load_list,
load_self,
add,
subtract,
multiply,
divide,
modulo,
equal,
less,
less_equal,
negate,
not,
test,
test_set,
call,
call_native,
jump,
r#return,
];
pub fn point(instruction: InstructionFields, thread: &mut Thread) { pub fn point(instruction: InstructionFields, thread: &mut Thread) {
let destination = instruction.a_field as usize; let destination = instruction.a_field as usize;
@ -703,6 +684,60 @@ pub fn add(instruction: InstructionFields, thread: &mut Thread) {
} }
} }
fn add_integers_optimized(instruction: InstructionFields, thread: &mut Thread) {
let current_frame = thread.current_frame_mut();
let pointer_cache = current_frame.pointer_caches[current_frame.ip];
if let PointerCache::Integers { left, right } = pointer_cache {
let destination = instruction.a_field as usize;
let destination_pointer = thread.get_integer_register_mut(destination) as *mut i64;
unsafe {
asm!(
"add {}, {}",
inout(reg) *left => *destination_pointer,
in(reg) *right,
)
}
} else if let PointerCache::Empty = pointer_cache {
let left = instruction.b_field as usize;
let right = instruction.c_field as usize;
let left_is_constant = instruction.b_is_constant;
let right_is_constant = instruction.c_is_constant;
let left_value = if left_is_constant {
if cfg!(debug_assertions) {
thread.get_constant(left).as_integer().unwrap()
} else {
unsafe { thread.get_constant(left).as_integer().unwrap_unchecked() }
}
} else {
thread.get_integer_register(left)
};
let right_value = if right_is_constant {
if cfg!(debug_assertions) {
thread.get_constant(right).as_integer().unwrap()
} else {
unsafe { thread.get_constant(right).as_integer().unwrap_unchecked() }
}
} else {
thread.get_integer_register(right)
};
let cache = PointerCache::Integers {
left: left_value,
right: right_value,
};
let destination = instruction.a_field as usize;
let sum = left_value.saturating_add(*right_value);
let register = Register::Value(sum);
thread.set_integer_register(destination, register);
thread.set_current_pointer_cache(cache);
} else {
unreachable!()
}
}
pub fn subtract(instruction: InstructionFields, thread: &mut Thread) { pub fn subtract(instruction: InstructionFields, thread: &mut Thread) {
let destination = instruction.a_field as usize; let destination = instruction.a_field as usize;
let left = instruction.b_field as usize; let left = instruction.b_field as usize;
@ -1413,6 +1448,57 @@ pub fn less(instruction: InstructionFields, thread: &mut Thread) {
} }
} }
fn less_integers_optimized(instruction: InstructionFields, thread: &mut Thread) {
let current_frame = thread.current_frame_mut();
let pointer_cache = current_frame.pointer_caches[current_frame.ip];
let is_less = if let PointerCache::Integers { left, right } = pointer_cache {
let left_value = unsafe { *left };
let right_value = unsafe { *right };
left_value < right_value
} else if let PointerCache::Empty = pointer_cache {
let left = instruction.b_field as usize;
let right = instruction.c_field as usize;
let left_is_constant = instruction.b_is_constant;
let right_is_constant = instruction.c_is_constant;
let left_value = if left_is_constant {
if cfg!(debug_assertions) {
thread.get_constant(left).as_integer().unwrap()
} else {
unsafe { thread.get_constant(left).as_integer().unwrap_unchecked() }
}
} else {
thread.get_integer_register(left)
};
let right_value = if right_is_constant {
if cfg!(debug_assertions) {
thread.get_constant(right).as_integer().unwrap()
} else {
unsafe { thread.get_constant(right).as_integer().unwrap_unchecked() }
}
} else {
thread.get_integer_register(right)
};
let cache = PointerCache::Integers {
left: left_value,
right: right_value,
};
let is_less = left_value < right_value;
thread.set_current_pointer_cache(cache);
is_less
} else {
unreachable!()
};
if is_less {
thread.current_frame_mut().ip += 1;
}
}
pub fn less_equal(instruction: InstructionFields, thread: &mut Thread) { pub fn less_equal(instruction: InstructionFields, thread: &mut Thread) {
let comparator = instruction.d_field; let comparator = instruction.d_field;
let left = instruction.b_field as usize; let left = instruction.b_field as usize;
@ -1668,224 +1754,3 @@ pub fn r#return(instruction: InstructionFields, thread: &mut Thread) {
thread.return_value = Some(None); thread.return_value = Some(None);
} }
} }
fn optimized_loop(instructions: Vec<(InstructionFields, PointerCache)>, thread: &mut Thread) {
let span = span!(Level::TRACE, "Optimized Loop");
let _ = span.enter();
let mut loop_ip = 0;
while loop_ip < instructions.len() {
let (instruction, mut pointer_cache) = instructions[loop_ip];
loop_ip += 1;
match instruction.operation {
Operation::ADD => {
trace!("Running loop-optimized ADD instruction");
let destination = instruction.a_field as usize;
let sum = if pointer_cache.integers[0].is_null() {
let left = instruction.b_field as usize;
let left_is_constant = instruction.b_is_constant;
let right = instruction.c_field as usize;
let right_is_constant = instruction.c_is_constant;
let left_value = if left_is_constant {
if cfg!(debug_assertions) {
thread.get_constant(left).as_integer().unwrap()
} else {
unsafe { thread.get_constant(left).as_integer().unwrap_unchecked() }
}
} else {
thread.get_integer_register(left)
};
let right_value = if right_is_constant {
if cfg!(debug_assertions) {
thread.get_constant(right).as_integer().unwrap()
} else {
unsafe { thread.get_constant(right).as_integer().unwrap_unchecked() }
}
} else {
thread.get_integer_register(right)
};
pointer_cache.integers[0] = left_value;
pointer_cache.integers[1] = right_value;
left_value.saturating_add(*right_value)
} else {
let left_value = unsafe { *pointer_cache.integers[0] };
let right_value = unsafe { *pointer_cache.integers[1] };
left_value.saturating_add(right_value)
};
let register = Register::Value(sum);
thread.set_integer_register(destination, register);
}
Operation::LESS => {
trace!("Running loop-optimized LESS instruction");
let comparator = instruction.d_field;
let result = if pointer_cache.integers[0].is_null() {
let left = instruction.b_field as usize;
let left_is_constant = instruction.b_is_constant;
let right = instruction.c_field as usize;
let right_is_constant = instruction.c_is_constant;
let left_value = if left_is_constant {
if cfg!(debug_assertions) {
thread.get_constant(left).as_integer().unwrap()
} else {
unsafe { thread.get_constant(left).as_integer().unwrap_unchecked() }
}
} else {
thread.get_integer_register(left)
};
let right_value = if right_is_constant {
if cfg!(debug_assertions) {
thread.get_constant(right).as_integer().unwrap()
} else {
unsafe { thread.get_constant(right).as_integer().unwrap_unchecked() }
}
} else {
thread.get_integer_register(right)
};
pointer_cache.integers[0] = left_value;
pointer_cache.integers[1] = right_value;
left_value < right_value
} else {
let left_value = unsafe { *pointer_cache.integers[0] };
let right_value = unsafe { *pointer_cache.integers[1] };
left_value < right_value
};
if result == comparator {
loop_ip += 1;
}
}
Operation::LESS_EQUAL => {
trace!("Running loop-optimized LESS_EQUAL instruction");
let comparator = instruction.d_field;
let result = if pointer_cache.integers[0].is_null() {
let left = instruction.b_field as usize;
let left_is_constant = instruction.b_is_constant;
let right = instruction.c_field as usize;
let right_is_constant = instruction.c_is_constant;
let left_value = if left_is_constant {
if cfg!(debug_assertions) {
thread.get_constant(left).as_integer().unwrap()
} else {
unsafe { thread.get_constant(left).as_integer().unwrap_unchecked() }
}
} else {
thread.get_integer_register(left)
};
let right_value = if right_is_constant {
if cfg!(debug_assertions) {
thread.get_constant(right).as_integer().unwrap()
} else {
unsafe { thread.get_constant(right).as_integer().unwrap_unchecked() }
}
} else {
thread.get_integer_register(right)
};
pointer_cache.integers[0] = left_value;
pointer_cache.integers[1] = right_value;
left_value <= right_value
} else {
let left_value = unsafe { *pointer_cache.integers[0] };
let right_value = unsafe { *pointer_cache.integers[1] };
left_value <= right_value
};
if result == comparator {
loop_ip += 1;
}
}
Operation::JUMP => {
trace!("Running loop-optimized JUMP instruction");
let offset = instruction.b_field as usize;
let is_positive = instruction.c_field != 0;
if is_positive {
loop_ip += offset;
} else {
loop_ip -= offset + 1;
}
}
_ => {
let runner = ACTION_LOGIC_TABLE[instruction.operation.0 as usize];
runner(instruction, thread);
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct PointerCache {
integers: [*const i64; 2],
}
impl PointerCache {
fn new() -> Self {
Self {
integers: [std::ptr::null(); 2],
}
}
}
#[cfg(test)]
mod tests {
use crate::Operation;
use super::*;
const ALL_OPERATIONS: [(Operation, ActionLogic); 23] = [
(Operation::POINT, point),
(Operation::CLOSE, close),
(Operation::LOAD_ENCODED, load_encoded),
(Operation::LOAD_CONSTANT, load_constant),
(Operation::LOAD_FUNCTION, load_function),
(Operation::LOAD_LIST, load_list),
(Operation::LOAD_SELF, load_self),
(Operation::ADD, add),
(Operation::SUBTRACT, subtract),
(Operation::MULTIPLY, multiply),
(Operation::DIVIDE, divide),
(Operation::MODULO, modulo),
(Operation::TEST, test),
(Operation::TEST_SET, test_set),
(Operation::EQUAL, equal),
(Operation::LESS, less),
(Operation::LESS_EQUAL, less_equal),
(Operation::NEGATE, negate),
(Operation::NOT, not),
(Operation::CALL, call),
(Operation::CALL_NATIVE, call_native),
(Operation::JUMP, jump),
(Operation::RETURN, r#return),
];
#[test]
fn operations_map_to_the_correct_runner() {
for (operation, expected_runner) in ALL_OPERATIONS {
let actual_runner = ACTION_LOGIC_TABLE[operation.0 as usize];
assert_eq!(
expected_runner, actual_runner,
"{operation} runner is incorrect"
);
}
}
}

View File

@ -16,12 +16,14 @@ pub struct CallFrame {
pub return_register: u16, pub return_register: u16,
pub registers: RegisterTable, pub registers: RegisterTable,
pub action_sequence: ActionSequence, pub action_sequence: ActionSequence,
pub pointer_caches: Vec<PointerCache>,
} }
impl CallFrame { impl CallFrame {
pub fn new(chunk: Arc<Chunk>, return_register: u16) -> Self { pub fn new(chunk: Arc<Chunk>, return_register: u16) -> Self {
let registers = RegisterTable::new(); let registers = RegisterTable::new();
let action_sequence = ActionSequence::new(&chunk.instructions); let action_sequence = ActionSequence::new(&chunk.instructions);
let optimization_data = vec![PointerCache::default(); chunk.instructions.len()];
Self { Self {
chunk, chunk,
@ -29,6 +31,7 @@ impl CallFrame {
return_register, return_register,
registers, registers,
action_sequence, action_sequence,
pointer_caches: optimization_data,
} }
} }
} }
@ -88,6 +91,16 @@ pub enum Register<T> {
Pointer(Pointer), Pointer(Pointer),
} }
impl<T> Register<T> {
pub fn contained_value_mut(&mut self) -> Option<&mut T> {
match self {
Self::Value(value) => Some(value),
Self::Closed(value) => Some(value),
_ => None,
}
}
}
impl<T: Display> Display for Register<T> { impl<T: Display> Display for Register<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self { match self {
@ -133,3 +146,13 @@ impl Display for Pointer {
} }
} }
} }
#[derive(Debug, Clone, Copy, Default)]
pub enum PointerCache {
#[default]
Empty,
Integers {
left: *const i64,
right: *const i64,
},
}

View File

@ -1,16 +1,15 @@
use std::{collections::HashMap, sync::Arc, thread::JoinHandle}; use std::{sync::Arc, thread::JoinHandle};
use tracing::{info, trace}; use tracing::{info, trace};
use crate::{AbstractList, Chunk, ConcreteValue, DustString, Span, Value, vm::CallFrame}; use crate::{AbstractList, Chunk, ConcreteValue, DustString, Span, Value, vm::CallFrame};
use super::call_frame::{Pointer, Register}; use super::call_frame::{Pointer, PointerCache, Register};
pub struct Thread { pub struct Thread {
chunk: Arc<Chunk>, chunk: Arc<Chunk>,
call_stack: Vec<CallFrame>, call_stack: Vec<CallFrame>,
pub return_value: Option<Option<Value>>, pub return_value: Option<Option<Value>>,
pub integer_cache: HashMap<usize, *const i64>,
_spawned_threads: Vec<JoinHandle<()>>, _spawned_threads: Vec<JoinHandle<()>>,
} }
@ -25,7 +24,6 @@ impl Thread {
chunk, chunk,
call_stack, call_stack,
return_value: None, return_value: None,
integer_cache: HashMap::new(),
_spawned_threads: Vec::new(), _spawned_threads: Vec::new(),
} }
} }
@ -41,6 +39,10 @@ impl Thread {
trace!("Thread actions: {}", self.current_frame().action_sequence); trace!("Thread actions: {}", self.current_frame().action_sequence);
loop { loop {
if let Some(return_value_option) = self.return_value {
return return_value_option;
}
let current_frame = self.current_frame_mut(); let current_frame = self.current_frame_mut();
let ip = { let ip = {
let ip = current_frame.ip; let ip = current_frame.ip;
@ -54,22 +56,7 @@ impl Thread {
unsafe { current_frame.action_sequence.actions.get_unchecked_mut(ip) } unsafe { current_frame.action_sequence.actions.get_unchecked_mut(ip) }
}; };
if let (Some(optimized_logic), Some(loop_instructions)) = ( (current_action.logic)(current_action.instruction, &mut self);
&current_action.optimized_logic,
&current_action.loop_instructions,
) {
let loop_instructions = loop_instructions.clone();
(optimized_logic)(loop_instructions, &mut self);
} else {
trace!("Instruction: {}", current_action.instruction.operation);
(current_action.logic)(current_action.instruction, &mut self);
}
if let Some(return_value_option) = self.return_value {
return return_value_option;
}
} }
} }
@ -95,6 +82,29 @@ impl Thread {
} }
} }
pub fn set_current_pointer_cache(&mut self, new_cache: PointerCache) {
let ip = self.current_frame().ip;
let old_cache = if cfg!(debug_assertions) {
self.call_stack
.last_mut()
.unwrap()
.pointer_caches
.get_mut(ip - 1)
.unwrap()
} else {
unsafe {
self.call_stack
.last_mut()
.unwrap_unchecked()
.pointer_caches
.get_unchecked_mut(ip - 1)
}
};
*old_cache = new_cache;
}
pub fn get_value_from_pointer(&self, pointer: &Pointer) -> ConcreteValue { pub fn get_value_from_pointer(&self, pointer: &Pointer) -> ConcreteValue {
match pointer { match pointer {
Pointer::RegisterBoolean(register_index) => { Pointer::RegisterBoolean(register_index) => {
@ -586,6 +596,31 @@ impl Thread {
} }
} }
pub fn get_integer_register_mut(&mut self, register_index: usize) -> &mut i64 {
if cfg!(debug_assertions) {
self.call_stack
.last_mut()
.unwrap()
.registers
.integers
.get_mut(register_index)
.unwrap()
.contained_value_mut()
.unwrap()
} else {
unsafe {
self.call_stack
.last_mut()
.unwrap_unchecked()
.registers
.integers
.get_unchecked_mut(register_index)
.contained_value_mut()
.unwrap_unchecked()
}
}
}
pub fn get_pointer_to_integer(&self, pointer: &Pointer) -> &i64 { pub fn get_pointer_to_integer(&self, pointer: &Pointer) -> &i64 {
match pointer { match pointer {
Pointer::RegisterInteger(register_index) => self.get_integer_register(*register_index), Pointer::RegisterInteger(register_index) => self.get_integer_register(*register_index),