Improve VM layout and performance
This commit is contained in:
parent
1f88d77476
commit
6cfa0f58e3
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -342,6 +342,7 @@ dependencies = [
|
||||
"smallvec",
|
||||
"smartstring",
|
||||
"tracing",
|
||||
"typed-arena",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -946,6 +947,12 @@ dependencies = [
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typed-arena"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.13"
|
||||
|
@ -22,6 +22,7 @@ smartstring = { version = "1.0.1", features = [
|
||||
"serde",
|
||||
], default-features = false }
|
||||
tracing = "0.1.41"
|
||||
typed-arena = "2.0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.3.4", features = ["html_reports"] }
|
||||
|
@ -42,7 +42,7 @@ pub struct Chunk {
|
||||
pub(crate) locals: SmallVec<[Local; 8]>,
|
||||
pub(crate) prototypes: Vec<Chunk>,
|
||||
|
||||
pub(crate) stack_size: usize,
|
||||
pub(crate) register_count: usize,
|
||||
pub(crate) prototype_index: u8,
|
||||
}
|
||||
|
||||
@ -65,7 +65,7 @@ impl Chunk {
|
||||
constants: constants.into(),
|
||||
locals: locals.into(),
|
||||
prototypes,
|
||||
stack_size: 0,
|
||||
register_count: 0,
|
||||
prototype_index: 0,
|
||||
}
|
||||
}
|
||||
|
@ -214,7 +214,7 @@ impl<'src> Compiler<'src> {
|
||||
constants: self.constants,
|
||||
locals,
|
||||
prototypes: self.prototypes,
|
||||
stack_size: self.stack_size,
|
||||
register_count: self.stack_size,
|
||||
prototype_index: self.prototype_index,
|
||||
}
|
||||
}
|
||||
@ -1000,9 +1000,9 @@ impl<'src> Compiler<'src> {
|
||||
return self.parse_call_native(native_function);
|
||||
} else if self.function_name.as_deref() == Some(identifier) {
|
||||
let destination = self.next_register();
|
||||
let load_function = Instruction::load_function(destination, self.prototype_index);
|
||||
let load_self = Instruction::load_self(destination);
|
||||
|
||||
self.emit_instruction(load_function, Type::SelfFunction, start_position);
|
||||
self.emit_instruction(load_self, Type::SelfFunction, start_position);
|
||||
|
||||
return Ok(());
|
||||
} else {
|
||||
@ -1700,6 +1700,7 @@ impl<'src> Compiler<'src> {
|
||||
});
|
||||
}
|
||||
};
|
||||
let is_recursive = last_instruction_type == &Type::SelfFunction;
|
||||
|
||||
let mut argument_count = 0;
|
||||
|
||||
@ -1727,7 +1728,7 @@ impl<'src> Compiler<'src> {
|
||||
|
||||
let end = self.current_position.1;
|
||||
let destination = self.next_register();
|
||||
let call = Instruction::call(destination, function_register, argument_count);
|
||||
let call = Instruction::call(destination, function_register, argument_count, is_recursive);
|
||||
|
||||
self.emit_instruction(call, function_return_type, Span(start, end));
|
||||
|
||||
|
@ -4,6 +4,7 @@ pub struct Call {
|
||||
pub destination: u8,
|
||||
pub function_register: u8,
|
||||
pub argument_count: u8,
|
||||
pub is_recursive: bool,
|
||||
}
|
||||
|
||||
impl From<Instruction> for Call {
|
||||
@ -11,11 +12,13 @@ impl From<Instruction> for Call {
|
||||
let destination = instruction.a_field();
|
||||
let function_register = instruction.b_field();
|
||||
let argument_count = instruction.c_field();
|
||||
let is_recursive = instruction.d_field();
|
||||
|
||||
Call {
|
||||
destination,
|
||||
function_register,
|
||||
argument_count,
|
||||
is_recursive,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -25,7 +28,8 @@ impl From<Call> for Instruction {
|
||||
let a = call.destination;
|
||||
let b = call.function_register;
|
||||
let c = call.argument_count;
|
||||
let d = call.is_recursive;
|
||||
|
||||
Instruction::new(Operation::CALL, a, b, c, false, false, false)
|
||||
Instruction::new(Operation::CALL, a, b, c, false, false, d)
|
||||
}
|
||||
}
|
||||
|
@ -375,11 +375,17 @@ impl Instruction {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn call(destination: u8, function_register: u8, argument_count: u8) -> Instruction {
|
||||
pub fn call(
|
||||
destination: u8,
|
||||
function_register: u8,
|
||||
argument_count: u8,
|
||||
is_recursive: bool,
|
||||
) -> Instruction {
|
||||
Instruction::from(Call {
|
||||
destination,
|
||||
function_register,
|
||||
argument_count,
|
||||
is_recursive,
|
||||
})
|
||||
}
|
||||
|
||||
@ -691,6 +697,7 @@ impl Instruction {
|
||||
destination,
|
||||
function_register,
|
||||
argument_count,
|
||||
..
|
||||
} = Call::from(*self);
|
||||
let arguments_start = destination.saturating_sub(argument_count);
|
||||
|
||||
|
@ -14,7 +14,7 @@ pub fn panic(
|
||||
let mut message = format!("Dust panic at {position}!");
|
||||
|
||||
for register_index in argument_range {
|
||||
let value = record.open_register(register_index);
|
||||
let value = record.open_register_unchecked(register_index);
|
||||
|
||||
if let Some(string) = value.as_string() {
|
||||
message.push_str(string);
|
||||
|
@ -10,7 +10,7 @@ pub fn to_string(
|
||||
destination: Option<u8>,
|
||||
argument_range: Range<u8>,
|
||||
) -> Result<ThreadSignal, NativeFunctionError> {
|
||||
let argument_value = record.open_register(argument_range.start);
|
||||
let argument_value = record.open_register_unchecked(argument_range.start);
|
||||
let argument_string = argument_value.display(record);
|
||||
let destination = destination.unwrap();
|
||||
let register = Register::Value(Value::Concrete(ConcreteValue::string(argument_string)));
|
||||
|
@ -6,7 +6,7 @@ use super::{stack::Stack, FunctionCall};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum VmError {
|
||||
CallStackUnderflow,
|
||||
StackUnderflow,
|
||||
ExpectedFunction {
|
||||
value: Value,
|
||||
},
|
||||
@ -22,7 +22,7 @@ pub enum VmError {
|
||||
impl Display for VmError {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::CallStackUnderflow => {
|
||||
Self::StackUnderflow => {
|
||||
write!(f, "Call stack underflow")
|
||||
}
|
||||
Self::ExpectedFunction { value } => {
|
||||
|
@ -13,7 +13,7 @@ use std::{
|
||||
|
||||
pub use error::VmError;
|
||||
pub use record::Record;
|
||||
pub use run_action::RunAction;
|
||||
pub use run_action::RecordAction;
|
||||
pub use stack::{FunctionCall, Stack};
|
||||
pub use thread::{Thread, ThreadSignal};
|
||||
use tracing::{span, Level};
|
||||
|
@ -10,7 +10,7 @@ use super::{Pointer, Register};
|
||||
pub struct Record<'a> {
|
||||
pub ip: usize,
|
||||
pub chunk: &'a Chunk,
|
||||
stack: Vec<Register>,
|
||||
registers: Vec<Register>,
|
||||
}
|
||||
|
||||
impl<'a> Record<'a> {
|
||||
@ -18,7 +18,7 @@ impl<'a> Record<'a> {
|
||||
pub fn new(chunk: &'a Chunk) -> Self {
|
||||
Self {
|
||||
ip: 0,
|
||||
stack: vec![Register::Empty; chunk.stack_size],
|
||||
registers: vec![Register::Empty; chunk.register_count],
|
||||
chunk,
|
||||
}
|
||||
}
|
||||
@ -28,7 +28,7 @@ impl<'a> Record<'a> {
|
||||
}
|
||||
|
||||
pub fn stack_size(&self) -> usize {
|
||||
self.stack.len()
|
||||
self.registers.len()
|
||||
}
|
||||
|
||||
pub fn current_position(&self) -> Span {
|
||||
@ -43,59 +43,46 @@ impl<'a> Record<'a> {
|
||||
trace!("Follow pointer {pointer}");
|
||||
|
||||
match pointer {
|
||||
Pointer::Stack(register_index) => self.open_register(register_index),
|
||||
Pointer::Stack(register_index) => self.open_register_unchecked(register_index),
|
||||
Pointer::Constant(constant_index) => self.get_constant(constant_index),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_register(&self, register_index: u8) -> &Register {
|
||||
pub fn get_register_unchecked(&self, register_index: u8) -> &Register {
|
||||
trace!("Get register R{register_index}");
|
||||
|
||||
let register_index = register_index as usize;
|
||||
|
||||
assert!(
|
||||
register_index < self.stack.len(),
|
||||
"VM Error: Register index out of bounds"
|
||||
);
|
||||
|
||||
&self.stack[register_index]
|
||||
if cfg!(debug_assertions) {
|
||||
&self.registers[register_index]
|
||||
} else {
|
||||
unsafe { self.registers.get_unchecked(register_index) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_register(&mut self, to_register: u8, register: Register) {
|
||||
let to_register = to_register as usize;
|
||||
|
||||
assert!(
|
||||
to_register < self.stack.len(),
|
||||
"VM Error: Register index out of bounds"
|
||||
);
|
||||
|
||||
self.stack[to_register] = register;
|
||||
self.registers[to_register] = register;
|
||||
}
|
||||
|
||||
pub fn reserve_registers(&mut self, count: usize) {
|
||||
for _ in 0..count {
|
||||
self.stack.push(Register::Empty);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_register(&self, register_index: u8) -> &Value {
|
||||
pub fn open_register_unchecked(&self, register_index: u8) -> &Value {
|
||||
let register_index = register_index as usize;
|
||||
|
||||
assert!(
|
||||
register_index < self.stack.len(),
|
||||
"VM Error: Register index out of bounds"
|
||||
);
|
||||
|
||||
let register = &self.stack[register_index];
|
||||
let register = if cfg!(debug_assertions) {
|
||||
&self.registers[register_index]
|
||||
} else {
|
||||
unsafe { self.registers.get_unchecked(register_index) }
|
||||
};
|
||||
|
||||
match register {
|
||||
Register::Value(value) => {
|
||||
trace!("Register R{register_index} openned to value {value}");
|
||||
trace!("Register R{register_index} opened to value {value}");
|
||||
|
||||
value
|
||||
}
|
||||
Register::Pointer(pointer) => {
|
||||
trace!("Open register R{register_index} openned to pointer {pointer}");
|
||||
trace!("Open register R{register_index} opened to pointer {pointer}");
|
||||
|
||||
self.follow_pointer(*pointer)
|
||||
}
|
||||
@ -109,11 +96,11 @@ impl<'a> Record<'a> {
|
||||
let register_index = register_index as usize;
|
||||
|
||||
assert!(
|
||||
register_index < self.stack.len(),
|
||||
register_index < self.registers.len(),
|
||||
"VM Error: Register index out of bounds"
|
||||
);
|
||||
|
||||
let register = &self.stack[register_index];
|
||||
let register = &self.registers[register_index];
|
||||
|
||||
match register {
|
||||
Register::Value(value) => {
|
||||
@ -130,24 +117,22 @@ impl<'a> Record<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty_register_or_clone_constant(
|
||||
&mut self,
|
||||
register_index: u8,
|
||||
new_register: Register,
|
||||
) -> Value {
|
||||
pub fn empty_register_or_clone_constant(&mut self, register_index: u8) -> Value {
|
||||
let register_index = register_index as usize;
|
||||
|
||||
assert!(
|
||||
register_index < self.stack.len(),
|
||||
register_index < self.registers.len(),
|
||||
"VM Error: Register index out of bounds"
|
||||
);
|
||||
|
||||
let old_register = replace(&mut self.stack[register_index], new_register);
|
||||
let old_register = replace(&mut self.registers[register_index], Register::Empty);
|
||||
|
||||
match old_register {
|
||||
Register::Value(value) => value,
|
||||
Register::Pointer(pointer) => match pointer {
|
||||
Pointer::Stack(register_index) => self.open_register(register_index).clone(),
|
||||
Pointer::Stack(register_index) => {
|
||||
self.empty_register_or_clone_constant(register_index)
|
||||
}
|
||||
Pointer::Constant(constant_index) => self.get_constant(constant_index).clone(),
|
||||
},
|
||||
Register::Empty => panic!("VM Error: Register {register_index} is empty"),
|
||||
@ -156,16 +141,18 @@ impl<'a> Record<'a> {
|
||||
|
||||
pub fn clone_register_value_or_constant(&self, register_index: u8) -> Value {
|
||||
assert!(
|
||||
(register_index as usize) < self.stack.len(),
|
||||
(register_index as usize) < self.registers.len(),
|
||||
"VM Error: Register index out of bounds"
|
||||
);
|
||||
|
||||
let register = &self.stack[register_index as usize];
|
||||
let register = &self.registers[register_index as usize];
|
||||
|
||||
match register {
|
||||
Register::Value(value) => value.clone(),
|
||||
Register::Pointer(pointer) => match pointer {
|
||||
Pointer::Stack(register_index) => self.open_register(*register_index).clone(),
|
||||
Pointer::Stack(register_index) => {
|
||||
self.open_register_unchecked(*register_index).clone()
|
||||
}
|
||||
Pointer::Constant(constant_index) => self.get_constant(*constant_index).clone(),
|
||||
},
|
||||
Register::Empty => panic!("VM Error: Register {register_index} is empty"),
|
||||
@ -176,7 +163,7 @@ impl<'a> Record<'a> {
|
||||
pub fn get_argument(&self, argument: Argument) -> &Value {
|
||||
match argument {
|
||||
Argument::Constant(constant_index) => self.get_constant(constant_index),
|
||||
Argument::Register(register_index) => self.open_register(register_index),
|
||||
Argument::Register(register_index) => self.open_register_unchecked(register_index),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,27 +6,28 @@ use crate::{
|
||||
LoadConstant, LoadFunction, LoadList, LoadSelf, Modulo, Multiply, Negate, Not, Point,
|
||||
Return, SetLocal, Subtract, Test, TestSet,
|
||||
},
|
||||
AbstractList, Argument, ConcreteValue, Instruction, Type, Value,
|
||||
vm::FunctionCall,
|
||||
AbstractList, Argument, ConcreteValue, DustString, Instruction, Type, Value,
|
||||
};
|
||||
|
||||
use super::{thread::ThreadSignal, Pointer, Record, Register};
|
||||
use super::{thread::ThreadData, Pointer, Record, Register};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct RunAction {
|
||||
pub struct RecordAction {
|
||||
pub logic: RunnerLogic,
|
||||
pub instruction: Instruction,
|
||||
}
|
||||
|
||||
impl From<Instruction> for RunAction {
|
||||
impl From<Instruction> for RecordAction {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
let operation = instruction.operation();
|
||||
let logic = RUNNER_LOGIC_TABLE[operation.0 as usize];
|
||||
|
||||
RunAction { logic, instruction }
|
||||
RecordAction { logic, instruction }
|
||||
}
|
||||
}
|
||||
|
||||
pub type RunnerLogic = fn(Instruction, &mut Record) -> ThreadSignal;
|
||||
pub type RunnerLogic = fn(Instruction, &mut ThreadData) -> Option<Value>;
|
||||
|
||||
pub const RUNNER_LOGIC_TABLE: [RunnerLogic; 25] = [
|
||||
point,
|
||||
@ -56,9 +57,10 @@ pub const RUNNER_LOGIC_TABLE: [RunnerLogic; 25] = [
|
||||
r#return,
|
||||
];
|
||||
|
||||
pub fn point(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
pub fn point(instruction: Instruction, data: &mut ThreadData) -> Option<Value> {
|
||||
let record = data.records.last_mut_unchecked();
|
||||
let Point { from, to } = instruction.into();
|
||||
let from_register = record.get_register(from);
|
||||
let from_register = record.get_register_unchecked(from);
|
||||
let from_register_is_empty = matches!(from_register, Register::Empty);
|
||||
|
||||
if !from_register_is_empty {
|
||||
@ -67,10 +69,13 @@ pub fn point(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
record.set_register(from, register);
|
||||
}
|
||||
|
||||
ThreadSignal::Continue
|
||||
record.ip += 1;
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn close(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
pub fn close(instruction: Instruction, data: &mut ThreadData) -> Option<Value> {
|
||||
let record = data.records.last_mut_unchecked();
|
||||
let Close { from, to } = instruction.into();
|
||||
|
||||
assert!(from < to, "Runtime Error: Malformed instruction");
|
||||
@ -84,10 +89,13 @@ pub fn close(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
record.set_register(register_index, Register::Empty);
|
||||
}
|
||||
|
||||
ThreadSignal::Continue
|
||||
record.ip += 1;
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn load_boolean(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
pub fn load_boolean(instruction: Instruction, data: &mut ThreadData) -> Option<Value> {
|
||||
let record = data.records.last_mut_unchecked();
|
||||
let LoadBoolean {
|
||||
destination,
|
||||
value,
|
||||
@ -102,10 +110,13 @@ pub fn load_boolean(instruction: Instruction, record: &mut Record) -> ThreadSign
|
||||
record.ip += 1;
|
||||
}
|
||||
|
||||
ThreadSignal::Continue
|
||||
record.ip += 1;
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn load_constant(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
pub fn load_constant(instruction: Instruction, data: &mut ThreadData) -> Option<Value> {
|
||||
let record = data.records.last_mut_unchecked();
|
||||
let LoadConstant {
|
||||
destination,
|
||||
constant_index,
|
||||
@ -121,10 +132,13 @@ pub fn load_constant(instruction: Instruction, record: &mut Record) -> ThreadSig
|
||||
record.ip += 1;
|
||||
}
|
||||
|
||||
ThreadSignal::Continue
|
||||
record.ip += 1;
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn load_list(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
pub fn load_list(instruction: Instruction, data: &mut ThreadData) -> Option<Value> {
|
||||
let record = data.records.last_mut_unchecked();
|
||||
let LoadList {
|
||||
destination,
|
||||
start_register,
|
||||
@ -133,7 +147,7 @@ pub fn load_list(instruction: Instruction, record: &mut Record) -> ThreadSignal
|
||||
let mut item_type = Type::Any;
|
||||
|
||||
for register_index in start_register..destination {
|
||||
match record.get_register(register_index) {
|
||||
match record.get_register_unchecked(register_index) {
|
||||
Register::Empty => continue,
|
||||
Register::Value(value) => {
|
||||
if item_type == Type::Any {
|
||||
@ -160,32 +174,43 @@ pub fn load_list(instruction: Instruction, record: &mut Record) -> ThreadSignal
|
||||
|
||||
record.set_register(destination, register);
|
||||
|
||||
ThreadSignal::Continue
|
||||
record.ip += 1;
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn load_function(instruction: Instruction, _: &mut Record) -> ThreadSignal {
|
||||
pub fn load_function(instruction: Instruction, data: &mut ThreadData) -> Option<Value> {
|
||||
let record = data.records.last_mut_unchecked();
|
||||
let LoadFunction {
|
||||
destination,
|
||||
prototype_index,
|
||||
} = instruction.into();
|
||||
let prototype_index = prototype_index as usize;
|
||||
let function = record.chunk.prototypes[prototype_index].as_function();
|
||||
let register = Register::Value(Value::Function(function));
|
||||
|
||||
ThreadSignal::LoadFunction {
|
||||
destination,
|
||||
prototype_index,
|
||||
}
|
||||
record.set_register(destination, register);
|
||||
|
||||
record.ip += 1;
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn load_self(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
pub fn load_self(instruction: Instruction, data: &mut ThreadData) -> Option<Value> {
|
||||
let record = data.records.last_mut_unchecked();
|
||||
let LoadSelf { destination } = instruction.into();
|
||||
let function = record.as_function();
|
||||
let register = Register::Value(Value::Function(function));
|
||||
|
||||
record.set_register(destination, register);
|
||||
|
||||
ThreadSignal::Continue
|
||||
record.ip += 1;
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_local(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
pub fn get_local(instruction: Instruction, data: &mut ThreadData) -> Option<Value> {
|
||||
let record = data.records.last_mut_unchecked();
|
||||
let GetLocal {
|
||||
destination,
|
||||
local_index,
|
||||
@ -195,10 +220,13 @@ pub fn get_local(instruction: Instruction, record: &mut Record) -> ThreadSignal
|
||||
|
||||
record.set_register(destination, register);
|
||||
|
||||
ThreadSignal::Continue
|
||||
record.ip += 1;
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn set_local(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
pub fn set_local(instruction: Instruction, data: &mut ThreadData) -> Option<Value> {
|
||||
let record = data.records.last_mut_unchecked();
|
||||
let SetLocal {
|
||||
register_index,
|
||||
local_index,
|
||||
@ -208,10 +236,13 @@ pub fn set_local(instruction: Instruction, record: &mut Record) -> ThreadSignal
|
||||
|
||||
record.set_register(local_register_index, register);
|
||||
|
||||
ThreadSignal::Continue
|
||||
record.ip += 1;
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn add(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
pub fn add(instruction: Instruction, data: &mut ThreadData) -> Option<Value> {
|
||||
let record = data.records.last_mut_unchecked();
|
||||
let Add {
|
||||
destination,
|
||||
left,
|
||||
@ -224,10 +255,13 @@ pub fn add(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
|
||||
record.set_register(destination, register);
|
||||
|
||||
ThreadSignal::Continue
|
||||
record.ip += 1;
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn subtract(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
pub fn subtract(instruction: Instruction, data: &mut ThreadData) -> Option<Value> {
|
||||
let record = data.records.last_mut_unchecked();
|
||||
let Subtract {
|
||||
destination,
|
||||
left,
|
||||
@ -240,10 +274,13 @@ pub fn subtract(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
|
||||
record.set_register(destination, register);
|
||||
|
||||
ThreadSignal::Continue
|
||||
record.ip += 1;
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn multiply(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
pub fn multiply(instruction: Instruction, data: &mut ThreadData) -> Option<Value> {
|
||||
let record = data.records.last_mut_unchecked();
|
||||
let Multiply {
|
||||
destination,
|
||||
left,
|
||||
@ -264,10 +301,13 @@ pub fn multiply(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
|
||||
record.set_register(destination, register);
|
||||
|
||||
ThreadSignal::Continue
|
||||
record.ip += 1;
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn divide(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
pub fn divide(instruction: Instruction, data: &mut ThreadData) -> Option<Value> {
|
||||
let record = data.records.last_mut_unchecked();
|
||||
let Divide {
|
||||
destination,
|
||||
left,
|
||||
@ -288,10 +328,13 @@ pub fn divide(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
|
||||
record.set_register(destination, register);
|
||||
|
||||
ThreadSignal::Continue
|
||||
record.ip += 1;
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn modulo(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
pub fn modulo(instruction: Instruction, data: &mut ThreadData) -> Option<Value> {
|
||||
let record = data.records.last_mut_unchecked();
|
||||
let Modulo {
|
||||
destination,
|
||||
left,
|
||||
@ -312,15 +355,18 @@ pub fn modulo(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
|
||||
record.set_register(destination, register);
|
||||
|
||||
ThreadSignal::Continue
|
||||
record.ip += 1;
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn test(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
pub fn test(instruction: Instruction, data: &mut ThreadData) -> Option<Value> {
|
||||
let record = data.records.last_mut_unchecked();
|
||||
let Test {
|
||||
operand_register,
|
||||
test_value,
|
||||
} = instruction.into();
|
||||
let value = record.open_register(operand_register);
|
||||
let value = record.open_register_unchecked(operand_register);
|
||||
let boolean = if let Value::Concrete(ConcreteValue::Boolean(boolean)) = value {
|
||||
*boolean
|
||||
} else {
|
||||
@ -331,10 +377,13 @@ pub fn test(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
record.ip += 1;
|
||||
}
|
||||
|
||||
ThreadSignal::Continue
|
||||
record.ip += 1;
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn test_set(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
pub fn test_set(instruction: Instruction, data: &mut ThreadData) -> Option<Value> {
|
||||
let record = data.records.last_mut_unchecked();
|
||||
let TestSet {
|
||||
destination,
|
||||
argument,
|
||||
@ -359,10 +408,13 @@ pub fn test_set(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
record.set_register(destination, register);
|
||||
}
|
||||
|
||||
ThreadSignal::Continue
|
||||
record.ip += 1;
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn equal(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
pub fn equal(instruction: Instruction, data: &mut ThreadData) -> Option<Value> {
|
||||
let record = data.records.last_mut_unchecked();
|
||||
let Equal { value, left, right } = instruction.into();
|
||||
let left = record.get_argument(left);
|
||||
let right = record.get_argument(right);
|
||||
@ -372,10 +424,13 @@ pub fn equal(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
record.ip += 1;
|
||||
}
|
||||
|
||||
ThreadSignal::Continue
|
||||
record.ip += 1;
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn less(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
pub fn less(instruction: Instruction, data: &mut ThreadData) -> Option<Value> {
|
||||
let record = data.records.last_mut_unchecked();
|
||||
let Less { value, left, right } = instruction.into();
|
||||
let left = record.get_argument(left);
|
||||
let right = record.get_argument(right);
|
||||
@ -385,10 +440,13 @@ pub fn less(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
record.ip += 1;
|
||||
}
|
||||
|
||||
ThreadSignal::Continue
|
||||
record.ip += 1;
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn less_equal(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
pub fn less_equal(instruction: Instruction, data: &mut ThreadData) -> Option<Value> {
|
||||
let record = data.records.last_mut_unchecked();
|
||||
let LessEqual { value, left, right } = instruction.into();
|
||||
let left = record.get_argument(left);
|
||||
let right = record.get_argument(right);
|
||||
@ -398,10 +456,13 @@ pub fn less_equal(instruction: Instruction, record: &mut Record) -> ThreadSignal
|
||||
record.ip += 1;
|
||||
}
|
||||
|
||||
ThreadSignal::Continue
|
||||
record.ip += 1;
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn negate(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
pub fn negate(instruction: Instruction, data: &mut ThreadData) -> Option<Value> {
|
||||
let record = data.records.last_mut_unchecked();
|
||||
let Negate {
|
||||
destination,
|
||||
argument,
|
||||
@ -412,10 +473,13 @@ pub fn negate(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
|
||||
record.set_register(destination, register);
|
||||
|
||||
ThreadSignal::Continue
|
||||
record.ip += 1;
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn not(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
pub fn not(instruction: Instruction, data: &mut ThreadData) -> Option<Value> {
|
||||
let record = data.records.last_mut_unchecked();
|
||||
let Not {
|
||||
destination,
|
||||
argument,
|
||||
@ -429,10 +493,13 @@ pub fn not(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
|
||||
record.set_register(destination, register);
|
||||
|
||||
ThreadSignal::Continue
|
||||
record.ip += 1;
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn jump(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
pub fn jump(instruction: Instruction, data: &mut ThreadData) -> Option<Value> {
|
||||
let record = data.records.last_mut_unchecked();
|
||||
let Jump {
|
||||
offset,
|
||||
is_positive,
|
||||
@ -445,24 +512,61 @@ pub fn jump(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
record.ip -= offset + 1;
|
||||
}
|
||||
|
||||
ThreadSignal::Continue
|
||||
record.ip += 1;
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn call(instruction: Instruction, _: &mut Record) -> ThreadSignal {
|
||||
pub fn call(instruction: Instruction, data: &mut ThreadData) -> Option<Value> {
|
||||
let record = data.records.last_mut_unchecked();
|
||||
let Call {
|
||||
destination: return_register,
|
||||
function_register,
|
||||
argument_count,
|
||||
is_recursive,
|
||||
} = instruction.into();
|
||||
|
||||
ThreadSignal::Call {
|
||||
function_register,
|
||||
let function = record
|
||||
.open_register_unchecked(function_register)
|
||||
.as_function()
|
||||
.unwrap();
|
||||
let first_argument_register = return_register - argument_count;
|
||||
let prototype = if is_recursive {
|
||||
record.chunk
|
||||
} else {
|
||||
&record.chunk.prototypes[function.prototype_index as usize]
|
||||
};
|
||||
let mut next_record = Record::new(prototype);
|
||||
let next_call = FunctionCall {
|
||||
name: next_record.name().cloned(),
|
||||
return_register,
|
||||
argument_count,
|
||||
ip: record.ip,
|
||||
};
|
||||
|
||||
for (argument_index, register_index) in (first_argument_register..return_register).enumerate() {
|
||||
let argument = record.clone_register_value_or_constant(register_index);
|
||||
|
||||
trace!(
|
||||
"Passing argument \"{argument}\" to {}",
|
||||
function
|
||||
.name
|
||||
.clone()
|
||||
.unwrap_or_else(|| DustString::from("anonymous"))
|
||||
);
|
||||
|
||||
next_record.set_register(argument_index as u8, Register::Value(argument));
|
||||
}
|
||||
|
||||
record.ip += 1;
|
||||
|
||||
data.call_stack.push(next_call);
|
||||
data.records.push(next_record);
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn call_native(instruction: Instruction, record: &mut Record) -> ThreadSignal {
|
||||
pub fn call_native(instruction: Instruction, data: &mut ThreadData) -> Option<Value> {
|
||||
let record = data.records.last_mut_unchecked();
|
||||
let CallNative {
|
||||
destination,
|
||||
function,
|
||||
@ -473,19 +577,47 @@ pub fn call_native(instruction: Instruction, record: &mut Record) -> ThreadSigna
|
||||
|
||||
function
|
||||
.call(record, Some(destination), argument_range)
|
||||
.unwrap_or_else(|error| panic!("{error:?}"))
|
||||
.unwrap_or_else(|error| panic!("{error:?}"));
|
||||
|
||||
record.ip += 1;
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn r#return(instruction: Instruction, _: &mut Record) -> ThreadSignal {
|
||||
pub fn r#return(instruction: Instruction, data: &mut ThreadData) -> Option<Value> {
|
||||
let record = data.records.last_mut_unchecked();
|
||||
let Return {
|
||||
should_return_value,
|
||||
return_register,
|
||||
} = instruction.into();
|
||||
|
||||
ThreadSignal::Return {
|
||||
should_return_value,
|
||||
return_register,
|
||||
trace!("Returning with call stack:\n{}", data.call_stack);
|
||||
|
||||
let return_value = if should_return_value {
|
||||
Some(record.empty_register_or_clone_constant(return_register))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let current_call = data.call_stack.pop_unchecked();
|
||||
let _current_record = data.records.pop_unchecked();
|
||||
let destination = current_call.return_register;
|
||||
|
||||
if data.call_stack.is_empty() {
|
||||
return if should_return_value {
|
||||
return_value
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
|
||||
let outer_record = data.records.last_mut_unchecked();
|
||||
|
||||
if should_return_value {
|
||||
outer_record.set_register(destination, Register::Value(return_value.unwrap()));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -33,6 +33,16 @@ impl<T> Stack<T> {
|
||||
self.items.len()
|
||||
}
|
||||
|
||||
pub fn get_unchecked(&self, index: usize) -> &T {
|
||||
if cfg!(debug_assertions) {
|
||||
assert!(index < self.len(), "{}", VmError::StackUnderflow);
|
||||
|
||||
&self.items[index]
|
||||
} else {
|
||||
unsafe { self.items.get_unchecked(index) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, item: T) {
|
||||
self.items.push(item);
|
||||
}
|
||||
@ -51,7 +61,7 @@ impl<T> Stack<T> {
|
||||
|
||||
pub fn pop_unchecked(&mut self) -> T {
|
||||
if cfg!(debug_assertions) {
|
||||
assert!(!self.is_empty(), "{}", VmError::CallStackUnderflow);
|
||||
assert!(!self.is_empty(), "{}", VmError::StackUnderflow);
|
||||
|
||||
self.items.pop().unwrap()
|
||||
} else {
|
||||
@ -61,7 +71,7 @@ impl<T> Stack<T> {
|
||||
|
||||
pub fn last_unchecked(&self) -> &T {
|
||||
if cfg!(debug_assertions) {
|
||||
assert!(!self.is_empty(), "{}", VmError::CallStackUnderflow);
|
||||
assert!(!self.is_empty(), "{}", VmError::StackUnderflow);
|
||||
|
||||
self.items.last().unwrap()
|
||||
} else {
|
||||
@ -71,7 +81,7 @@ impl<T> Stack<T> {
|
||||
|
||||
pub fn last_mut_unchecked(&mut self) -> &mut T {
|
||||
if cfg!(debug_assertions) {
|
||||
assert!(!self.is_empty(), "{}", VmError::CallStackUnderflow);
|
||||
assert!(!self.is_empty(), "{}", VmError::StackUnderflow);
|
||||
|
||||
self.items.last_mut().unwrap()
|
||||
} else {
|
||||
@ -100,9 +110,9 @@ impl<T> IndexMut<Range<usize>> for Stack<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Stack<FunctionCall> {
|
||||
impl<T: Debug> Debug for Stack<T> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{self}")
|
||||
write!(f, "{:?}", self.items)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,12 +2,9 @@ use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use tracing::{info, trace};
|
||||
|
||||
use crate::{
|
||||
vm::{FunctionCall, Register},
|
||||
Chunk, DustString, Value,
|
||||
};
|
||||
use crate::{vm::FunctionCall, Chunk, DustString, Value};
|
||||
|
||||
use super::{record::Record, RunAction, Stack};
|
||||
use super::{record::Record, RecordAction, Stack};
|
||||
|
||||
pub struct Thread {
|
||||
chunk: Chunk,
|
||||
@ -28,127 +25,43 @@ impl Thread {
|
||||
};
|
||||
let main_record = Record::new(&self.chunk);
|
||||
|
||||
call_stack.push(main_call);
|
||||
records.push(main_record);
|
||||
|
||||
let mut active_record = records.last_mut_unchecked();
|
||||
|
||||
info!(
|
||||
"Starting thread with {}",
|
||||
active_record
|
||||
.as_function()
|
||||
self.chunk
|
||||
.name
|
||||
.clone()
|
||||
.unwrap_or_else(|| DustString::from("anonymous"))
|
||||
);
|
||||
|
||||
call_stack.push(main_call);
|
||||
records.push(main_record);
|
||||
|
||||
let mut thread_data = ThreadData {
|
||||
call_stack,
|
||||
records,
|
||||
};
|
||||
|
||||
loop {
|
||||
trace!(
|
||||
"Run \"{}\" | IP = {}",
|
||||
active_record
|
||||
.name()
|
||||
.cloned()
|
||||
.unwrap_or_else(|| DustString::from("anonymous")),
|
||||
active_record.ip
|
||||
);
|
||||
|
||||
let active_record = thread_data.records.last_unchecked();
|
||||
let instruction = active_record.chunk.instructions[active_record.ip];
|
||||
let action = RunAction::from(instruction);
|
||||
let signal = (action.logic)(action.instruction, active_record);
|
||||
let record_action = RecordAction::from(instruction);
|
||||
let value_option = (record_action.logic)(record_action.instruction, &mut thread_data);
|
||||
|
||||
trace!("Thread Signal: {}", signal);
|
||||
if thread_data.call_stack.is_empty() {
|
||||
trace!("Returning {value_option:?} from function");
|
||||
|
||||
active_record.ip += 1;
|
||||
|
||||
match signal {
|
||||
ThreadSignal::Continue => {}
|
||||
ThreadSignal::LoadFunction {
|
||||
destination,
|
||||
prototype_index,
|
||||
} => {
|
||||
let function_record_index = prototype_index as usize;
|
||||
let function = self.chunk.prototypes[function_record_index].as_function();
|
||||
let register = Register::Value(Value::Function(function));
|
||||
|
||||
active_record.set_register(destination, register);
|
||||
}
|
||||
ThreadSignal::Call {
|
||||
function_register,
|
||||
return_register,
|
||||
argument_count,
|
||||
} => {
|
||||
let function = active_record
|
||||
.open_register(function_register)
|
||||
.as_function()
|
||||
.unwrap();
|
||||
let first_argument_register = return_register - argument_count;
|
||||
let mut arguments = Vec::with_capacity(argument_count as usize);
|
||||
|
||||
for register_index in first_argument_register..return_register {
|
||||
let value = active_record.clone_register_value_or_constant(register_index);
|
||||
|
||||
arguments.push(value);
|
||||
}
|
||||
|
||||
trace!("Passing arguments: {arguments:?}");
|
||||
|
||||
let prototype = &self.chunk.prototypes[function.prototype_index as usize];
|
||||
let next_record = Record::new(prototype);
|
||||
let next_call = FunctionCall {
|
||||
name: next_record.name().cloned(),
|
||||
return_register,
|
||||
ip: active_record.ip,
|
||||
};
|
||||
|
||||
call_stack.push(next_call);
|
||||
records.push(next_record);
|
||||
|
||||
active_record = records.last_mut_unchecked();
|
||||
|
||||
for (index, argument) in arguments.into_iter().enumerate() {
|
||||
active_record.set_register(index as u8, Register::Value(argument));
|
||||
}
|
||||
}
|
||||
ThreadSignal::Return {
|
||||
should_return_value,
|
||||
return_register,
|
||||
} => {
|
||||
trace!("Returning with call stack:\n{call_stack}");
|
||||
|
||||
let return_value = if should_return_value {
|
||||
Some(
|
||||
active_record
|
||||
.empty_register_or_clone_constant(return_register, Register::Empty),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let current_call = call_stack.pop_unchecked();
|
||||
let _current_record = records.pop_unchecked();
|
||||
let destination = current_call.return_register;
|
||||
|
||||
if call_stack.is_empty() {
|
||||
return if should_return_value {
|
||||
Some(return_value.unwrap())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
|
||||
let outer_record = records.last_mut_unchecked();
|
||||
|
||||
if should_return_value {
|
||||
outer_record
|
||||
.set_register(destination, Register::Value(return_value.unwrap()));
|
||||
}
|
||||
|
||||
active_record = outer_record;
|
||||
}
|
||||
return value_option;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ThreadData<'a> {
|
||||
pub call_stack: Stack<FunctionCall>,
|
||||
pub records: Stack<Record<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ThreadSignal {
|
||||
Continue,
|
||||
|
Loading…
x
Reference in New Issue
Block a user