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

Commit 9eaf276

Browse files
introduce optimizer
1 parent a1182e9 commit 9eaf276

File tree

7 files changed

+140
-46
lines changed

7 files changed

+140
-46
lines changed

sof-rs/src/cli.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,13 @@ impl Args {
3232
/// - if --interactive, always open REPL
3333
/// - otherwise, only open REPL if neither input file nor inline command was used.
3434
pub fn should_open_repl(&self) -> bool {
35-
self.interactive || (!self.input.is_some() && !self.command.is_some())
35+
self.interactive || (self.input.is_none() && self.command.is_none())
3636
}
3737

3838
/// Configure the logger with the options that control logging behavior.
3939
pub fn configure_env_logger(&self, builder: &mut env_logger::Builder) {
4040
let mut should_apply_default = true;
41+
#[allow(clippy::match_wildcard_for_single_variants)]
4142
for log_level in self.debug_options.iter().filter_map(|f| match f {
4243
DebugOption::LogLevel(log_level) => Some(log_level),
4344
_ => None,
@@ -74,7 +75,7 @@ pub enum LogLevel {
7475
}
7576

7677
impl LogLevel {
77-
pub fn configure_env_logger(&self, builder: &mut env_logger::Builder) {
78+
pub fn configure_env_logger(self, builder: &mut env_logger::Builder) {
7879
match self {
7980
Self::Off => builder.filter_level(log::LevelFilter::Off),
8081
Self::SelfDebug => builder.filter_module("sof", log::LevelFilter::Debug),

sof-rs/src/main.rs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ mod cli;
2121
mod error;
2222
mod identifier;
2323
mod lib;
24+
mod optimizer;
2425
mod parser;
2526
mod runtime;
2627
mod token;
@@ -146,15 +147,11 @@ fn main() -> miette::Result<()> {
146147

147148
// Finalize.
148149

149-
if let Some(snapshot_filename) = args
150-
.debug_options
151-
.iter()
152-
.filter_map(|d| match d {
153-
DebugOption::ExportSnapshot { target_filename } => Some(target_filename),
154-
_ => None,
155-
})
156-
.next()
157-
{
150+
#[allow(clippy::match_wildcard_for_single_variants)]
151+
if let Some(snapshot_filename) = args.debug_options.iter().find_map(|d| match d {
152+
DebugOption::ExportSnapshot { target_filename } => Some(target_filename),
153+
_ => None,
154+
}) {
158155
info!("Output snapshot to {}", snapshot_filename.display());
159156
}
160157

@@ -168,8 +165,9 @@ fn run_code_on_arena(
168165
library_path: impl Into<PathBuf>,
169166
) -> miette::Result<()> {
170167
let result = parser::lexer::lex(code)?;
171-
let parsed = Arc::new(parser::parse(result)?);
168+
let mut parsed = Arc::new(parser::parse(result)?);
172169
debug!("parsed code as {parsed:#?}");
170+
optimizer::run_passes(&mut parsed);
173171
run_on_arena(arena, parsed, path, library_path)?;
174172
Ok(())
175173
}

sof-rs/src/optimizer.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//! Program optimization.
2+
3+
use std::sync::Arc;
4+
5+
use log::{debug, trace};
6+
7+
use crate::runtime::stackable::TokenVec;
8+
use crate::token::Token;
9+
10+
/// Single optimization pass that can be executed on a token list.
11+
pub type Pass = fn(tokens: &mut Vec<Token>);
12+
13+
pub static DEFAULT_PASSES: [Pass; 1] = [passes::combine_literal_pushes];
14+
15+
pub fn run_passes(tokens: &mut TokenVec) {
16+
debug!("running optimizer on {} tokens…", tokens.len());
17+
// TODO: super wasteful
18+
let mut work_tokens = (**tokens).clone();
19+
for pass in DEFAULT_PASSES {
20+
pass(&mut work_tokens);
21+
}
22+
trace!("after optimizer: {work_tokens:#?}");
23+
*tokens = Arc::new(work_tokens);
24+
}
25+
26+
mod passes {
27+
use log::{debug, trace};
28+
use miette::SourceSpan;
29+
use smallvec::SmallVec;
30+
31+
use crate::token::{InnerToken, Token};
32+
33+
/// Optimization pass which combines multiple literal tokens into a single token which pushes multiple literals at
34+
/// once.
35+
pub fn combine_literal_pushes(tokens: &mut Vec<Token>) {
36+
debug!("combine_literal_pushes");
37+
38+
let mut start_span = SourceSpan::new(0.into(), 0);
39+
let mut end_span = SourceSpan::new(0.into(), 0);
40+
let mut start_index = 0;
41+
let mut previous_literal_tokens = smallvec::SmallVec::new();
42+
43+
let mut idx = 0;
44+
while idx < tokens.len() {
45+
let current = &tokens[idx];
46+
if let InnerToken::Literals(literals) = &current.inner {
47+
// Set start span via first token.
48+
if previous_literal_tokens.is_empty() {
49+
start_span = current.span;
50+
start_index = idx;
51+
}
52+
end_span = current.span;
53+
previous_literal_tokens.extend(literals.clone());
54+
} else {
55+
if idx > start_index + 1 && !previous_literal_tokens.is_empty() {
56+
// End the literal list
57+
trace!("combining literals in range [{start_index}; {}]", idx - 1);
58+
let previous_lit_tokens = previous_literal_tokens;
59+
previous_literal_tokens = SmallVec::new();
60+
tokens.splice(start_index .. idx, [Token {
61+
inner: InnerToken::Literals(previous_lit_tokens),
62+
span: SourceSpan::new(
63+
start_span.offset().into(),
64+
end_span.offset() + end_span.len() - start_span.offset(),
65+
),
66+
}]);
67+
idx = start_index + 1;
68+
continue;
69+
}
70+
previous_literal_tokens = SmallVec::new();
71+
}
72+
idx += 1;
73+
}
74+
}
75+
}

sof-rs/src/parser/mod.rs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use std::borrow::Borrow;
22

33
use miette::SourceSpan;
4+
use smallvec::smallvec;
45

56
use crate::error::Error;
6-
use crate::token::{Command, InnerToken, Token};
7+
use crate::token::{Command, InnerToken, Literal, Token};
78

89
pub mod lexer;
910

@@ -18,14 +19,19 @@ where
1819
let span = t.borrow().span;
1920
match token {
2021
lexer::RawToken::Keyword(lexer::Keyword::ListStart) =>
21-
output.push(Token { inner: InnerToken::ListStart, span }),
22-
lexer::RawToken::Keyword(lexer::Keyword::Curry) => output.push(Token { inner: InnerToken::Curry, span }),
23-
lexer::RawToken::Decimal(decimal) => output.push(Token { inner: InnerToken::Decimal(*decimal), span }),
24-
lexer::RawToken::Integer(int) => output.push(Token { inner: InnerToken::Integer(*int), span }),
25-
lexer::RawToken::String(string) => output.push(Token { inner: InnerToken::String(string.clone()), span }),
26-
lexer::RawToken::Boolean(boolean) => output.push(Token { inner: InnerToken::Boolean(*boolean), span }),
27-
lexer::RawToken::Identifier(identifier) =>
28-
output.push(Token { inner: InnerToken::Identifier(identifier.clone()), span }),
22+
output.push(Token { inner: InnerToken::Literals(smallvec![Literal::ListStart]), span }),
23+
lexer::RawToken::Keyword(lexer::Keyword::Curry) =>
24+
output.push(Token { inner: InnerToken::Literals(smallvec![Literal::Curry]), span }),
25+
lexer::RawToken::Decimal(decimal) =>
26+
output.push(Token { inner: InnerToken::Literals(smallvec![Literal::Decimal(*decimal)]), span }),
27+
lexer::RawToken::Integer(int) =>
28+
output.push(Token { inner: InnerToken::Literals(smallvec![Literal::Integer(*int)]), span }),
29+
lexer::RawToken::String(string) =>
30+
output.push(Token { inner: InnerToken::Literals(smallvec![Literal::String(string.clone())]), span }),
31+
lexer::RawToken::Boolean(boolean) =>
32+
output.push(Token { inner: InnerToken::Literals(smallvec![Literal::Boolean(*boolean)]), span }),
33+
lexer::RawToken::Identifier(identifier) => output
34+
.push(Token { inner: InnerToken::Literals(smallvec![Literal::Identifier(identifier.clone())]), span }),
2935
lexer::RawToken::Keyword(lexer::Keyword::CodeBlockStart) => {
3036
let mut depth = 1usize;
3137
let mut inner_tokens = Vec::new();

sof-rs/src/runtime.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,15 @@ impl<'gc> Stack<'gc> {
8585
self.main.push(value);
8686
}
8787

88+
pub fn push_n(&mut self, values: impl ExactSizeIterator<Item = Stackable<'gc>>) {
89+
let needed_size = values.len();
90+
self.main.reserve(needed_size);
91+
for value in values {
92+
// FIXME: could be some kind of unchecked push since reserve guarantees space
93+
self.main.push(value);
94+
}
95+
}
96+
8897
pub fn push_nametable(&mut self, nametable: GcRefLock<'gc, Nametable<'gc>>) {
8998
self.push(Stackable::Nametable(nametable));
9099
self.top_nametable = self.main.len() - 1;

sof-rs/src/runtime/interpreter.rs

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

1313
use crate::arc_iter::ArcVecIter;
14-
use crate::call_builtin_function;
1514
use crate::error::Error;
1615
use crate::identifier::Identifier;
1716
use crate::lib::DEFAULT_REGISTRY;
1817
use crate::runtime::list::List;
1918
use crate::runtime::module::ModuleRegistry;
2019
use crate::runtime::nametable::{Nametable, NametableType};
21-
use crate::runtime::stackable::{BuiltinType, Function, TokenVec};
20+
use crate::runtime::stackable::{BuiltinType, CodeBlock, Function, TokenVec};
2221
use crate::runtime::util::{SwitchCase, SwitchCases, UtilityData};
2322
use crate::runtime::{Stack, StackArena, Stackable};
24-
use crate::token::{Command, InnerToken, Token};
23+
use crate::token::{Command, InnerToken, Literal, Token};
24+
use crate::{call_builtin_function, optimizer};
2525

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

35-
pub fn run(tokens: TokenVec, file_path: impl Into<PathBuf>, library_path: &Path) -> Result<Metrics, Error> {
35+
pub fn run(mut 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);
3738
run_on_arena(&mut arena, tokens, file_path, library_path)
3839
}
3940

4041
/// Shortest token sequence that runs the preamble:
4142
/// `"preamble" use`
4243
static RUN_PREAMBLE: LazyLock<[Token; 2]> = LazyLock::new(|| {
43-
[Token { inner: InnerToken::String("preamble".into()), span: SourceSpan::new(0.into(), 0) }, Token {
44-
inner: InnerToken::Command(Command::Use),
45-
span: SourceSpan::new(0.into(), 0),
46-
}]
44+
[
45+
Token {
46+
inner: InnerToken::Literals(smallvec![Literal::String("preamble".into())]),
47+
span: SourceSpan::new(0.into(), 0),
48+
},
49+
Token { inner: InnerToken::Command(Command::Use), span: SourceSpan::new(0.into(), 0) },
50+
]
4751
});
4852

4953
/// Constructs a new arena and runs the preamble on it.
@@ -867,8 +871,12 @@ fn execute_token<'a>(token: &Token, mc: &Mutation<'a>, stack: &mut Stack<'a>) ->
867871
no_action()
868872
},
869873
InnerToken::Command(cmd) => todo!("{cmd:?} is unimplemented"),
870-
literal => {
871-
stack.push(literal.as_stackable(mc));
874+
InnerToken::Literals(literals) => {
875+
stack.push_n(literals.into_iter().map(Literal::as_stackable));
876+
no_action()
877+
},
878+
InnerToken::CodeBlock(cb) => {
879+
stack.push(Stackable::CodeBlock(Gc::new(mc, CodeBlock { code: cb.clone() })));
872880
no_action()
873881
},
874882
}

sof-rs/src/token.rs

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@ use std::cmp::Ordering;
22
use std::fmt::{Debug, Display};
33

44
use flexstr::SharedStr;
5-
use gc_arena::{Gc, Mutation};
65
use miette::SourceSpan;
76

87
use crate::identifier::Identifier;
98
use crate::parser::lexer;
10-
use crate::runtime::stackable::{CodeBlock, Stackable, TokenVec};
9+
use crate::runtime::stackable::{Stackable, TokenVec};
1110

1211
#[derive(Debug, PartialEq, Clone)]
1312
pub enum InnerToken {
@@ -17,19 +16,25 @@ pub enum InnerToken {
1716
/// Special token only used internally to support switch cases.
1817
SwitchBody,
1918

20-
// literals
19+
Literals(smallvec::SmallVec<[Literal; 3]>),
20+
21+
CodeBlock(TokenVec),
22+
}
23+
24+
/// Nonrecursive literals.
25+
#[derive(Debug, PartialEq, Clone)]
26+
pub enum Literal {
2127
Integer(i64),
2228
Decimal(f64),
2329
Identifier(Identifier),
2430
String(SharedStr),
2531
Boolean(bool),
26-
CodeBlock(TokenVec),
2732
ListStart,
2833
Curry,
2934
}
3035

31-
impl InnerToken {
32-
pub fn as_stackable<'gc>(&self, mc: &Mutation<'gc>) -> Stackable<'gc> {
36+
impl Literal {
37+
pub fn as_stackable<'gc>(&self) -> Stackable<'gc> {
3338
match self {
3439
Self::Integer(int) => Stackable::Integer(*int),
3540
Self::Decimal(decimal) => Stackable::Decimal(*decimal),
@@ -38,8 +43,6 @@ impl InnerToken {
3843
Self::Boolean(boolean) => Stackable::Boolean(*boolean),
3944
Self::ListStart => Stackable::ListStart,
4045
Self::Curry => Stackable::Curry,
41-
Self::CodeBlock(code) => Stackable::CodeBlock(Gc::new(mc, CodeBlock { code: code.clone() })),
42-
_ => unreachable!(),
4346
}
4447
}
4548
}
@@ -239,13 +242,7 @@ impl Debug for Token {
239242
match &self.inner {
240243
InnerToken::Command(arg0) => f.debug_tuple("Command").field(arg0).finish(),
241244
InnerToken::CodeBlock(arg0) => f.debug_tuple("CodeBlock").field(arg0).finish(),
242-
InnerToken::Integer(arg0) => f.debug_tuple("Integer").field(arg0).finish(),
243-
InnerToken::Decimal(arg0) => f.debug_tuple("Decimal").field(arg0).finish(),
244-
InnerToken::Identifier(arg0) => f.debug_tuple("Identifier").field(arg0).finish(),
245-
InnerToken::String(arg0) => f.debug_tuple("String").field(arg0).finish(),
246-
InnerToken::Boolean(arg0) => f.debug_tuple("Boolean").field(arg0).finish(),
247-
InnerToken::ListStart => f.debug_tuple("ListStart").finish(),
248-
InnerToken::Curry => f.debug_tuple("Curry").finish(),
245+
InnerToken::Literals(arg0) => f.debug_list().entries(arg0).finish(),
249246
InnerToken::WhileBody => f.debug_tuple("WhileBody").finish(),
250247
InnerToken::SwitchBody => f.debug_tuple("SwitchBody").finish(),
251248
}?;

0 commit comments

Comments
 (0)