Skip to content
This repository was archived by the owner on Jan 10, 2026. It is now read-only.

Commit 69aba21

Browse files
more optimization
1 parent cae10cc commit 69aba21

File tree

6 files changed

+130
-22
lines changed

6 files changed

+130
-22
lines changed

sof-rs/src/main.rs

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -176,21 +176,36 @@ fn sof_main(code: impl AsRef<str>, path: &Path, library_path: impl Into<PathBuf>
176176
let start_time = time::Instant::now();
177177
let lexed = parser::lexer::lex(code)?;
178178
debug!(target: "sof::lexer", "lexed: {lexed:#?}");
179-
let parsed = Arc::new(parser::parse(lexed)?);
179+
let mut parsed = Arc::new(parser::parse(lexed)?);
180180
debug!(target: "sof::parser", "parsed: {parsed:#?}");
181+
let optimizer_start_time = time::Instant::now();
182+
optimizer::run_passes(&mut parsed);
183+
let optimizer_end_time = time::Instant::now();
181184
let metrics = run(parsed, path, &library_path.into())?;
182185
let end_time = time::Instant::now();
183186

187+
let parse_time = optimizer_start_time - start_time;
188+
let optimize_time = optimizer_end_time - optimizer_start_time;
189+
let execution_time = end_time - optimizer_end_time;
190+
184191
info!(
185192
"Performance metrics:
186-
total time: {:>13.2}μs
187-
tokens run: {:>10}
188-
time / token: {:>13.2?}μs
189-
calls: {:>10}
190-
GC runs: {:>10}",
193+
time: {:>13.2}μs
194+
* parser: {:>13.2}μs
195+
* optimizer: {:>13.2}μs
196+
* execution: {:>13.2}μs
197+
exe time / token: {:>13.2}μs
198+
* optimizer: {:>13.2}μs
199+
tokens run: {:>10}
200+
calls: {:>10}
201+
GC runs: {:>10}",
191202
(end_time - start_time).as_nanos() as f64 / 1_000.,
203+
parse_time.as_nanos() as f64 / 1_000.,
204+
optimize_time.as_nanos() as f64 / 1_000.,
205+
execution_time.as_nanos() as f64 / 1_000.,
206+
(execution_time.as_nanos() as f64 / 1000.) / metrics.token_count as f64,
207+
(optimize_time.as_nanos() as f64 / 1000.) / metrics.token_count as f64,
192208
metrics.token_count,
193-
(end_time - start_time).as_micros() as f64 / metrics.token_count as f64,
194209
metrics.call_count,
195210
metrics.gc_count,
196211
);

sof-rs/src/optimizer.rs

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,103 @@ use std::sync::Arc;
55
use log::{debug, trace};
66

77
use crate::runtime::stackable::TokenVec;
8-
use crate::token::Token;
8+
use crate::token::{InnerToken, Literal, Token};
99

1010
/// Single optimization pass that can be executed on a token list.
1111
pub type Pass = fn(tokens: &mut Vec<Token>);
1212

13-
pub static DEFAULT_PASSES: [Pass; 1] = [passes::combine_literal_pushes];
13+
pub static DEFAULT_PASSES: [Pass; 2] = [passes::combine_id_calls, passes::combine_literal_pushes];
1414

1515
pub fn run_passes(tokens: &mut TokenVec) {
1616
debug!("running optimizer on {} tokens…", tokens.len());
1717
// TODO: super wasteful
1818
let mut work_tokens = (**tokens).clone();
1919
for pass in DEFAULT_PASSES {
2020
pass(&mut work_tokens);
21+
trace!("after pass {:?}: {work_tokens:#?}", pass);
2122
}
22-
trace!("after optimizer: {work_tokens:#?}");
2323
*tokens = Arc::new(work_tokens);
2424
}
2525

26+
/// Recursively apply a pass to any nested token lists.
27+
#[inline]
28+
fn recurse_pass(pass: Pass, token: &mut Token) {
29+
match &mut token.inner {
30+
InnerToken::Literal(Literal::CodeBlock(cb)) => {
31+
let mut new_block = cb.as_ref().clone();
32+
pass(&mut new_block);
33+
*cb = Arc::new(new_block);
34+
},
35+
_ => {},
36+
}
37+
}
38+
2639
mod passes {
2740
use log::{debug, trace};
2841
use miette::SourceSpan;
2942
use smallvec::SmallVec;
3043

31-
use crate::token::{InnerToken, Token};
44+
use crate::optimizer::recurse_pass;
45+
use crate::token::{Command, InnerToken, Literal, Token};
46+
47+
/// Optimization pass which combines identifiers and following calls / double calls into simplified lookup/call
48+
/// tokens.
49+
pub fn combine_id_calls(tokens: &mut Vec<Token>) {
50+
debug!("combine_id_calls");
51+
52+
let mut idx = 0;
53+
while idx < tokens.len() {
54+
recurse_pass(combine_id_calls, &mut tokens[idx]);
55+
56+
let current = &tokens[idx];
57+
match &current.inner {
58+
InnerToken::Literal(Literal::Identifier(id)) => {
59+
let start_index = idx;
60+
idx += 1;
61+
let Some(Token { inner: InnerToken::Command(Command::Call), span: end_span }) = tokens.get(idx)
62+
else {
63+
// Nothing to optimize.
64+
debug_assert!(idx == start_index + 1);
65+
continue;
66+
};
67+
68+
idx += 1;
69+
let Some(Token { inner: InnerToken::Command(Command::Call), span: end_span }) = tokens.get(idx)
70+
else {
71+
let end_index = idx - 1;
72+
// Found only a single call token afterwards.
73+
trace!("combining lookup in range [{start_index}; {end_index}]");
74+
tokens.splice(start_index ..= end_index, [Token {
75+
inner: InnerToken::LookupName(id.clone()),
76+
span: SourceSpan::new(
77+
current.span.offset().into(),
78+
end_span.offset() + end_span.len() - current.span.offset(),
79+
),
80+
}]);
81+
idx -= 1;
82+
debug_assert!(idx == start_index + 1);
83+
continue;
84+
};
85+
86+
// Found two call tokens afterwards.
87+
let end_index = idx;
88+
trace!("combining name call in range [{start_index}; {end_index}]");
89+
tokens.splice(start_index ..= end_index, [Token {
90+
inner: InnerToken::CallName(id.clone()),
91+
span: SourceSpan::new(
92+
current.span.offset().into(),
93+
end_span.offset() + end_span.len() - current.span.offset(),
94+
),
95+
}]);
96+
idx -= 1;
97+
debug_assert!(idx == start_index + 1);
98+
continue;
99+
},
100+
_ => {},
101+
}
102+
idx += 1;
103+
}
104+
}
32105

33106
/// Optimization pass which combines multiple literal tokens into a single token which pushes multiple literals at
34107
/// once.
@@ -42,6 +115,8 @@ mod passes {
42115

43116
let mut idx = 0;
44117
while idx < tokens.len() {
118+
recurse_pass(combine_literal_pushes, &mut tokens[idx]);
119+
45120
let current = &tokens[idx];
46121
match &current.inner {
47122
InnerToken::Literals(literals) => {

sof-rs/src/runtime.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ impl<'gc> Stack<'gc> {
6969
// fast path for top nametable
7070
match self.main[self.top_nametable] {
7171
Stackable::Nametable(nt) => nt.borrow().lookup(name, span).ok(),
72-
_ => None,
72+
_ => unreachable!("missing nametable"),
7373
}
7474
.or_else(|| {
7575
self.main.iter().rev().find_map(|stackable| match stackable {

sof-rs/src/runtime/interpreter.rs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use miette::SourceSpan;
1111
use smallvec::{SmallVec, smallvec};
1212

1313
use crate::arc_iter::ArcVecIter;
14+
use crate::call_builtin_function;
1415
use crate::error::Error;
1516
use crate::identifier::Identifier;
1617
use crate::lib::DEFAULT_REGISTRY;
@@ -21,7 +22,6 @@ use crate::runtime::stackable::{BuiltinType, Function, TokenVec};
2122
use crate::runtime::util::{SwitchCase, SwitchCases, UtilityData};
2223
use crate::runtime::{Stack, StackArena, Stackable};
2324
use crate::token::{Command, InnerToken, Literal, Token};
24-
use crate::{call_builtin_function, optimizer};
2525

2626
#[derive(Default, Clone, Copy)]
2727
#[non_exhaustive]
@@ -32,9 +32,8 @@ pub struct Metrics {
3232
pub call_count: usize,
3333
}
3434

35-
pub fn run(mut tokens: TokenVec, file_path: impl Into<PathBuf>, library_path: &Path) -> Result<Metrics, Error> {
35+
pub fn run(tokens: TokenVec, file_path: impl Into<PathBuf>, library_path: &Path) -> Result<Metrics, Error> {
3636
let mut arena: StackArena = new_arena(library_path);
37-
optimizer::run_passes(&mut tokens);
3837
run_on_arena(&mut arena, tokens, file_path, library_path)
3938
}
4039

@@ -502,6 +501,15 @@ fn execute_token<'a>(token: &Token, mc: &Mutation<'a>, stack: &mut Stack<'a>) ->
502501
let callable = stack.pop(token.span)?;
503502
callable.enter_call(mc, stack, token.span)
504503
},
504+
InnerToken::CallName(id) => {
505+
let value = stack.lookup(id, token.span)?;
506+
value.enter_call(mc, stack, token.span)
507+
},
508+
InnerToken::LookupName(id) => {
509+
let value = stack.lookup(id, token.span)?;
510+
stack.push(value);
511+
no_action()
512+
},
505513
InnerToken::Command(Command::FieldAccess) => {
506514
let callable = stack.pop(token.span)?;
507515
let object = stack.pop(token.span)?;
@@ -642,11 +650,13 @@ fn execute_token<'a>(token: &Token, mc: &Mutation<'a>, stack: &mut Stack<'a>) ->
642650
// insert the while body logic at the start so it is executed after the initial loop body action(s)
643651
// the body action is *empty* so the return behavior is immediately run and it figures out the state of
644652
// things from the utility stack setup
645-
actions.insert(0, InterpreterAction::ExecuteCall {
646-
#[allow(clippy::borrow_interior_mutable_const)]
647-
code: EMPTY_VEC.clone(),
648-
return_behavior: CallReturnBehavior::Loop,
649-
}).expect("interpreter actions grew too large");
653+
actions
654+
.insert(0, InterpreterAction::ExecuteCall {
655+
#[allow(clippy::borrow_interior_mutable_const)]
656+
code: EMPTY_VEC.clone(),
657+
return_behavior: CallReturnBehavior::Loop,
658+
})
659+
.expect("interpreter actions grew too large");
650660
Ok(actions)
651661
},
652662
// almost like if, but with the special utility stack

sof-rs/src/runtime/module.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use internment::ArcIntern;
88
use log::debug;
99

1010
use crate::error::Error;
11+
use crate::optimizer;
1112
use crate::parser::{self, lexer};
1213
use crate::runtime::stackable::TokenVec;
1314

@@ -59,7 +60,8 @@ impl ModuleRegistry {
5960
let code = std::fs::read_to_string(&*module_path)
6061
.map_err(|err| Error::ModuleFileNotReadable { path: module_path.to_path_buf(), inner: err })?;
6162
let lexed = lexer::lex(code)?;
62-
let parsed = Arc::new(parser::parse(lexed)?);
63+
let mut parsed = Arc::new(parser::parse(lexed)?);
64+
optimizer::run_passes(&mut parsed);
6365

6466
self.parsed_modules.insert(module_path.clone(), parsed.clone());
6567
Ok((module_path, parsed))

sof-rs/src/token.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use std::cmp::Ordering;
22
use std::fmt::{Debug, Display};
33

4-
use lean_string::LeanString;
54
use gc_arena::{Gc, Mutation};
5+
use lean_string::LeanString;
66
use miette::SourceSpan;
77

88
use crate::identifier::Identifier;
@@ -21,6 +21,10 @@ pub enum InnerToken {
2121
// since they are the most common case, this saves ~10% runtime.
2222
Literal(Literal),
2323
Literals(smallvec::SmallVec<[Literal; 3]>),
24+
25+
// optimizer-generated tokens
26+
LookupName(Identifier),
27+
CallName(Identifier),
2428
}
2529

2630
/// Nonrecursive literals.
@@ -249,6 +253,8 @@ impl Debug for Token {
249253
InnerToken::Literal(arg0) => f.debug_list().entry(arg0).finish(),
250254
InnerToken::WhileBody => f.debug_tuple("WhileBody").finish(),
251255
InnerToken::SwitchBody => f.debug_tuple("SwitchBody").finish(),
256+
InnerToken::CallName(name) => f.debug_tuple("Call").field(name).finish(),
257+
InnerToken::LookupName(name) => f.debug_tuple("Lookup").field(name).finish(),
252258
}?;
253259
write!(f, ", {:?} }}", (self.span.offset(), self.span.len()))
254260
}

0 commit comments

Comments
 (0)