diff --git a/Cargo.lock b/Cargo.lock index 86146ba..ee62af3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -196,6 +196,7 @@ dependencies = [ "rand", "serde", "serde_json", + "smallvec", ] [[package]] @@ -486,6 +487,15 @@ dependencies = [ "serde", ] +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] + [[package]] name = "spin" version = "0.9.8" diff --git a/README.md b/README.md index 283488c..ea6ad90 100644 --- a/README.md +++ b/README.md @@ -121,8 +121,9 @@ assigning each argument a register and associating the register with the local. When generating instructions for a register-based virtual machine, there are opportunities to optimize the generated code by using fewer instructions or fewer registers. While it is best to -output optimal code in the first place, it is not always possible. Dust's compiler uses simple -functions that modify isolated sections of the instruction list through a mutable reference. +output optimal code in the first place, it is not always possible. Dust's compiler modifies the +instruction list during parsing to apply optimizations before the chunk is completed. There is no +separate optimization pass, and the compiler cannot be run in a mode that disables optimizations. #### Type Checking diff --git a/bench/assets/count_to_one_billion.ds b/bench/assets/count_to_one_billion.ds new file mode 100644 index 0000000..8e4c8a5 --- /dev/null +++ b/bench/assets/count_to_one_billion.ds @@ -0,0 +1,5 @@ +let mut i = 0 + +while i < 100_000_000 { + i += 1 +} diff --git a/bench/assets/count_to_one_billion.js b/bench/assets/count_to_one_billion.js new file mode 100644 index 0000000..998aedd --- /dev/null +++ b/bench/assets/count_to_one_billion.js @@ -0,0 +1,5 @@ +var i = 0; + +while (i < 1_000_000_000) { + i++; +} diff --git a/examples/assets/fibonacci.js b/bench/assets/fibonacci.js similarity index 100% rename from examples/assets/fibonacci.js rename to bench/assets/fibonacci.js diff --git a/examples/assets/fibonacci.py b/bench/assets/fibonacci.py similarity index 100% rename from examples/assets/fibonacci.py rename to bench/assets/fibonacci.py diff --git a/dust-lang/Cargo.toml b/dust-lang/Cargo.toml index c8598d4..e983b81 100644 --- a/dust-lang/Cargo.toml +++ b/dust-lang/Cargo.toml @@ -18,6 +18,7 @@ serde_json = "1.0.117" getrandom = { version = "0.2", features = [ "js", ] } # Indirect dependency, for wasm builds +smallvec = { version = "1.13.2", features = ["serde"] } [dev-dependencies] env_logger = "0.11.5" diff --git a/dust-lang/src/chunk.rs b/dust-lang/src/chunk.rs index 13d5b3a..a603a2e 100644 --- a/dust-lang/src/chunk.rs +++ b/dust-lang/src/chunk.rs @@ -7,6 +7,7 @@ use std::fmt::{self, Debug, Display, Write}; use serde::{Deserialize, Serialize}; +use smallvec::SmallVec; use crate::{ConcreteValue, Disassembler, FunctionType, Instruction, Scope, Span, Type}; @@ -18,18 +19,18 @@ pub struct Chunk { name: Option, r#type: FunctionType, - instructions: Vec<(Instruction, Span)>, - constants: Vec, - locals: Vec, + instructions: SmallVec<[(Instruction, Span); 32]>, + constants: SmallVec<[ConcreteValue; 16]>, + locals: SmallVec<[Local; 8]>, } impl Chunk { pub fn new(name: Option) -> Self { Self { name, - instructions: Vec::new(), - constants: Vec::new(), - locals: Vec::new(), + instructions: SmallVec::new(), + constants: SmallVec::new(), + locals: SmallVec::new(), r#type: FunctionType { type_parameters: None, value_parameters: None, @@ -48,9 +49,9 @@ impl Chunk { Self { name, r#type, - instructions, - constants, - locals, + instructions: instructions.into(), + constants: constants.into(), + locals: locals.into(), } } @@ -70,18 +71,32 @@ impl Chunk { self.instructions.is_empty() } - pub fn constants(&self) -> &Vec { + pub fn constants(&self) -> &SmallVec<[ConcreteValue; 16]> { &self.constants } - pub fn instructions(&self) -> &Vec<(Instruction, Span)> { + pub fn instructions(&self) -> &SmallVec<[(Instruction, Span); 32]> { &self.instructions } - pub fn locals(&self) -> &Vec { + pub fn locals(&self) -> &SmallVec<[Local; 8]> { &self.locals } + pub fn stack_size(&self) -> usize { + self.instructions() + .iter() + .rev() + .find_map(|(instruction, _)| { + if instruction.yields_value() { + Some(instruction.a() as usize) + } else { + None + } + }) + .unwrap_or(0) + } + pub fn disassembler(&self) -> Disassembler { Disassembler::new(self) } diff --git a/dust-lang/src/compiler.rs b/dust-lang/src/compiler.rs index 56d9360..8d8e1ef 100644 --- a/dust-lang/src/compiler.rs +++ b/dust-lang/src/compiler.rs @@ -505,13 +505,19 @@ impl<'src> Compiler<'src> { if let Token::Integer(text) = self.current_token { self.advance()?; - let integer = text - .parse::() - .map_err(|error| CompileError::ParseIntError { - error, - position: self.previous_position, - })?; - let value = ConcreteValue::Integer(integer); + let mut integer_value = 0_i64; + + for digit in text.chars() { + let digit = if let Some(digit) = digit.to_digit(10) { + digit as i64 + } else { + continue; + }; + + integer_value = integer_value * 10 + digit; + } + + let value = ConcreteValue::Integer(integer_value); self.emit_constant(value, position)?; @@ -1519,7 +1525,7 @@ impl<'src> Compiler<'src> { self.current_position = function_compiler.current_position; let function = - ConcreteValue::Function(function_compiler.finish(None, value_parameters.clone())); + ConcreteValue::function(function_compiler.finish(None, value_parameters.clone())); let constant_index = self.push_or_get_constant(function); let register = self.next_register(); let function_type = FunctionType { diff --git a/dust-lang/src/lexer.rs b/dust-lang/src/lexer.rs index 5a0aad8..b5056a6 100644 --- a/dust-lang/src/lexer.rs +++ b/dust-lang/src/lexer.rs @@ -239,7 +239,7 @@ impl<'src> Lexer<'src> { } } - if c.is_ascii_digit() { + if c.is_ascii_digit() || c == '_' { self.next_char(); } else { break; diff --git a/dust-lang/src/value/abstract_value.rs b/dust-lang/src/value/abstract_value.rs index 6d59096..5eb1bcf 100644 --- a/dust-lang/src/value/abstract_value.rs +++ b/dust-lang/src/value/abstract_value.rs @@ -19,7 +19,7 @@ impl AbstractValue { pub fn to_concrete_owned(&self, vm: &Vm) -> Result { match self { - AbstractValue::FunctionSelf => Ok(ConcreteValue::Function(vm.chunk().clone())), + AbstractValue::FunctionSelf => Ok(ConcreteValue::function(vm.chunk().clone())), AbstractValue::List { items, .. } => { let mut resolved_items = Vec::with_capacity(items.len()); diff --git a/dust-lang/src/value/concrete_value.rs b/dust-lang/src/value/concrete_value.rs index 88dc768..3b3de31 100644 --- a/dust-lang/src/value/concrete_value.rs +++ b/dust-lang/src/value/concrete_value.rs @@ -12,7 +12,7 @@ pub enum ConcreteValue { Byte(u8), Character(char), Float(f64), - Function(Chunk), + Function(Box), Integer(i64), List(Vec), Range(RangeValue), @@ -28,6 +28,10 @@ impl ConcreteValue { ValueRef::Concrete(self) } + pub fn function(chunk: Chunk) -> Self { + ConcreteValue::Function(Box::new(chunk)) + } + pub fn list>>(into_list: T) -> Self { ConcreteValue::List(into_list.into()) } diff --git a/dust-lang/src/value/mod.rs b/dust-lang/src/value/mod.rs index 5d8b04a..47079f9 100644 --- a/dust-lang/src/value/mod.rs +++ b/dust-lang/src/value/mod.rs @@ -80,7 +80,7 @@ impl ValueRef<'_> { pub fn add(&self, other: ValueRef) -> Result { match (self, other) { (ValueRef::Concrete(left), ValueRef::Concrete(right)) => { - left.add(right).map(|result| result.to_value()) + left.add(right).map(Value::Concrete) } _ => Err(ValueError::CannotAdd(self.to_owned(), other.to_owned())), } @@ -89,7 +89,7 @@ impl ValueRef<'_> { pub fn subtract(&self, other: ValueRef) -> Result { match (self, other) { (ValueRef::Concrete(left), ValueRef::Concrete(right)) => { - left.subtract(right).map(|result| result.to_value()) + left.subtract(right).map(Value::Concrete) } _ => Err(ValueError::CannotSubtract( self.to_owned(), @@ -101,7 +101,7 @@ impl ValueRef<'_> { pub fn multiply(&self, other: ValueRef) -> Result { match (self, other) { (ValueRef::Concrete(left), ValueRef::Concrete(right)) => { - left.multiply(right).map(|result| result.to_value()) + left.multiply(right).map(Value::Concrete) } _ => Err(ValueError::CannotMultiply( self.to_owned(), @@ -113,7 +113,7 @@ impl ValueRef<'_> { pub fn divide(&self, other: ValueRef) -> Result { match (self, other) { (ValueRef::Concrete(left), ValueRef::Concrete(right)) => { - left.divide(right).map(|result| result.to_value()) + left.divide(right).map(Value::Concrete) } _ => Err(ValueError::CannotDivide(self.to_owned(), other.to_owned())), } @@ -122,7 +122,7 @@ impl ValueRef<'_> { pub fn modulo(&self, other: ValueRef) -> Result { match (self, other) { (ValueRef::Concrete(left), ValueRef::Concrete(right)) => { - left.modulo(right).map(|result| result.to_value()) + left.modulo(right).map(Value::Concrete) } _ => Err(ValueError::CannotModulo(self.to_owned(), other.to_owned())), } @@ -130,18 +130,14 @@ impl ValueRef<'_> { pub fn negate(&self) -> Result { match self { - ValueRef::Concrete(concrete_value) => { - concrete_value.negate().map(|result| result.to_value()) - } + ValueRef::Concrete(concrete_value) => concrete_value.negate().map(Value::Concrete), _ => Err(ValueError::CannotNegate(self.to_owned())), } } pub fn not(&self) -> Result { match self { - ValueRef::Concrete(concrete_value) => { - concrete_value.not().map(|result| result.to_value()) - } + ValueRef::Concrete(concrete_value) => concrete_value.not().map(Value::Concrete), _ => Err(ValueError::CannotNot(self.to_owned())), } } @@ -149,7 +145,7 @@ impl ValueRef<'_> { pub fn equal(&self, other: ValueRef) -> Result { match (self, other) { (ValueRef::Concrete(left), ValueRef::Concrete(right)) => { - left.equal(right).map(|result| result.to_value()) + left.equal(right).map(Value::Concrete) } _ => Err(ValueError::CannotCompare(self.to_owned(), other.to_owned())), } @@ -158,7 +154,7 @@ impl ValueRef<'_> { pub fn less_than(&self, other: ValueRef) -> Result { match (self, other) { (ValueRef::Concrete(left), ValueRef::Concrete(right)) => { - left.less_than(right).map(|result| result.to_value()) + left.less_than(right).map(Value::Concrete) } _ => Err(ValueError::CannotCompare(self.to_owned(), other.to_owned())), } @@ -166,9 +162,9 @@ impl ValueRef<'_> { pub fn less_than_or_equal(&self, other: ValueRef) -> Result { match (self, other) { - (ValueRef::Concrete(left), ValueRef::Concrete(right)) => left - .less_than_or_equal(right) - .map(|result| result.to_value()), + (ValueRef::Concrete(left), ValueRef::Concrete(right)) => { + left.less_than_or_equal(right).map(Value::Concrete) + } _ => Err(ValueError::CannotCompare(self.to_owned(), other.to_owned())), } } diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index e713479..518c7d4 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -5,6 +5,8 @@ use std::{ io, }; +use smallvec::{smallvec, SmallVec}; + use crate::{ compile, instruction::*, AbstractValue, AnnotatedError, Argument, Chunk, ConcreteValue, Destination, DustError, Instruction, NativeFunctionError, Operation, Span, Value, ValueError, @@ -25,9 +27,9 @@ pub fn run(source: &str) -> Result, DustError> { #[derive(Debug)] pub struct Vm<'a> { chunk: &'a Chunk, - stack: Vec, + stack: SmallVec<[Register; 64]>, parent: Option<&'a Vm<'a>>, - local_definitions: Vec>, + local_definitions: SmallVec<[Option; 16]>, ip: usize, last_assigned_register: Option, @@ -35,14 +37,14 @@ pub struct Vm<'a> { } impl<'a> Vm<'a> { - const STACK_LIMIT: usize = u16::MAX as usize; + const STACK_LIMIT: u16 = u16::MAX; pub fn new(chunk: &'a Chunk, parent: Option<&'a Vm<'a>>) -> Self { Self { chunk, - stack: Vec::new(), + stack: smallvec![Register::Empty; chunk.stack_size()], parent, - local_definitions: vec![None; chunk.locals().len()], + local_definitions: smallvec![None; chunk.locals().len()], ip: 0, last_assigned_register: None, current_position: Span(0, 0), @@ -643,11 +645,9 @@ impl<'a> Vm<'a> { Ok(value_ref) } + #[inline(always)] fn set_register(&mut self, to_register: u16, register: Register) -> Result<(), VmError> { - self.last_assigned_register = Some(to_register); - - let length = self.stack.len(); - let to_register = to_register as usize; + let length = self.stack.len() as u16; if length == Self::STACK_LIMIT { return Err(VmError::StackOverflow { @@ -659,16 +659,12 @@ impl<'a> Vm<'a> { Ordering::Less => { log::trace!("Change R{to_register} to {register}"); - self.stack[to_register] = register; - - Ok(()) + self.stack[to_register as usize] = register; } Ordering::Equal => { log::trace!("Set R{to_register} to {register}"); self.stack.push(register); - - Ok(()) } Ordering::Greater => { let difference = to_register - length; @@ -682,10 +678,12 @@ impl<'a> Vm<'a> { log::trace!("Set R{to_register} to {register}"); self.stack.push(register); - - Ok(()) } } + + self.last_assigned_register = Some(to_register); + + Ok(()) } fn get_constant(&self, constant_index: u16) -> Result<&ConcreteValue, VmError> { diff --git a/dust-lang/tests/functions.rs b/dust-lang/tests/functions.rs index 9a46ed9..5002201 100644 --- a/dust-lang/tests/functions.rs +++ b/dust-lang/tests/functions.rs @@ -6,7 +6,7 @@ fn function() { assert_eq!( run(source), - Ok(Some(ConcreteValue::Function(Chunk::with_data( + Ok(Some(ConcreteValue::function(Chunk::with_data( None, FunctionType { type_parameters: None, @@ -70,7 +70,7 @@ fn function_call() { (Instruction::r#return(true), Span(41, 41)), ], vec![ - ConcreteValue::Function(Chunk::with_data( + ConcreteValue::function(Chunk::with_data( None, FunctionType { type_parameters: None, @@ -126,7 +126,7 @@ fn function_declaration() { (Instruction::r#return(false), Span(40, 40)) ], vec![ - ConcreteValue::Function(Chunk::with_data( + ConcreteValue::function(Chunk::with_data( Some("add".to_string()), FunctionType { type_parameters: None, diff --git a/examples/assets/count.js b/examples/assets/count.js deleted file mode 100644 index ba97d27..0000000 --- a/examples/assets/count.js +++ /dev/null @@ -1,5 +0,0 @@ -var i = 0; - -while (i < 10000) { - i++; -} diff --git a/flamegraph.svg b/flamegraph.svg new file mode 100644 index 0000000..3813bf2 --- /dev/null +++ b/flamegraph.svg @@ -0,0 +1,491 @@ +Flame Graph Reset ZoomSearch <core::result::Result<T,E> as core::ops::try_trait::Try>::branch (1,560,579,007 samples, 9.93%)<core::result:..<dust_lang::instruction::jump::Jump as core::convert::From<&dust_lang::instruction::Instruction>>::from (5,136,709 samples, 0.03%)dust_lang::instruction::Instruction::b (5,136,709 samples, 0.03%)core::result::Result<T,E>::map_err (1,922,298,094 samples, 12.24%)core::result::Resu..core::result::Result<T,E>::map (2,075,171,161 samples, 13.21%)core::result::Result..dust_lang::value::ValueRef::add (5,533,570,295 samples, 35.22%)dust_lang::value::ValueRef::adddust_lang::value::concrete_value::ConcreteValue::add (3,417,155,502 samples, 21.75%)dust_lang::value::concrete_value::..core::result::Result<T,E>::map (5,053,956,012 samples, 32.17%)core::result::Result<T,E>::map[unknown] (5,165,849 samples, 0.03%)[unknown] (5,165,849 samples, 0.03%)[unknown] (5,165,849 samples, 0.03%)[unknown] (5,165,849 samples, 0.03%)[unknown] (5,165,849 samples, 0.03%)dust_lang::value::ValueRef::less_than (6,679,061,476 samples, 42.51%)dust_lang::value::ValueRef::less_thandust_lang::value::concrete_value::ConcreteValue::less_than (1,439,043,424 samples, 9.16%)dust_lang::va..core::cmp::impls::<impl core::cmp::PartialOrd<&B> for &A>::lt (273,670,000 samples, 1.74%)core::cmp::impls::<impl core::cmp::PartialOrd for i64>::lt (273,670,000 samples, 1.74%)dust_lang::vm::Vm::get_argument (5,140,762 samples, 0.03%)dust_lang::vm::Vm::read (5,169,945 samples, 0.03%)all (15,711,250,040 samples, 100%)dust_lang::vm::run (15,710,957,362 samples, 100.00%)dust_lang::vm::rundust_lang::vm::Vm::run (15,710,957,362 samples, 100.00%)dust_lang::vm::Vm::run \ No newline at end of file diff --git a/perf.data b/perf.data new file mode 100644 index 0000000..dc2b963 Binary files /dev/null and b/perf.data differ diff --git a/perf.data.old b/perf.data.old new file mode 100644 index 0000000..4886fe9 --- /dev/null +++ b/perf.data.old @@ -0,0 +1,22 @@ +# started on Tue Dec 3 23:57:01 2024 + + + Performance counter stats for 'target/release/dust bench/assets/count_to_one_billion.ds': + + 3,821.33 msec task-clock:u # 1.000 CPUs utilized + 0 context-switches:u # 0.000 /sec + 0 cpu-migrations:u # 0.000 /sec + 128 page-faults:u # 33.496 /sec + 19,568,365,731 cycles:u # 5.121 GHz + 22,811,191 stalled-cycles-frontend:u # 0.12% frontend cycles idle + 70,300,518,284 instructions:u # 3.59 insn per cycle + # 0.00 stalled cycles per insn + 12,500,111,636 branches:u # 3.271 G/sec + 19,795 branch-misses:u # 0.00% of all branches + + 3.822391878 seconds time elapsed + + 3.811230000 seconds user + 0.000999000 seconds sys + +