1
0

Optimization experiments

This commit is contained in:
Jeff 2024-12-04 00:04:56 -05:00
parent 9bd88483c4
commit 95c811f3b5
19 changed files with 614 additions and 65 deletions

10
Cargo.lock generated
View File

@ -196,6 +196,7 @@ dependencies = [
"rand", "rand",
"serde", "serde",
"serde_json", "serde_json",
"smallvec",
] ]
[[package]] [[package]]
@ -486,6 +487,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "spin" name = "spin"
version = "0.9.8" version = "0.9.8"

View File

@ -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 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 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 output optimal code in the first place, it is not always possible. Dust's compiler modifies the
functions that modify isolated sections of the instruction list through a mutable reference. 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 #### Type Checking

View File

@ -0,0 +1,5 @@
let mut i = 0
while i < 100_000_000 {
i += 1
}

View File

@ -0,0 +1,5 @@
var i = 0;
while (i < 1_000_000_000) {
i++;
}

View File

@ -18,6 +18,7 @@ serde_json = "1.0.117"
getrandom = { version = "0.2", features = [ getrandom = { version = "0.2", features = [
"js", "js",
] } # Indirect dependency, for wasm builds ] } # Indirect dependency, for wasm builds
smallvec = { version = "1.13.2", features = ["serde"] }
[dev-dependencies] [dev-dependencies]
env_logger = "0.11.5" env_logger = "0.11.5"

View File

@ -7,6 +7,7 @@
use std::fmt::{self, Debug, Display, Write}; use std::fmt::{self, Debug, Display, Write};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use crate::{ConcreteValue, Disassembler, FunctionType, Instruction, Scope, Span, Type}; use crate::{ConcreteValue, Disassembler, FunctionType, Instruction, Scope, Span, Type};
@ -18,18 +19,18 @@ pub struct Chunk {
name: Option<String>, name: Option<String>,
r#type: FunctionType, r#type: FunctionType,
instructions: Vec<(Instruction, Span)>, instructions: SmallVec<[(Instruction, Span); 32]>,
constants: Vec<ConcreteValue>, constants: SmallVec<[ConcreteValue; 16]>,
locals: Vec<Local>, locals: SmallVec<[Local; 8]>,
} }
impl Chunk { impl Chunk {
pub fn new(name: Option<String>) -> Self { pub fn new(name: Option<String>) -> Self {
Self { Self {
name, name,
instructions: Vec::new(), instructions: SmallVec::new(),
constants: Vec::new(), constants: SmallVec::new(),
locals: Vec::new(), locals: SmallVec::new(),
r#type: FunctionType { r#type: FunctionType {
type_parameters: None, type_parameters: None,
value_parameters: None, value_parameters: None,
@ -48,9 +49,9 @@ impl Chunk {
Self { Self {
name, name,
r#type, r#type,
instructions, instructions: instructions.into(),
constants, constants: constants.into(),
locals, locals: locals.into(),
} }
} }
@ -70,18 +71,32 @@ impl Chunk {
self.instructions.is_empty() self.instructions.is_empty()
} }
pub fn constants(&self) -> &Vec<ConcreteValue> { pub fn constants(&self) -> &SmallVec<[ConcreteValue; 16]> {
&self.constants &self.constants
} }
pub fn instructions(&self) -> &Vec<(Instruction, Span)> { pub fn instructions(&self) -> &SmallVec<[(Instruction, Span); 32]> {
&self.instructions &self.instructions
} }
pub fn locals(&self) -> &Vec<Local> { pub fn locals(&self) -> &SmallVec<[Local; 8]> {
&self.locals &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 { pub fn disassembler(&self) -> Disassembler {
Disassembler::new(self) Disassembler::new(self)
} }

View File

@ -505,13 +505,19 @@ impl<'src> Compiler<'src> {
if let Token::Integer(text) = self.current_token { if let Token::Integer(text) = self.current_token {
self.advance()?; self.advance()?;
let integer = text let mut integer_value = 0_i64;
.parse::<i64>()
.map_err(|error| CompileError::ParseIntError { for digit in text.chars() {
error, let digit = if let Some(digit) = digit.to_digit(10) {
position: self.previous_position, digit as i64
})?; } else {
let value = ConcreteValue::Integer(integer); continue;
};
integer_value = integer_value * 10 + digit;
}
let value = ConcreteValue::Integer(integer_value);
self.emit_constant(value, position)?; self.emit_constant(value, position)?;
@ -1519,7 +1525,7 @@ impl<'src> Compiler<'src> {
self.current_position = function_compiler.current_position; self.current_position = function_compiler.current_position;
let function = 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 constant_index = self.push_or_get_constant(function);
let register = self.next_register(); let register = self.next_register();
let function_type = FunctionType { let function_type = FunctionType {

View File

@ -239,7 +239,7 @@ impl<'src> Lexer<'src> {
} }
} }
if c.is_ascii_digit() { if c.is_ascii_digit() || c == '_' {
self.next_char(); self.next_char();
} else { } else {
break; break;

View File

@ -19,7 +19,7 @@ impl AbstractValue {
pub fn to_concrete_owned(&self, vm: &Vm) -> Result<ConcreteValue, VmError> { pub fn to_concrete_owned(&self, vm: &Vm) -> Result<ConcreteValue, VmError> {
match self { match self {
AbstractValue::FunctionSelf => Ok(ConcreteValue::Function(vm.chunk().clone())), AbstractValue::FunctionSelf => Ok(ConcreteValue::function(vm.chunk().clone())),
AbstractValue::List { items, .. } => { AbstractValue::List { items, .. } => {
let mut resolved_items = Vec::with_capacity(items.len()); let mut resolved_items = Vec::with_capacity(items.len());

View File

@ -12,7 +12,7 @@ pub enum ConcreteValue {
Byte(u8), Byte(u8),
Character(char), Character(char),
Float(f64), Float(f64),
Function(Chunk), Function(Box<Chunk>),
Integer(i64), Integer(i64),
List(Vec<ConcreteValue>), List(Vec<ConcreteValue>),
Range(RangeValue), Range(RangeValue),
@ -28,6 +28,10 @@ impl ConcreteValue {
ValueRef::Concrete(self) ValueRef::Concrete(self)
} }
pub fn function(chunk: Chunk) -> Self {
ConcreteValue::Function(Box::new(chunk))
}
pub fn list<T: Into<Vec<ConcreteValue>>>(into_list: T) -> Self { pub fn list<T: Into<Vec<ConcreteValue>>>(into_list: T) -> Self {
ConcreteValue::List(into_list.into()) ConcreteValue::List(into_list.into())
} }

View File

@ -80,7 +80,7 @@ impl ValueRef<'_> {
pub fn add(&self, other: ValueRef) -> Result<Value, ValueError> { pub fn add(&self, other: ValueRef) -> Result<Value, ValueError> {
match (self, other) { match (self, other) {
(ValueRef::Concrete(left), ValueRef::Concrete(right)) => { (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())), _ => Err(ValueError::CannotAdd(self.to_owned(), other.to_owned())),
} }
@ -89,7 +89,7 @@ impl ValueRef<'_> {
pub fn subtract(&self, other: ValueRef) -> Result<Value, ValueError> { pub fn subtract(&self, other: ValueRef) -> Result<Value, ValueError> {
match (self, other) { match (self, other) {
(ValueRef::Concrete(left), ValueRef::Concrete(right)) => { (ValueRef::Concrete(left), ValueRef::Concrete(right)) => {
left.subtract(right).map(|result| result.to_value()) left.subtract(right).map(Value::Concrete)
} }
_ => Err(ValueError::CannotSubtract( _ => Err(ValueError::CannotSubtract(
self.to_owned(), self.to_owned(),
@ -101,7 +101,7 @@ impl ValueRef<'_> {
pub fn multiply(&self, other: ValueRef) -> Result<Value, ValueError> { pub fn multiply(&self, other: ValueRef) -> Result<Value, ValueError> {
match (self, other) { match (self, other) {
(ValueRef::Concrete(left), ValueRef::Concrete(right)) => { (ValueRef::Concrete(left), ValueRef::Concrete(right)) => {
left.multiply(right).map(|result| result.to_value()) left.multiply(right).map(Value::Concrete)
} }
_ => Err(ValueError::CannotMultiply( _ => Err(ValueError::CannotMultiply(
self.to_owned(), self.to_owned(),
@ -113,7 +113,7 @@ impl ValueRef<'_> {
pub fn divide(&self, other: ValueRef) -> Result<Value, ValueError> { pub fn divide(&self, other: ValueRef) -> Result<Value, ValueError> {
match (self, other) { match (self, other) {
(ValueRef::Concrete(left), ValueRef::Concrete(right)) => { (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())), _ => Err(ValueError::CannotDivide(self.to_owned(), other.to_owned())),
} }
@ -122,7 +122,7 @@ impl ValueRef<'_> {
pub fn modulo(&self, other: ValueRef) -> Result<Value, ValueError> { pub fn modulo(&self, other: ValueRef) -> Result<Value, ValueError> {
match (self, other) { match (self, other) {
(ValueRef::Concrete(left), ValueRef::Concrete(right)) => { (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())), _ => Err(ValueError::CannotModulo(self.to_owned(), other.to_owned())),
} }
@ -130,18 +130,14 @@ impl ValueRef<'_> {
pub fn negate(&self) -> Result<Value, ValueError> { pub fn negate(&self) -> Result<Value, ValueError> {
match self { match self {
ValueRef::Concrete(concrete_value) => { ValueRef::Concrete(concrete_value) => concrete_value.negate().map(Value::Concrete),
concrete_value.negate().map(|result| result.to_value())
}
_ => Err(ValueError::CannotNegate(self.to_owned())), _ => Err(ValueError::CannotNegate(self.to_owned())),
} }
} }
pub fn not(&self) -> Result<Value, ValueError> { pub fn not(&self) -> Result<Value, ValueError> {
match self { match self {
ValueRef::Concrete(concrete_value) => { ValueRef::Concrete(concrete_value) => concrete_value.not().map(Value::Concrete),
concrete_value.not().map(|result| result.to_value())
}
_ => Err(ValueError::CannotNot(self.to_owned())), _ => Err(ValueError::CannotNot(self.to_owned())),
} }
} }
@ -149,7 +145,7 @@ impl ValueRef<'_> {
pub fn equal(&self, other: ValueRef) -> Result<Value, ValueError> { pub fn equal(&self, other: ValueRef) -> Result<Value, ValueError> {
match (self, other) { match (self, other) {
(ValueRef::Concrete(left), ValueRef::Concrete(right)) => { (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())), _ => Err(ValueError::CannotCompare(self.to_owned(), other.to_owned())),
} }
@ -158,7 +154,7 @@ impl ValueRef<'_> {
pub fn less_than(&self, other: ValueRef) -> Result<Value, ValueError> { pub fn less_than(&self, other: ValueRef) -> Result<Value, ValueError> {
match (self, other) { match (self, other) {
(ValueRef::Concrete(left), ValueRef::Concrete(right)) => { (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())), _ => 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<Value, ValueError> { pub fn less_than_or_equal(&self, other: ValueRef) -> Result<Value, ValueError> {
match (self, other) { match (self, other) {
(ValueRef::Concrete(left), ValueRef::Concrete(right)) => left (ValueRef::Concrete(left), ValueRef::Concrete(right)) => {
.less_than_or_equal(right) left.less_than_or_equal(right).map(Value::Concrete)
.map(|result| result.to_value()), }
_ => Err(ValueError::CannotCompare(self.to_owned(), other.to_owned())), _ => Err(ValueError::CannotCompare(self.to_owned(), other.to_owned())),
} }
} }

View File

@ -5,6 +5,8 @@ use std::{
io, io,
}; };
use smallvec::{smallvec, SmallVec};
use crate::{ use crate::{
compile, instruction::*, AbstractValue, AnnotatedError, Argument, Chunk, ConcreteValue, compile, instruction::*, AbstractValue, AnnotatedError, Argument, Chunk, ConcreteValue,
Destination, DustError, Instruction, NativeFunctionError, Operation, Span, Value, ValueError, Destination, DustError, Instruction, NativeFunctionError, Operation, Span, Value, ValueError,
@ -25,9 +27,9 @@ pub fn run(source: &str) -> Result<Option<ConcreteValue>, DustError> {
#[derive(Debug)] #[derive(Debug)]
pub struct Vm<'a> { pub struct Vm<'a> {
chunk: &'a Chunk, chunk: &'a Chunk,
stack: Vec<Register>, stack: SmallVec<[Register; 64]>,
parent: Option<&'a Vm<'a>>, parent: Option<&'a Vm<'a>>,
local_definitions: Vec<Option<u16>>, local_definitions: SmallVec<[Option<u16>; 16]>,
ip: usize, ip: usize,
last_assigned_register: Option<u16>, last_assigned_register: Option<u16>,
@ -35,14 +37,14 @@ pub struct Vm<'a> {
} }
impl<'a> 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 { pub fn new(chunk: &'a Chunk, parent: Option<&'a Vm<'a>>) -> Self {
Self { Self {
chunk, chunk,
stack: Vec::new(), stack: smallvec![Register::Empty; chunk.stack_size()],
parent, parent,
local_definitions: vec![None; chunk.locals().len()], local_definitions: smallvec![None; chunk.locals().len()],
ip: 0, ip: 0,
last_assigned_register: None, last_assigned_register: None,
current_position: Span(0, 0), current_position: Span(0, 0),
@ -643,11 +645,9 @@ impl<'a> Vm<'a> {
Ok(value_ref) Ok(value_ref)
} }
#[inline(always)]
fn set_register(&mut self, to_register: u16, register: Register) -> Result<(), VmError> { fn set_register(&mut self, to_register: u16, register: Register) -> Result<(), VmError> {
self.last_assigned_register = Some(to_register); let length = self.stack.len() as u16;
let length = self.stack.len();
let to_register = to_register as usize;
if length == Self::STACK_LIMIT { if length == Self::STACK_LIMIT {
return Err(VmError::StackOverflow { return Err(VmError::StackOverflow {
@ -659,16 +659,12 @@ impl<'a> Vm<'a> {
Ordering::Less => { Ordering::Less => {
log::trace!("Change R{to_register} to {register}"); log::trace!("Change R{to_register} to {register}");
self.stack[to_register] = register; self.stack[to_register as usize] = register;
Ok(())
} }
Ordering::Equal => { Ordering::Equal => {
log::trace!("Set R{to_register} to {register}"); log::trace!("Set R{to_register} to {register}");
self.stack.push(register); self.stack.push(register);
Ok(())
} }
Ordering::Greater => { Ordering::Greater => {
let difference = to_register - length; let difference = to_register - length;
@ -682,11 +678,13 @@ impl<'a> Vm<'a> {
log::trace!("Set R{to_register} to {register}"); log::trace!("Set R{to_register} to {register}");
self.stack.push(register); self.stack.push(register);
}
}
self.last_assigned_register = Some(to_register);
Ok(()) Ok(())
} }
}
}
fn get_constant(&self, constant_index: u16) -> Result<&ConcreteValue, VmError> { fn get_constant(&self, constant_index: u16) -> Result<&ConcreteValue, VmError> {
self.chunk self.chunk

View File

@ -6,7 +6,7 @@ fn function() {
assert_eq!( assert_eq!(
run(source), run(source),
Ok(Some(ConcreteValue::Function(Chunk::with_data( Ok(Some(ConcreteValue::function(Chunk::with_data(
None, None,
FunctionType { FunctionType {
type_parameters: None, type_parameters: None,
@ -70,7 +70,7 @@ fn function_call() {
(Instruction::r#return(true), Span(41, 41)), (Instruction::r#return(true), Span(41, 41)),
], ],
vec![ vec![
ConcreteValue::Function(Chunk::with_data( ConcreteValue::function(Chunk::with_data(
None, None,
FunctionType { FunctionType {
type_parameters: None, type_parameters: None,
@ -126,7 +126,7 @@ fn function_declaration() {
(Instruction::r#return(false), Span(40, 40)) (Instruction::r#return(false), Span(40, 40))
], ],
vec![ vec![
ConcreteValue::Function(Chunk::with_data( ConcreteValue::function(Chunk::with_data(
Some("add".to_string()), Some("add".to_string()),
FunctionType { FunctionType {
type_parameters: None, type_parameters: None,

View File

@ -1,5 +0,0 @@
var i = 0;
while (i < 10000) {
i++;
}

491
flamegraph.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 23 KiB

BIN
perf.data Normal file

Binary file not shown.

22
perf.data.old Normal file
View File

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