1
0

Continue thread-based VM refactor

This commit is contained in:
Jeff 2024-12-17 07:10:47 -05:00
parent bd590e0643
commit 4527f7b6ef
26 changed files with 566 additions and 340 deletions

View File

@ -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();

View File

@ -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(),
}
}
}

View File

@ -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));

View File

@ -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,
}
}
}

View File

@ -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)

View File

@ -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;

View File

@ -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,
}
}
}

View File

@ -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,

View File

@ -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,

View File

@ -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)
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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),

View File

@ -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,
}
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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()
}
}

View File

@ -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)
}

View File

@ -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,
}

View File

@ -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(),

View File

@ -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(())

View File

@ -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}")
}
}
}
}

View File

@ -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,
}

View File

@ -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 {

View File

@ -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);
}
let function = NativeFunction::from(b);
let return_value = function.call(record.arguments).unwrap();
if let Some(value) = return_value {
let register = Register::Value(value);
record.set_register(a, register);
}
thread_signal
}
pub fn r#return(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let should_return_value = instruction_data.b != 0;
pub fn r#return(instruction_data: InstructionData, _: &mut Record) -> ThreadSignal {
let should_return_value = instruction_data.b_field != 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)]

View File

@ -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),
}