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<NativeFunction>` 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.
This commit is contained in:
Michael Schimmel
2026-02-22 10:50:37 +01:00
parent b54a449369
commit a726b79d8a
9 changed files with 111 additions and 83 deletions
+45 -44
View File
@@ -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::<Closure>() {
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
);
}
}