Continue thread-based VM refactor
This commit is contained in:
parent
bd590e0643
commit
4527f7b6ef
@ -196,10 +196,10 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
let chunk = compiler.finish(None, None);
|
||||
let chunk = compiler.finish();
|
||||
let compile_end = start_time.elapsed();
|
||||
|
||||
let vm = Vm::new(&chunk, None, None);
|
||||
let vm = Vm::new(chunk);
|
||||
let return_value = vm.run();
|
||||
let run_end = start_time.elapsed();
|
||||
|
||||
|
@ -231,7 +231,7 @@ impl AnnotatedError for CompileError {
|
||||
)
|
||||
]
|
||||
}
|
||||
_ => todo!(),
|
||||
_ => SmallVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -248,7 +248,7 @@ impl AnnotatedError for CompileError {
|
||||
Span(left_position.0, right_position.1)
|
||||
)]
|
||||
}
|
||||
_ => todo!(),
|
||||
_ => SmallVec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,8 +20,7 @@ use smallvec::{smallvec, SmallVec};
|
||||
|
||||
use crate::{
|
||||
instruction::{
|
||||
CallNative, Close, GetLocal, Jump, LoadList, LoadSelf, Negate, Not, Point, Return,
|
||||
SetLocal, Test,
|
||||
CallNative, Close, GetLocal, Jump, LoadList, Negate, Not, Point, Return, SetLocal, Test,
|
||||
},
|
||||
Argument, Chunk, ConcreteValue, DustError, DustString, FunctionType, Instruction, Lexer, Local,
|
||||
NativeFunction, Operation, Scope, Span, Token, TokenKind, Type, Value,
|
||||
@ -46,7 +45,7 @@ pub fn compile(source: &str) -> Result<Chunk, DustError> {
|
||||
.compile()
|
||||
.map_err(|error| DustError::compile(error, source))?;
|
||||
|
||||
let chunk = compiler.finish(None, None);
|
||||
let chunk = compiler.finish();
|
||||
|
||||
Ok(chunk)
|
||||
}
|
||||
@ -63,9 +62,9 @@ pub struct Compiler<'src> {
|
||||
/// [`Compiler::finish`] is called.
|
||||
self_name: Option<DustString>,
|
||||
|
||||
/// Return type of the function being compiled. This is assigned to the chunk when
|
||||
/// [`Compiler::finish`] is called.
|
||||
return_type: Option<Type>,
|
||||
/// Type of the function being compiled. This is assigned to the chunk when [`Compiler::finish`]
|
||||
/// is called.
|
||||
r#type: FunctionType,
|
||||
|
||||
/// Instructions, along with their types and positions, that have been compiled. The
|
||||
/// instructions and positions are assigned to the chunk when [`Compiler::finish`] is called.
|
||||
@ -76,8 +75,8 @@ pub struct Compiler<'src> {
|
||||
/// is called.
|
||||
constants: SmallVec<[Value; 16]>,
|
||||
|
||||
/// Locals that have been compiled. These are assigned to the chunk when [`Compiler::finish`] is
|
||||
/// called.
|
||||
/// Block-local variables and their types. The locals are assigned to the chunk when
|
||||
/// [`Compiler::finish`] is called. The types are discarded after compilation.
|
||||
locals: SmallVec<[(Local, Type); 8]>,
|
||||
|
||||
/// Prototypes that have been compiled. These are assigned to the chunk when
|
||||
@ -108,6 +107,12 @@ pub struct Compiler<'src> {
|
||||
/// Index of the record (i.e. runtime data) that the VM will use when calling the function. This
|
||||
/// is a depth-first index.
|
||||
record_index: u8,
|
||||
|
||||
/// Record index for the next nested chunk that is compiled. When a function is compiled, its
|
||||
/// `record_index` is assigned from this value. Then `next_record_index` is incremented to
|
||||
/// maintain the depth-first index. After the function is compiled, the its `next_record_index`
|
||||
/// is assigned to this chunk.
|
||||
next_record_index: u8,
|
||||
}
|
||||
|
||||
impl<'src> Compiler<'src> {
|
||||
@ -122,6 +127,11 @@ impl<'src> Compiler<'src> {
|
||||
|
||||
Ok(Compiler {
|
||||
self_name: None,
|
||||
r#type: FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Type::None,
|
||||
},
|
||||
instructions: SmallVec::new(),
|
||||
constants: SmallVec::new(),
|
||||
locals: SmallVec::new(),
|
||||
@ -132,26 +142,17 @@ impl<'src> Compiler<'src> {
|
||||
current_position,
|
||||
previous_token: Token::Eof,
|
||||
previous_position: Span(0, 0),
|
||||
return_type: None,
|
||||
minimum_register: 0,
|
||||
block_index: 0,
|
||||
current_scope: Scope::default(),
|
||||
record_index: 0,
|
||||
next_record_index: 1,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn finish(
|
||||
self,
|
||||
type_parameters: Option<SmallVec<[u8; 4]>>,
|
||||
value_parameters: Option<SmallVec<[(u8, Type); 4]>>,
|
||||
) -> Chunk {
|
||||
pub fn finish(self) -> Chunk {
|
||||
log::info!("End chunk");
|
||||
|
||||
let r#type = FunctionType {
|
||||
type_parameters,
|
||||
value_parameters,
|
||||
return_type: self.return_type.unwrap_or(Type::None),
|
||||
};
|
||||
let (instructions, positions): (SmallVec<[Instruction; 32]>, SmallVec<[Span; 32]>) = self
|
||||
.instructions
|
||||
.into_iter()
|
||||
@ -165,7 +166,7 @@ impl<'src> Compiler<'src> {
|
||||
|
||||
Chunk::new(
|
||||
self.self_name,
|
||||
r#type,
|
||||
self.r#type,
|
||||
instructions,
|
||||
positions,
|
||||
self.constants,
|
||||
@ -400,26 +401,22 @@ impl<'src> Compiler<'src> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Updates [Self::return_type] with the given [Type].
|
||||
/// Updates [`Self::type`] with the given [Type] as `return_type`.
|
||||
///
|
||||
/// If [Self::return_type] is already set, it will check if the given [Type] is compatible with
|
||||
/// it and set it to the least restrictive of the two.
|
||||
/// If [`Self::type`] is already set, it will check if the given [Type] is compatible.
|
||||
fn update_return_type(&mut self, new_return_type: Type) -> Result<(), CompileError> {
|
||||
if let Some(return_type) = &self.return_type {
|
||||
return_type.check(&new_return_type).map_err(|conflict| {
|
||||
CompileError::ReturnTypeConflict {
|
||||
if self.r#type.return_type != Type::None {
|
||||
self.r#type
|
||||
.return_type
|
||||
.check(&new_return_type)
|
||||
.map_err(|conflict| CompileError::ReturnTypeConflict {
|
||||
conflict,
|
||||
position: self.current_position,
|
||||
}
|
||||
})?;
|
||||
|
||||
if *return_type != Type::Any {
|
||||
self.return_type = Some(new_return_type);
|
||||
};
|
||||
} else {
|
||||
self.return_type = Some(new_return_type);
|
||||
}
|
||||
|
||||
self.r#type.return_type = new_return_type;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -1026,9 +1023,9 @@ impl<'src> Compiler<'src> {
|
||||
return self.parse_call_native(native_function);
|
||||
} else if self.self_name.as_deref() == Some(identifier) {
|
||||
let destination = self.next_register();
|
||||
let load_self = Instruction::from(LoadSelf { destination });
|
||||
let load_function = Instruction::load_function(destination, self.record_index);
|
||||
|
||||
self.emit_instruction(load_self, Type::SelfFunction, start_position);
|
||||
self.emit_instruction(load_function, Type::SelfFunction, start_position);
|
||||
|
||||
return Ok(());
|
||||
} else {
|
||||
@ -1572,8 +1569,8 @@ impl<'src> Compiler<'src> {
|
||||
let function_start = self.current_position.0;
|
||||
let mut function_compiler = Compiler::new(self.lexer)?;
|
||||
|
||||
self.record_index += 1;
|
||||
function_compiler.record_index = self.record_index;
|
||||
function_compiler.record_index = self.next_record_index;
|
||||
function_compiler.next_record_index = self.next_record_index + 1;
|
||||
|
||||
let identifier_info = if let Token::Identifier(text) = function_compiler.current_token {
|
||||
let position = function_compiler.current_position;
|
||||
@ -1646,8 +1643,13 @@ impl<'src> Compiler<'src> {
|
||||
} else {
|
||||
Type::None
|
||||
};
|
||||
let function_type = FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters,
|
||||
return_type,
|
||||
};
|
||||
|
||||
function_compiler.return_type = Some((return_type).clone());
|
||||
function_compiler.r#type = function_type.clone();
|
||||
|
||||
function_compiler.expect(Token::LeftBrace)?;
|
||||
function_compiler.compile()?;
|
||||
@ -1657,20 +1659,15 @@ impl<'src> Compiler<'src> {
|
||||
self.previous_position = function_compiler.previous_position;
|
||||
self.current_token = function_compiler.current_token;
|
||||
self.current_position = function_compiler.current_position;
|
||||
self.record_index = function_compiler.record_index;
|
||||
self.next_record_index = function_compiler.next_record_index;
|
||||
|
||||
self.lexer.skip_to(self.current_position.1);
|
||||
|
||||
let function_end = function_compiler.previous_position.1;
|
||||
let prototype = function_compiler.finish(None, value_parameters.clone());
|
||||
let chunk = function_compiler.finish();
|
||||
let destination = self.next_register();
|
||||
let function_type = FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters,
|
||||
return_type,
|
||||
};
|
||||
|
||||
self.prototypes.push(prototype);
|
||||
self.prototypes.push(chunk);
|
||||
|
||||
if let Some((identifier, _)) = identifier_info {
|
||||
self.declare_local(
|
||||
@ -1680,7 +1677,8 @@ impl<'src> Compiler<'src> {
|
||||
false,
|
||||
self.current_scope,
|
||||
);
|
||||
} else {
|
||||
}
|
||||
|
||||
let load_function = Instruction::load_function(destination, self.record_index);
|
||||
|
||||
self.emit_instruction(
|
||||
@ -1688,12 +1686,15 @@ impl<'src> Compiler<'src> {
|
||||
Type::function(function_type),
|
||||
Span(function_start, function_end),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_call(&mut self) -> Result<(), CompileError> {
|
||||
let start = self.current_position.0;
|
||||
|
||||
self.advance()?;
|
||||
|
||||
let (last_instruction, last_instruction_type, _) =
|
||||
self.instructions
|
||||
.last()
|
||||
@ -1710,10 +1711,10 @@ impl<'src> Compiler<'src> {
|
||||
});
|
||||
}
|
||||
|
||||
let prototype_index = last_instruction.b_field();
|
||||
let function_register = last_instruction.a_field();
|
||||
let function_return_type = match last_instruction_type {
|
||||
Type::Function(function_type) => function_type.return_type.clone(),
|
||||
Type::SelfFunction => self.return_type.clone().unwrap_or(Type::None),
|
||||
Type::SelfFunction => self.r#type.return_type.clone(),
|
||||
_ => {
|
||||
return Err(CompileError::ExpectedFunction {
|
||||
found: self.previous_token.to_owned(),
|
||||
@ -1722,9 +1723,6 @@ impl<'src> Compiler<'src> {
|
||||
});
|
||||
}
|
||||
};
|
||||
let start = self.current_position.0;
|
||||
|
||||
self.advance()?;
|
||||
|
||||
let mut argument_count = 0;
|
||||
|
||||
@ -1752,7 +1750,7 @@ impl<'src> Compiler<'src> {
|
||||
|
||||
let end = self.current_position.1;
|
||||
let destination = self.next_register();
|
||||
let call = Instruction::call(destination, prototype_index, argument_count);
|
||||
let call = Instruction::call(destination, function_register, argument_count);
|
||||
|
||||
self.emit_instruction(call, function_return_type, Span(start, end));
|
||||
|
||||
|
@ -5,7 +5,7 @@ use std::fmt::{self, Display, Formatter};
|
||||
use annotate_snippets::{Level, Renderer, Snippet};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{CompileError, Span};
|
||||
use crate::{CompileError, NativeFunctionError, Span};
|
||||
|
||||
/// A top-level error that can occur during the interpretation of Dust code.
|
||||
#[derive(Debug, PartialEq)]
|
||||
@ -14,6 +14,10 @@ pub enum DustError<'src> {
|
||||
error: CompileError,
|
||||
source: &'src str,
|
||||
},
|
||||
NativeFunction {
|
||||
error: NativeFunctionError,
|
||||
source: &'src str,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'src> DustError<'src> {
|
||||
@ -57,12 +61,19 @@ impl<'src> DustError<'src> {
|
||||
error.detail_snippets(),
|
||||
error.help_snippets(),
|
||||
),
|
||||
Self::NativeFunction { error, .. } => (
|
||||
NativeFunctionError::title(),
|
||||
error.description(),
|
||||
error.detail_snippets(),
|
||||
error.help_snippets(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn source(&self) -> &str {
|
||||
match self {
|
||||
Self::Compile { source, .. } => source,
|
||||
Self::NativeFunction { source, .. } => source,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,36 @@
|
||||
use crate::{Instruction, Operation};
|
||||
|
||||
use super::InstructionData;
|
||||
|
||||
pub struct Call {
|
||||
pub destination: u8,
|
||||
pub prototype_index: u8,
|
||||
pub function_register: u8,
|
||||
pub argument_count: u8,
|
||||
}
|
||||
|
||||
impl From<&Instruction> for Call {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
let destination = instruction.a_field();
|
||||
let prototype_index = instruction.b_field();
|
||||
let function_register = instruction.b_field();
|
||||
let argument_count = instruction.c_field();
|
||||
|
||||
Call {
|
||||
destination,
|
||||
prototype_index,
|
||||
function_register,
|
||||
argument_count,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InstructionData> for Call {
|
||||
fn from(instruction: InstructionData) -> Self {
|
||||
let destination = instruction.a_field;
|
||||
let function_register = instruction.b_field;
|
||||
let argument_count = instruction.c_field;
|
||||
|
||||
Call {
|
||||
destination,
|
||||
function_register,
|
||||
argument_count,
|
||||
}
|
||||
}
|
||||
@ -23,7 +39,7 @@ impl From<&Instruction> for Call {
|
||||
impl From<Call> for Instruction {
|
||||
fn from(call: Call) -> Self {
|
||||
let a = call.destination;
|
||||
let b = call.prototype_index;
|
||||
let b = call.function_register;
|
||||
let c = call.argument_count;
|
||||
|
||||
Instruction::new(Operation::CALL, a, b, c, false, false, false)
|
||||
|
@ -1,5 +1,7 @@
|
||||
use crate::{Instruction, NativeFunction, Operation};
|
||||
|
||||
use super::InstructionData;
|
||||
|
||||
pub struct CallNative {
|
||||
pub destination: u8,
|
||||
pub function: NativeFunction,
|
||||
@ -19,6 +21,16 @@ impl From<&Instruction> for CallNative {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InstructionData> for CallNative {
|
||||
fn from(data: InstructionData) -> Self {
|
||||
CallNative {
|
||||
destination: data.a_field,
|
||||
function: NativeFunction::from(data.b_field),
|
||||
argument_count: data.c_field,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CallNative> for Instruction {
|
||||
fn from(call_native: CallNative) -> Self {
|
||||
let operation = Operation::CALL_NATIVE;
|
||||
|
@ -19,8 +19,8 @@ impl From<&Instruction> for Close {
|
||||
impl From<InstructionData> for Close {
|
||||
fn from(instruction: InstructionData) -> Self {
|
||||
Close {
|
||||
from: instruction.b,
|
||||
to: instruction.c,
|
||||
from: instruction.b_field,
|
||||
to: instruction.c_field,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,9 +10,9 @@ pub struct LoadBoolean {
|
||||
|
||||
impl From<InstructionData> for LoadBoolean {
|
||||
fn from(instruction: InstructionData) -> Self {
|
||||
let destination = instruction.a;
|
||||
let value = instruction.b != 0;
|
||||
let jump_next = instruction.c != 0;
|
||||
let destination = instruction.a_field;
|
||||
let value = instruction.b_field != 0;
|
||||
let jump_next = instruction.c_field != 0;
|
||||
|
||||
LoadBoolean {
|
||||
destination,
|
||||
|
@ -26,9 +26,9 @@ impl From<&Instruction> for LoadConstant {
|
||||
|
||||
impl From<InstructionData> for LoadConstant {
|
||||
fn from(instruction: InstructionData) -> Self {
|
||||
let destination = instruction.a;
|
||||
let constant_index = instruction.b;
|
||||
let jump_next = instruction.c != 0;
|
||||
let destination = instruction.a_field;
|
||||
let constant_index = instruction.b_field;
|
||||
let jump_next = instruction.c_field != 0;
|
||||
|
||||
LoadConstant {
|
||||
destination,
|
||||
|
@ -1,32 +1,39 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use super::{Instruction, Operation};
|
||||
use super::{Instruction, InstructionData, Operation};
|
||||
|
||||
pub struct LoadFunction {
|
||||
pub destination: u8,
|
||||
pub prototype_index: u8,
|
||||
pub record_index: u8,
|
||||
}
|
||||
|
||||
impl From<&Instruction> for LoadFunction {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
let destination = instruction.a_field();
|
||||
let prototype_index = instruction.b_field();
|
||||
let record_index = instruction.b_field();
|
||||
|
||||
LoadFunction {
|
||||
destination,
|
||||
prototype_index,
|
||||
record_index,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InstructionData> for LoadFunction {
|
||||
fn from(instruction: InstructionData) -> Self {
|
||||
LoadFunction {
|
||||
destination: instruction.a_field,
|
||||
record_index: instruction.b_field,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LoadFunction> for Instruction {
|
||||
fn from(load_function: LoadFunction) -> Self {
|
||||
let operation = Operation::LOAD_FUNCTION;
|
||||
|
||||
Instruction::new(
|
||||
operation,
|
||||
Operation::LOAD_FUNCTION,
|
||||
load_function.destination,
|
||||
load_function.prototype_index,
|
||||
load_function.record_index,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
@ -37,6 +44,6 @@ impl From<LoadFunction> for Instruction {
|
||||
|
||||
impl Display for LoadFunction {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "R{} = P{}", self.destination, self.prototype_index)
|
||||
write!(f, "R{} = P{}", self.destination, self.record_index)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
use crate::{Instruction, Operation};
|
||||
|
||||
use super::InstructionData;
|
||||
|
||||
pub struct LoadList {
|
||||
pub destination: u8,
|
||||
pub start_register: u8,
|
||||
@ -17,6 +19,18 @@ impl From<&Instruction> for LoadList {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InstructionData> for LoadList {
|
||||
fn from(instruction: InstructionData) -> Self {
|
||||
let destination = instruction.a_field;
|
||||
let start_register = instruction.b_field;
|
||||
|
||||
LoadList {
|
||||
destination,
|
||||
start_register,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LoadList> for Instruction {
|
||||
fn from(load_list: LoadList) -> Self {
|
||||
let operation = Operation::LOAD_LIST;
|
||||
|
@ -1,5 +1,7 @@
|
||||
use crate::{Instruction, Operation};
|
||||
|
||||
use super::InstructionData;
|
||||
|
||||
pub struct LoadSelf {
|
||||
pub destination: u8,
|
||||
}
|
||||
@ -12,6 +14,14 @@ impl From<&Instruction> for LoadSelf {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InstructionData> for LoadSelf {
|
||||
fn from(instruction: InstructionData) -> Self {
|
||||
let destination = instruction.a_field;
|
||||
|
||||
LoadSelf { destination }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LoadSelf> for Instruction {
|
||||
fn from(load_self: LoadSelf) -> Self {
|
||||
let operation = Operation::LOAD_SELF;
|
||||
|
@ -154,16 +154,6 @@ use crate::NativeFunction;
|
||||
#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct Instruction(u32);
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct InstructionData {
|
||||
pub a: u8,
|
||||
pub b: u8,
|
||||
pub c: u8,
|
||||
pub b_is_constant: bool,
|
||||
pub c_is_constant: bool,
|
||||
pub d: bool,
|
||||
}
|
||||
|
||||
impl Instruction {
|
||||
pub fn new(
|
||||
operation: Operation,
|
||||
@ -231,12 +221,12 @@ impl Instruction {
|
||||
(
|
||||
self.operation(),
|
||||
InstructionData {
|
||||
a: self.a_field(),
|
||||
b: self.b_field(),
|
||||
c: self.c_field(),
|
||||
a_field: self.a_field(),
|
||||
b_field: self.b_field(),
|
||||
c_field: self.c_field(),
|
||||
b_is_constant: self.b_is_constant(),
|
||||
c_is_constant: self.c_is_constant(),
|
||||
d: self.d_field(),
|
||||
d_field: self.d_field(),
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -265,10 +255,10 @@ impl Instruction {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn load_function(destination: u8, prototype_index: u8) -> Instruction {
|
||||
pub fn load_function(destination: u8, record_index: u8) -> Instruction {
|
||||
Instruction::from(LoadFunction {
|
||||
destination,
|
||||
prototype_index,
|
||||
record_index,
|
||||
})
|
||||
}
|
||||
|
||||
@ -385,10 +375,10 @@ impl Instruction {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn call(destination: u8, prototype_index: u8, argument_count: u8) -> Instruction {
|
||||
pub fn call(destination: u8, function_register: u8, argument_count: u8) -> Instruction {
|
||||
Instruction::from(Call {
|
||||
destination,
|
||||
prototype_index,
|
||||
function_register,
|
||||
argument_count,
|
||||
})
|
||||
}
|
||||
@ -487,6 +477,7 @@ impl Instruction {
|
||||
match self.operation() {
|
||||
Operation::LOAD_BOOLEAN
|
||||
| Operation::LOAD_CONSTANT
|
||||
| Operation::LOAD_FUNCTION
|
||||
| Operation::LOAD_LIST
|
||||
| Operation::LOAD_SELF
|
||||
| Operation::GET_LOCAL
|
||||
@ -697,16 +688,18 @@ impl Instruction {
|
||||
Operation::CALL => {
|
||||
let Call {
|
||||
destination,
|
||||
prototype_index,
|
||||
function_register: record_index,
|
||||
argument_count,
|
||||
} = Call::from(self);
|
||||
let arguments_start = destination.saturating_sub(argument_count);
|
||||
|
||||
match argument_count {
|
||||
0 => format!("R{destination} = P{prototype_index}()"),
|
||||
1 => format!("R{destination} = P{prototype_index}(R{arguments_start})"),
|
||||
0 => format!("R{destination} = P{record_index}()"),
|
||||
1 => format!("R{destination} = P{record_index}(R{arguments_start})"),
|
||||
_ => {
|
||||
format!("R{destination} = P{prototype_index}(R{arguments_start}..R{destination})")
|
||||
format!(
|
||||
"R{destination} = P{record_index}(R{arguments_start}..R{destination})"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -760,10 +753,32 @@ impl Instruction {
|
||||
|
||||
impl Debug for Instruction {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{self}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Instruction {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{} {}", self.operation(), self.disassembly_info())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct InstructionData {
|
||||
pub a_field: u8,
|
||||
pub b_field: u8,
|
||||
pub c_field: u8,
|
||||
pub d_field: bool,
|
||||
pub b_is_constant: bool,
|
||||
pub c_is_constant: bool,
|
||||
}
|
||||
|
||||
impl Display for InstructionData {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub enum Argument {
|
||||
Constant(u8),
|
||||
|
@ -21,8 +21,8 @@ impl From<&Instruction> for Point {
|
||||
impl From<InstructionData> for Point {
|
||||
fn from(instruction: InstructionData) -> Self {
|
||||
Point {
|
||||
from: instruction.b,
|
||||
to: instruction.c,
|
||||
from: instruction.b_field,
|
||||
to: instruction.c_field,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +1,26 @@
|
||||
use std::panic;
|
||||
use std::{ops::Range, panic};
|
||||
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{vm::Record, DustString, NativeFunctionError, Value};
|
||||
use crate::{
|
||||
vm::{Record, ThreadSignal},
|
||||
NativeFunctionError,
|
||||
};
|
||||
|
||||
pub fn panic(
|
||||
record: &mut Record,
|
||||
arguments: SmallVec<[&Value; 4]>,
|
||||
) -> Result<Option<Value>, NativeFunctionError> {
|
||||
let mut message: Option<DustString> = None;
|
||||
_: Option<u8>,
|
||||
argument_range: Range<u8>,
|
||||
) -> Result<ThreadSignal, NativeFunctionError> {
|
||||
let position = record.current_position();
|
||||
let mut message = format!("Dust panic at {position}!");
|
||||
|
||||
for value_ref in arguments {
|
||||
let string = value_ref.display(record);
|
||||
for register_index in argument_range {
|
||||
let value = record.open_register(register_index);
|
||||
|
||||
match message {
|
||||
Some(ref mut message) => message.push_str(&string),
|
||||
None => message = Some(string),
|
||||
if let Some(string) = value.as_string() {
|
||||
message.push_str(&string);
|
||||
message.push('\n');
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(message) = message {
|
||||
panic!("{message}");
|
||||
} else {
|
||||
panic!("Explicit panic");
|
||||
}
|
||||
panic!("{}", message)
|
||||
}
|
||||
|
@ -1,13 +1,15 @@
|
||||
use std::io::{stdin, stdout, Write};
|
||||
use std::ops::Range;
|
||||
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::vm::{Register, ThreadSignal};
|
||||
use crate::{vm::Record, ConcreteValue, NativeFunctionError, Value};
|
||||
|
||||
pub fn read_line(
|
||||
record: &mut Record,
|
||||
_: SmallVec<[&Value; 4]>,
|
||||
) -> Result<Option<Value>, NativeFunctionError> {
|
||||
destination: Option<u8>,
|
||||
_argument_range: Range<u8>,
|
||||
) -> Result<ThreadSignal, NativeFunctionError> {
|
||||
let destination = destination.unwrap();
|
||||
let mut buffer = String::new();
|
||||
|
||||
match stdin().read_line(&mut buffer) {
|
||||
@ -16,54 +18,77 @@ pub fn read_line(
|
||||
|
||||
buffer.truncate(length.saturating_sub(1));
|
||||
|
||||
Ok(Some(Value::Concrete(ConcreteValue::string(buffer))))
|
||||
let register = Register::Value(Value::Concrete(ConcreteValue::string(buffer)));
|
||||
|
||||
record.set_register(destination, register);
|
||||
}
|
||||
Err(error) => Err(NativeFunctionError::Io {
|
||||
Err(error) => {
|
||||
return Err(NativeFunctionError::Io {
|
||||
error: error.kind(),
|
||||
}),
|
||||
position: record.current_position(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ThreadSignal::Continue)
|
||||
}
|
||||
|
||||
pub fn write(
|
||||
record: &mut Record,
|
||||
arguments: SmallVec<[&Value; 4]>,
|
||||
) -> Result<Option<Value>, NativeFunctionError> {
|
||||
_destination: Option<u8>,
|
||||
argument_range: Range<u8>,
|
||||
) -> Result<ThreadSignal, NativeFunctionError> {
|
||||
let mut stdout = stdout();
|
||||
|
||||
for argument in arguments {
|
||||
let string = argument.display(record);
|
||||
for register_index in argument_range {
|
||||
let value = record.open_register(register_index);
|
||||
let string = value.display(record);
|
||||
|
||||
stdout
|
||||
.write_all(string.as_bytes())
|
||||
.write(string.as_bytes())
|
||||
.map_err(|io_error| NativeFunctionError::Io {
|
||||
error: io_error.kind(),
|
||||
position: record.current_position(),
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
stdout.flush().map_err(|io_error| NativeFunctionError::Io {
|
||||
error: io_error.kind(),
|
||||
position: record.current_position(),
|
||||
})?;
|
||||
|
||||
Ok(ThreadSignal::Continue)
|
||||
}
|
||||
|
||||
pub fn write_line(
|
||||
record: &mut Record,
|
||||
arguments: SmallVec<[&Value; 4]>,
|
||||
) -> Result<Option<Value>, NativeFunctionError> {
|
||||
let mut stdout = stdout();
|
||||
_destination: Option<u8>,
|
||||
argument_range: Range<u8>,
|
||||
) -> Result<ThreadSignal, NativeFunctionError> {
|
||||
let mut stdout = stdout().lock();
|
||||
|
||||
for argument in arguments {
|
||||
let string = argument.display(record);
|
||||
for register_index in argument_range {
|
||||
let value = record.open_register(register_index);
|
||||
let string = value.display(record);
|
||||
|
||||
stdout
|
||||
.write_all(string.as_bytes())
|
||||
.write(string.as_bytes())
|
||||
.map_err(|io_error| NativeFunctionError::Io {
|
||||
error: io_error.kind(),
|
||||
position: record.current_position(),
|
||||
})?;
|
||||
}
|
||||
|
||||
stdout
|
||||
.write(b"\n")
|
||||
.map_err(|io_error| NativeFunctionError::Io {
|
||||
error: io_error.kind(),
|
||||
position: record.current_position(),
|
||||
})?;
|
||||
}
|
||||
|
||||
stdout.flush().map_err(|io_error| NativeFunctionError::Io {
|
||||
error: io_error.kind(),
|
||||
position: record.current_position(),
|
||||
})?;
|
||||
|
||||
Ok(None)
|
||||
Ok(ThreadSignal::Continue)
|
||||
}
|
||||
|
@ -9,13 +9,17 @@ mod string;
|
||||
use std::{
|
||||
fmt::{self, Display, Formatter},
|
||||
io::ErrorKind as IoErrorKind,
|
||||
ops::Range,
|
||||
string::ParseError,
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
|
||||
use crate::{AnnotatedError, FunctionType, Span, Type, Value, Vm};
|
||||
use crate::{
|
||||
vm::{Record, ThreadSignal},
|
||||
AnnotatedError, FunctionType, Span, Type,
|
||||
};
|
||||
|
||||
macro_rules! define_native_function {
|
||||
($(($name:ident, $bytes:literal, $str:expr, $type:expr, $function:expr)),*) => {
|
||||
@ -32,12 +36,13 @@ macro_rules! define_native_function {
|
||||
impl NativeFunction {
|
||||
pub fn call(
|
||||
&self,
|
||||
vm: &Vm<'_>,
|
||||
arguments: SmallVec<[&Value; 4]>,
|
||||
) -> Result<Option<Value>, NativeFunctionError> {
|
||||
record: &mut Record,
|
||||
destination: Option<u8>,
|
||||
argument_range: Range<u8>,
|
||||
) -> Result<ThreadSignal, NativeFunctionError> {
|
||||
match self {
|
||||
$(
|
||||
NativeFunction::$name => $function(vm, arguments),
|
||||
NativeFunction::$name => $function(record, destination, argument_range),
|
||||
)*
|
||||
}
|
||||
}
|
||||
@ -258,7 +263,7 @@ pub enum NativeFunctionError {
|
||||
position: Span,
|
||||
},
|
||||
Panic {
|
||||
message: Option<String>,
|
||||
message: String,
|
||||
position: Span,
|
||||
},
|
||||
Parse {
|
||||
@ -288,10 +293,28 @@ impl AnnotatedError for NativeFunctionError {
|
||||
}
|
||||
|
||||
fn detail_snippets(&self) -> SmallVec<[(String, Span); 2]> {
|
||||
todo!()
|
||||
match self {
|
||||
NativeFunctionError::ExpectedArgumentCount {
|
||||
expected,
|
||||
found,
|
||||
position,
|
||||
} => smallvec![(
|
||||
format!("Expected {expected} arguments, found {found}"),
|
||||
*position
|
||||
)],
|
||||
NativeFunctionError::Panic { message, position } => {
|
||||
smallvec![(format!("Dust panic!\n{message}"), *position)]
|
||||
}
|
||||
NativeFunctionError::Parse { error, position } => {
|
||||
smallvec![(format!("{error}"), *position)]
|
||||
}
|
||||
NativeFunctionError::Io { error, position } => {
|
||||
smallvec![(format!("{error}"), *position)]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn help_snippets(&self) -> SmallVec<[(String, Span); 2]> {
|
||||
todo!()
|
||||
SmallVec::new()
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,21 @@
|
||||
use smallvec::SmallVec;
|
||||
use std::ops::Range;
|
||||
|
||||
use crate::{ConcreteValue, NativeFunctionError, Value, Vm};
|
||||
use crate::{
|
||||
vm::{Record, Register, ThreadSignal},
|
||||
ConcreteValue, NativeFunctionError, Value,
|
||||
};
|
||||
|
||||
pub fn to_string(
|
||||
vm: &Vm,
|
||||
arguments: SmallVec<[&Value; 4]>,
|
||||
) -> Result<Option<Value>, NativeFunctionError> {
|
||||
if arguments.len() != 1 {
|
||||
return Err(NativeFunctionError::ExpectedArgumentCount {
|
||||
expected: 1,
|
||||
found: 0,
|
||||
position: vm.current_position(),
|
||||
});
|
||||
}
|
||||
record: &mut Record,
|
||||
destination: Option<u8>,
|
||||
argument_range: Range<u8>,
|
||||
) -> Result<ThreadSignal, NativeFunctionError> {
|
||||
let argument_value = record.open_register(argument_range.start);
|
||||
let argument_string = argument_value.display(record);
|
||||
let destination = destination.unwrap();
|
||||
let register = Register::Value(Value::Concrete(ConcreteValue::string(argument_string)));
|
||||
|
||||
let argument_string = arguments[0].display(vm);
|
||||
record.set_register(destination, register);
|
||||
|
||||
Ok(Some(Value::Concrete(ConcreteValue::string(
|
||||
argument_string,
|
||||
))))
|
||||
Ok(ThreadSignal::Continue)
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ use super::DustString;
|
||||
pub struct Function {
|
||||
pub name: Option<DustString>,
|
||||
pub r#type: FunctionType,
|
||||
pub record_index: usize,
|
||||
pub prototype_index: usize,
|
||||
}
|
||||
|
||||
|
@ -45,6 +45,14 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_string(&self) -> Option<&DustString> {
|
||||
if let Value::Concrete(ConcreteValue::String(value)) = self {
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn r#type(&self) -> Type {
|
||||
match self {
|
||||
Value::Concrete(concrete_value) => concrete_value.r#type(),
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
|
||||
use super::FunctionCall;
|
||||
use super::{FunctionCall, VmError};
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct CallStack {
|
||||
@ -31,6 +31,22 @@ impl CallStack {
|
||||
pub fn pop(&mut self) -> Option<FunctionCall> {
|
||||
self.calls.pop()
|
||||
}
|
||||
|
||||
pub fn last(&self) -> Option<&FunctionCall> {
|
||||
self.calls.last()
|
||||
}
|
||||
|
||||
pub fn pop_or_panic(&mut self) -> FunctionCall {
|
||||
assert!(!self.is_empty(), "{}", VmError::CallStackUnderflow);
|
||||
|
||||
self.calls.pop().unwrap()
|
||||
}
|
||||
|
||||
pub fn last_or_panic(&self) -> &FunctionCall {
|
||||
assert!(!self.is_empty(), "{}", VmError::CallStackUnderflow);
|
||||
|
||||
self.calls.last().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for CallStack {
|
||||
@ -43,8 +59,8 @@ impl Display for CallStack {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
writeln!(f, "-- DUST CALL STACK --")?;
|
||||
|
||||
for FunctionCall { function, .. } in &self.calls {
|
||||
writeln!(f, "{function}")?;
|
||||
for function_call in &self.calls {
|
||||
writeln!(f, "{function_call:?}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -1,12 +1,13 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use crate::DustString;
|
||||
use crate::{DustString, InstructionData, Value};
|
||||
|
||||
use super::call_stack::CallStack;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum VmError {
|
||||
CallStackUnderflow { thread_name: DustString },
|
||||
CallStackUnderflow,
|
||||
ExpectedFunction { value: Value },
|
||||
InstructionIndexOutOfBounds { call_stack: CallStack, ip: usize },
|
||||
MalformedInstruction { instruction: InstructionData },
|
||||
}
|
||||
@ -14,12 +15,18 @@ pub enum VmError {
|
||||
impl Display for VmError {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::CallStackUnderflow { thread_name } => {
|
||||
write!(f, "Call stack underflow in thread {thread_name}")
|
||||
Self::CallStackUnderflow => {
|
||||
write!(f, "Call stack underflow")
|
||||
}
|
||||
Self::ExpectedFunction { value } => {
|
||||
write!(f, "Expected function, found {value}")
|
||||
}
|
||||
Self::InstructionIndexOutOfBounds { call_stack, ip } => {
|
||||
write!(f, "Instruction index {} out of bounds\n{call_stack}", ip)
|
||||
}
|
||||
Self::MalformedInstruction { instruction } => {
|
||||
write!(f, "Malformed instruction {instruction}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,9 +11,10 @@ use std::{
|
||||
thread::spawn,
|
||||
};
|
||||
|
||||
pub use call_stack::CallStack;
|
||||
pub use error::VmError;
|
||||
pub use record::Record;
|
||||
use thread::Thread;
|
||||
pub use thread::{Thread, ThreadSignal};
|
||||
|
||||
use crate::{compile, Chunk, DustError, Value};
|
||||
|
||||
@ -94,7 +95,7 @@ impl Display for Pointer {
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct FunctionCall {
|
||||
prototype_index: usize,
|
||||
record_index: usize,
|
||||
return_register: u8,
|
||||
argument_count: u8,
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::env::consts::OS;
|
||||
use std::mem::replace;
|
||||
|
||||
use smallvec::SmallVec;
|
||||
|
||||
@ -134,6 +134,30 @@ impl Record {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replace_register_or_clone_constant(
|
||||
&mut self,
|
||||
register_index: u8,
|
||||
new_register: Register,
|
||||
) -> Value {
|
||||
let register_index = register_index as usize;
|
||||
|
||||
assert!(
|
||||
register_index < self.stack.len(),
|
||||
"VM Error: Register index out of bounds"
|
||||
);
|
||||
|
||||
let old_register = replace(&mut self.stack[register_index], new_register);
|
||||
|
||||
match old_register {
|
||||
Register::Value(value) => value,
|
||||
Register::Pointer(pointer) => match pointer {
|
||||
Pointer::Stack(register_index) => self.open_register(register_index).clone(),
|
||||
Pointer::Constant(constant_index) => self.get_constant(constant_index).clone(),
|
||||
},
|
||||
Register::Empty => panic!("VM Error: Register {register_index} is empty"),
|
||||
}
|
||||
}
|
||||
|
||||
/// DRY helper to get a value from an Argument
|
||||
pub fn get_argument(&self, index: u8, is_constant: bool) -> &Value {
|
||||
if is_constant {
|
||||
|
@ -1,11 +1,15 @@
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{
|
||||
instruction::{Close, LoadBoolean, LoadConstant, Point},
|
||||
AbstractList, ConcreteValue, Instruction, InstructionData, NativeFunction, Type, Value,
|
||||
instruction::{
|
||||
Call, CallNative, Close, LoadBoolean, LoadConstant, LoadFunction, LoadList, LoadSelf, Point,
|
||||
},
|
||||
vm::VmError,
|
||||
AbstractList, ConcreteValue, Function, Instruction, InstructionData, NativeFunction, Type,
|
||||
Value,
|
||||
};
|
||||
|
||||
use super::{thread::ThreadSignal, Pointer, Record, Register};
|
||||
use super::{thread::ThreadSignal, FunctionCall, Pointer, Record, Register};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct RunAction {
|
||||
@ -33,11 +37,12 @@ impl From<Instruction> for RunAction {
|
||||
|
||||
pub type RunnerLogic = fn(InstructionData, &mut Record) -> ThreadSignal;
|
||||
|
||||
pub const RUNNER_LOGIC_TABLE: [RunnerLogic; 24] = [
|
||||
pub const RUNNER_LOGIC_TABLE: [RunnerLogic; 25] = [
|
||||
r#move,
|
||||
close,
|
||||
load_boolean,
|
||||
load_constant,
|
||||
load_function,
|
||||
load_list,
|
||||
load_self,
|
||||
get_local,
|
||||
@ -127,11 +132,14 @@ pub fn load_constant(instruction_data: InstructionData, record: &mut Record) ->
|
||||
}
|
||||
|
||||
pub fn load_list(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
|
||||
let InstructionData { a, b, .. } = instruction_data;
|
||||
let mut item_pointers = Vec::with_capacity((a - b) as usize);
|
||||
let LoadList {
|
||||
destination,
|
||||
start_register,
|
||||
} = instruction_data.into();
|
||||
let mut item_pointers = Vec::with_capacity((destination - start_register) as usize);
|
||||
let mut item_type = Type::Any;
|
||||
|
||||
for register_index in b..a {
|
||||
for register_index in start_register..destination {
|
||||
match record.get_register(register_index) {
|
||||
Register::Empty => continue,
|
||||
Register::Value(value) => {
|
||||
@ -157,37 +165,62 @@ pub fn load_list(instruction_data: InstructionData, record: &mut Record) -> Thre
|
||||
});
|
||||
let register = Register::Value(list_value);
|
||||
|
||||
record.set_register(a, register);
|
||||
record.set_register(destination, register);
|
||||
|
||||
ThreadSignal::Continue
|
||||
}
|
||||
|
||||
pub fn load_function(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
|
||||
let LoadFunction {
|
||||
destination,
|
||||
record_index,
|
||||
} = instruction_data.into();
|
||||
|
||||
ThreadSignal::Continue
|
||||
}
|
||||
|
||||
pub fn load_self(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
|
||||
let InstructionData { a, .. } = instruction_data;
|
||||
let LoadSelf { destination } = instruction_data.into();
|
||||
let register = Register::Value(Value::SelfFunction);
|
||||
|
||||
record.set_register(a, register);
|
||||
record.set_register(destination, register);
|
||||
|
||||
ThreadSignal::Continue
|
||||
}
|
||||
|
||||
pub fn get_local(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
|
||||
let InstructionData { a, b, .. } = instruction_data;
|
||||
let InstructionData {
|
||||
a_field: a,
|
||||
b_field: b,
|
||||
..
|
||||
} = instruction_data;
|
||||
let local_register_index = record.get_local_register(b);
|
||||
let register = Register::Pointer(Pointer::Stack(local_register_index));
|
||||
|
||||
record.set_register(a, register);
|
||||
|
||||
ThreadSignal::Continue
|
||||
}
|
||||
|
||||
pub fn set_local(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
|
||||
let InstructionData { b, c, .. } = instruction_data;
|
||||
let InstructionData {
|
||||
b_field: b,
|
||||
c_field: c,
|
||||
..
|
||||
} = instruction_data;
|
||||
let local_register_index = record.get_local_register(c);
|
||||
let register = Register::Pointer(Pointer::Stack(b));
|
||||
|
||||
record.set_register(local_register_index, register);
|
||||
|
||||
ThreadSignal::Continue
|
||||
}
|
||||
|
||||
pub fn add(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
|
||||
let InstructionData {
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
a_field: a,
|
||||
b_field: b,
|
||||
c_field: c,
|
||||
b_is_constant,
|
||||
c_is_constant,
|
||||
..
|
||||
@ -206,13 +239,15 @@ pub fn add(instruction_data: InstructionData, record: &mut Record) -> ThreadSign
|
||||
let register = Register::Value(sum);
|
||||
|
||||
record.set_register(a, register);
|
||||
|
||||
ThreadSignal::Continue
|
||||
}
|
||||
|
||||
pub fn subtract(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
|
||||
let InstructionData {
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
a_field: a,
|
||||
b_field: b,
|
||||
c_field: c,
|
||||
b_is_constant,
|
||||
c_is_constant,
|
||||
..
|
||||
@ -231,13 +266,15 @@ pub fn subtract(instruction_data: InstructionData, record: &mut Record) -> Threa
|
||||
let register = Register::Value(difference);
|
||||
|
||||
record.set_register(a, register);
|
||||
|
||||
ThreadSignal::Continue
|
||||
}
|
||||
|
||||
pub fn multiply(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
|
||||
let InstructionData {
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
a_field: a,
|
||||
b_field: b,
|
||||
c_field: c,
|
||||
b_is_constant,
|
||||
c_is_constant,
|
||||
..
|
||||
@ -256,13 +293,15 @@ pub fn multiply(instruction_data: InstructionData, record: &mut Record) -> Threa
|
||||
let register = Register::Value(product);
|
||||
|
||||
record.set_register(a, register);
|
||||
|
||||
ThreadSignal::Continue
|
||||
}
|
||||
|
||||
pub fn divide(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
|
||||
let InstructionData {
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
a_field: a,
|
||||
b_field: b,
|
||||
c_field: c,
|
||||
b_is_constant,
|
||||
c_is_constant,
|
||||
..
|
||||
@ -281,13 +320,15 @@ pub fn divide(instruction_data: InstructionData, record: &mut Record) -> ThreadS
|
||||
let register = Register::Value(quotient);
|
||||
|
||||
record.set_register(a, register);
|
||||
|
||||
ThreadSignal::Continue
|
||||
}
|
||||
|
||||
pub fn modulo(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
|
||||
let InstructionData {
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
a_field: a,
|
||||
b_field: b,
|
||||
c_field: c,
|
||||
b_is_constant,
|
||||
c_is_constant,
|
||||
..
|
||||
@ -306,13 +347,15 @@ pub fn modulo(instruction_data: InstructionData, record: &mut Record) -> ThreadS
|
||||
let register = Register::Value(remainder);
|
||||
|
||||
record.set_register(a, register);
|
||||
|
||||
ThreadSignal::Continue
|
||||
}
|
||||
|
||||
pub fn test(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
|
||||
let InstructionData {
|
||||
b,
|
||||
b_field: b,
|
||||
b_is_constant,
|
||||
c,
|
||||
c_field: c,
|
||||
..
|
||||
} = instruction_data;
|
||||
let value = record.get_argument(b, b_is_constant);
|
||||
@ -324,16 +367,17 @@ pub fn test(instruction_data: InstructionData, record: &mut Record) -> ThreadSig
|
||||
let test_value = c != 0;
|
||||
|
||||
if boolean == test_value {
|
||||
record.ip += 2;
|
||||
} else {
|
||||
record.ip += 1;
|
||||
}
|
||||
|
||||
ThreadSignal::Continue
|
||||
}
|
||||
|
||||
pub fn test_set(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
|
||||
let InstructionData {
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
a_field: a,
|
||||
b_field: b,
|
||||
c_field: c,
|
||||
b_is_constant,
|
||||
..
|
||||
} = instruction_data;
|
||||
@ -346,7 +390,7 @@ pub fn test_set(instruction_data: InstructionData, record: &mut Record) -> Threa
|
||||
let test_value = c != 0;
|
||||
|
||||
if boolean == test_value {
|
||||
record.ip += 2;
|
||||
record.ip += 1;
|
||||
} else {
|
||||
let pointer = if b_is_constant {
|
||||
Pointer::Constant(b)
|
||||
@ -357,15 +401,17 @@ pub fn test_set(instruction_data: InstructionData, record: &mut Record) -> Threa
|
||||
|
||||
record.set_register(a, register);
|
||||
}
|
||||
|
||||
ThreadSignal::Continue
|
||||
}
|
||||
|
||||
pub fn equal(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
|
||||
let InstructionData {
|
||||
b,
|
||||
c,
|
||||
b_field: b,
|
||||
c_field: c,
|
||||
b_is_constant,
|
||||
c_is_constant,
|
||||
d,
|
||||
d_field: d,
|
||||
..
|
||||
} = instruction_data;
|
||||
let left = record.get_argument(b, b_is_constant);
|
||||
@ -373,18 +419,19 @@ pub fn equal(instruction_data: InstructionData, record: &mut Record) -> ThreadSi
|
||||
let is_equal = left == right;
|
||||
|
||||
if is_equal == d {
|
||||
record.ip += 2;
|
||||
} else {
|
||||
record.ip += 1;
|
||||
}
|
||||
|
||||
ThreadSignal::Continue
|
||||
}
|
||||
|
||||
pub fn less(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
|
||||
let InstructionData {
|
||||
b,
|
||||
c,
|
||||
b_field: b,
|
||||
c_field: c,
|
||||
b_is_constant,
|
||||
c_is_constant,
|
||||
d,
|
||||
d_field: d,
|
||||
..
|
||||
} = instruction_data;
|
||||
let left = record.get_argument(b, b_is_constant);
|
||||
@ -392,18 +439,19 @@ pub fn less(instruction_data: InstructionData, record: &mut Record) -> ThreadSig
|
||||
let is_less = left < right;
|
||||
|
||||
if is_less == d {
|
||||
record.ip += 2;
|
||||
} else {
|
||||
record.ip += 1;
|
||||
}
|
||||
|
||||
ThreadSignal::Continue
|
||||
}
|
||||
|
||||
pub fn less_equal(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
|
||||
let InstructionData {
|
||||
b,
|
||||
c,
|
||||
b_field: b,
|
||||
c_field: c,
|
||||
b_is_constant,
|
||||
c_is_constant,
|
||||
d,
|
||||
d_field: d,
|
||||
..
|
||||
} = instruction_data;
|
||||
let left = record.get_argument(b, b_is_constant);
|
||||
@ -411,15 +459,16 @@ pub fn less_equal(instruction_data: InstructionData, record: &mut Record) -> Thr
|
||||
let is_less_or_equal = left <= right;
|
||||
|
||||
if is_less_or_equal == d {
|
||||
record.ip += 2;
|
||||
} else {
|
||||
record.ip += 1;
|
||||
}
|
||||
|
||||
ThreadSignal::Continue
|
||||
}
|
||||
|
||||
pub fn negate(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
|
||||
let InstructionData {
|
||||
a,
|
||||
b,
|
||||
a_field: a,
|
||||
b_field: b,
|
||||
b_is_constant,
|
||||
..
|
||||
} = instruction_data;
|
||||
@ -428,12 +477,14 @@ pub fn negate(instruction_data: InstructionData, record: &mut Record) -> ThreadS
|
||||
let register = Register::Value(negated);
|
||||
|
||||
record.set_register(a, register);
|
||||
|
||||
ThreadSignal::Continue
|
||||
}
|
||||
|
||||
pub fn not(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
|
||||
let InstructionData {
|
||||
a,
|
||||
b,
|
||||
a_field: a,
|
||||
b_field: b,
|
||||
b_is_constant,
|
||||
..
|
||||
} = instruction_data;
|
||||
@ -445,10 +496,16 @@ pub fn not(instruction_data: InstructionData, record: &mut Record) -> ThreadSign
|
||||
let register = Register::Value(Value::Concrete(not));
|
||||
|
||||
record.set_register(a, register);
|
||||
|
||||
ThreadSignal::Continue
|
||||
}
|
||||
|
||||
pub fn jump(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
|
||||
let InstructionData { b, c, .. } = instruction_data;
|
||||
let InstructionData {
|
||||
b_field: b,
|
||||
c_field: c,
|
||||
..
|
||||
} = instruction_data;
|
||||
let offset = b as usize;
|
||||
let is_positive = c != 0;
|
||||
|
||||
@ -457,88 +514,55 @@ pub fn jump(instruction_data: InstructionData, record: &mut Record) -> ThreadSig
|
||||
} else {
|
||||
record.ip -= offset
|
||||
}
|
||||
|
||||
ThreadSignal::Continue
|
||||
}
|
||||
|
||||
pub fn call(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
|
||||
let InstructionData { a, b, c, .. } = instruction_data;
|
||||
let prototype = record.get_prototype(b);
|
||||
|
||||
let first_argument_index = a - c;
|
||||
let mut argument_index = 0;
|
||||
|
||||
for argument_register_index in first_argument_index..a {
|
||||
let target_register_is_empty = matches!(
|
||||
record.stack[argument_register_index as usize],
|
||||
Register::Empty
|
||||
);
|
||||
|
||||
if target_register_is_empty {
|
||||
continue;
|
||||
let Call {
|
||||
destination,
|
||||
function_register,
|
||||
argument_count,
|
||||
} = instruction_data.into();
|
||||
let function_value = record.open_register(function_register);
|
||||
let function = match function_value {
|
||||
Value::Function(function) => function,
|
||||
_ => panic!(
|
||||
"{}",
|
||||
VmError::ExpectedFunction {
|
||||
value: function_value.clone()
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
function_record.set_register(
|
||||
argument_index as u8,
|
||||
Register::Pointer(Pointer::ParentStack(argument_register_index)),
|
||||
);
|
||||
|
||||
argument_index += 1;
|
||||
}
|
||||
|
||||
let return_value = function_record.run();
|
||||
|
||||
if let Some(concrete_value) = return_value {
|
||||
let register = Register::Value(concrete_value);
|
||||
|
||||
record.set_register(a, register);
|
||||
}
|
||||
ThreadSignal::Call(FunctionCall {
|
||||
record_index: function.record_index,
|
||||
return_register: destination,
|
||||
argument_count,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn call_native(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
|
||||
let InstructionData { a, b, c, .. } = instruction_data;
|
||||
let first_argument_index = (a - c) as usize;
|
||||
let argument_range = first_argument_index..a as usize;
|
||||
let mut arguments: SmallVec<[&Value; 4]> = SmallVec::new();
|
||||
let CallNative {
|
||||
destination,
|
||||
function,
|
||||
argument_count,
|
||||
} = instruction_data.into();
|
||||
let first_argument_index = destination - argument_count;
|
||||
let argument_range = first_argument_index..destination;
|
||||
|
||||
for register_index in argument_range {
|
||||
let register = &record.stack[register_index];
|
||||
let value = match register {
|
||||
Register::Value(value) => value,
|
||||
Register::Pointer(pointer) => {
|
||||
let value_option = record.follow_pointer_allow_empty(*pointer);
|
||||
let function = NativeFunction::from(function);
|
||||
let thread_signal = function
|
||||
.call(record, Some(destination), argument_range)
|
||||
.unwrap_or_else(|error| panic!("{error:?}"));
|
||||
|
||||
match value_option {
|
||||
Some(value) => value,
|
||||
None => continue,
|
||||
}
|
||||
}
|
||||
Register::Empty => continue,
|
||||
};
|
||||
|
||||
arguments.push(value);
|
||||
thread_signal
|
||||
}
|
||||
|
||||
let function = NativeFunction::from(b);
|
||||
let return_value = function.call(record.arguments).unwrap();
|
||||
pub fn r#return(instruction_data: InstructionData, _: &mut Record) -> ThreadSignal {
|
||||
let should_return_value = instruction_data.b_field != 0;
|
||||
|
||||
if let Some(value) = return_value {
|
||||
let register = Register::Value(value);
|
||||
|
||||
record.set_register(a, register);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn r#return(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
|
||||
let should_return_value = instruction_data.b != 0;
|
||||
|
||||
if !should_return_value {}
|
||||
|
||||
if let Some(register_index) = &record.last_assigned_register {
|
||||
let return_value = record.open_register(*register_index).clone();
|
||||
|
||||
record.return_value = Some(return_value);
|
||||
} else {
|
||||
panic!("Stack underflow");
|
||||
}
|
||||
ThreadSignal::Return(should_return_value)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1,16 +1,15 @@
|
||||
use crate::{Chunk, Value};
|
||||
use std::mem::swap;
|
||||
|
||||
use super::{call_stack::CallStack, record::Record, runner::RunAction, FunctionCall, VmError};
|
||||
use crate::{vm::Register, Chunk, Value};
|
||||
|
||||
use super::{record::Record, runner::RunAction, CallStack, FunctionCall, VmError};
|
||||
|
||||
fn create_records(chunk: Chunk, records: &mut Vec<Record>) {
|
||||
let (_, _, instructions, positions, constants, locals, prototypes, stack_size) =
|
||||
chunk.take_data();
|
||||
let actions = instructions
|
||||
.into_iter()
|
||||
.map(|instruction| RunAction::from(instruction))
|
||||
.collect();
|
||||
let actions = instructions.into_iter().map(RunAction::from).collect();
|
||||
let record = Record::new(
|
||||
Vec::with_capacity(stack_size),
|
||||
vec![Register::Empty; stack_size],
|
||||
constants,
|
||||
locals,
|
||||
actions,
|
||||
@ -27,27 +26,23 @@ fn create_records(chunk: Chunk, records: &mut Vec<Record>) {
|
||||
pub struct Thread {
|
||||
call_stack: CallStack,
|
||||
records: Vec<Record>,
|
||||
return_register: Option<u8>,
|
||||
}
|
||||
|
||||
impl Thread {
|
||||
pub fn new(chunk: Chunk) -> Self {
|
||||
let call_stack = CallStack::new();
|
||||
let mut records = Vec::with_capacity(chunk.prototypes().len());
|
||||
let call_stack = CallStack::with_capacity(chunk.prototypes().len() + 1);
|
||||
let mut records = Vec::with_capacity(chunk.prototypes().len() + 1);
|
||||
|
||||
create_records(chunk, &mut records);
|
||||
|
||||
Thread {
|
||||
call_stack,
|
||||
records,
|
||||
return_register: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> Option<Value> {
|
||||
assert!(!self.call_stack.is_empty());
|
||||
|
||||
let mut record = &mut self.records[0];
|
||||
let (record, remaining_records) = self.records.split_first_mut().unwrap();
|
||||
|
||||
loop {
|
||||
assert!(
|
||||
@ -60,25 +55,46 @@ impl Thread {
|
||||
);
|
||||
|
||||
let action = record.actions[record.ip];
|
||||
let signal = (action.logic)(action.data, &mut record);
|
||||
let signal = (action.logic)(action.data, record);
|
||||
|
||||
match signal {
|
||||
ThreadSignal::Continue => {
|
||||
record.ip += 1;
|
||||
}
|
||||
ThreadSignal::Call(FunctionCall {
|
||||
record_index,
|
||||
return_register,
|
||||
..
|
||||
}) => {
|
||||
record = &mut self.records[record_index];
|
||||
self.return_register = Some(return_register);
|
||||
ThreadSignal::Call(function_call) => {
|
||||
swap(record, &mut remaining_records[function_call.record_index]);
|
||||
self.call_stack.push(function_call);
|
||||
}
|
||||
ThreadSignal::Return(value_option) => {
|
||||
let outer_call = self.call_stack.pop();
|
||||
ThreadSignal::Return(should_return_value) => {
|
||||
let returning_call = match self.call_stack.pop() {
|
||||
Some(function_call) => function_call,
|
||||
None => {
|
||||
if should_return_value {
|
||||
return record.last_assigned_register().map(|register| {
|
||||
record.replace_register_or_clone_constant(
|
||||
register,
|
||||
Register::Empty,
|
||||
)
|
||||
});
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
};
|
||||
let outer_call = self.call_stack.last_or_panic();
|
||||
|
||||
if outer_call.is_none() {
|
||||
return value_option;
|
||||
if should_return_value {
|
||||
let return_register = record
|
||||
.last_assigned_register()
|
||||
.unwrap_or_else(|| panic!("Expected return value"));
|
||||
let value = record
|
||||
.replace_register_or_clone_constant(return_register, Register::Empty);
|
||||
|
||||
swap(record, &mut remaining_records[outer_call.record_index]);
|
||||
|
||||
record.set_register(returning_call.return_register, Register::Value(value));
|
||||
} else {
|
||||
swap(record, &mut remaining_records[outer_call.record_index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -89,5 +105,5 @@ impl Thread {
|
||||
pub enum ThreadSignal {
|
||||
Continue,
|
||||
Call(FunctionCall),
|
||||
Return(Option<Value>),
|
||||
Return(bool),
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user