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