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

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

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 = [
"js",
] } # Indirect dependency, for wasm builds
smallvec = { version = "1.13.2", features = ["serde"] }
[dev-dependencies]
env_logger = "0.11.5"

View File

@ -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<String>,
r#type: FunctionType,
instructions: Vec<(Instruction, Span)>,
constants: Vec<ConcreteValue>,
locals: Vec<Local>,
instructions: SmallVec<[(Instruction, Span); 32]>,
constants: SmallVec<[ConcreteValue; 16]>,
locals: SmallVec<[Local; 8]>,
}
impl Chunk {
pub fn new(name: Option<String>) -> 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<ConcreteValue> {
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<Local> {
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)
}

View File

@ -505,13 +505,19 @@ impl<'src> Compiler<'src> {
if let Token::Integer(text) = self.current_token {
self.advance()?;
let integer = text
.parse::<i64>()
.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 {

View File

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

View File

@ -19,7 +19,7 @@ impl AbstractValue {
pub fn to_concrete_owned(&self, vm: &Vm) -> Result<ConcreteValue, VmError> {
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());

View File

@ -12,7 +12,7 @@ pub enum ConcreteValue {
Byte(u8),
Character(char),
Float(f64),
Function(Chunk),
Function(Box<Chunk>),
Integer(i64),
List(Vec<ConcreteValue>),
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<T: Into<Vec<ConcreteValue>>>(into_list: T) -> Self {
ConcreteValue::List(into_list.into())
}

View File

@ -80,7 +80,7 @@ impl ValueRef<'_> {
pub fn add(&self, other: ValueRef) -> Result<Value, ValueError> {
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<Value, ValueError> {
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<Value, ValueError> {
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<Value, ValueError> {
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<Value, ValueError> {
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<Value, ValueError> {
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<Value, ValueError> {
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<Value, ValueError> {
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<Value, ValueError> {
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<Value, ValueError> {
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())),
}
}

View File

@ -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<Option<ConcreteValue>, DustError> {
#[derive(Debug)]
pub struct Vm<'a> {
chunk: &'a Chunk,
stack: Vec<Register>,
stack: SmallVec<[Register; 64]>,
parent: Option<&'a Vm<'a>>,
local_definitions: Vec<Option<u16>>,
local_definitions: SmallVec<[Option<u16>; 16]>,
ip: usize,
last_assigned_register: Option<u16>,
@ -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> {

View File

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

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