Refactor: Replace optimization level with boolean flag

The optimizer now uses a simple boolean flag (`optimization`) instead of
an `optimization_level` (u32). This simplifies the optimizer's logic and
makes it easier to enable or disable optimizations.

The command-line interface and internal testing have been updated to
reflect this change.
This commit is contained in:
Michael Schimmel
2026-02-22 01:50:14 +01:00
parent d3cdafbb85
commit 7c997ca841
7 changed files with 52 additions and 49 deletions
+8 -6
View File
@@ -53,9 +53,11 @@ Arguments:
[FILE] The script file to run or benchmark [FILE] The script file to run or benchmark
Options: Options:
-e, --eval <EVAL> Run a script string directly -e, --eval <EVAL> Run a script string directly
-b, --bench Run benchmarks (Only allowed in Release mode) -b, --bench Run benchmarks (Only allowed in Release mode)
-u, --update-bench Update the benchmark baseline (Only allowed in Release mode) -u, --update-bench Update the benchmark baseline (Only allowed in Release mode)
-d, --dump Dump the compiled AST -d, --dump Dump the compiled AST
-h, --help Print help -t, --trace Run with TracingObserver enabled
-V, --version Print version --no-opt Disable optimization
-h, --help Print help
-V, --version Print version
+22 -21
View File
@@ -9,15 +9,15 @@ use std::collections::{HashMap, HashSet};
/// The Optimizer performs Phase 2 (Cracking), Phase 2.5/2.6 (Aggressive Collapsing & DCE) /// The Optimizer performs Phase 2 (Cracking), Phase 2.5/2.6 (Aggressive Collapsing & DCE)
/// and Phase 4 (Global Inlining). /// and Phase 4 (Global Inlining).
pub struct Optimizer { pub struct Optimizer {
pub level: u32, pub enabled: bool,
max_passes: usize, max_passes: usize,
pub globals: Option<Rc<RefCell<Vec<Value>>>>, pub globals: Option<Rc<RefCell<Vec<Value>>>>,
pub global_purity: Option<Rc<RefCell<HashMap<u32, bool>>>>, pub global_purity: Option<Rc<RefCell<HashMap<u32, bool>>>>,
} }
impl Optimizer { impl Optimizer {
pub fn new(level: u32) -> Self { pub fn new(enabled: bool) -> Self {
Self { level, max_passes: 5, globals: None, global_purity: None } Self { enabled, max_passes: 5, globals: None, global_purity: None }
} }
pub fn with_globals(mut self, globals: Rc<RefCell<Vec<Value>>>) -> Self { pub fn with_globals(mut self, globals: Rc<RefCell<Vec<Value>>>) -> Self {
@@ -31,7 +31,7 @@ impl Optimizer {
} }
pub fn optimize(&self, node: TypedNode) -> TypedNode { pub fn optimize(&self, node: TypedNode) -> TypedNode {
if self.level == 0 { if !self.enabled {
return node; return node;
} }
@@ -132,7 +132,7 @@ impl Optimizer {
let callee = self.visit_node(*callee, sub); let callee = self.visit_node(*callee, sub);
let args = self.visit_node(*args, sub); let args = self.visit_node(*args, sub);
if self.level >= 2 { if self.enabled {
// Case 1: Beta-Reduction for Lambda Literals // Case 1: Beta-Reduction for Lambda Literals
if let BoundKind::Lambda { params, body, .. } = &callee.kind if let BoundKind::Lambda { params, body, .. } = &callee.kind
&& let Some(collapsed) = self.try_beta_reduce(params, &args, (**body).clone()) { && let Some(collapsed) = self.try_beta_reduce(params, &args, (**body).clone()) {
@@ -158,7 +158,7 @@ impl Optimizer {
} }
} }
if self.level >= 1 if self.enabled
&& let BoundKind::Constant(Value::Object(ref obj)) = callee.kind && let BoundKind::Constant(Value::Object(ref obj)) = callee.kind
&& let Some(closure) = obj.as_any().downcast_ref::<Closure>() { && let Some(closure) = obj.as_any().downcast_ref::<Closure>() {
let mut closure_sub = SubstitutionMap::new(); let mut closure_sub = SubstitutionMap::new();
@@ -189,7 +189,7 @@ impl Optimizer {
BoundKind::If { cond, then_br, else_br } => { BoundKind::If { cond, then_br, else_br } => {
let cond = self.visit_node(*cond, sub); let cond = self.visit_node(*cond, sub);
if self.level >= 2 if self.enabled
&& let BoundKind::Constant(ref val) = cond.kind { && let BoundKind::Constant(ref val) = cond.kind {
if val.is_truthy() { if val.is_truthy() {
return self.visit_node(*then_br, sub); return self.visit_node(*then_br, sub);
@@ -224,7 +224,7 @@ impl Optimizer {
for (i, e) in exprs.into_iter().enumerate() { for (i, e) in exprs.into_iter().enumerate() {
let is_last = i == last_idx; let is_last = i == last_idx;
if self.level >= 2 && !is_last { if self.enabled && !is_last {
let removable = match &e.kind { let removable = match &e.kind {
BoundKind::DefLocal { slot, value, .. } => { BoundKind::DefLocal { slot, value, .. } => {
!used_in_block.contains(slot) && !sub.captured_slots.contains(slot) && self.is_pure(value) !used_in_block.contains(slot) && !sub.captured_slots.contains(slot) && self.is_pure(value)
@@ -241,13 +241,13 @@ impl Optimizer {
} }
let opt = self.visit_node(e, sub); let opt = self.visit_node(e, sub);
if self.level >= 2 && matches!(opt.kind, BoundKind::Nop) && !is_last { if self.enabled && matches!(opt.kind, BoundKind::Nop) && !is_last {
continue; continue;
} }
new_exprs.push(opt); new_exprs.push(opt);
} }
if self.level >= 2 { if self.enabled {
if new_exprs.is_empty() { if new_exprs.is_empty() {
return Node { identity: node.identity, kind: BoundKind::Nop, ty: StaticType::Void }; return Node { identity: node.identity, kind: BoundKind::Nop, ty: StaticType::Void };
} else if new_exprs.len() == 1 { } else if new_exprs.len() == 1 {
@@ -259,6 +259,7 @@ impl Optimizer {
(BoundKind::Block { exprs: new_exprs }, ty) (BoundKind::Block { exprs: new_exprs }, ty)
}, },
BoundKind::Lambda { params, upvalues: original_upvalues, body, positional_count } => { BoundKind::Lambda { params, upvalues: original_upvalues, body, positional_count } => {
let mut new_upvalues = Vec::new(); let mut new_upvalues = Vec::new();
let mut mapping = Vec::new(); let mut mapping = Vec::new();
@@ -675,55 +676,55 @@ impl SubstitutionMap {
mod tests { mod tests {
use crate::ast::environment::Environment; use crate::ast::environment::Environment;
fn get_optimized_dump(source: &str, level: u32) -> String { fn get_optimized_dump(source: &str, enabled: bool) -> String {
let mut env = Environment::new(); let mut env = Environment::new();
env.optimization_level = level; env.optimization = enabled;
env.dump_ast(source).expect("Compilation failed during test") env.dump_ast(source).expect("Compilation failed during test")
} }
#[test] #[test]
fn test_opt_folding() { fn test_opt_folding() {
let dump = get_optimized_dump("(+ 10 20)", 2); let dump = get_optimized_dump("(+ 10 20)", true);
insta::assert_snapshot!(dump); insta::assert_snapshot!(dump);
} }
#[test] #[test]
fn test_opt_folding_complex() { fn test_opt_folding_complex() {
let dump_float = get_optimized_dump("(+ 1.5 2.5)", 2); let dump_float = get_optimized_dump("(+ 1.5 2.5)", true);
assert!(dump_float.contains("Constant: 4")); assert!(dump_float.contains("Constant: 4"));
let dump_text = get_optimized_dump("(+ \"hello \" \"world\")", 2); let dump_text = get_optimized_dump("(+ \"hello \" \"world\")", true);
assert!(dump_text.contains("Constant: \"hello world\"")); assert!(dump_text.contains("Constant: \"hello world\""));
let dump_date = get_optimized_dump("(date \"2023-01-01\")", 2); let dump_date = get_optimized_dump("(date \"2023-01-01\")", true);
assert!(dump_date.contains("Constant: #2023-01-01 00:00:00#")); assert!(dump_date.contains("Constant: #2023-01-01 00:00:00#"));
let dump_cmp = get_optimized_dump("(> 10 5)", 2); let dump_cmp = get_optimized_dump("(> 10 5)", true);
assert!(dump_cmp.contains("Constant: true")); assert!(dump_cmp.contains("Constant: true"));
} }
#[test] #[test]
fn test_opt_local_inlining() { fn test_opt_local_inlining() {
let source = "(fn [] (do (def x 10) (+ x 5)))"; let source = "(fn [] (do (def x 10) (+ x 5)))";
let dump = get_optimized_dump(source, 2); let dump = get_optimized_dump(source, true);
insta::assert_snapshot!(dump); insta::assert_snapshot!(dump);
} }
#[test] #[test]
fn test_opt_dce_unused() { fn test_opt_dce_unused() {
let source = "(fn [] (do (def x 10) 42))"; let source = "(fn [] (do (def x 10) 42))";
let dump = get_optimized_dump(source, 2); let dump = get_optimized_dump(source, true);
insta::assert_snapshot!(dump); insta::assert_snapshot!(dump);
} }
#[test] #[test]
fn test_opt_assignment_safety() { fn test_opt_assignment_safety() {
let source = "(fn [] (do (def x 10) (assign x 20) x))"; let source = "(fn [] (do (def x 10) (assign x 20) x))";
let dump = get_optimized_dump(source, 2); let dump = get_optimized_dump(source, true);
insta::assert_snapshot!(dump); insta::assert_snapshot!(dump);
} }
#[test] #[test]
fn test_opt_lambda_cracking() { fn test_opt_lambda_cracking() {
let source = "(fn [] (do (def x 10) (fn [] x)))"; let source = "(fn [] (do (def x 10) (fn [] x)))";
let dump = get_optimized_dump(source, 2); let dump = get_optimized_dump(source, true);
insta::assert_snapshot!(dump); insta::assert_snapshot!(dump);
} }
} }
+5 -5
View File
@@ -26,7 +26,7 @@ pub struct Environment {
pub function_registry: Rc<RefCell<HashMap<u32, BoundNode>>>, pub function_registry: Rc<RefCell<HashMap<u32, BoundNode>>>,
pub monomorph_cache: Rc<RefCell<MonoCache>>, pub monomorph_cache: Rc<RefCell<MonoCache>>,
pub debug_mode: bool, pub debug_mode: bool,
pub optimization_level: u32, pub optimization: bool,
} }
struct EnvFunctionRegistry { struct EnvFunctionRegistry {
@@ -91,7 +91,7 @@ impl Environment {
function_registry: Rc::new(RefCell::new(HashMap::new())), function_registry: Rc::new(RefCell::new(HashMap::new())),
monomorph_cache: Rc::new(RefCell::new(HashMap::new())), monomorph_cache: Rc::new(RefCell::new(HashMap::new())),
debug_mode: false, debug_mode: false,
optimization_level: 2, optimization: true,
}; };
env.register_stdlib(); env.register_stdlib();
env env
@@ -189,7 +189,7 @@ impl Environment {
let specialized = self.specialize_node(node); let specialized = self.specialize_node(node);
// 2. Optimize (Level 1: Cracking, Level 2: Collapsing) // 2. Optimize (Level 1: Cracking, Level 2: Collapsing)
let optimizer = Optimizer::new(self.optimization_level) let optimizer = Optimizer::new(self.optimization)
.with_globals(self.global_values.clone()) .with_globals(self.global_values.clone())
.with_purity(self.global_purity.clone()); .with_purity(self.global_purity.clone());
let optimized = optimizer.optimize(specialized); let optimized = optimizer.optimize(specialized);
@@ -210,7 +210,7 @@ impl Environment {
let global_values = self.global_values.clone(); let global_values = self.global_values.clone();
let global_types = self.global_types.clone(); let global_types = self.global_types.clone();
let global_purity = self.global_purity.clone(); let global_purity = self.global_purity.clone();
let opt_level = self.optimization_level; let optimization = self.optimization;
let compiler = Rc::new( let compiler = Rc::new(
move |func_template: BoundNode, move |func_template: BoundNode,
@@ -237,7 +237,7 @@ impl Environment {
let specialized_ast = sub_specializer.specialize(retyped_ast); let specialized_ast = sub_specializer.specialize(retyped_ast);
// 3. Optimize (Phase 2: Cracking & Folding) // 3. Optimize (Phase 2: Cracking & Folding)
let optimizer = Optimizer::new(opt_level) let optimizer = Optimizer::new(optimization)
.with_globals(global_values.clone()) .with_globals(global_values.clone())
.with_purity(global_purity.clone()); .with_purity(global_purity.clone());
let optimized_ast = optimizer.optimize(specialized_ast); let optimized_ast = optimizer.optimize(specialized_ast);
+4 -4
View File
@@ -31,15 +31,15 @@ struct Cli {
#[arg(short, long)] #[arg(short, long)]
trace: bool, trace: bool,
/// Optimization level (0: None, 1: Cracking, 2: Aggressive) /// Disable optimization
#[arg(short, long, default_value_t = 0)] #[arg(long)]
opt: u32, no_opt: bool,
} }
fn main() { fn main() {
let cli = Cli::parse(); let cli = Cli::parse();
let mut env = Environment::new(); let mut env = Environment::new();
env.optimization_level = cli.opt; env.optimization = !cli.no_opt;
if cli.bench || cli.update_bench { if cli.bench || cli.update_bench {
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
+5 -5
View File
@@ -82,13 +82,13 @@ mod tests {
#[test] #[test]
fn test_examples() { fn test_examples() {
for level in 0..=2 { for opt in [false, true] {
let results = crate::utils::tester::run_functional_tests_with_level(level); let results = crate::utils::tester::run_functional_tests_with_optimization(opt);
for res in results { for res in results {
assert!( assert!(
res.success, res.success,
"Example {} failed at level {}: {}", "Example {} failed at opt {}: {}",
res.name, level, res.message res.name, opt, res.message
); );
} }
} }
@@ -97,7 +97,7 @@ mod tests {
#[test] #[test]
fn test_debug_mode_logging() { fn test_debug_mode_logging() {
let mut env = Environment::new(); let mut env = Environment::new();
env.optimization_level = 0; env.optimization = false;
let source = "(+ 10 20)"; let source = "(+ 10 20)";
let result = env.run_debug(source).expect("Failed to run debug"); let result = env.run_debug(source).expect("Failed to run debug");
+2 -2
View File
@@ -95,7 +95,7 @@ impl CompilerApp {
// Reset environment for a fresh run // Reset environment for a fresh run
self.env = Environment::new(); self.env = Environment::new();
self.env.optimization_level = 2; self.env.optimization = true;
let res = (|| -> Result<(myc::ast::types::Value, std::time::Duration), String> { let res = (|| -> Result<(myc::ast::types::Value, std::time::Duration), String> {
let start_run = std::time::Instant::now(); let start_run = std::time::Instant::now();
@@ -157,7 +157,7 @@ impl CompilerApp {
fn dump_ast(&mut self) { fn dump_ast(&mut self) {
self.env = Environment::new(); self.env = Environment::new();
self.env.optimization_level = 2; // Enable optimization for AST dump self.env.optimization = true; // Enable optimization for AST dump
match self.env.dump_ast(&self.source_code) { match self.env.dump_ast(&self.source_code) {
Ok(dump) => { Ok(dump) => {
self.output_log = format!("--- BOUND AST DUMP ---\n\n{}", dump); self.output_log = format!("--- BOUND AST DUMP ---\n\n{}", dump);
+6 -6
View File
@@ -18,17 +18,17 @@ pub struct BenchmarkResult {
} }
pub fn run_functional_tests() -> Vec<TestResult> { pub fn run_functional_tests() -> Vec<TestResult> {
run_functional_tests_with_level(0) run_functional_tests_with_optimization(false)
} }
pub fn run_functional_tests_with_level(level: u32) -> Vec<TestResult> { pub fn run_functional_tests_with_optimization(enabled: bool) -> Vec<TestResult> {
let mut results = Vec::new(); let mut results = Vec::new();
let entries = fs::read_dir("examples").unwrap(); let entries = fs::read_dir("examples").unwrap();
let output_re = Regex::new(r";; Output: (.*)").unwrap(); let output_re = Regex::new(r";; Output: (.*)").unwrap();
for entry in entries.filter_map(|e| e.ok()) { for entry in entries.filter_map(|e| e.ok()) {
let mut env = Environment::new(); // Fresh environment per test file let mut env = Environment::new(); // Fresh environment per test file
env.optimization_level = level; env.optimization = enabled;
let path = entry.path(); let path = entry.path();
if path.extension().is_some_and(|ext| ext == "myc") { if path.extension().is_some_and(|ext| ext == "myc") {
let content = fs::read_to_string(&path).unwrap(); let content = fs::read_to_string(&path).unwrap();
@@ -53,8 +53,8 @@ pub fn run_functional_tests_with_level(level: u32) -> Vec<TestResult> {
name, name,
success: false, success: false,
message: format!( message: format!(
"Level {}: Expected {}, got {}", "Opt {}: Expected {}, got {}",
level, expected, val_str if enabled { "ON" } else { "OFF" }, expected, val_str
), ),
}); });
} }
@@ -62,7 +62,7 @@ pub fn run_functional_tests_with_level(level: u32) -> Vec<TestResult> {
Err(e) => results.push(TestResult { Err(e) => results.push(TestResult {
name, name,
success: false, success: false,
message: format!("Level {}: Error: {}", level, e), message: format!("Opt {}: Error: {}", if enabled { "ON" } else { "OFF" }, e),
}), }),
} }
} }