Refactor: Analyze node purity and recursion

The Analyzer has been refactored to decorate `TypedNode`s with their
purity and recursion status. This involves creating a new `AnalyzedNode`
type and a `NodeMetrics` struct to hold this information. The `Analyzer`
now returns an `AnalyzedNode` instead of a separate `Analysis` struct.
This change lays the groundwork for future optimizations and analysis
passes.
This commit is contained in:
Michael Schimmel
2026-02-22 16:11:46 +01:00
parent 8f7947bde1
commit 2fdeff1db4
7 changed files with 503 additions and 1130 deletions
+103 -62
View File
@@ -1,37 +1,32 @@
use crate::ast::compiler::bound_nodes::{Address, BoundKind, TypedNode}; use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, NodeMetrics, TypedNode};
use crate::ast::types::{Identity, Purity, StaticType}; use crate::ast::types::Purity;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::rc::Rc;
#[derive(Debug, Clone, Default)]
pub struct Analysis {
pub purity: HashMap<Identity, Purity>,
pub is_recursive: HashSet<Identity>,
}
pub struct Analyzer<'a> { pub struct Analyzer<'a> {
global_purity: &'a HashMap<u32, Purity>, global_purity: &'a HashMap<u32, Purity>,
results: Analysis,
/// Stack of currently visiting lambdas to detect direct recursion. /// Stack of currently visiting lambdas to detect direct recursion.
lambda_stack: Vec<Identity>, lambda_stack: Vec<crate::ast::types::Identity>,
/// Map of global index to its Lambda identity if known. /// Map of global index to its Lambda identity if known.
globals_to_lambdas: HashMap<u32, Identity>, globals_to_lambdas: HashMap<u32, crate::ast::types::Identity>,
/// Set of identities that were found to be recursive.
recursive_identities: HashSet<crate::ast::types::Identity>,
} }
impl<'a> Analyzer<'a> { impl<'a> Analyzer<'a> {
pub fn analyze(node: &TypedNode, global_purity: &'a HashMap<u32, Purity>) -> Analysis { pub fn analyze(node: &TypedNode, global_purity: &'a HashMap<u32, Purity>) -> AnalyzedNode {
let mut analyzer = Self { let mut analyzer = Self {
global_purity, global_purity,
results: Analysis::default(),
lambda_stack: Vec::new(), lambda_stack: Vec::new(),
globals_to_lambdas: HashMap::new(), globals_to_lambdas: HashMap::new(),
recursive_identities: HashSet::new(),
}; };
// First pass: map globals to their lambda identities // First pass: map globals to their lambda identities
analyzer.collect_globals(node); analyzer.collect_globals(node);
// Second pass: full analysis // Second pass: full analysis (decorating TypedNode into AnalyzedNode)
analyzer.visit(node); analyzer.visit(Rc::new(node.clone()))
analyzer.results
} }
fn collect_globals(&mut self, node: &TypedNode) { fn collect_globals(&mut self, node: &TypedNode) {
@@ -46,104 +41,147 @@ impl<'a> Analyzer<'a> {
for e in exprs { self.collect_globals(e); } for e in exprs { self.collect_globals(e); }
} }
_ => { _ => {
// Simplified traversal for global collection
node.kind.for_each_child(|child| self.collect_globals(child)); node.kind.for_each_child(|child| self.collect_globals(child));
} }
} }
} }
fn visit(&mut self, node: &TypedNode) -> Purity { fn visit(&mut self, node_rc: Rc<TypedNode>) -> AnalyzedNode {
let purity = match &node.kind { let node = &*node_rc;
BoundKind::Constant(_) | BoundKind::Nop | BoundKind::Parameter { .. } => Purity::Pure, let mut is_recursive = false;
BoundKind::Get { addr, .. } => match addr {
Address::Global(idx) => self.global_purity.get(idx).cloned().unwrap_or(Purity::Pure),
_ => Purity::Pure, // Locals are considered pure access in this model
},
BoundKind::Set { .. } => Purity::Impure, let (new_kind, purity) = match &node.kind {
BoundKind::Constant(v) => (BoundKind::Constant(v.clone()), Purity::Pure),
BoundKind::Nop => (BoundKind::Nop, Purity::Pure),
BoundKind::Parameter { name, slot } => {
(BoundKind::Parameter { name: name.clone(), slot: *slot }, Purity::Pure)
}
BoundKind::DefLocal { value, .. } | BoundKind::DefGlobal { value, .. } => { BoundKind::Get { addr, name } => {
self.visit(value) let p = match addr {
Address::Global(idx) => self.global_purity.get(idx).cloned().unwrap_or(Purity::Pure),
_ => Purity::Pure,
};
(BoundKind::Get { addr: *addr, name: name.clone() }, p)
}
BoundKind::Set { addr, value } => {
let val_m = self.visit(Rc::new((**value).clone()));
(BoundKind::Set { addr: *addr, value: Box::new(val_m) }, Purity::Impure)
}
BoundKind::DefLocal { name, slot, value, captured_by } => {
let val_m = self.visit(Rc::new((**value).clone()));
let p = val_m.ty.purity;
(BoundKind::DefLocal { name: name.clone(), slot: *slot, value: Box::new(val_m), captured_by: captured_by.clone() }, p)
}
BoundKind::DefGlobal { name, global_index, value } => {
let val_m = self.visit(Rc::new((**value).clone()));
let p = val_m.ty.purity;
(BoundKind::DefGlobal { name: name.clone(), global_index: *global_index, value: Box::new(val_m) }, p)
} }
BoundKind::If { cond, then_br, else_br } => { BoundKind::If { cond, then_br, else_br } => {
let p_cond = self.visit(cond); let cond_m = self.visit(Rc::new((**cond).clone()));
let p_then = self.visit(then_br); let then_m = self.visit(Rc::new((**then_br).clone()));
let p_else = else_br.as_ref().map(|e| self.visit(e)).unwrap_or(Purity::Pure); let else_m = else_br.as_ref().map(|e| self.visit(Rc::new((**e).clone())));
p_cond.min(p_then).min(p_else)
let mut p = cond_m.ty.purity.min(then_m.ty.purity);
if let Some(ref em) = else_m { p = p.min(em.ty.purity); }
(BoundKind::If { cond: Box::new(cond_m), then_br: Box::new(then_m), else_br: else_m.map(Box::new) }, p)
} }
BoundKind::Lambda { body, .. } => { BoundKind::Lambda { params, upvalues, body, positional_count } => {
self.lambda_stack.push(node.identity.clone()); self.lambda_stack.push(node.identity.clone());
self.visit(body); let params_m = self.visit(params.clone());
let body_m = self.visit(body.clone());
self.lambda_stack.pop(); self.lambda_stack.pop();
Purity::Pure // Creating a lambda is pure
is_recursive = self.recursive_identities.contains(&node.identity);
(BoundKind::Lambda { params: Rc::new(params_m), upvalues: upvalues.clone(), body: Rc::new(body_m), positional_count: *positional_count }, Purity::Pure)
} }
BoundKind::Call { callee, args } => { BoundKind::Call { callee, args } => {
let p_callee = self.visit(callee); let callee_m = self.visit(Rc::new((**callee).clone()));
let p_args = self.visit(args); let args_m = self.visit(Rc::new((**args).clone()));
// Detect recursion
if let BoundKind::Get { addr: Address::Global(idx), .. } = &callee.kind if let BoundKind::Get { addr: Address::Global(idx), .. } = &callee.kind
&& let Some(lambda_id) = self.globals_to_lambdas.get(idx) && let Some(lambda_id) = self.globals_to_lambdas.get(idx)
&& self.lambda_stack.contains(lambda_id) && self.lambda_stack.contains(lambda_id)
{ {
self.results.is_recursive.insert(lambda_id.clone()); self.recursive_identities.insert(lambda_id.clone());
// Also mark the call itself if needed is_recursive = true;
self.results.is_recursive.insert(node.identity.clone());
} }
// For purity, we'd need to know the function's purity.
// For now, if it's a call, we conservatively check if it's a known pure global.
let p_func = if let BoundKind::Get { addr: Address::Global(idx), .. } = &callee.kind { let p_func = if let BoundKind::Get { addr: Address::Global(idx), .. } = &callee.kind {
self.global_purity.get(idx).cloned().unwrap_or(Purity::Impure) self.global_purity.get(idx).cloned().unwrap_or(Purity::Impure)
} else { } else {
Purity::Impure Purity::Impure
}; };
let p = callee_m.ty.purity.min(args_m.ty.purity).min(p_func);
p_callee.min(p_args).min(p_func) (BoundKind::Call { callee: Box::new(callee_m), args: Box::new(args_m) }, p)
} }
BoundKind::Block { exprs } => { BoundKind::Block { exprs } => {
let mut new_exprs = Vec::with_capacity(exprs.len());
let mut p = Purity::Pure; let mut p = Purity::Pure;
for e in exprs { for e in exprs {
p = p.min(self.visit(e)); let em = self.visit(Rc::new(e.clone()));
p = p.min(em.ty.purity);
new_exprs.push(em);
} }
p (BoundKind::Block { exprs: new_exprs }, p)
} }
BoundKind::Tuple { elements } => { BoundKind::Tuple { elements } => {
let mut new_elements = Vec::with_capacity(elements.len());
let mut p = Purity::Pure; let mut p = Purity::Pure;
for e in elements { for e in elements {
p = p.min(self.visit(e)); let em = self.visit(Rc::new(e.clone()));
p = p.min(em.ty.purity);
new_elements.push(em);
} }
p (BoundKind::Tuple { elements: new_elements }, p)
} }
BoundKind::Record { fields } => { BoundKind::Record { fields } => {
let mut new_fields = Vec::with_capacity(fields.len());
let mut p = Purity::Pure; let mut p = Purity::Pure;
for (k, v) in fields { for (k, v) in fields {
p = p.min(self.visit(k)).min(self.visit(v)); let km = self.visit(Rc::new(k.clone()));
let vm = self.visit(Rc::new(v.clone()));
p = p.min(km.ty.purity).min(vm.ty.purity);
new_fields.push((km, vm));
} }
p (BoundKind::Record { fields: new_fields }, p)
} }
_ => Purity::Impure, BoundKind::Expansion { original_call, bound_expanded } => {
let expanded_m = self.visit(Rc::new((**bound_expanded).clone()));
(BoundKind::Expansion { original_call: original_call.clone(), bound_expanded: Box::new(expanded_m.clone()) }, expanded_m.ty.purity)
}
BoundKind::Extension(_) => (BoundKind::Nop, Purity::Impure),
}; };
self.results.purity.insert(node.identity.clone(), purity); crate::ast::nodes::Node {
purity identity: node.identity.clone(),
kind: new_kind,
ty: NodeMetrics {
original: node_rc,
purity,
is_recursive,
},
}
} }
} }
/// Extension trait to make traversal easier
trait NodeExt { trait NodeExt {
fn for_each_child<F: FnMut(&TypedNode)>(&self, f: F); fn for_each_child<F: FnMut(&TypedNode)>(&self, f: F);
} }
impl NodeExt for BoundKind<StaticType> { impl NodeExt for BoundKind<crate::ast::types::StaticType> {
fn for_each_child<F: FnMut(&TypedNode)>(&self, mut f: F) { fn for_each_child<F: FnMut(&TypedNode)>(&self, mut f: F) {
match self { match self {
BoundKind::If { cond, then_br, else_br } => { BoundKind::If { cond, then_br, else_br } => {
@@ -159,14 +197,17 @@ impl NodeExt for BoundKind<StaticType> {
f(callee); f(args); f(callee); f(args);
} }
BoundKind::Block { exprs } => { BoundKind::Block { exprs } => {
for e in exprs { f(e); } // Block for e in exprs { f(e); }
} }
BoundKind::Tuple { elements } => { BoundKind::Tuple { elements } => {
for e in elements { f(e); } // Tuple for e in elements { f(e); }
} }
BoundKind::Record { fields } => { BoundKind::Record { fields } => {
for (k, v) in fields { f(k); f(v); } for (k, v) in fields { f(k); f(v); }
} }
BoundKind::Expansion { bound_expanded, .. } => {
f(bound_expanded);
}
_ => {} _ => {}
} }
} }
+11
View File
@@ -27,6 +27,17 @@ pub type BoundNode<T = ()> = Node<BoundKind<T>, T>;
/// Type alias for a node that has been fully type-checked. /// Type alias for a node that has been fully type-checked.
pub type TypedNode = BoundNode<StaticType>; pub type TypedNode = BoundNode<StaticType>;
/// Metrics collected during the analysis phase.
#[derive(Debug, Clone, PartialEq)]
pub struct NodeMetrics {
pub original: Rc<TypedNode>,
pub purity: crate::ast::types::Purity,
pub is_recursive: bool,
}
/// Type alias for a node that has been analyzed.
pub type AnalyzedNode = BoundNode<NodeMetrics>;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum BoundKind<T = ()> { pub enum BoundKind<T = ()> {
Nop, Nop,
File diff suppressed because it is too large Load Diff
+92 -179
View File
@@ -1,7 +1,6 @@
use crate::ast::compiler::analyzer::Analysis; use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, BoundNode, NodeMetrics};
use crate::ast::compiler::bound_nodes::{Address, BoundKind, BoundNode, TypedNode};
use crate::ast::nodes::Node; use crate::ast::nodes::Node;
use crate::ast::types::{Signature, StaticType, Value}; use crate::ast::types::{Purity, Signature, StaticType, Value};
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
@@ -17,13 +16,13 @@ pub type RtlLookupFunc = Rc<dyn Fn(&str, &[StaticType]) -> Option<(Value, Static
pub trait FunctionRegistry { pub trait FunctionRegistry {
fn resolve(&self, addr: Address) -> Option<BoundNode>; fn resolve(&self, addr: Address) -> Option<BoundNode>;
fn resolve_analyzed(&self, _addr: Address) -> Option<AnalyzedNode> { None }
} }
pub type MonoCache = HashMap<MonoCacheKey, (Value, StaticType)>; pub type MonoCache = HashMap<MonoCacheKey, (Value, StaticType)>;
pub struct Specializer { pub struct Specializer {
pub cache: Rc<RefCell<MonoCache>>, pub cache: Rc<RefCell<MonoCache>>,
pub analysis: Analysis,
registry: Option<Rc<dyn FunctionRegistry>>, registry: Option<Rc<dyn FunctionRegistry>>,
compiler: Option<CompileFunc>, compiler: Option<CompileFunc>,
rtl_lookup: Option<RtlLookupFunc>, rtl_lookup: Option<RtlLookupFunc>,
@@ -35,279 +34,193 @@ impl Specializer {
compiler: Option<CompileFunc>, compiler: Option<CompileFunc>,
rtl_lookup: Option<RtlLookupFunc>, rtl_lookup: Option<RtlLookupFunc>,
cache: Option<Rc<RefCell<MonoCache>>>, cache: Option<Rc<RefCell<MonoCache>>>,
analysis: Analysis,
) -> Self { ) -> Self {
Self { Self {
cache: cache.unwrap_or_else(|| Rc::new(RefCell::new(HashMap::new()))), cache: cache.unwrap_or_else(|| Rc::new(RefCell::new(HashMap::new()))),
analysis,
registry, registry,
compiler, compiler,
rtl_lookup, rtl_lookup,
} }
} }
pub fn specialize(&self, node: TypedNode) -> TypedNode { pub fn specialize(&self, node: AnalyzedNode) -> AnalyzedNode {
self.visit_node(node) self.visit_node(node)
} }
fn visit_node(&self, node: TypedNode) -> TypedNode { fn visit_node(&self, node: AnalyzedNode) -> AnalyzedNode {
let (new_kind, new_ty) = match node.kind { let (new_kind, metrics) = match node.kind {
BoundKind::Call { callee, args } => { BoundKind::Call { callee, args } => {
let (new_callee, new_args, ret_ty) = let (new_callee, new_args, _ret_ty) =
self.specialize_call_logic(*callee, *args, node.ty.clone()); self.specialize_call_logic(*callee, *args, node.ty.original.ty.clone());
let new_metrics = node.ty.clone();
( (
BoundKind::Call { BoundKind::Call {
callee: Box::new(new_callee), callee: Box::new(new_callee),
args: Box::new(new_args), args: Box::new(new_args),
}, },
ret_ty, new_metrics,
) )
} }
// Recursive traversal for other nodes BoundKind::If { cond, then_br, else_br } => {
BoundKind::If {
cond,
then_br,
else_br,
} => {
let cond = Box::new(self.visit_node(*cond)); let cond = Box::new(self.visit_node(*cond));
let then_br = Box::new(self.visit_node(*then_br)); let then_br = Box::new(self.visit_node(*then_br));
let else_br = else_br.map(|e| Box::new(self.visit_node(*e))); let else_br = else_br.map(|e| Box::new(self.visit_node(*e)));
( (BoundKind::If { cond, then_br, else_br }, node.ty.clone())
BoundKind::If {
cond,
then_br,
else_br,
},
node.ty,
)
} }
BoundKind::Block { exprs } => { BoundKind::Block { exprs } => {
let exprs = exprs.into_iter().map(|e| self.visit_node(e)).collect(); let exprs = exprs.into_iter().map(|e| self.visit_node(e)).collect();
(BoundKind::Block { exprs }, node.ty) (BoundKind::Block { exprs }, node.ty.clone())
} }
BoundKind::Lambda { BoundKind::Lambda { params, upvalues, body, positional_count } => {
params,
upvalues,
body,
positional_count,
} => {
let params = Rc::new(self.visit_node(params.as_ref().clone())); let params = Rc::new(self.visit_node(params.as_ref().clone()));
let body = Rc::new(self.visit_node((*body).clone())); let body = Rc::new(self.visit_node((*body).clone()));
( (BoundKind::Lambda { params, upvalues, body, positional_count }, node.ty.clone())
BoundKind::Lambda {
params,
upvalues,
body,
positional_count,
},
node.ty,
)
} }
BoundKind::DefLocal { BoundKind::DefLocal { name, slot, value, captured_by } => {
name,
slot,
value,
captured_by,
} => {
let value = Box::new(self.visit_node(*value)); let value = Box::new(self.visit_node(*value));
( (BoundKind::DefLocal { name, slot, value, captured_by }, node.ty.clone())
BoundKind::DefLocal {
name,
slot,
value,
captured_by,
},
node.ty,
)
} }
BoundKind::DefGlobal { BoundKind::DefGlobal { name, global_index, value } => {
name,
global_index,
value,
} => {
let value = Box::new(self.visit_node(*value)); let value = Box::new(self.visit_node(*value));
( (BoundKind::DefGlobal { name, global_index, value }, node.ty.clone())
BoundKind::DefGlobal {
name,
global_index,
value,
},
node.ty,
)
} }
BoundKind::Set { addr, value } => { BoundKind::Set { addr, value } => {
let value = Box::new(self.visit_node(*value)); let value = Box::new(self.visit_node(*value));
(BoundKind::Set { addr, value }, node.ty) (BoundKind::Set { addr, value }, node.ty.clone())
} }
BoundKind::Tuple { elements } => { BoundKind::Tuple { elements } => {
let elements = elements.into_iter().map(|e| self.visit_node(e)).collect(); let elements = elements.into_iter().map(|e| self.visit_node(e)).collect();
(BoundKind::Tuple { elements }, node.ty) (BoundKind::Tuple { elements }, node.ty.clone())
} }
BoundKind::Record { fields } => { BoundKind::Record { fields } => {
let fields = fields let fields = fields
.into_iter() .into_iter()
.map(|(k, v)| (self.visit_node(k), self.visit_node(v))) .map(|(k, v)| (self.visit_node(k), self.visit_node(v)))
.collect(); .collect();
(BoundKind::Record { fields }, node.ty) (BoundKind::Record { fields }, node.ty.clone())
} }
BoundKind::Expansion { BoundKind::Expansion { original_call, bound_expanded } => {
original_call,
bound_expanded,
} => {
let bound_expanded = Box::new(self.visit_node(*bound_expanded)); let bound_expanded = Box::new(self.visit_node(*bound_expanded));
( (BoundKind::Expansion { original_call, bound_expanded }, node.ty.clone())
BoundKind::Expansion {
original_call,
bound_expanded,
},
node.ty,
)
} }
k => (k, node.ty.clone()),
// Leaf nodes or uninteresting nodes
k => (k, node.ty),
}; };
Node { Node {
identity: node.identity, identity: node.identity,
kind: new_kind, kind: new_kind,
ty: new_ty, ty: metrics,
} }
} }
fn specialize_call_logic( fn specialize_call_logic(
&self, &self,
callee: TypedNode, callee: AnalyzedNode,
args: TypedNode, args: AnalyzedNode,
original_ty: StaticType, original_ty: StaticType,
) -> (TypedNode, TypedNode, StaticType) { ) -> (AnalyzedNode, AnalyzedNode, StaticType) {
// 1. Specialize children first
let new_callee = self.visit_node(callee); let new_callee = self.visit_node(callee);
let new_args = self.visit_node(args); let new_args = self.visit_node(args);
// 2. Check if this call is a candidate (Callee is Get(Address))
let address = if let BoundKind::Get { addr, .. } = &new_callee.kind { let address = if let BoundKind::Get { addr, .. } = &new_callee.kind {
*addr *addr
} else { } else {
// Not a direct call to a named function/variable
return (new_callee, new_args, original_ty); return (new_callee, new_args, original_ty);
}; };
// 3. Check if all argument types are statically known let arg_types: Vec<StaticType> = if let StaticType::Tuple(elements) = &new_args.ty.original.ty {
let arg_types: Vec<StaticType> = if let StaticType::Tuple(elements) = &new_args.ty {
elements.clone() elements.clone()
} else { } else {
vec![new_args.ty.clone()] vec![new_args.ty.original.ty.clone()]
}; };
if arg_types.iter().any(|t| matches!(t, StaticType::Any)) { if arg_types.iter().any(|t| matches!(t, StaticType::Any)) {
// Cannot specialize with unknown types
return (new_callee, new_args, original_ty); return (new_callee, new_args, original_ty);
} }
// --- Optimization Candidate ---
let key = MonoCacheKey { let key = MonoCacheKey {
address, address,
arg_types: arg_types.clone(), arg_types: arg_types.clone(),
}; };
// 4. Check Cache
if let Some((val, ret_ty)) = self.cache.borrow().get(&key) { if let Some((val, ret_ty)) = self.cache.borrow().get(&key) {
// Cache Hit! Replace Callee with Constant(Function) let specialized_callee = self.make_constant_node(val.clone(), StaticType::Function(Box::new(Signature {
let specialized_callee = Node { params: StaticType::Tuple(arg_types),
identity: new_callee.identity.clone(), ret: ret_ty.clone(),
kind: BoundKind::Constant(val.clone()), })), &new_callee);
ty: StaticType::Function(Box::new(Signature {
params: StaticType::Tuple(arg_types),
ret: ret_ty.clone(),
})),
};
return (specialized_callee, new_args, ret_ty.clone()); return (specialized_callee, new_args, ret_ty.clone());
} }
// 5. Check RTL (Host Functions)
if let Some(rtl_lookup) = &self.rtl_lookup if let Some(rtl_lookup) = &self.rtl_lookup
&& let BoundKind::Get { name, .. } = &new_callee.kind && let BoundKind::Get { name, .. } = &new_callee.kind
&& let Some((val, ret_ty)) = rtl_lookup(&name.name, &arg_types) && let Some((val, ret_ty)) = rtl_lookup(&name.name, &arg_types)
{ {
// Cache Hit (RTL) self.cache.borrow_mut().insert(key.clone(), (val.clone(), ret_ty.clone()));
self.cache let specialized_callee = self.make_constant_node(val.clone(), StaticType::Function(Box::new(Signature {
.borrow_mut() params: StaticType::Tuple(arg_types),
.insert(key.clone(), (val.clone(), ret_ty.clone())); ret: ret_ty.clone(),
})), &new_callee);
let specialized_callee = Node {
identity: new_callee.identity.clone(),
kind: BoundKind::Constant(val.clone()),
ty: StaticType::Function(Box::new(Signature {
params: StaticType::Tuple(arg_types),
ret: ret_ty.clone(),
})),
};
return (specialized_callee, new_args, ret_ty); return (specialized_callee, new_args, ret_ty);
} }
// 6. Resolve Function Definition if let Some(registry) = &self.registry
if let Some(func_node) = self.registry.as_ref().and_then(|r| r.resolve(address)) { && let Some(func_node) = registry.resolve_analyzed(address)
// Check for recursion from pre-pass. && func_node.ty.is_recursive
if self.analysis.is_recursive.contains(&func_node.identity) { {
return (new_callee, new_args, original_ty); return (new_callee, new_args, original_ty);
} }
// Check constraints (no closures with state) if let Some(compiler) = &self.compiler
if let BoundKind::Lambda { upvalues, .. } = &func_node.kind { && let Some(func_node) = self.registry.as_ref().and_then(|r| r.resolve(address))
if !upvalues.is_empty() { && let Ok((compiled_val, ret_ty)) = compiler(func_node, &arg_types)
return (new_callee, new_args, original_ty); {
} self.cache.borrow_mut().insert(key, (compiled_val.clone(), ret_ty.clone()));
} else { let flat_elements = self.flatten_tuple(new_args.clone());
return (new_callee, new_args, original_ty); let flat_types = flat_elements.iter().map(|e| e.ty.original.ty.clone()).collect();
} let flattened_args = Node {
identity: new_args.identity.clone(),
// 7. Compile Specialization (User Code) kind: BoundKind::Tuple { elements: flat_elements },
if let Some(compiler) = &self.compiler { ty: NodeMetrics {
match compiler(func_node, &arg_types) { original: Rc::new(Node {
Ok((compiled_val, ret_ty)) => { identity: new_args.identity.clone(),
let res_val: Value = compiled_val; kind: BoundKind::Tuple { elements: vec![] },
let res_ty: StaticType = ret_ty; ty: StaticType::Tuple(flat_types),
}),
// Store in cache purity: new_args.ty.purity,
self.cache is_recursive: new_args.ty.is_recursive,
.borrow_mut() },
.insert(key, (res_val.clone(), res_ty.clone())); };
// PERFORMANCE: Flatten the argument tuple to match the specialized signature. let specialized_callee = self.make_constant_node(compiled_val, StaticType::Function(Box::new(Signature {
let flat_elements = self.flatten_tuple(new_args.clone()); params: flattened_args.ty.original.ty.clone(),
let flat_types = flat_elements.iter().map(|e| e.ty.clone()).collect(); ret: ret_ty.clone(),
let flattened_args = Node { })), &new_callee);
identity: new_args.identity.clone(), return (specialized_callee, flattened_args, ret_ty);
kind: BoundKind::Tuple {
elements: flat_elements,
},
ty: StaticType::Tuple(flat_types),
};
let specialized_callee = Node {
identity: new_callee.identity.clone(),
kind: BoundKind::Constant(res_val),
ty: StaticType::Function(Box::new(Signature {
params: flattened_args.ty.clone(),
ret: res_ty.clone(),
})),
};
return (specialized_callee, flattened_args, res_ty);
}
Err(_) => {
// Fallback on error
}
}
}
} }
// Fallback: Dynamic Call
(new_callee, new_args, original_ty) (new_callee, new_args, original_ty)
} }
fn flatten_tuple(&self, node: TypedNode) -> Vec<TypedNode> { fn make_constant_node(&self, val: Value, ty: StaticType, template: &AnalyzedNode) -> AnalyzedNode {
let typed_original = Rc::new(Node {
identity: template.identity.clone(),
kind: BoundKind::Constant(val.clone()),
ty: ty.clone(),
});
Node {
identity: template.identity.clone(),
kind: BoundKind::Constant(val),
ty: NodeMetrics {
original: typed_original,
purity: Purity::Pure,
is_recursive: false,
},
}
}
fn flatten_tuple(&self, node: AnalyzedNode) -> Vec<AnalyzedNode> {
match node.kind { match node.kind {
BoundKind::Tuple { elements } => { BoundKind::Tuple { elements } => {
let mut flat = Vec::new(); let mut flat = Vec::new();
+8 -9
View File
@@ -1,16 +1,15 @@
use crate::ast::compiler::bound_nodes::{BoundKind, TypedNode}; use crate::ast::compiler::bound_nodes::{AnalyzedNode, BoundKind};
use crate::ast::nodes::Node; use crate::ast::nodes::Node;
use crate::ast::types::StaticType; use crate::ast::types::StaticType;
use std::rc::Rc; use std::rc::Rc;
use std::fmt::Debug; use std::fmt::Debug;
#[derive(Clone)] #[derive(Clone)]
pub struct RuntimeMetadata { pub struct RuntimeMetadata {
pub ty: StaticType, pub ty: StaticType,
pub is_tail: bool, pub is_tail: bool,
/// The original, high-level typed node. Perfect for Debuggers and Optimizers. /// The analyzed node, containing metrics and a link to the original TypedNode.
pub original: Rc<TypedNode>, pub original: Rc<AnalyzedNode>,
} }
impl Debug for RuntimeMetadata { impl Debug for RuntimeMetadata {
@@ -22,18 +21,18 @@ impl Debug for RuntimeMetadata {
} }
} }
/// The ExecNode is the AST used by the VM. It carries TCO flags and links to source. /// The ExecNode is the AST used by the VM. It carries TCO flags and links to metrics.
pub type ExecNode = Node<BoundKind<RuntimeMetadata>, RuntimeMetadata>; pub type ExecNode = Node<BoundKind<RuntimeMetadata>, RuntimeMetadata>;
pub struct TCO; pub struct TCO;
impl TCO { impl TCO {
/// Lowers a TypedNode (Compiler-AST) to an ExecNode (VM-AST) and marks tail positions. /// Lowers an AnalyzedNode to an ExecNode and marks tail positions.
pub fn optimize(node: TypedNode) -> ExecNode { pub fn optimize(node: AnalyzedNode) -> ExecNode {
Self::transform(Rc::new(node), true) Self::transform(Rc::new(node), true)
} }
fn transform(node_rc: Rc<TypedNode>, is_tail_position: bool) -> ExecNode { fn transform(node_rc: Rc<AnalyzedNode>, is_tail_position: bool) -> ExecNode {
let node = &*node_rc; let node = &*node_rc;
let new_kind = match &node.kind { let new_kind = match &node.kind {
BoundKind::Call { callee, args } => BoundKind::Call { BoundKind::Call { callee, args } => BoundKind::Call {
@@ -158,7 +157,7 @@ impl TCO {
identity: node.identity.clone(), identity: node.identity.clone(),
kind: new_kind, kind: new_kind,
ty: RuntimeMetadata { ty: RuntimeMetadata {
ty: node.ty.clone(), ty: node.ty.original.ty.clone(),
is_tail: is_tail_position, is_tail: is_tail_position,
original: node_rc, original: node_rc,
}, },
+43 -98
View File
@@ -1,4 +1,4 @@
use crate::ast::compiler::analyzer::{Analysis, Analyzer}; use crate::ast::compiler::analyzer::Analyzer;
use crate::ast::compiler::binder::Binder; use crate::ast::compiler::binder::Binder;
use crate::ast::compiler::{TypeChecker, TypedNode}; use crate::ast::compiler::{TypeChecker, TypedNode};
use crate::ast::nodes::{Node, Symbol, UntypedKind}; use crate::ast::nodes::{Node, Symbol, UntypedKind};
@@ -8,7 +8,7 @@ use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use crate::ast::compiler::bound_nodes::{Address, BoundKind, BoundNode}; use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, BoundNode};
use crate::ast::compiler::dumper::Dumper; use crate::ast::compiler::dumper::Dumper;
use crate::ast::compiler::lambda_collector::LambdaCollector; use crate::ast::compiler::lambda_collector::LambdaCollector;
use crate::ast::compiler::macros::{MacroEvaluator, MacroExpander, MacroRegistry}; use crate::ast::compiler::macros::{MacroEvaluator, MacroExpander, MacroRegistry};
@@ -26,15 +26,15 @@ pub struct Environment {
pub global_values: Rc<RefCell<Vec<Value>>>, pub global_values: Rc<RefCell<Vec<Value>>>,
pub prng: Rc<RefCell<fastrand::Rng>>, pub prng: Rc<RefCell<fastrand::Rng>>,
pub function_registry: Rc<RefCell<HashMap<u32, BoundNode>>>, pub function_registry: Rc<RefCell<HashMap<u32, BoundNode>>>,
pub typed_function_registry: Rc<RefCell<HashMap<u32, TypedNode>>>, pub typed_function_registry: Rc<RefCell<HashMap<u32, AnalyzedNode>>>,
pub monomorph_cache: Rc<RefCell<MonoCache>>, pub monomorph_cache: Rc<RefCell<MonoCache>>,
pub debug_mode: bool, pub debug_mode: bool,
pub optimization: bool, pub optimization: bool,
pub last_analysis: RefCell<Analysis>,
} }
struct EnvFunctionRegistry { struct EnvFunctionRegistry {
registry: Rc<RefCell<HashMap<u32, BoundNode>>>, registry: Rc<RefCell<HashMap<u32, BoundNode>>>,
analyzed_registry: Rc<RefCell<HashMap<u32, AnalyzedNode>>>,
} }
impl FunctionRegistry for EnvFunctionRegistry { impl FunctionRegistry for EnvFunctionRegistry {
@@ -45,9 +45,15 @@ impl FunctionRegistry for EnvFunctionRegistry {
None None
} }
} }
fn resolve_analyzed(&self, addr: Address) -> Option<AnalyzedNode> {
if let Address::Global(idx) = addr {
self.analyzed_registry.borrow().get(&idx).cloned()
} else {
None
}
}
} }
/// Evaluator used during macro expansion to allow compile-time logic.
struct RuntimeMacroEvaluator { struct RuntimeMacroEvaluator {
global_names: Rc<RefCell<HashMap<Symbol, u32>>>, global_names: Rc<RefCell<HashMap<Symbol, u32>>>,
global_types: Rc<RefCell<HashMap<u32, StaticType>>>, global_types: Rc<RefCell<HashMap<u32, StaticType>>>,
@@ -60,19 +66,16 @@ impl MacroEvaluator for RuntimeMacroEvaluator {
node: &Node<UntypedKind>, node: &Node<UntypedKind>,
bindings: &HashMap<Rc<str>, Node<UntypedKind>>, bindings: &HashMap<Rc<str>, Node<UntypedKind>>,
) -> Result<Value, String> { ) -> Result<Value, String> {
// 1. Check if it's a simple parameter substitution
if let UntypedKind::Identifier(sym) = &node.kind if let UntypedKind::Identifier(sym) = &node.kind
&& let Some(arg_node) = bindings.get(&sym.name) && let Some(arg_node) = bindings.get(&sym.name)
{ {
return Ok(Value::Object(Rc::new(arg_node.clone()) as Rc<dyn Object>)); return Ok(Value::Object(Rc::new(arg_node.clone()) as Rc<dyn Object>));
} }
// 2. Full evaluation for complex compile-time expressions
let bound_ast = Binder::bind_root(self.global_names.clone(), node)?; let bound_ast = Binder::bind_root(self.global_names.clone(), node)?;
let checker = TypeChecker::new(self.global_types.clone()); let checker = TypeChecker::new(self.global_types.clone());
let typed_ast = checker.check(bound_ast, &[])?; let typed_ast = checker.check(bound_ast, &[])?;
let exec_ast = TCO::optimize(typed_ast); let exec_ast = TCO::optimize(Analyzer::analyze(&typed_ast, &HashMap::new())); // Minimal analysis for macro eval
let mut vm = VM::new(self.global_values.clone()); let mut vm = VM::new(self.global_values.clone());
vm.run(&exec_ast) vm.run(&exec_ast)
@@ -98,7 +101,6 @@ impl Environment {
monomorph_cache: Rc::new(RefCell::new(HashMap::new())), monomorph_cache: Rc::new(RefCell::new(HashMap::new())),
debug_mode: false, debug_mode: false,
optimization: true, optimization: true,
last_analysis: RefCell::new(Analysis::default()),
}; };
env.register_stdlib(); env.register_stdlib();
env env
@@ -135,7 +137,6 @@ impl Environment {
values.push(Value::Function(func)); values.push(Value::Function(func));
} }
/// Utility to register a native function from a closure.
pub fn register_native_fn( pub fn register_native_fn(
&self, &self,
name: &str, name: &str,
@@ -162,12 +163,11 @@ impl Environment {
let idx = values.len() as u32; let idx = values.len() as u32;
names.insert(Symbol::from(name), idx); names.insert(Symbol::from(name), idx);
types.insert(idx, ty); types.insert(idx, ty);
purity.insert(idx, Purity::Pure); // Constants are always pure purity.insert(idx, Purity::Pure);
values.push(val); values.push(val);
} }
fn register_stdlib(&self) { fn register_stdlib(&self) {
// Register all standard library functions via RTL module
rtl::register(self); rtl::register(self);
} }
@@ -177,79 +177,54 @@ impl Environment {
Ok(Dumper::dump(&linked)) Ok(Dumper::dump(&linked))
} }
/// Frontend: Parse -> Expand Macros -> Bind -> Type Check
pub fn compile(&self, source: &str) -> Result<TypedNode, String> { pub fn compile(&self, source: &str) -> Result<TypedNode, String> {
// 1. Parse
let mut parser = Parser::new(source)?; let mut parser = Parser::new(source)?;
let untyped_ast = parser.parse_expression()?; let untyped_ast = parser.parse_expression()?;
// 2. Check for trailing tokens
if !parser.at_eof() { if !parser.at_eof() {
return Err( return Err("Unexpected trailing expressions in script.".to_string());
"Unexpected trailing expressions in script. Use (do ...) for sequences."
.to_string(),
);
} }
// 3. Expand Macros
let expanded_ast = self.get_expander().expand(untyped_ast)?; let expanded_ast = self.get_expander().expand(untyped_ast)?;
// 4. Bind
let bound_ast = Binder::bind_root(self.global_names.clone(), &expanded_ast)?; let bound_ast = Binder::bind_root(self.global_names.clone(), &expanded_ast)?;
// 5. Collect Lambdas (Populate the registry with untyped templates)
LambdaCollector::collect(&bound_ast, &mut self.function_registry.borrow_mut()); LambdaCollector::collect(&bound_ast, &mut self.function_registry.borrow_mut());
// 6. Type Check
let checker = TypeChecker::new(self.global_types.clone()); let checker = TypeChecker::new(self.global_types.clone());
let typed_ast = checker.check(bound_ast, &[])?; let typed_ast = checker.check(bound_ast, &[])?;
// 7. Collect Typed Lambdas
LambdaCollector::collect(&typed_ast, &mut self.typed_function_registry.borrow_mut());
// 8. Analyze (Purity, Recursion)
let analysis = Analyzer::analyze(&typed_ast, &self.global_purity.borrow());
*self.last_analysis.borrow_mut() = analysis;
Ok(typed_ast) Ok(typed_ast)
} }
/// Backend Phase 1: Optimization (TCO, etc.) and Lowering
pub fn link(&self, node: TypedNode) -> ExecNode { pub fn link(&self, node: TypedNode) -> ExecNode {
// 1. Specialize (Always performed for correctness) // 1. Analyze
let specialized = self.specialize_node(node); let analyzed = Analyzer::analyze(&node, &self.global_purity.borrow());
// 2. Collect Analyzed Lambdas
LambdaCollector::collect(&analyzed, &mut self.typed_function_registry.borrow_mut());
// 2. Optimize (Level 1: Cracking, Level 2: Collapsing) // 3. Specialize
let specialized = self.specialize_node(analyzed);
// 4. Optimize
let optimizer = Optimizer::new(self.optimization) 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())
.with_registry(self.typed_function_registry.clone()) .with_registry(self.typed_function_registry.clone());
.with_analysis(self.last_analysis.borrow().clone());
let optimized = optimizer.optimize(specialized); let optimized = optimizer.optimize(specialized);
// 3. TCO (Always performed, converts to ExecNode) // 5. TCO
TCO::optimize(optimized) TCO::optimize(optimized)
} }
/// Backend Phase 2: Packaging into an invokable NativeFunction
pub fn instantiate(&self, node: ExecNode) -> Rc<crate::ast::types::NativeFunction> { pub fn instantiate(&self, node: ExecNode) -> Rc<crate::ast::types::NativeFunction> {
let global_values = self.global_values.clone(); let global_values = self.global_values.clone();
if let BoundKind::Lambda { params, upvalues, body, positional_count } = &node.kind
// OPTIMIZATION: Fast path for top-level Lambdas.
// If the script root is a Lambda with no captures (root functions usually have none),
// we can pre-create the closure and call it directly.
if let BoundKind::Lambda {
params,
upvalues,
body,
positional_count,
} = &node.kind
&& upvalues.is_empty() && upvalues.is_empty()
{ {
let closure = Rc::new(crate::ast::vm::Closure::new( let closure = Rc::new(crate::ast::vm::Closure::new(
params.ty.original.clone(), params.ty.original.clone(),
body.ty.original.clone(), body.ty.original.clone(),
body.clone(), body.clone(),
vec![], vec![],
*positional_count, *positional_count,
)); ));
@@ -267,20 +242,16 @@ impl Environment {
}); });
} }
// FALLBACK: Generic script body (Block, If, etc.)
let exec_node = Rc::new(node); let exec_node = Rc::new(node);
Rc::new(crate::ast::types::NativeFunction { Rc::new(crate::ast::types::NativeFunction {
purity: Purity::Impure, purity: Purity::Impure,
func: Rc::new(move |args| { func: Rc::new(move |args| {
let mut vm = VM::new(global_values.clone()); let mut vm = VM::new(global_values.clone());
// 1. Execute the main body (Block, etc.)
let res = match vm.run(&exec_node) { let res = match vm.run(&exec_node) {
Ok(v) => v, Ok(v) => v,
Err(e) => panic!("Myc Runtime Error: {}", e), Err(e) => panic!("Myc Runtime Error: {}", e),
}; };
// 2. Auto-apply: If the script returned a closure, we apply arguments to it.
let mut final_res = res; let mut final_res = res;
if let Value::Object(obj) = &final_res if let Value::Object(obj) = &final_res
&& let Some(closure) = obj.as_any().downcast_ref::<crate::ast::vm::Closure>() && let Some(closure) = obj.as_any().downcast_ref::<crate::ast::vm::Closure>()
@@ -290,78 +261,63 @@ impl Environment {
Err(e) => panic!("Myc Runtime Error (Closure): {}", e), Err(e) => panic!("Myc Runtime Error (Closure): {}", e),
}; };
} }
// 3. Resolve Tail Calls
vm.resolve_tail_calls(final_res) vm.resolve_tail_calls(final_res)
}), }),
}) })
} }
fn specialize_node(&self, node: TypedNode) -> TypedNode { fn specialize_node(&self, node: AnalyzedNode) -> AnalyzedNode {
let registry = Rc::new(EnvFunctionRegistry { let registry = Rc::new(EnvFunctionRegistry {
registry: self.function_registry.clone(), registry: self.function_registry.clone(),
analyzed_registry: self.typed_function_registry.clone(),
}); });
let rtl_lookup = Rc::new(|name: &str, args: &[StaticType]| intrinsics::lookup(name, args)); let rtl_lookup = Rc::new(|name: &str, args: &[StaticType]| intrinsics::lookup(name, args));
let typed_reg = self.typed_function_registry.clone();
let func_reg = self.function_registry.clone(); let untyped_reg = self.function_registry.clone();
let mono_cache = self.monomorph_cache.clone(); let mono_cache = self.monomorph_cache.clone();
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 optimization = self.optimization; let optimization = self.optimization;
let analysis = self.last_analysis.borrow().clone();
let compiler_analysis = analysis.clone();
let compiler = Rc::new( let compiler = Rc::new(
move |func_template: BoundNode, move |func_template: BoundNode,
arg_types: &[StaticType]| arg_types: &[StaticType]|
-> Result<(Value, StaticType), String> { -> Result<(Value, StaticType), String> {
// 1. Re-TypeCheck the template with concrete argument types
let checker = TypeChecker::new(global_types.clone()); let checker = TypeChecker::new(global_types.clone());
let retyped_ast = checker.check(func_template, arg_types)?; let retyped_ast = checker.check(func_template, arg_types)?;
// 2. Specialize (Recursive) let analyzed = Analyzer::analyze(&retyped_ast, &global_purity.borrow());
let sub_registry = Rc::new(EnvFunctionRegistry { let sub_registry = Rc::new(EnvFunctionRegistry {
registry: func_reg.clone(), registry: untyped_reg.clone(),
analyzed_registry: typed_reg.clone(),
}); });
let sub_rtl_lookup = let sub_rtl_lookup = Rc::new(|name: &str, args: &[StaticType]| intrinsics::lookup(name, args));
Rc::new(|name: &str, args: &[StaticType]| intrinsics::lookup(name, args));
let sub_specializer = Specializer::new( let sub_specializer = Specializer::new(
Some(sub_registry), Some(sub_registry),
None, None,
Some(sub_rtl_lookup), Some(sub_rtl_lookup),
Some(mono_cache.clone()), Some(mono_cache.clone()),
compiler_analysis.clone(),
); );
let specialized_ast = sub_specializer.specialize(retyped_ast); let specialized_ast = sub_specializer.specialize(analyzed);
// 3. Optimize (Phase 2: Cracking & Folding)
let optimizer = Optimizer::new(optimization) 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());
.with_analysis(compiler_analysis.clone());
let optimized_ast = optimizer.optimize(specialized_ast); let optimized_ast = optimizer.optimize(specialized_ast);
// 4. TCO (converts to ExecNode)
let tco_ast = TCO::optimize(optimized_ast); let tco_ast = TCO::optimize(optimized_ast);
// 5. Compile to Value (VM)
let mut vm = VM::new(global_values.clone()); let mut vm = VM::new(global_values.clone());
let compiled_val = match vm.run(&tco_ast) { let compiled_val = match vm.run(&tco_ast) {
Ok(v) => v, Ok(v) => v,
Err(e) => return Err(format!("VM Error during specialization: {}", e)), Err(e) => return Err(format!("VM Error during specialization: {}", e)),
}; };
// 6. Determine correct return type from the newly inferred function signature let ret_type = tco_ast.ty.ty.clone();
let ret_type = if let StaticType::Function(sig) = &tco_ast.ty.ty {
sig.ret.clone()
} else {
StaticType::Any
};
Ok((compiled_val, ret_type)) Ok((compiled_val, ret_type))
}, },
); );
@@ -371,7 +327,6 @@ impl Environment {
Some(compiler), Some(compiler),
Some(rtl_lookup), Some(rtl_lookup),
Some(self.monomorph_cache.clone()), Some(self.monomorph_cache.clone()),
analysis,
); );
specializer.specialize(node) specializer.specialize(node)
@@ -380,9 +335,7 @@ impl Environment {
pub fn run_script(&self, source: &str) -> Result<Value, String> { pub fn run_script(&self, source: &str) -> Result<Value, String> {
if self.debug_mode { if self.debug_mode {
let (res, logs) = self.run_debug(source)?; let (res, logs) = self.run_debug(source)?;
for line in logs { for line in logs { println!("{}", line); }
println!("{}", line);
}
res res
} else { } else {
let compiled = self.compile(source)?; let compiled = self.compile(source)?;
@@ -395,33 +348,25 @@ impl Environment {
pub fn run_debug(&self, source: &str) -> Result<(Result<Value, String>, Vec<String>), String> { pub fn run_debug(&self, source: &str) -> Result<(Result<Value, String>, Vec<String>), String> {
let compiled = self.compile(source)?; let compiled = self.compile(source)?;
let linked = self.link(compiled); let linked = self.link(compiled);
// Execute with TracingObserver
let mut vm = VM::new(self.global_values.clone()); let mut vm = VM::new(self.global_values.clone());
let mut observer = TracingObserver::new(); let mut observer = TracingObserver::new();
let mut result = vm.run_with_observer(&mut observer, &linked); let mut result = vm.run_with_observer(&mut observer, &linked);
// If result is a closure (script entry), execute the body too
if let Ok(Value::Object(obj)) = &result if let Ok(Value::Object(obj)) = &result
&& let Some(closure) = obj.as_any().downcast_ref::<crate::ast::vm::Closure>() && let Some(closure) = obj.as_any().downcast_ref::<crate::ast::vm::Closure>()
{ {
result = vm.run_with_observer(&mut observer, &closure.exec_node); result = vm.run_with_observer(&mut observer, &closure.exec_node);
} }
// Resolve top-level tail calls
while let Ok(Value::TailCallRequest(payload)) = result { while let Ok(Value::TailCallRequest(payload)) = result {
let (next_obj, next_args) = *payload; let (next_obj, next_args) = *payload;
if let Some(closure) = next_obj.as_any().downcast_ref::<crate::ast::vm::Closure>() { if let Some(closure) = next_obj.as_any().downcast_ref::<crate::ast::vm::Closure>() {
result = vm.run_with_args_observed(&mut observer, closure, next_args); result = vm.run_with_args_observed(&mut observer, closure, next_args);
} else { } else {
result = Err(format!( result = Err(format!("Tail call target is not a closure: {}", next_obj.type_name()));
"Tail call target is not a closure: {}",
next_obj.type_name()
));
break; break;
} }
} }
Ok((result, observer.logs)) Ok((result, observer.logs))
} }
} }
+20 -117
View File
@@ -1,4 +1,4 @@
use crate::ast::compiler::bound_nodes::{Address, BoundKind, TypedNode}; use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind};
use crate::ast::compiler::tco::ExecNode; use crate::ast::compiler::tco::ExecNode;
use crate::ast::nodes::Node; use crate::ast::nodes::Node;
use crate::ast::types::{Object, Value}; use crate::ast::types::{Object, Value};
@@ -8,8 +8,11 @@ use std::rc::Rc;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Closure { pub struct Closure {
pub parameter_node: Rc<TypedNode>, /// The analyzed parameter pattern.
pub function_node: Rc<TypedNode>, pub parameter_node: Rc<AnalyzedNode>,
/// The analyzed body (before TCO).
pub function_node: Rc<AnalyzedNode>,
/// The executable node (after TCO).
pub exec_node: Rc<ExecNode>, pub exec_node: Rc<ExecNode>,
pub upvalues: Vec<Rc<RefCell<Value>>>, pub upvalues: Vec<Rc<RefCell<Value>>>,
pub positional_count: Option<u32>, pub positional_count: Option<u32>,
@@ -18,8 +21,8 @@ pub struct Closure {
impl Closure { impl Closure {
#[inline] #[inline]
pub fn new( pub fn new(
params: Rc<TypedNode>, params: Rc<AnalyzedNode>,
body: Rc<TypedNode>, body: Rc<AnalyzedNode>,
exec: Rc<ExecNode>, exec: Rc<ExecNode>,
upvalues: Vec<Rc<RefCell<Value>>>, upvalues: Vec<Rc<RefCell<Value>>>,
positional_count: Option<u32>, positional_count: Option<u32>,
@@ -85,11 +88,14 @@ impl VMObserver for TracingObserver {
const ACTIVE: bool = true; const ACTIVE: bool = true;
fn before_eval(&mut self, _vm: &VM, node: &ExecNode) { fn before_eval(&mut self, _vm: &VM, node: &ExecNode) {
let pad = self.pad(); let pad = self.pad();
let metrics = &node.ty.original.ty;
self.logs.push(format!( self.logs.push(format!(
"{}{} [{}]: {{", "{}{} [{} | P:{:?}{}]: {{",
pad, pad,
node.kind.display_name(), node.kind.display_name(),
node.ty.ty node.ty.ty,
metrics.purity,
if metrics.is_recursive { " | REC" } else { "" }
)); ));
self.indent += 1; self.indent += 1;
} }
@@ -166,8 +172,13 @@ macro_rules! dispatch_eval {
BoundKind::Lambda { params, upvalues, body, positional_count } => { BoundKind::Lambda { params, upvalues, body, positional_count } => {
let mut captured = Vec::with_capacity(upvalues.len()); let mut captured = Vec::with_capacity(upvalues.len());
for addr in upvalues { captured.push($self.capture_upvalue(*addr)?); } for addr in upvalues { captured.push($self.capture_upvalue(*addr)?); }
// CRITICAL FIX: body.clone() is O(1), body.as_ref().clone() was O(N)! let closure = Closure::new(
let closure = Closure::new(params.ty.original.clone(), body.ty.original.clone(), body.clone(), captured, *positional_count); params.ty.original.clone(),
body.ty.original.clone(),
body.clone(),
captured,
*positional_count
);
Ok(Value::Object(Rc::new(closure))) Ok(Value::Object(Rc::new(closure)))
}, },
BoundKind::Call { callee, args } => { BoundKind::Call { callee, args } => {
@@ -361,7 +372,6 @@ impl VM {
dispatch_eval!(self, node, eval) dispatch_eval!(self, node, eval)
} }
/// Resolves potential tail call requests iteratively until a final value is reached.
pub fn resolve_tail_calls(&mut self, mut result: Value) -> Value { pub fn resolve_tail_calls(&mut self, mut result: Value) -> Value {
while let Value::TailCallRequest(payload) = result { while let Value::TailCallRequest(payload) = result {
let (next_obj, next_args) = *payload; let (next_obj, next_args) = *payload;
@@ -622,110 +632,3 @@ impl VM {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::compiler::tco::TCO;
use crate::ast::nodes::{Node, Symbol};
use crate::ast::types::{NodeIdentity, SourceLocation, StaticType};
fn make_dummy_identity() -> Rc<NodeIdentity> {
Rc::new(NodeIdentity {
location: SourceLocation { line: 0, col: 0 },
})
}
#[test]
fn test_capture_boxing_modification() {
let id = make_dummy_identity();
let lambda_body = Node {
identity: id.clone(),
ty: StaticType::Void,
kind: BoundKind::Set {
addr: Address::Upvalue(0),
value: Box::new(Node {
identity: id.clone(),
ty: StaticType::Int,
kind: BoundKind::Constant(Value::Int(20)),
}),
},
};
let root = Node {
identity: id.clone(),
ty: StaticType::Int,
kind: BoundKind::Block {
exprs: vec![
Node {
identity: id.clone(),
ty: StaticType::Int,
kind: BoundKind::Set {
addr: Address::Local(0),
value: Box::new(Node {
identity: id.clone(),
ty: StaticType::Int,
kind: BoundKind::Constant(Value::Int(10)),
}),
},
},
Node {
identity: id.clone(),
ty: StaticType::Any,
kind: BoundKind::Set {
addr: Address::Local(1),
value: Box::new(Node {
identity: id.clone(),
ty: StaticType::Any,
kind: BoundKind::Lambda {
params: Rc::new(Node {
identity: id.clone(),
ty: StaticType::Tuple(vec![]),
kind: BoundKind::Tuple { elements: vec![] },
}),
upvalues: vec![Address::Local(0)],
body: Rc::new(lambda_body),
positional_count: Some(0),
},
}),
},
},
Node {
identity: id.clone(),
ty: StaticType::Void,
kind: BoundKind::Call {
callee: Box::new(Node {
identity: id.clone(),
ty: StaticType::Any,
kind: BoundKind::Get {
addr: Address::Local(1),
name: Symbol::from("f"),
},
}),
args: Box::new(Node {
identity: id.clone(),
ty: StaticType::Tuple(vec![]),
kind: BoundKind::Tuple { elements: vec![] },
}),
},
},
Node {
identity: id.clone(),
ty: StaticType::Int,
kind: BoundKind::Get {
addr: Address::Local(0),
name: Symbol::from("x"),
},
},
],
},
};
let globals = Rc::new(RefCell::new(Vec::new()));
let mut vm = VM::new(globals);
let exec_root = TCO::optimize(root);
let result = vm.run(&exec_root);
match result {
Ok(Value::Int(val)) => assert_eq!(val, 20),
_ => panic!("Expected Int(20)"),
}
}
}