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:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user