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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<RefCell<HashMap<Symbol, u32>>>,
|
||||
@@ -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) {
|
||||
|
||||
+1
-2
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+19
-20
@@ -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,
|
||||
)),
|
||||
|
||||
|
||||
+1
-2
@@ -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) {
|
||||
|
||||
@@ -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>) -> 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 {
|
||||
|
||||
+32
-2
@@ -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<dyn Fn(Vec<Value>) -> Value>,
|
||||
pub purity: Purity,
|
||||
}
|
||||
|
||||
impl fmt::Debug for NativeFunction {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "<native fn, {:?}>", 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<RecordData>),
|
||||
Function(Rc<dyn Fn(Vec<Value>) -> Value>),
|
||||
Function(Rc<NativeFunction>),
|
||||
Object(Rc<dyn Object>), // For compiled Closures and other opaque types
|
||||
Cell(Rc<RefCell<Value>>), // Boxed value for captures
|
||||
TailCallRequest(Box<(Rc<dyn Object>, Vec<Value>)>), // 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>) -> 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, "<native fn>"),
|
||||
Value::Function(f_meta) => write!(f, "<native fn, {:?}>", f_meta.purity),
|
||||
Value::Object(o) => write!(f, "<{}>", o.type_name()),
|
||||
Value::Cell(c) => write!(f, "{}", c.borrow()),
|
||||
Value::TailCallRequest(_) => write!(f, "<tail call request>"),
|
||||
|
||||
+2
-2
@@ -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::<Closure>() {
|
||||
let old_stack_top = $self.stack.len();
|
||||
|
||||
Reference in New Issue
Block a user