diff --git a/Cargo.lock b/Cargo.lock index 6811bcd..f62c495 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -269,6 +269,15 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" +[[package]] +name = "crossbeam-channel" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -335,6 +344,7 @@ dependencies = [ "annotate-snippets", "colored", "criterion", + "crossbeam-channel", "getrandom", "rand", "serde", diff --git a/dust-lang/Cargo.toml b/dust-lang/Cargo.toml index bef5fb8..96fedb5 100644 --- a/dust-lang/Cargo.toml +++ b/dust-lang/Cargo.toml @@ -12,7 +12,7 @@ version.workspace = true annotate-snippets = "0.11.4" colored = "2.1.0" rand = "0.8.5" -serde = { version = "1.0.203", features = ["derive"] } +serde = { version = "1.0.203", features = ["derive", "rc"] } serde_json = "1.0.117" getrandom = { version = "0.2", features = [ "js", @@ -21,6 +21,7 @@ smartstring = { version = "1.0.1", features = [ "serde", ], default-features = false } tracing = "0.1.41" +crossbeam-channel = "0.5.14" [dev-dependencies] criterion = { version = "0.3.4", features = ["html_reports"] } @@ -33,6 +34,10 @@ harness = false name = "fibonacci" harness = false +[[bench]] +name = "threads" +harness = false + [[test]] name = "logic_and" path = "tests/logic/and.rs" diff --git a/dust-lang/benches/threads.rs b/dust-lang/benches/threads.rs new file mode 100644 index 0000000..8c4ef2e --- /dev/null +++ b/dust-lang/benches/threads.rs @@ -0,0 +1,31 @@ +use std::time::Duration; + +use criterion::{Criterion, black_box, criterion_group, criterion_main}; +use dust_lang::run; + +const SOURCE: &str = r#" + let mut i = 0 + + while i < 1_000 { + i += 1 + + spawn( + fn () { random_int(0, 10); } + ) + } +"#; + +fn threads(source: &str) { + run(source).unwrap(); +} + +fn criterion_benchmark(c: &mut Criterion) { + let mut group = c.benchmark_group("threads"); + + group.measurement_time(Duration::from_secs(15)); + group.bench_function("threads", |b| b.iter(|| threads(black_box(SOURCE)))); + group.finish(); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/dust-lang/src/chunk/mod.rs b/dust-lang/src/chunk/mod.rs index 07c7eae..d9a7364 100644 --- a/dust-lang/src/chunk/mod.rs +++ b/dust-lang/src/chunk/mod.rs @@ -23,6 +23,7 @@ pub use scope::Scope; use std::fmt::{self, Debug, Display, Formatter, Write as FmtWrite}; use std::io::Write; +use std::sync::Arc; use serde::{Deserialize, Serialize}; @@ -40,7 +41,7 @@ pub struct Chunk { pub(crate) positions: Vec, pub(crate) constants: Vec, pub(crate) locals: Vec, - pub(crate) prototypes: Vec, + pub(crate) prototypes: Vec>, pub(crate) register_count: usize, pub(crate) prototype_index: u8, @@ -55,7 +56,7 @@ impl Chunk { positions: impl Into>, constants: impl Into>, locals: impl Into>, - prototypes: Vec, + prototypes: impl IntoIterator, ) -> Self { Self { name, @@ -64,7 +65,7 @@ impl Chunk { positions: positions.into(), constants: constants.into(), locals: locals.into(), - prototypes, + prototypes: prototypes.into_iter().map(Arc::new).collect(), register_count: 0, prototype_index: 0, } diff --git a/dust-lang/src/compiler/mod.rs b/dust-lang/src/compiler/mod.rs index edbb040..270fc81 100644 --- a/dust-lang/src/compiler/mod.rs +++ b/dust-lang/src/compiler/mod.rs @@ -28,7 +28,7 @@ use parse_rule::{ParseRule, Precedence}; use tracing::{Level, debug, info, span}; use type_checks::{check_math_type, check_math_types}; -use std::mem::replace; +use std::{mem::replace, sync::Arc}; use optimize::control_flow_register_consolidation; @@ -96,7 +96,7 @@ pub struct Compiler<'src> { /// Prototypes that have been compiled. These are assigned to the chunk when /// [`Compiler::finish`] is called. - prototypes: Vec, + prototypes: Vec>, /// Maximum stack size required by the chunk. This is assigned to the chunk when /// [`Compiler::finish`] is called. @@ -1645,7 +1645,7 @@ impl<'src> Compiler<'src> { let chunk = function_compiler.finish(); let destination = self.next_register(); - self.prototypes.push(chunk); + self.prototypes.push(Arc::new(chunk)); if let Some(identifier) = identifier { self.declare_local( diff --git a/dust-lang/src/lexer.rs b/dust-lang/src/lexer.rs index 0c09983..df03324 100644 --- a/dust-lang/src/lexer.rs +++ b/dust-lang/src/lexer.rs @@ -5,7 +5,7 @@ //! - [`Lexer`], which lexes the input a token at a time use serde::{Deserialize, Serialize}; -use crate::{dust_error::AnnotatedError, CompileError, DustError, Span, Token}; +use crate::{CompileError, DustError, Span, Token, dust_error::AnnotatedError}; /// Lexes the input and returns a vector of tokens and their positions. /// @@ -83,7 +83,7 @@ impl<'src> Lexer<'src> { self.skip_whitespace(); let (token, span) = if let Some(character) = self.peek_char() { - let lexer = LexRule::from(&character).lexer; + let lexer = LexRule::from(&character).lex_action; lexer(self)? } else { @@ -613,92 +613,92 @@ impl<'src> Lexer<'src> { } } -type LexerFn<'src> = fn(&mut Lexer<'src>) -> Result<(Token<'src>, Span), LexError>; +type LexAction<'src> = fn(&mut Lexer<'src>) -> Result<(Token<'src>, Span), LexError>; pub struct LexRule<'src> { - lexer: LexerFn<'src>, + lex_action: LexAction<'src>, } impl From<&char> for LexRule<'_> { fn from(char: &char) -> Self { match char { '0'..='9' => LexRule { - lexer: Lexer::lex_numeric, + lex_action: Lexer::lex_numeric, }, char if char.is_alphabetic() => LexRule { - lexer: Lexer::lex_keyword_or_identifier, + lex_action: Lexer::lex_keyword_or_identifier, }, '"' => LexRule { - lexer: Lexer::lex_string, + lex_action: Lexer::lex_string, }, '\'' => LexRule { - lexer: Lexer::lex_char, + lex_action: Lexer::lex_char, }, '+' => LexRule { - lexer: Lexer::lex_plus, + lex_action: Lexer::lex_plus, }, '-' => LexRule { - lexer: Lexer::lex_minus, + lex_action: Lexer::lex_minus, }, '*' => LexRule { - lexer: Lexer::lex_star, + lex_action: Lexer::lex_star, }, '/' => LexRule { - lexer: Lexer::lex_slash, + lex_action: Lexer::lex_slash, }, '%' => LexRule { - lexer: Lexer::lex_percent, + lex_action: Lexer::lex_percent, }, '!' => LexRule { - lexer: Lexer::lex_exclamation_mark, + lex_action: Lexer::lex_exclamation_mark, }, '=' => LexRule { - lexer: Lexer::lex_equal, + lex_action: Lexer::lex_equal, }, '<' => LexRule { - lexer: Lexer::lex_less_than, + lex_action: Lexer::lex_less_than, }, '>' => LexRule { - lexer: Lexer::lex_greater_than, + lex_action: Lexer::lex_greater_than, }, '&' => LexRule { - lexer: Lexer::lex_ampersand, + lex_action: Lexer::lex_ampersand, }, '|' => LexRule { - lexer: Lexer::lex_pipe, + lex_action: Lexer::lex_pipe, }, '(' => LexRule { - lexer: Lexer::lex_left_parenthesis, + lex_action: Lexer::lex_left_parenthesis, }, ')' => LexRule { - lexer: Lexer::lex_right_parenthesis, + lex_action: Lexer::lex_right_parenthesis, }, '[' => LexRule { - lexer: Lexer::lex_left_bracket, + lex_action: Lexer::lex_left_bracket, }, ']' => LexRule { - lexer: Lexer::lex_right_bracket, + lex_action: Lexer::lex_right_bracket, }, '{' => LexRule { - lexer: Lexer::lex_left_brace, + lex_action: Lexer::lex_left_brace, }, '}' => LexRule { - lexer: Lexer::lex_right_brace, + lex_action: Lexer::lex_right_brace, }, ';' => LexRule { - lexer: Lexer::lex_semicolon, + lex_action: Lexer::lex_semicolon, }, ':' => LexRule { - lexer: Lexer::lex_colon, + lex_action: Lexer::lex_colon, }, ',' => LexRule { - lexer: Lexer::lex_comma, + lex_action: Lexer::lex_comma, }, '.' => LexRule { - lexer: Lexer::lex_dot, + lex_action: Lexer::lex_dot, }, _ => LexRule { - lexer: Lexer::lex_unexpected, + lex_action: Lexer::lex_unexpected, }, } } diff --git a/dust-lang/src/native_function/assert.rs b/dust-lang/src/native_function/assert.rs index b2704a4..b748ee7 100644 --- a/dust-lang/src/native_function/assert.rs +++ b/dust-lang/src/native_function/assert.rs @@ -2,7 +2,7 @@ use std::{ops::Range, panic}; use crate::vm::ThreadData; -pub fn panic(data: &mut ThreadData, _: Option, argument_range: Range) -> bool { +pub fn panic(data: &mut ThreadData, _: u8, argument_range: Range) -> bool { let position = data.current_position(); let mut message = format!("Dust panic at {position}!"); diff --git a/dust-lang/src/native_function/io.rs b/dust-lang/src/native_function/io.rs index 0267009..696e5d5 100644 --- a/dust-lang/src/native_function/io.rs +++ b/dust-lang/src/native_function/io.rs @@ -1,17 +1,12 @@ -use std::io::{stdin, stdout, Write}; +use std::io::{Write, stdin, stdout}; use std::ops::Range; use crate::{ - vm::{get_next_action, Register, ThreadData}, ConcreteValue, Value, + vm::{Register, ThreadData, get_next_action}, }; -pub fn read_line( - data: &mut ThreadData, - destination: Option, - _argument_range: Range, -) -> bool { - let destination = destination.unwrap(); +pub fn read_line(data: &mut ThreadData, destination: u8, _argument_range: Range) -> bool { let mut buffer = String::new(); if stdin().read_line(&mut buffer).is_ok() { @@ -29,7 +24,7 @@ pub fn read_line( false } -pub fn write(data: &mut ThreadData, _destination: Option, argument_range: Range) -> bool { +pub fn write(data: &mut ThreadData, _: u8, argument_range: Range) -> bool { let mut stdout = stdout(); for register_index in argument_range { @@ -45,11 +40,7 @@ pub fn write(data: &mut ThreadData, _destination: Option, argument_range: Ra false } -pub fn write_line( - data: &mut ThreadData, - _destination: Option, - argument_range: Range, -) -> bool { +pub fn write_line(data: &mut ThreadData, _: u8, argument_range: Range) -> bool { let mut stdout = stdout().lock(); for register_index in argument_range { diff --git a/dust-lang/src/native_function/mod.rs b/dust-lang/src/native_function/mod.rs index e56f54a..4917843 100644 --- a/dust-lang/src/native_function/mod.rs +++ b/dust-lang/src/native_function/mod.rs @@ -4,7 +4,9 @@ //! itself or that are more efficient to implement in Rust. mod assert; mod io; +mod random; mod string; +mod thread; use std::{ fmt::{self, Display, Formatter}, @@ -15,7 +17,7 @@ use std::{ use serde::{Deserialize, Serialize}; -use crate::{vm::ThreadData, AnnotatedError, FunctionType, Span, Type}; +use crate::{AnnotatedError, FunctionType, Span, Type, vm::ThreadData}; macro_rules! define_native_function { ($(($name:ident, $bytes:literal, $str:expr, $type:expr, $function:expr)),*) => { @@ -33,7 +35,7 @@ macro_rules! define_native_function { pub fn call( &self, data: &mut ThreadData, - destination: Option, + destination: u8, argument_range: Range, ) -> bool { match self { @@ -244,11 +246,42 @@ define_native_function! { return_type: Type::None }, io::write_line - ) + ), // // Random - // (Random, 58_u8, "random", true), - // (RandomInRange, 59_u8, "random_in_range", true) + ( + RandomInteger, + 58, + "random_int", + FunctionType { + type_parameters: Vec::with_capacity(0), + value_parameters: vec![(0, Type::Integer), (1, Type::Integer)], + return_type: Type::Integer + }, + random::random_int + ), + + // Thread + ( + Spawn, + 60, + "spawn", + FunctionType { + type_parameters: Vec::with_capacity(0), + value_parameters: vec![ + ( + 0, + Type::Function(Box::new(FunctionType { + type_parameters: Vec::with_capacity(0), + value_parameters: Vec::with_capacity(0), + return_type: Type::Any + })) + ) + ], + return_type: Type::None + }, + thread::spawn + ) } #[derive(Debug, Clone, PartialEq)] diff --git a/dust-lang/src/native_function/random.rs b/dust-lang/src/native_function/random.rs new file mode 100644 index 0000000..f743b0b --- /dev/null +++ b/dust-lang/src/native_function/random.rs @@ -0,0 +1,40 @@ +use std::ops::Range; + +use rand::Rng; + +use crate::{ + Value, + vm::{Register, ThreadData, get_next_action}, +}; + +pub fn random_int(data: &mut ThreadData, destination: u8, argument_range: Range) -> bool { + let mut argument_range_iter = argument_range.into_iter(); + let (min, max) = { + let mut min = None; + + loop { + let register_index = argument_range_iter + .next() + .unwrap_or_else(|| panic!("No argument was passed to \"random_int\"")); + let value_option = data.open_register_allow_empty_unchecked(register_index); + + if let Some(argument) = value_option { + if let Some(integer) = argument.as_integer() { + if min.is_none() { + min = Some(integer); + } else { + break (min, integer); + } + } + } + } + }; + + let random_integer = rand::thread_rng().gen_range(min.unwrap()..max); + + data.set_register(destination, Register::Value(Value::integer(random_integer))); + + data.next_action = get_next_action(data); + + false +} diff --git a/dust-lang/src/native_function/string.rs b/dust-lang/src/native_function/string.rs index f368185..8d03c96 100644 --- a/dust-lang/src/native_function/string.rs +++ b/dust-lang/src/native_function/string.rs @@ -1,18 +1,13 @@ use std::ops::Range; use crate::{ - vm::{get_next_action, Register, ThreadData}, ConcreteValue, Value, + vm::{Register, ThreadData, get_next_action}, }; -pub fn to_string( - data: &mut ThreadData, - destination: Option, - argument_range: Range, -) -> bool { +pub fn to_string(data: &mut ThreadData, destination: u8, argument_range: Range) -> bool { let argument_value = data.open_register_unchecked(argument_range.start); let argument_string = argument_value.display(data); - let destination = destination.unwrap(); let register = Register::Value(Value::Concrete(ConcreteValue::string(argument_string))); data.set_register(destination, register); diff --git a/dust-lang/src/native_function/thread.rs b/dust-lang/src/native_function/thread.rs new file mode 100644 index 0000000..4786065 --- /dev/null +++ b/dust-lang/src/native_function/thread.rs @@ -0,0 +1,64 @@ +use std::{ + ops::Range, + thread::{Builder, JoinHandle}, +}; + +use tracing::{Level, info, span}; + +use crate::{ + DustString, + vm::{Thread, ThreadData, get_next_action}, +}; + +fn start_thread(data: &mut ThreadData, argument_range: Range) -> JoinHandle<()> { + let mut argument_range_iter = argument_range.into_iter(); + let function_argument = { + loop { + let register_index = argument_range_iter + .next() + .unwrap_or_else(|| panic!("No argument was passed to \"spawn\"")); + let value_option = data.open_register_allow_empty_unchecked(register_index); + + if let Some(argument) = value_option { + break argument; + } + } + }; + let function = function_argument.as_function().unwrap(); + let prototype_index = function.prototype_index as usize; + let current_call = data.call_stack.last_unchecked(); + let prototype = current_call.chunk.prototypes[prototype_index].clone(); + + info!( + "Spawning thread for \"{}\"", + function + .name + .as_ref() + .cloned() + .unwrap_or_else(|| DustString::from("anonymous")) + ); + + let thread_name = prototype + .name + .as_ref() + .map(|name| name.to_string()) + .unwrap_or_else(|| "anonymous".to_string()); + let mut thread = Thread::new(prototype); + + Builder::new() + .name(thread_name) + .spawn(move || { + let span = span!(Level::INFO, "Spawned thread"); + let _enter = span.enter(); + + thread.run(); + }) + .expect("Critical VM Error: Failed to spawn thread") +} + +pub fn spawn(data: &mut ThreadData, _: u8, argument_range: Range) -> bool { + let _ = start_thread(data, argument_range); + data.next_action = get_next_action(data); + + false +} diff --git a/dust-lang/src/value/mod.rs b/dust-lang/src/value/mod.rs index 6da63fb..6a37fbf 100644 --- a/dust-lang/src/value/mod.rs +++ b/dust-lang/src/value/mod.rs @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize}; use std::fmt::{self, Debug, Display, Formatter}; -use crate::{vm::ThreadData, Type}; +use crate::{Type, vm::ThreadData}; #[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)] pub enum Value { @@ -50,9 +50,9 @@ impl Value { Value::Concrete(ConcreteValue::String(string.into())) } - pub fn as_boolean(&self) -> Option<&bool> { - if let Value::Concrete(ConcreteValue::Boolean(value)) = self { - Some(value) + pub fn as_boolean(&self) -> Option { + if let Value::Concrete(ConcreteValue::Boolean(boolean)) = self { + Some(*boolean) } else { None } @@ -66,6 +66,14 @@ impl Value { } } + pub fn as_integer(&self) -> Option { + if let Value::Concrete(ConcreteValue::Integer(integer)) = self { + Some(*integer) + } else { + None + } + } + pub fn as_string(&self) -> Option<&DustString> { if let Value::Concrete(ConcreteValue::String(value)) = self { Some(value) @@ -74,6 +82,14 @@ impl Value { } } + pub fn is_string(&self) -> bool { + matches!(self, Value::Concrete(ConcreteValue::String(_))) + } + + pub fn is_function(&self) -> bool { + matches!(self, Value::Function(_)) + } + pub fn r#type(&self) -> Type { match self { Value::Concrete(concrete_value) => concrete_value.r#type(), diff --git a/dust-lang/src/vm/function_call.rs b/dust-lang/src/vm/function_call.rs index 86a0991..c9cbe84 100644 --- a/dust-lang/src/vm/function_call.rs +++ b/dust-lang/src/vm/function_call.rs @@ -1,29 +1,34 @@ -use std::fmt::{self, Debug, Display, Formatter}; +use std::{ + fmt::{self, Debug, Display, Formatter}, + sync::Arc, +}; use crate::{Chunk, DustString}; use super::Register; #[derive(Debug)] -pub struct FunctionCall<'a> { - pub chunk: &'a Chunk, +pub struct FunctionCall { + pub chunk: Arc, pub ip: usize, pub return_register: u8, pub registers: Vec, } -impl<'a> FunctionCall<'a> { - pub fn new(chunk: &'a Chunk, return_register: u8) -> Self { +impl FunctionCall { + pub fn new(chunk: Arc, return_register: u8) -> Self { + let register_count = chunk.register_count; + Self { chunk, ip: 0, return_register, - registers: vec![Register::Empty; chunk.register_count], + registers: vec![Register::Empty; register_count], } } } -impl Display for FunctionCall<'_> { +impl Display for FunctionCall { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!( f, diff --git a/dust-lang/src/vm/mod.rs b/dust-lang/src/vm/mod.rs index 4f16b3e..9e0ec9b 100644 --- a/dust-lang/src/vm/mod.rs +++ b/dust-lang/src/vm/mod.rs @@ -6,19 +6,20 @@ mod thread; use std::{ fmt::{self, Debug, Display, Formatter}, - sync::mpsc, - thread::spawn, + sync::Arc, + thread::Builder, }; pub use function_call::FunctionCall; -pub(crate) use run_action::get_next_action; pub use run_action::RunAction; +pub(crate) use run_action::get_next_action; pub use stack::Stack; pub use thread::{Thread, ThreadData}; -use tracing::{span, Level}; +use crossbeam_channel::bounded; +use tracing::{Level, span}; -use crate::{compile, Chunk, DustError, Value}; +use crate::{Chunk, DustError, Value, compile}; pub fn run(source: &str) -> Result, DustError> { let chunk = compile(source)?; @@ -28,41 +29,31 @@ pub fn run(source: &str) -> Result, DustError> { } pub struct Vm { - threads: Vec, + main_chunk: Chunk, } impl Vm { - pub fn new(chunk: Chunk) -> Self { - let threads = vec![Thread::new(chunk)]; - - debug_assert_eq!(1, threads.capacity()); - - Self { threads } + pub fn new(main_chunk: Chunk) -> Self { + Self { main_chunk } } - pub fn run(mut self) -> Option { + pub fn run(self) -> Option { let span = span!(Level::INFO, "Run"); let _enter = span.enter(); + let thread_name = self + .main_chunk + .name + .as_ref() + .map(|name| name.to_string()) + .unwrap_or_else(|| "anonymous".to_string()); + let mut main_thread = Thread::new(Arc::new(self.main_chunk)); + let (tx, rx) = bounded(1); + let _ = Builder::new().name(thread_name).spawn(move || { + let value_option = main_thread.run(); + let _ = tx.send(value_option); + }); - if self.threads.len() == 1 { - return self.threads[0].run(); - } - - let (tx, rx) = mpsc::channel(); - - for mut thread in self.threads { - let tx = tx.clone(); - - spawn(move || { - let return_value = thread.run(); - - if let Some(value) = return_value { - tx.send(value).unwrap(); - } - }); - } - - rx.into_iter().last() + rx.recv().unwrap() } } diff --git a/dust-lang/src/vm/run_action.rs b/dust-lang/src/vm/run_action.rs index 3af2630..5bb6cb4 100644 --- a/dust-lang/src/vm/run_action.rs +++ b/dust-lang/src/vm/run_action.rs @@ -1,16 +1,16 @@ use tracing::trace; use crate::{ + AbstractList, Argument, ConcreteValue, Instruction, Type, Value, instruction::{ Add, Call, CallNative, Close, Divide, Equal, GetLocal, Jump, Less, LessEqual, LoadBoolean, LoadConstant, LoadFunction, LoadList, LoadSelf, Modulo, Multiply, Negate, Not, Point, Return, SetLocal, Subtract, Test, TestSet, }, vm::FunctionCall, - AbstractList, Argument, ConcreteValue, Instruction, Type, Value, }; -use super::{thread::ThreadData, Pointer, Register}; +use super::{Pointer, Register, thread::ThreadData}; #[derive(Clone, Copy, Debug, PartialEq)] pub struct RunAction { @@ -525,14 +525,14 @@ pub fn call(instruction: Instruction, data: &mut ThreadData) -> bool { let current_call = data.call_stack.last_unchecked(); let first_argument_register = return_register - argument_count; let prototype = if is_recursive { - current_call.chunk + current_call.chunk.clone() } else { let function = data .open_register_unchecked(function_register) .as_function() .unwrap(); - ¤t_call.chunk.prototypes[function.prototype_index as usize] + current_call.chunk.prototypes[function.prototype_index as usize].clone() }; let mut next_call = FunctionCall::new(prototype, return_register); let mut argument_index = 0; @@ -565,7 +565,7 @@ pub fn call_native(instruction: Instruction, data: &mut ThreadData) -> bool { let first_argument_index = destination - argument_count; let argument_range = first_argument_index..destination; - function.call(data, Some(destination), argument_range) + function.call(data, destination, argument_range) } pub fn r#return(instruction: Instruction, data: &mut ThreadData) -> bool { diff --git a/dust-lang/src/vm/stack.rs b/dust-lang/src/vm/stack.rs index ac8d2e9..dd43253 100644 --- a/dust-lang/src/vm/stack.rs +++ b/dust-lang/src/vm/stack.rs @@ -124,7 +124,7 @@ impl Debug for Stack { } } -impl Display for Stack> { +impl Display for Stack { fn fmt(&self, f: &mut Formatter) -> fmt::Result { writeln!(f, "----- DUST CALL STACK -----")?; diff --git a/dust-lang/src/vm/thread.rs b/dust-lang/src/vm/thread.rs index 8152a5b..35c9811 100644 --- a/dust-lang/src/vm/thread.rs +++ b/dust-lang/src/vm/thread.rs @@ -1,17 +1,17 @@ -use std::mem::replace; +use std::{mem::replace, sync::Arc, thread::JoinHandle}; use tracing::{info, trace}; -use crate::{vm::FunctionCall, Argument, Chunk, DustString, Span, Value}; +use crate::{Argument, Chunk, DustString, Span, Value, vm::FunctionCall}; use super::{Pointer, Register, RunAction, Stack}; pub struct Thread { - chunk: Chunk, + chunk: Arc, } impl Thread { - pub fn new(chunk: Chunk) -> Self { + pub fn new(chunk: Arc) -> Self { Thread { chunk } } @@ -25,7 +25,7 @@ impl Thread { ); let mut call_stack = Stack::with_capacity(self.chunk.prototypes.len() + 1); - let main_call = FunctionCall::new(&self.chunk, 0); + let main_call = FunctionCall::new(self.chunk.clone(), 0); call_stack.push(main_call); @@ -34,6 +34,7 @@ impl Thread { call_stack, next_action: first_action, return_value_index: None, + spawned_threads: Vec::new(), }; loop { @@ -45,7 +46,7 @@ impl Thread { ); if should_end { - let return_value = if let Some(register_index) = thread_data.return_value_index { + let value_option = if let Some(register_index) = thread_data.return_value_index { let value = thread_data.empty_register_or_clone_constant_unchecked(register_index); @@ -54,20 +55,28 @@ impl Thread { None }; - return return_value; + thread_data + .spawned_threads + .into_iter() + .for_each(|join_handle| { + let _ = join_handle.join(); + }); + + return value_option; } } } } #[derive(Debug)] -pub struct ThreadData<'a> { - pub call_stack: Stack>, +pub struct ThreadData { + pub call_stack: Stack, pub next_action: RunAction, pub return_value_index: Option, + pub spawned_threads: Vec>, } -impl ThreadData<'_> { +impl ThreadData { pub fn current_position(&self) -> Span { let current_call = self.call_stack.last_unchecked(); @@ -75,7 +84,7 @@ impl ThreadData<'_> { } pub(crate) fn follow_pointer_unchecked(&self, pointer: Pointer) -> &Value { - trace!("Follow pointer {pointer}"); + trace!("Follow {pointer}"); match pointer { Pointer::Register(register_index) => self.open_register_unchecked(register_index), @@ -97,7 +106,7 @@ impl ThreadData<'_> { } pub fn get_register_unchecked(&self, register_index: u8) -> &Register { - trace!("Get register R{register_index}"); + trace!("Get R{register_index}"); let register_index = register_index as usize; @@ -133,37 +142,25 @@ impl ThreadData<'_> { } }; + trace!("Open R{register_index} to {register}"); + match register { - Register::Value(value) => { - trace!("Register R{register_index} opened to value {value}"); - - value - } - Register::Pointer(pointer) => { - trace!("Open register R{register_index} opened to pointer {pointer}"); - - self.follow_pointer_unchecked(*pointer) - } + Register::Value(value) => value, + Register::Pointer(pointer) => self.follow_pointer_unchecked(*pointer), Register::Empty => panic!("VM Error: Register {register_index} is empty"), } } pub fn open_register_allow_empty_unchecked(&self, register_index: u8) -> Option<&Value> { - trace!("Open register R{register_index}"); + trace!("Open R{register_index}"); let register = self.get_register_unchecked(register_index); + trace!("Open R{register_index} to {register}"); + match register { - Register::Value(value) => { - trace!("Register R{register_index} openned to value {value}"); - - Some(value) - } - Register::Pointer(pointer) => { - trace!("Open register R{register_index} openned to pointer {pointer}"); - - Some(self.follow_pointer_unchecked(*pointer)) - } + Register::Value(value) => Some(value), + Register::Pointer(pointer) => Some(self.follow_pointer_unchecked(*pointer)), Register::Empty => None, } } @@ -260,7 +257,7 @@ impl ThreadData<'_> { pub fn get_local_register(&self, local_index: u8) -> u8 { let local_index = local_index as usize; - let chunk = self.call_stack.last_unchecked().chunk; + let chunk = &self.call_stack.last_unchecked().chunk; assert!( local_index < chunk.locals.len(),