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