From a726b79d8a7eebb01d491bd477f322f06779bfc9 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Sun, 22 Feb 2026 10:50:37 +0100 Subject: [PATCH] Refactor Purity enum and NativeFunction struct Move `Purity` enum definition from `optimizer.rs` to `types.rs` and create a `NativeFunction` struct to hold the function and its purity. Update `Value::Function` to store `Rc` and `Value::make_function` helper to simplify creation. This change allows tracking the purity of native functions, which is useful for optimization and static analysis. --- src/ast/compiler/optimizer.rs | 89 ++++++++++++++++++----------------- src/ast/environment.rs | 6 +-- src/ast/rtl/core.rs | 3 +- src/ast/rtl/datetime.rs | 3 +- src/ast/rtl/intrinsics.rs | 39 ++++++++------- src/ast/rtl/math.rs | 3 +- src/ast/rtl/type_registry.rs | 13 ++--- src/ast/types.rs | 34 ++++++++++++- src/ast/vm.rs | 4 +- 9 files changed, 111 insertions(+), 83 deletions(-) diff --git a/src/ast/compiler/optimizer.rs b/src/ast/compiler/optimizer.rs index 61a4724..3e6ee17 100644 --- a/src/ast/compiler/optimizer.rs +++ b/src/ast/compiler/optimizer.rs @@ -1,18 +1,11 @@ use crate::ast::compiler::bound_nodes::{Address, BoundKind, TypedNode}; use crate::ast::nodes::Node; -use crate::ast::types::{StaticType, Value}; +use crate::ast::types::{Purity, StaticType, Value}; use crate::ast::vm::Closure; use std::cell::RefCell; use std::collections::{HashMap, HashSet}; use std::rc::Rc; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum Purity { - Impure, // Has side effects (e.g. Set, Print) - SideEffectFree, // No side effects, but not deterministic (e.g. now(), random()) - Pure, // No side effects and deterministic (e.g. sin(x), 1 + 2) -} - /// The Optimizer performs Phase 2 (Cracking), Phase 2.5/2.6 (Aggressive Collapsing & DCE) /// and Phase 4 (Global Inlining). pub struct Optimizer { @@ -117,20 +110,20 @@ impl Optimizer { let mut info = UsageInfo::default(); self.collect_usage(node, &mut info); - if let Some(idx) = global_idx { - if info.used_globals.contains(&idx) { - return true; - } + if let Some(idx) = global_idx + && info.used_globals.contains(&idx) + { + return true; } - if let Some(slot) = local_slot { - if info.used_locals.contains(&slot) { - return true; - } + if let Some(slot) = local_slot + && info.used_locals.contains(&slot) + { + return true; } - if let Some(id) = identity { - if info.used_identities.contains(id) { - return true; - } + if let Some(id) = identity + && info.used_identities.contains(id) + { + return true; } false } @@ -172,14 +165,14 @@ impl Optimizer { } Address::Global(idx) => { if !sub.assigned_globals.contains(&idx) { - if let Some(val) = sub.globals.get(&idx) { - if self.is_inlinable_value(val, Some(idx)) { - return Node { - identity: node.identity, - ty: val.static_type(), - kind: BoundKind::Constant(val.clone()), - }; - } + if let Some(val) = sub.globals.get(&idx) + && self.is_inlinable_value(val, Some(idx)) + { + return Node { + identity: node.identity, + ty: val.static_type(), + kind: BoundKind::Constant(val.clone()), + }; } if let Some(globals_rc) = &self.globals { let globals = globals_rc.borrow(); @@ -306,7 +299,12 @@ impl Optimizer { && positional_count.is_some() { // STRICT RECURSION CHECK: Don't inline if recursive (idx or identity) - if !self.is_recursive(body, Some(*idx), None, Some(&lambda_node.identity)) { + if !self.is_recursive( + body, + Some(*idx), + None, + Some(&lambda_node.identity), + ) { let mut inner_sub = SubstitutionMap::new(); path.inlining_stack.insert(*idx); path.inlining_depth += 1; @@ -681,10 +679,10 @@ impl Optimizer { value, } => { let value = Box::new(self.visit_node(*value, sub, path)); - if let BoundKind::Constant(val) = &value.kind { - if self.is_inlinable_value(val, Some(global_index)) { - sub.add_global(global_index, val.clone()); - } + if let BoundKind::Constant(val) = &value.kind + && self.is_inlinable_value(val, Some(global_index)) + { + sub.add_global(global_index, val.clone()); } let p = self.purity_of(&value); if p > Purity::Impure @@ -712,12 +710,7 @@ impl Optimizer { BoundKind::Record { fields } => { let fields = fields .into_iter() - .map(|(k, v)| { - ( - self.visit_node(k, sub, path), - self.visit_node(v, sub, path), - ) - }) + .map(|(k, v)| (self.visit_node(k, sub, path), self.visit_node(v, sub, path))) .collect(); (BoundKind::Record { fields }, node.ty) } @@ -846,7 +839,7 @@ impl Optimizer { Purity::Impure } } - BoundKind::Constant(Value::Function(_)) => Purity::Pure, + BoundKind::Constant(Value::Function(f)) => f.purity, BoundKind::Constant(Value::Object(obj)) => { if let Some(closure) = obj.as_any().downcast_ref::() { self.purity_of(&closure.function_node) @@ -894,7 +887,7 @@ impl Optimizer { _ => return None, }; let result = match func_val { - Value::Function(f) => f(arg_values), + Value::Function(f) => (f.func)(arg_values), _ => return None, }; Some(Node { @@ -1351,10 +1344,18 @@ mod tests { // Hier sollte der Optimizer 'f' nicht unendlich oft in sich selbst inlinen. let source = "(fn [] (do (def f (fn [x] (f x))) (f 1)))"; let dump = get_optimized_dump(source, true); - + // Der Dump sollte 'Call' auf 'f' (oder Slot-Get) noch enthalten, // statt 5 Ebenen tief entfaltet zu sein. - assert!(dump.contains("Call"), "Recursive call should remain as a call. Dump: \n{}", dump); - assert!(!dump.contains("- Capturer:"), "Should not be over-optimized. Dump: \n{}", dump); + assert!( + dump.contains("Call"), + "Recursive call should remain as a call. Dump: \n{}", + dump + ); + assert!( + !dump.contains("- Capturer:"), + "Should not be over-optimized. Dump: \n{}", + dump + ); } } diff --git a/src/ast/environment.rs b/src/ast/environment.rs index d859eb5..b426b04 100644 --- a/src/ast/environment.rs +++ b/src/ast/environment.rs @@ -2,7 +2,6 @@ use crate::ast::compiler::binder::Binder; use crate::ast::compiler::{TypeChecker, TypedNode}; use crate::ast::nodes::{Node, Symbol, UntypedKind}; use crate::ast::parser::Parser; -use crate::ast::types::{Object, StaticType, Value}; use crate::ast::vm::{TracingObserver, VM}; use std::cell::RefCell; use std::collections::HashMap; @@ -12,11 +11,12 @@ use crate::ast::compiler::bound_nodes::{Address, BoundNode}; use crate::ast::compiler::dumper::Dumper; use crate::ast::compiler::lambda_collector::LambdaCollector; use crate::ast::compiler::macros::{MacroEvaluator, MacroExpander, MacroRegistry}; -use crate::ast::compiler::optimizer::{Optimizer, Purity}; +use crate::ast::compiler::optimizer::Optimizer; use crate::ast::compiler::specializer::{FunctionRegistry, MonoCache, Specializer}; use crate::ast::compiler::tco::{ExecNode, TCO}; use crate::ast::rtl; use crate::ast::rtl::intrinsics; +use crate::ast::types::{Object, Purity, StaticType, Value}; pub struct Environment { pub global_names: Rc>>, @@ -130,7 +130,7 @@ impl Environment { names.insert(Symbol::from(name), idx); types.insert(idx, ty); purity.insert(idx, purity_level); - values.push(Value::Function(Rc::new(func))); + values.push(Value::make_function(purity_level, func)); } pub fn register_constant(&self, name: &str, ty: StaticType, val: Value) { diff --git a/src/ast/rtl/core.rs b/src/ast/rtl/core.rs index bedae24..2a1ad0a 100644 --- a/src/ast/rtl/core.rs +++ b/src/ast/rtl/core.rs @@ -1,6 +1,5 @@ -use crate::ast::compiler::optimizer::Purity; use crate::ast::environment::Environment; -use crate::ast::types::{Signature, StaticType, Value}; +use crate::ast::types::{Purity, Signature, StaticType, Value}; use std::rc::Rc; pub fn register(env: &Environment) { diff --git a/src/ast/rtl/datetime.rs b/src/ast/rtl/datetime.rs index 28ae86d..081624c 100644 --- a/src/ast/rtl/datetime.rs +++ b/src/ast/rtl/datetime.rs @@ -1,6 +1,5 @@ -use crate::ast::compiler::optimizer::Purity; use crate::ast::environment::Environment; -use crate::ast::types::{Signature, StaticType, Value}; +use crate::ast::types::{Purity, Signature, StaticType, Value}; use chrono::{NaiveDate, NaiveDateTime}; pub fn register(env: &Environment) { diff --git a/src/ast/rtl/intrinsics.rs b/src/ast/rtl/intrinsics.rs index b205a93..01e0891 100644 --- a/src/ast/rtl/intrinsics.rs +++ b/src/ast/rtl/intrinsics.rs @@ -1,5 +1,4 @@ -use crate::ast::types::{StaticType, Value}; -use std::rc::Rc; +use crate::ast::types::{Purity, StaticType, Value}; /// Looks up a specialized intrinsic function for the given operator and argument types. /// Returns (Executable Value, Return Type) if a fast-path exists. @@ -7,29 +6,29 @@ pub fn lookup(name: &str, args: &[StaticType]) -> Option<(Value, StaticType)> { match (name, args) { // --- Integer Arithmetic --- ("+", [StaticType::Int, StaticType::Int]) => Some(( - Value::Function(Rc::new(|args| { + Value::make_function(Purity::Pure, |args| { if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { Value::Int(a + b) } else { Value::Int(0) // Should not happen if type checker works } - })), + }), StaticType::Int, )), ("-", [StaticType::Int, StaticType::Int]) => Some(( - Value::Function(Rc::new(|args| { + Value::make_function(Purity::Pure, |args| { if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { Value::Int(a - b) } else { Value::Int(0) } - })), + }), StaticType::Int, )), ("-", [StaticType::Int, StaticType::Int, StaticType::Int]) => Some(( // Variadic optimization for 3 args (common in some Lisp dialects, though - usually is binary/unary) // MyC's core.rs supports variadic subtraction. - Value::Function(Rc::new(|args| { + Value::make_function(Purity::Pure, |args| { let a = match args[0] { Value::Int(i) => i, _ => 0, @@ -43,69 +42,69 @@ pub fn lookup(name: &str, args: &[StaticType]) -> Option<(Value, StaticType)> { _ => 0, }; Value::Int(a - b - c) - })), + }), StaticType::Int, )), ("*", [StaticType::Int, StaticType::Int]) => Some(( - Value::Function(Rc::new(|args| { + Value::make_function(Purity::Pure, |args| { if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { Value::Int(a * b) } else { Value::Int(0) } - })), + }), StaticType::Int, )), // --- Integer Comparison --- ("<=", [StaticType::Int, StaticType::Int]) => Some(( - Value::Function(Rc::new(|args| { + Value::make_function(Purity::Pure, |args| { if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { Value::Bool(a <= b) } else { Value::Bool(false) } - })), + }), StaticType::Bool, )), ("<", [StaticType::Int, StaticType::Int]) => Some(( - Value::Function(Rc::new(|args| { + Value::make_function(Purity::Pure, |args| { if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { Value::Bool(a < b) } else { Value::Bool(false) } - })), + }), StaticType::Bool, )), (">", [StaticType::Int, StaticType::Int]) => Some(( - Value::Function(Rc::new(|args| { + Value::make_function(Purity::Pure, |args| { if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { Value::Bool(a > b) } else { Value::Bool(false) } - })), + }), StaticType::Bool, )), (">=", [StaticType::Int, StaticType::Int]) => Some(( - Value::Function(Rc::new(|args| { + Value::make_function(Purity::Pure, |args| { if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { Value::Bool(a >= b) } else { Value::Bool(false) } - })), + }), StaticType::Bool, )), ("=", [StaticType::Int, StaticType::Int]) => Some(( - Value::Function(Rc::new(|args| { + Value::make_function(Purity::Pure, |args| { if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { Value::Bool(a == b) } else { Value::Bool(false) } - })), + }), StaticType::Bool, )), diff --git a/src/ast/rtl/math.rs b/src/ast/rtl/math.rs index 4ae3b27..7b6eb0a 100644 --- a/src/ast/rtl/math.rs +++ b/src/ast/rtl/math.rs @@ -1,6 +1,5 @@ -use crate::ast::compiler::optimizer::Purity; use crate::ast::environment::Environment; -use crate::ast::types::{Signature, StaticType, Value}; +use crate::ast::types::{Purity, Signature, StaticType, Value}; use std::f64::consts; pub fn register(env: &Environment) { diff --git a/src/ast/rtl/type_registry.rs b/src/ast/rtl/type_registry.rs index 4459a8d..d9fcd1f 100644 --- a/src/ast/rtl/type_registry.rs +++ b/src/ast/rtl/type_registry.rs @@ -1,4 +1,4 @@ -use crate::ast::types::{Keyword, Signature, StaticType, Value}; +use crate::ast::types::{Keyword, Purity, Signature, StaticType, Value}; use std::any::TypeId; use std::collections::HashMap; use std::rc::Rc; @@ -68,7 +68,7 @@ impl TypeRegistry { } } }; - Value::Function(Rc::new(closure)) + Value::make_function(Purity::Impure, closure) } } @@ -94,7 +94,8 @@ impl RecordBuilder { F: Fn(Vec) -> Value + 'static, { let key = Keyword::intern(name); - self.fields.push((key, Value::Function(Rc::new(func)))); + self.fields + .push((key, Value::make_function(Purity::Impure, func))); self } @@ -217,7 +218,7 @@ mod tests { let greet_fn = &r.values[idx]; if let Value::Function(f) = greet_fn { - let res = f(vec![]); + let res = (f.func)(vec![]); if let Value::Text(s) = res { assert_eq!(&*s, "Hello Alice"); } else { @@ -249,7 +250,7 @@ mod tests { }); if let Value::Function(f) = factory_val { - let instance = f(vec![Value::Text("Bob".into()), Value::Int(40)]); + let instance = (f.func)(vec![Value::Text("Bob".into()), Value::Int(40)]); if let Value::Record(r) = instance { let idx = r .keys @@ -259,7 +260,7 @@ mod tests { let greet_fn = &r.values[idx]; if let Value::Function(gf) = greet_fn { - let res = gf(vec![]); + let res = (gf.func)(vec![]); if let Value::Text(s) = res { assert_eq!(&*s, "Hello Bob"); } else { diff --git a/src/ast/types.rs b/src/ast/types.rs index d27483b..f5e2f32 100644 --- a/src/ast/types.rs +++ b/src/ast/types.rs @@ -76,6 +76,29 @@ pub struct RecordData { pub values: ValueList, } +/// Purity level of an expression or function. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Purity { + /// Has side effects (e.g. Set, Print) + Impure, + /// No side effects, but not deterministic (e.g. now(), random()) + SideEffectFree, + /// No side effects and deterministic (e.g. sin(x), 1 + 2) + Pure, +} + +/// A native host function with metadata. +pub struct NativeFunction { + pub func: Rc) -> Value>, + pub purity: Purity, +} + +impl fmt::Debug for NativeFunction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "", self.purity) + } +} + /// Core data value in Myc Script (similar to TDataValue) #[derive(Clone)] pub enum Value { @@ -88,7 +111,7 @@ pub enum Value { Keyword(Keyword), Tuple(ValueList), Record(Rc), - Function(Rc) -> Value>), + Function(Rc), Object(Rc), // For compiled Closures and other opaque types Cell(Rc>), // Boxed value for captures TailCallRequest(Box<(Rc, Vec)>), // Internal: For TCO (Boxed to keep Value small) @@ -304,6 +327,13 @@ impl Value { })) } + pub fn make_function(purity: Purity, func: impl Fn(Vec) -> Value + 'static) -> Self { + Value::Function(Rc::new(NativeFunction { + func: Rc::new(func), + purity, + })) + } + pub fn static_type(&self) -> StaticType { match self { Value::Void => StaticType::Void, @@ -391,7 +421,7 @@ impl fmt::Display for Value { } write!(f, "}}") } - Value::Function(_) => write!(f, ""), + Value::Function(f_meta) => write!(f, "", f_meta.purity), Value::Object(o) => write!(f, "<{}>", o.type_name()), Value::Cell(c) => write!(f, "{}", c.borrow()), Value::TailCallRequest(_) => write!(f, ""), diff --git a/src/ast/vm.rs b/src/ast/vm.rs index 54c0f47..6d5ab42 100644 --- a/src/ast/vm.rs +++ b/src/ast/vm.rs @@ -194,14 +194,14 @@ macro_rules! dispatch_eval { if $node.ty.is_tail { match func_val { Value::Object(obj) => return Ok(Value::TailCallRequest(Box::new((obj, arg_vals)))), - Value::Function(f) => return Ok(f(arg_vals)), + Value::Function(f) => return Ok((f.func)(arg_vals)), _ => return Err(format!("Tail call target is not a function: {}", func_val)), } } loop { match func_val { - Value::Function(f) => break Ok(f(arg_vals)), + Value::Function(f) => break Ok((f.func)(arg_vals)), Value::Object(obj) => { if let Some(closure) = obj.as_any().downcast_ref::() { let old_stack_top = $self.stack.len();