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
Options:
-e, --eval <EVAL> Run a script string directly
-b, --bench Run benchmarks (Only allowed in Release mode)
-u, --update-bench Update the benchmark baseline (Only allowed in Release mode)
-d, --dump Dump the compiled AST
-h, --help Print help
-V, --version Print version
-e, --eval <EVAL> Run a script string directly
-b, --bench Run benchmarks (Only allowed in Release mode)
-u, --update-bench Update the benchmark baseline (Only allowed in Release mode)
-d, --dump Dump the compiled AST
-t, --trace Run with TracingObserver enabled
--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)
/// and Phase 4 (Global Inlining).
pub struct Optimizer {
pub level: u32,
pub enabled: bool,
max_passes: usize,
pub globals: Option<Rc<RefCell<Vec<Value>>>>,
pub global_purity: Option<Rc<RefCell<HashMap<u32, bool>>>>,
}
impl Optimizer {
pub fn new(level: u32) -> Self {
Self { level, max_passes: 5, globals: None, global_purity: None }
pub fn new(enabled: bool) -> Self {
Self { enabled, max_passes: 5, globals: None, global_purity: None }
}
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 {
if self.level == 0 {
if !self.enabled {
return node;
}
@@ -132,7 +132,7 @@ impl Optimizer {
let callee = self.visit_node(*callee, sub);
let args = self.visit_node(*args, sub);
if self.level >= 2 {
if self.enabled {
// Case 1: Beta-Reduction for Lambda Literals
if let BoundKind::Lambda { params, body, .. } = &callee.kind
&& 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 Some(closure) = obj.as_any().downcast_ref::<Closure>() {
let mut closure_sub = SubstitutionMap::new();
@@ -189,7 +189,7 @@ impl Optimizer {
BoundKind::If { cond, then_br, else_br } => {
let cond = self.visit_node(*cond, sub);
if self.level >= 2
if self.enabled
&& let BoundKind::Constant(ref val) = cond.kind {
if val.is_truthy() {
return self.visit_node(*then_br, sub);
@@ -224,7 +224,7 @@ impl Optimizer {
for (i, e) in exprs.into_iter().enumerate() {
let is_last = i == last_idx;
if self.level >= 2 && !is_last {
if self.enabled && !is_last {
let removable = match &e.kind {
BoundKind::DefLocal { slot, 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);
if self.level >= 2 && matches!(opt.kind, BoundKind::Nop) && !is_last {
if self.enabled && matches!(opt.kind, BoundKind::Nop) && !is_last {
continue;
}
new_exprs.push(opt);
}
if self.level >= 2 {
if self.enabled {
if new_exprs.is_empty() {
return Node { identity: node.identity, kind: BoundKind::Nop, ty: StaticType::Void };
} else if new_exprs.len() == 1 {
@@ -259,6 +259,7 @@ impl Optimizer {
(BoundKind::Block { exprs: new_exprs }, ty)
},
BoundKind::Lambda { params, upvalues: original_upvalues, body, positional_count } => {
let mut new_upvalues = Vec::new();
let mut mapping = Vec::new();
@@ -675,55 +676,55 @@ impl SubstitutionMap {
mod tests {
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();
env.optimization_level = level;
env.optimization = enabled;
env.dump_ast(source).expect("Compilation failed during test")
}
#[test]
fn test_opt_folding() {
let dump = get_optimized_dump("(+ 10 20)", 2);
let dump = get_optimized_dump("(+ 10 20)", true);
insta::assert_snapshot!(dump);
}
#[test]
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"));
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\""));
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#"));
let dump_cmp = get_optimized_dump("(> 10 5)", 2);
let dump_cmp = get_optimized_dump("(> 10 5)", true);
assert!(dump_cmp.contains("Constant: true"));
}
#[test]
fn test_opt_local_inlining() {
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);
}
#[test]
fn test_opt_dce_unused() {
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);
}
#[test]
fn test_opt_assignment_safety() {
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);
}
#[test]
fn test_opt_lambda_cracking() {
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);
}
}
+5 -5
View File
@@ -26,7 +26,7 @@ pub struct Environment {
pub function_registry: Rc<RefCell<HashMap<u32, BoundNode>>>,
pub monomorph_cache: Rc<RefCell<MonoCache>>,
pub debug_mode: bool,
pub optimization_level: u32,
pub optimization: bool,
}
struct EnvFunctionRegistry {
@@ -91,7 +91,7 @@ impl Environment {
function_registry: Rc::new(RefCell::new(HashMap::new())),
monomorph_cache: Rc::new(RefCell::new(HashMap::new())),
debug_mode: false,
optimization_level: 2,
optimization: true,
};
env.register_stdlib();
env
@@ -189,7 +189,7 @@ impl Environment {
let specialized = self.specialize_node(node);
// 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_purity(self.global_purity.clone());
let optimized = optimizer.optimize(specialized);
@@ -210,7 +210,7 @@ impl Environment {
let global_values = self.global_values.clone();
let global_types = self.global_types.clone();
let global_purity = self.global_purity.clone();
let opt_level = self.optimization_level;
let optimization = self.optimization;
let compiler = Rc::new(
move |func_template: BoundNode,
@@ -237,7 +237,7 @@ impl Environment {
let specialized_ast = sub_specializer.specialize(retyped_ast);
// 3. Optimize (Phase 2: Cracking & Folding)
let optimizer = Optimizer::new(opt_level)
let optimizer = Optimizer::new(optimization)
.with_globals(global_values.clone())
.with_purity(global_purity.clone());
let optimized_ast = optimizer.optimize(specialized_ast);
+4 -4
View File
@@ -31,15 +31,15 @@ struct Cli {
#[arg(short, long)]
trace: bool,
/// Optimization level (0: None, 1: Cracking, 2: Aggressive)
#[arg(short, long, default_value_t = 0)]
opt: u32,
/// Disable optimization
#[arg(long)]
no_opt: bool,
}
fn main() {
let cli = Cli::parse();
let mut env = Environment::new();
env.optimization_level = cli.opt;
env.optimization = !cli.no_opt;
if cli.bench || cli.update_bench {
if cfg!(debug_assertions) {
+5 -5
View File
@@ -82,13 +82,13 @@ mod tests {
#[test]
fn test_examples() {
for level in 0..=2 {
let results = crate::utils::tester::run_functional_tests_with_level(level);
for opt in [false, true] {
let results = crate::utils::tester::run_functional_tests_with_optimization(opt);
for res in results {
assert!(
res.success,
"Example {} failed at level {}: {}",
res.name, level, res.message
"Example {} failed at opt {}: {}",
res.name, opt, res.message
);
}
}
@@ -97,7 +97,7 @@ mod tests {
#[test]
fn test_debug_mode_logging() {
let mut env = Environment::new();
env.optimization_level = 0;
env.optimization = false;
let source = "(+ 10 20)";
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
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 start_run = std::time::Instant::now();
@@ -157,7 +157,7 @@ impl CompilerApp {
fn dump_ast(&mut self) {
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) {
Ok(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> {
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 entries = fs::read_dir("examples").unwrap();
let output_re = Regex::new(r";; Output: (.*)").unwrap();
for entry in entries.filter_map(|e| e.ok()) {
let mut env = Environment::new(); // Fresh environment per test file
env.optimization_level = level;
env.optimization = enabled;
let path = entry.path();
if path.extension().is_some_and(|ext| ext == "myc") {
let content = fs::read_to_string(&path).unwrap();
@@ -53,8 +53,8 @@ pub fn run_functional_tests_with_level(level: u32) -> Vec<TestResult> {
name,
success: false,
message: format!(
"Level {}: Expected {}, got {}",
level, expected, val_str
"Opt {}: Expected {}, got {}",
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 {
name,
success: false,
message: format!("Level {}: Error: {}", level, e),
message: format!("Opt {}: Error: {}", if enabled { "ON" } else { "OFF" }, e),
}),
}
}