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:
+103
-62
@@ -1,37 +1,32 @@
|
||||
use crate::ast::compiler::bound_nodes::{Address, BoundKind, TypedNode};
|
||||
use crate::ast::types::{Identity, Purity, StaticType};
|
||||
use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, NodeMetrics, TypedNode};
|
||||
use crate::ast::types::Purity;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Analysis {
|
||||
pub purity: HashMap<Identity, Purity>,
|
||||
pub is_recursive: HashSet<Identity>,
|
||||
}
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct Analyzer<'a> {
|
||||
global_purity: &'a HashMap<u32, Purity>,
|
||||
results: Analysis,
|
||||
/// 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.
|
||||
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> {
|
||||
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 {
|
||||
global_purity,
|
||||
results: Analysis::default(),
|
||||
lambda_stack: Vec::new(),
|
||||
globals_to_lambdas: HashMap::new(),
|
||||
recursive_identities: HashSet::new(),
|
||||
};
|
||||
|
||||
|
||||
// First pass: map globals to their lambda identities
|
||||
analyzer.collect_globals(node);
|
||||
|
||||
// Second pass: full analysis
|
||||
analyzer.visit(node);
|
||||
analyzer.results
|
||||
|
||||
// Second pass: full analysis (decorating TypedNode into AnalyzedNode)
|
||||
analyzer.visit(Rc::new(node.clone()))
|
||||
}
|
||||
|
||||
fn collect_globals(&mut self, node: &TypedNode) {
|
||||
@@ -46,104 +41,147 @@ impl<'a> Analyzer<'a> {
|
||||
for e in exprs { self.collect_globals(e); }
|
||||
}
|
||||
_ => {
|
||||
// Simplified traversal for global collection
|
||||
node.kind.for_each_child(|child| self.collect_globals(child));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit(&mut self, node: &TypedNode) -> Purity {
|
||||
let purity = match &node.kind {
|
||||
BoundKind::Constant(_) | BoundKind::Nop | BoundKind::Parameter { .. } => Purity::Pure,
|
||||
|
||||
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
|
||||
},
|
||||
fn visit(&mut self, node_rc: Rc<TypedNode>) -> AnalyzedNode {
|
||||
let node = &*node_rc;
|
||||
let mut is_recursive = false;
|
||||
|
||||
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, .. } => {
|
||||
self.visit(value)
|
||||
BoundKind::Get { addr, name } => {
|
||||
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 } => {
|
||||
let p_cond = self.visit(cond);
|
||||
let p_then = self.visit(then_br);
|
||||
let p_else = else_br.as_ref().map(|e| self.visit(e)).unwrap_or(Purity::Pure);
|
||||
p_cond.min(p_then).min(p_else)
|
||||
let cond_m = self.visit(Rc::new((**cond).clone()));
|
||||
let then_m = self.visit(Rc::new((**then_br).clone()));
|
||||
let else_m = else_br.as_ref().map(|e| self.visit(Rc::new((**e).clone())));
|
||||
|
||||
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.visit(body);
|
||||
let params_m = self.visit(params.clone());
|
||||
let body_m = self.visit(body.clone());
|
||||
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 } => {
|
||||
let p_callee = self.visit(callee);
|
||||
let p_args = self.visit(args);
|
||||
|
||||
// Detect recursion
|
||||
let callee_m = self.visit(Rc::new((**callee).clone()));
|
||||
let args_m = self.visit(Rc::new((**args).clone()));
|
||||
|
||||
if let BoundKind::Get { addr: Address::Global(idx), .. } = &callee.kind
|
||||
&& let Some(lambda_id) = self.globals_to_lambdas.get(idx)
|
||||
&& self.lambda_stack.contains(lambda_id)
|
||||
{
|
||||
self.results.is_recursive.insert(lambda_id.clone());
|
||||
// Also mark the call itself if needed
|
||||
self.results.is_recursive.insert(node.identity.clone());
|
||||
self.recursive_identities.insert(lambda_id.clone());
|
||||
is_recursive = true;
|
||||
}
|
||||
|
||||
// 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 {
|
||||
self.global_purity.get(idx).cloned().unwrap_or(Purity::Impure)
|
||||
} else {
|
||||
Purity::Impure
|
||||
};
|
||||
|
||||
p_callee.min(p_args).min(p_func)
|
||||
let p = callee_m.ty.purity.min(args_m.ty.purity).min(p_func);
|
||||
(BoundKind::Call { callee: Box::new(callee_m), args: Box::new(args_m) }, p)
|
||||
}
|
||||
|
||||
BoundKind::Block { exprs } => {
|
||||
let mut new_exprs = Vec::with_capacity(exprs.len());
|
||||
let mut p = Purity::Pure;
|
||||
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 } => {
|
||||
let mut new_elements = Vec::with_capacity(elements.len());
|
||||
let mut p = Purity::Pure;
|
||||
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 } => {
|
||||
let mut new_fields = Vec::with_capacity(fields.len());
|
||||
let mut p = Purity::Pure;
|
||||
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);
|
||||
purity
|
||||
crate::ast::nodes::Node {
|
||||
identity: node.identity.clone(),
|
||||
kind: new_kind,
|
||||
ty: NodeMetrics {
|
||||
original: node_rc,
|
||||
purity,
|
||||
is_recursive,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension trait to make traversal easier
|
||||
trait NodeExt {
|
||||
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) {
|
||||
match self {
|
||||
BoundKind::If { cond, then_br, else_br } => {
|
||||
@@ -159,14 +197,17 @@ impl NodeExt for BoundKind<StaticType> {
|
||||
f(callee); f(args);
|
||||
}
|
||||
BoundKind::Block { exprs } => {
|
||||
for e in exprs { f(e); } // Block
|
||||
for e in exprs { f(e); }
|
||||
}
|
||||
BoundKind::Tuple { elements } => {
|
||||
for e in elements { f(e); } // Tuple
|
||||
for e in elements { f(e); }
|
||||
}
|
||||
BoundKind::Record { fields } => {
|
||||
for (k, v) in fields { f(k); f(v); }
|
||||
}
|
||||
BoundKind::Expansion { bound_expanded, .. } => {
|
||||
f(bound_expanded);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,17 @@ pub type BoundNode<T = ()> = Node<BoundKind<T>, T>;
|
||||
/// Type alias for a node that has been fully type-checked.
|
||||
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)]
|
||||
pub enum BoundKind<T = ()> {
|
||||
Nop,
|
||||
|
||||
+226
-665
File diff suppressed because it is too large
Load Diff
+92
-179
@@ -1,7 +1,6 @@
|
||||
use crate::ast::compiler::analyzer::Analysis;
|
||||
use crate::ast::compiler::bound_nodes::{Address, BoundKind, BoundNode, TypedNode};
|
||||
use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, BoundNode, NodeMetrics};
|
||||
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::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
@@ -17,13 +16,13 @@ pub type RtlLookupFunc = Rc<dyn Fn(&str, &[StaticType]) -> Option<(Value, Static
|
||||
|
||||
pub trait FunctionRegistry {
|
||||
fn resolve(&self, addr: Address) -> Option<BoundNode>;
|
||||
fn resolve_analyzed(&self, _addr: Address) -> Option<AnalyzedNode> { None }
|
||||
}
|
||||
|
||||
pub type MonoCache = HashMap<MonoCacheKey, (Value, StaticType)>;
|
||||
|
||||
pub struct Specializer {
|
||||
pub cache: Rc<RefCell<MonoCache>>,
|
||||
pub analysis: Analysis,
|
||||
registry: Option<Rc<dyn FunctionRegistry>>,
|
||||
compiler: Option<CompileFunc>,
|
||||
rtl_lookup: Option<RtlLookupFunc>,
|
||||
@@ -35,279 +34,193 @@ impl Specializer {
|
||||
compiler: Option<CompileFunc>,
|
||||
rtl_lookup: Option<RtlLookupFunc>,
|
||||
cache: Option<Rc<RefCell<MonoCache>>>,
|
||||
analysis: Analysis,
|
||||
) -> Self {
|
||||
Self {
|
||||
cache: cache.unwrap_or_else(|| Rc::new(RefCell::new(HashMap::new()))),
|
||||
analysis,
|
||||
registry,
|
||||
compiler,
|
||||
rtl_lookup,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn specialize(&self, node: TypedNode) -> TypedNode {
|
||||
pub fn specialize(&self, node: AnalyzedNode) -> AnalyzedNode {
|
||||
self.visit_node(node)
|
||||
}
|
||||
|
||||
fn visit_node(&self, node: TypedNode) -> TypedNode {
|
||||
let (new_kind, new_ty) = match node.kind {
|
||||
fn visit_node(&self, node: AnalyzedNode) -> AnalyzedNode {
|
||||
let (new_kind, metrics) = match node.kind {
|
||||
BoundKind::Call { callee, args } => {
|
||||
let (new_callee, new_args, ret_ty) =
|
||||
self.specialize_call_logic(*callee, *args, node.ty.clone());
|
||||
let (new_callee, new_args, _ret_ty) =
|
||||
self.specialize_call_logic(*callee, *args, node.ty.original.ty.clone());
|
||||
|
||||
let new_metrics = node.ty.clone();
|
||||
(
|
||||
BoundKind::Call {
|
||||
callee: Box::new(new_callee),
|
||||
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 then_br = Box::new(self.visit_node(*then_br));
|
||||
let else_br = else_br.map(|e| Box::new(self.visit_node(*e)));
|
||||
(
|
||||
BoundKind::If {
|
||||
cond,
|
||||
then_br,
|
||||
else_br,
|
||||
},
|
||||
node.ty,
|
||||
)
|
||||
(BoundKind::If { cond, then_br, else_br }, node.ty.clone())
|
||||
}
|
||||
BoundKind::Block { exprs } => {
|
||||
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 {
|
||||
params,
|
||||
upvalues,
|
||||
body,
|
||||
positional_count,
|
||||
} => {
|
||||
BoundKind::Lambda { params, upvalues, body, positional_count } => {
|
||||
let params = Rc::new(self.visit_node(params.as_ref().clone()));
|
||||
let body = Rc::new(self.visit_node((*body).clone()));
|
||||
(
|
||||
BoundKind::Lambda {
|
||||
params,
|
||||
upvalues,
|
||||
body,
|
||||
positional_count,
|
||||
},
|
||||
node.ty,
|
||||
)
|
||||
(BoundKind::Lambda { params, upvalues, body, positional_count }, node.ty.clone())
|
||||
}
|
||||
BoundKind::DefLocal {
|
||||
name,
|
||||
slot,
|
||||
value,
|
||||
captured_by,
|
||||
} => {
|
||||
BoundKind::DefLocal { name, slot, value, captured_by } => {
|
||||
let value = Box::new(self.visit_node(*value));
|
||||
(
|
||||
BoundKind::DefLocal {
|
||||
name,
|
||||
slot,
|
||||
value,
|
||||
captured_by,
|
||||
},
|
||||
node.ty,
|
||||
)
|
||||
(BoundKind::DefLocal { name, slot, value, captured_by }, node.ty.clone())
|
||||
}
|
||||
BoundKind::DefGlobal {
|
||||
name,
|
||||
global_index,
|
||||
value,
|
||||
} => {
|
||||
BoundKind::DefGlobal { name, global_index, value } => {
|
||||
let value = Box::new(self.visit_node(*value));
|
||||
(
|
||||
BoundKind::DefGlobal {
|
||||
name,
|
||||
global_index,
|
||||
value,
|
||||
},
|
||||
node.ty,
|
||||
)
|
||||
(BoundKind::DefGlobal { name, global_index, value }, node.ty.clone())
|
||||
}
|
||||
BoundKind::Set { addr, 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 } => {
|
||||
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 } => {
|
||||
let fields = fields
|
||||
.into_iter()
|
||||
.map(|(k, v)| (self.visit_node(k), self.visit_node(v)))
|
||||
.collect();
|
||||
(BoundKind::Record { fields }, node.ty)
|
||||
(BoundKind::Record { fields }, node.ty.clone())
|
||||
}
|
||||
BoundKind::Expansion {
|
||||
original_call,
|
||||
bound_expanded,
|
||||
} => {
|
||||
BoundKind::Expansion { original_call, bound_expanded } => {
|
||||
let bound_expanded = Box::new(self.visit_node(*bound_expanded));
|
||||
(
|
||||
BoundKind::Expansion {
|
||||
original_call,
|
||||
bound_expanded,
|
||||
},
|
||||
node.ty,
|
||||
)
|
||||
(BoundKind::Expansion { original_call, bound_expanded }, node.ty.clone())
|
||||
}
|
||||
|
||||
// Leaf nodes or uninteresting nodes
|
||||
k => (k, node.ty),
|
||||
k => (k, node.ty.clone()),
|
||||
};
|
||||
|
||||
Node {
|
||||
identity: node.identity,
|
||||
kind: new_kind,
|
||||
ty: new_ty,
|
||||
ty: metrics,
|
||||
}
|
||||
}
|
||||
|
||||
fn specialize_call_logic(
|
||||
&self,
|
||||
callee: TypedNode,
|
||||
args: TypedNode,
|
||||
callee: AnalyzedNode,
|
||||
args: AnalyzedNode,
|
||||
original_ty: StaticType,
|
||||
) -> (TypedNode, TypedNode, StaticType) {
|
||||
// 1. Specialize children first
|
||||
) -> (AnalyzedNode, AnalyzedNode, StaticType) {
|
||||
let new_callee = self.visit_node(callee);
|
||||
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 {
|
||||
*addr
|
||||
} else {
|
||||
// Not a direct call to a named function/variable
|
||||
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 {
|
||||
let arg_types: Vec<StaticType> = if let StaticType::Tuple(elements) = &new_args.ty.original.ty {
|
||||
elements.clone()
|
||||
} else {
|
||||
vec![new_args.ty.clone()]
|
||||
vec![new_args.ty.original.ty.clone()]
|
||||
};
|
||||
|
||||
if arg_types.iter().any(|t| matches!(t, StaticType::Any)) {
|
||||
// Cannot specialize with unknown types
|
||||
return (new_callee, new_args, original_ty);
|
||||
}
|
||||
|
||||
// --- Optimization Candidate ---
|
||||
let key = MonoCacheKey {
|
||||
address,
|
||||
arg_types: arg_types.clone(),
|
||||
};
|
||||
|
||||
// 4. Check Cache
|
||||
if let Some((val, ret_ty)) = self.cache.borrow().get(&key) {
|
||||
// Cache Hit! Replace Callee with Constant(Function)
|
||||
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(),
|
||||
})),
|
||||
};
|
||||
let specialized_callee = self.make_constant_node(val.clone(), StaticType::Function(Box::new(Signature {
|
||||
params: StaticType::Tuple(arg_types),
|
||||
ret: ret_ty.clone(),
|
||||
})), &new_callee);
|
||||
return (specialized_callee, new_args, ret_ty.clone());
|
||||
}
|
||||
|
||||
// 5. Check RTL (Host Functions)
|
||||
if let Some(rtl_lookup) = &self.rtl_lookup
|
||||
&& let BoundKind::Get { name, .. } = &new_callee.kind
|
||||
&& 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()));
|
||||
|
||||
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(),
|
||||
})),
|
||||
};
|
||||
self.cache.borrow_mut().insert(key.clone(), (val.clone(), ret_ty.clone()));
|
||||
let specialized_callee = self.make_constant_node(val.clone(), StaticType::Function(Box::new(Signature {
|
||||
params: StaticType::Tuple(arg_types),
|
||||
ret: ret_ty.clone(),
|
||||
})), &new_callee);
|
||||
return (specialized_callee, new_args, ret_ty);
|
||||
}
|
||||
|
||||
// 6. Resolve Function Definition
|
||||
if let Some(func_node) = self.registry.as_ref().and_then(|r| r.resolve(address)) {
|
||||
// Check for recursion from pre-pass.
|
||||
if self.analysis.is_recursive.contains(&func_node.identity) {
|
||||
return (new_callee, new_args, original_ty);
|
||||
}
|
||||
|
||||
// Check constraints (no closures with state)
|
||||
if let BoundKind::Lambda { upvalues, .. } = &func_node.kind {
|
||||
if !upvalues.is_empty() {
|
||||
return (new_callee, new_args, original_ty);
|
||||
}
|
||||
} else {
|
||||
return (new_callee, new_args, original_ty);
|
||||
}
|
||||
|
||||
// 7. Compile Specialization (User Code)
|
||||
if let Some(compiler) = &self.compiler {
|
||||
match compiler(func_node, &arg_types) {
|
||||
Ok((compiled_val, ret_ty)) => {
|
||||
let res_val: Value = compiled_val;
|
||||
let res_ty: StaticType = ret_ty;
|
||||
|
||||
// Store in cache
|
||||
self.cache
|
||||
.borrow_mut()
|
||||
.insert(key, (res_val.clone(), res_ty.clone()));
|
||||
|
||||
// PERFORMANCE: Flatten the argument tuple to match the specialized signature.
|
||||
let flat_elements = self.flatten_tuple(new_args.clone());
|
||||
let flat_types = flat_elements.iter().map(|e| e.ty.clone()).collect();
|
||||
let flattened_args = Node {
|
||||
identity: new_args.identity.clone(),
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(registry) = &self.registry
|
||||
&& let Some(func_node) = registry.resolve_analyzed(address)
|
||||
&& func_node.ty.is_recursive
|
||||
{
|
||||
return (new_callee, new_args, original_ty);
|
||||
}
|
||||
|
||||
if let Some(compiler) = &self.compiler
|
||||
&& let Some(func_node) = self.registry.as_ref().and_then(|r| r.resolve(address))
|
||||
&& let Ok((compiled_val, ret_ty)) = compiler(func_node, &arg_types)
|
||||
{
|
||||
self.cache.borrow_mut().insert(key, (compiled_val.clone(), ret_ty.clone()));
|
||||
let flat_elements = self.flatten_tuple(new_args.clone());
|
||||
let flat_types = flat_elements.iter().map(|e| e.ty.original.ty.clone()).collect();
|
||||
let flattened_args = Node {
|
||||
identity: new_args.identity.clone(),
|
||||
kind: BoundKind::Tuple { elements: flat_elements },
|
||||
ty: NodeMetrics {
|
||||
original: Rc::new(Node {
|
||||
identity: new_args.identity.clone(),
|
||||
kind: BoundKind::Tuple { elements: vec![] },
|
||||
ty: StaticType::Tuple(flat_types),
|
||||
}),
|
||||
purity: new_args.ty.purity,
|
||||
is_recursive: new_args.ty.is_recursive,
|
||||
},
|
||||
};
|
||||
|
||||
let specialized_callee = self.make_constant_node(compiled_val, StaticType::Function(Box::new(Signature {
|
||||
params: flattened_args.ty.original.ty.clone(),
|
||||
ret: ret_ty.clone(),
|
||||
})), &new_callee);
|
||||
return (specialized_callee, flattened_args, ret_ty);
|
||||
}
|
||||
|
||||
// Fallback: Dynamic Call
|
||||
(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 {
|
||||
BoundKind::Tuple { elements } => {
|
||||
let mut flat = Vec::new();
|
||||
|
||||
@@ -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::types::StaticType;
|
||||
use std::rc::Rc;
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RuntimeMetadata {
|
||||
pub ty: StaticType,
|
||||
pub is_tail: bool,
|
||||
/// The original, high-level typed node. Perfect for Debuggers and Optimizers.
|
||||
pub original: Rc<TypedNode>,
|
||||
/// The analyzed node, containing metrics and a link to the original TypedNode.
|
||||
pub original: Rc<AnalyzedNode>,
|
||||
}
|
||||
|
||||
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 struct TCO;
|
||||
|
||||
impl TCO {
|
||||
/// Lowers a TypedNode (Compiler-AST) to an ExecNode (VM-AST) and marks tail positions.
|
||||
pub fn optimize(node: TypedNode) -> ExecNode {
|
||||
/// Lowers an AnalyzedNode to an ExecNode and marks tail positions.
|
||||
pub fn optimize(node: AnalyzedNode) -> ExecNode {
|
||||
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 new_kind = match &node.kind {
|
||||
BoundKind::Call { callee, args } => BoundKind::Call {
|
||||
@@ -158,7 +157,7 @@ impl TCO {
|
||||
identity: node.identity.clone(),
|
||||
kind: new_kind,
|
||||
ty: RuntimeMetadata {
|
||||
ty: node.ty.clone(),
|
||||
ty: node.ty.original.ty.clone(),
|
||||
is_tail: is_tail_position,
|
||||
original: node_rc,
|
||||
},
|
||||
|
||||
+43
-98
@@ -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::{TypeChecker, TypedNode};
|
||||
use crate::ast::nodes::{Node, Symbol, UntypedKind};
|
||||
@@ -8,7 +8,7 @@ use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
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::lambda_collector::LambdaCollector;
|
||||
use crate::ast::compiler::macros::{MacroEvaluator, MacroExpander, MacroRegistry};
|
||||
@@ -26,15 +26,15 @@ pub struct Environment {
|
||||
pub global_values: Rc<RefCell<Vec<Value>>>,
|
||||
pub prng: Rc<RefCell<fastrand::Rng>>,
|
||||
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 debug_mode: bool,
|
||||
pub optimization: bool,
|
||||
pub last_analysis: RefCell<Analysis>,
|
||||
}
|
||||
|
||||
struct EnvFunctionRegistry {
|
||||
registry: Rc<RefCell<HashMap<u32, BoundNode>>>,
|
||||
analyzed_registry: Rc<RefCell<HashMap<u32, AnalyzedNode>>>,
|
||||
}
|
||||
|
||||
impl FunctionRegistry for EnvFunctionRegistry {
|
||||
@@ -45,9 +45,15 @@ impl FunctionRegistry for EnvFunctionRegistry {
|
||||
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 {
|
||||
global_names: Rc<RefCell<HashMap<Symbol, u32>>>,
|
||||
global_types: Rc<RefCell<HashMap<u32, StaticType>>>,
|
||||
@@ -60,19 +66,16 @@ impl MacroEvaluator for RuntimeMacroEvaluator {
|
||||
node: &Node<UntypedKind>,
|
||||
bindings: &HashMap<Rc<str>, Node<UntypedKind>>,
|
||||
) -> Result<Value, String> {
|
||||
// 1. Check if it's a simple parameter substitution
|
||||
if let UntypedKind::Identifier(sym) = &node.kind
|
||||
&& let Some(arg_node) = bindings.get(&sym.name)
|
||||
{
|
||||
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 checker = TypeChecker::new(self.global_types.clone());
|
||||
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());
|
||||
vm.run(&exec_ast)
|
||||
@@ -98,7 +101,6 @@ impl Environment {
|
||||
monomorph_cache: Rc::new(RefCell::new(HashMap::new())),
|
||||
debug_mode: false,
|
||||
optimization: true,
|
||||
last_analysis: RefCell::new(Analysis::default()),
|
||||
};
|
||||
env.register_stdlib();
|
||||
env
|
||||
@@ -135,7 +137,6 @@ impl Environment {
|
||||
values.push(Value::Function(func));
|
||||
}
|
||||
|
||||
/// Utility to register a native function from a closure.
|
||||
pub fn register_native_fn(
|
||||
&self,
|
||||
name: &str,
|
||||
@@ -162,12 +163,11 @@ impl Environment {
|
||||
let idx = values.len() as u32;
|
||||
names.insert(Symbol::from(name), idx);
|
||||
types.insert(idx, ty);
|
||||
purity.insert(idx, Purity::Pure); // Constants are always pure
|
||||
purity.insert(idx, Purity::Pure);
|
||||
values.push(val);
|
||||
}
|
||||
|
||||
fn register_stdlib(&self) {
|
||||
// Register all standard library functions via RTL module
|
||||
rtl::register(self);
|
||||
}
|
||||
|
||||
@@ -177,79 +177,54 @@ impl Environment {
|
||||
Ok(Dumper::dump(&linked))
|
||||
}
|
||||
|
||||
/// Frontend: Parse -> Expand Macros -> Bind -> Type Check
|
||||
pub fn compile(&self, source: &str) -> Result<TypedNode, String> {
|
||||
// 1. Parse
|
||||
let mut parser = Parser::new(source)?;
|
||||
let untyped_ast = parser.parse_expression()?;
|
||||
|
||||
// 2. Check for trailing tokens
|
||||
if !parser.at_eof() {
|
||||
return Err(
|
||||
"Unexpected trailing expressions in script. Use (do ...) for sequences."
|
||||
.to_string(),
|
||||
);
|
||||
return Err("Unexpected trailing expressions in script.".to_string());
|
||||
}
|
||||
|
||||
// 3. Expand Macros
|
||||
let expanded_ast = self.get_expander().expand(untyped_ast)?;
|
||||
|
||||
// 4. Bind
|
||||
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());
|
||||
|
||||
// 6. Type Check
|
||||
let checker = TypeChecker::new(self.global_types.clone());
|
||||
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)
|
||||
}
|
||||
|
||||
/// Backend Phase 1: Optimization (TCO, etc.) and Lowering
|
||||
pub fn link(&self, node: TypedNode) -> ExecNode {
|
||||
// 1. Specialize (Always performed for correctness)
|
||||
let specialized = self.specialize_node(node);
|
||||
// 1. Analyze
|
||||
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)
|
||||
.with_globals(self.global_values.clone())
|
||||
.with_purity(self.global_purity.clone())
|
||||
.with_registry(self.typed_function_registry.clone())
|
||||
.with_analysis(self.last_analysis.borrow().clone());
|
||||
.with_registry(self.typed_function_registry.clone());
|
||||
let optimized = optimizer.optimize(specialized);
|
||||
|
||||
// 3. TCO (Always performed, converts to ExecNode)
|
||||
// 5. TCO
|
||||
TCO::optimize(optimized)
|
||||
}
|
||||
|
||||
/// Backend Phase 2: Packaging into an invokable NativeFunction
|
||||
pub fn instantiate(&self, node: ExecNode) -> Rc<crate::ast::types::NativeFunction> {
|
||||
let global_values = self.global_values.clone();
|
||||
|
||||
// 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
|
||||
if let BoundKind::Lambda { params, upvalues, body, positional_count } = &node.kind
|
||||
&& upvalues.is_empty()
|
||||
{
|
||||
let closure = Rc::new(crate::ast::vm::Closure::new(
|
||||
params.ty.original.clone(),
|
||||
body.ty.original.clone(),
|
||||
body.clone(),
|
||||
params.ty.original.clone(),
|
||||
body.ty.original.clone(),
|
||||
body.clone(),
|
||||
vec![],
|
||||
*positional_count,
|
||||
));
|
||||
@@ -267,20 +242,16 @@ impl Environment {
|
||||
});
|
||||
}
|
||||
|
||||
// FALLBACK: Generic script body (Block, If, etc.)
|
||||
let exec_node = Rc::new(node);
|
||||
Rc::new(crate::ast::types::NativeFunction {
|
||||
purity: Purity::Impure,
|
||||
func: Rc::new(move |args| {
|
||||
let mut vm = VM::new(global_values.clone());
|
||||
|
||||
// 1. Execute the main body (Block, etc.)
|
||||
let res = match vm.run(&exec_node) {
|
||||
Ok(v) => v,
|
||||
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;
|
||||
if let Value::Object(obj) = &final_res
|
||||
&& 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),
|
||||
};
|
||||
}
|
||||
|
||||
// 3. Resolve Tail Calls
|
||||
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 {
|
||||
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 func_reg = self.function_registry.clone();
|
||||
let typed_reg = self.typed_function_registry.clone();
|
||||
let untyped_reg = self.function_registry.clone();
|
||||
let mono_cache = self.monomorph_cache.clone();
|
||||
let global_values = self.global_values.clone();
|
||||
let global_types = self.global_types.clone();
|
||||
let global_purity = self.global_purity.clone();
|
||||
let optimization = self.optimization;
|
||||
let analysis = self.last_analysis.borrow().clone();
|
||||
|
||||
let compiler_analysis = analysis.clone();
|
||||
let compiler = Rc::new(
|
||||
move |func_template: BoundNode,
|
||||
arg_types: &[StaticType]|
|
||||
-> Result<(Value, StaticType), String> {
|
||||
// 1. Re-TypeCheck the template with concrete argument types
|
||||
let checker = TypeChecker::new(global_types.clone());
|
||||
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 {
|
||||
registry: func_reg.clone(),
|
||||
registry: untyped_reg.clone(),
|
||||
analyzed_registry: typed_reg.clone(),
|
||||
});
|
||||
let sub_rtl_lookup =
|
||||
Rc::new(|name: &str, args: &[StaticType]| intrinsics::lookup(name, args));
|
||||
let sub_rtl_lookup = Rc::new(|name: &str, args: &[StaticType]| intrinsics::lookup(name, args));
|
||||
|
||||
let sub_specializer = Specializer::new(
|
||||
Some(sub_registry),
|
||||
None,
|
||||
Some(sub_rtl_lookup),
|
||||
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)
|
||||
.with_globals(global_values.clone())
|
||||
.with_purity(global_purity.clone())
|
||||
.with_analysis(compiler_analysis.clone());
|
||||
.with_purity(global_purity.clone());
|
||||
let optimized_ast = optimizer.optimize(specialized_ast);
|
||||
|
||||
// 4. TCO (converts to ExecNode)
|
||||
let tco_ast = TCO::optimize(optimized_ast);
|
||||
|
||||
// 5. Compile to Value (VM)
|
||||
let mut vm = VM::new(global_values.clone());
|
||||
let compiled_val = match vm.run(&tco_ast) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err(format!("VM Error during specialization: {}", e)),
|
||||
};
|
||||
|
||||
// 6. Determine correct return type from the newly inferred function signature
|
||||
let ret_type = if let StaticType::Function(sig) = &tco_ast.ty.ty {
|
||||
sig.ret.clone()
|
||||
} else {
|
||||
StaticType::Any
|
||||
};
|
||||
|
||||
let ret_type = tco_ast.ty.ty.clone();
|
||||
Ok((compiled_val, ret_type))
|
||||
},
|
||||
);
|
||||
@@ -371,7 +327,6 @@ impl Environment {
|
||||
Some(compiler),
|
||||
Some(rtl_lookup),
|
||||
Some(self.monomorph_cache.clone()),
|
||||
analysis,
|
||||
);
|
||||
|
||||
specializer.specialize(node)
|
||||
@@ -380,9 +335,7 @@ impl Environment {
|
||||
pub fn run_script(&self, source: &str) -> Result<Value, String> {
|
||||
if self.debug_mode {
|
||||
let (res, logs) = self.run_debug(source)?;
|
||||
for line in logs {
|
||||
println!("{}", line);
|
||||
}
|
||||
for line in logs { println!("{}", line); }
|
||||
res
|
||||
} else {
|
||||
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> {
|
||||
let compiled = self.compile(source)?;
|
||||
let linked = self.link(compiled);
|
||||
|
||||
// Execute with TracingObserver
|
||||
let mut vm = VM::new(self.global_values.clone());
|
||||
let mut observer = TracingObserver::new();
|
||||
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
|
||||
&& let Some(closure) = obj.as_any().downcast_ref::<crate::ast::vm::Closure>()
|
||||
{
|
||||
result = vm.run_with_observer(&mut observer, &closure.exec_node);
|
||||
}
|
||||
|
||||
// Resolve top-level tail calls
|
||||
while let Ok(Value::TailCallRequest(payload)) = result {
|
||||
let (next_obj, next_args) = *payload;
|
||||
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);
|
||||
} else {
|
||||
result = Err(format!(
|
||||
"Tail call target is not a closure: {}",
|
||||
next_obj.type_name()
|
||||
));
|
||||
result = Err(format!("Tail call target is not a closure: {}", next_obj.type_name()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok((result, observer.logs))
|
||||
}
|
||||
}
|
||||
|
||||
+20
-117
@@ -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::nodes::Node;
|
||||
use crate::ast::types::{Object, Value};
|
||||
@@ -8,8 +8,11 @@ use std::rc::Rc;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Closure {
|
||||
pub parameter_node: Rc<TypedNode>,
|
||||
pub function_node: Rc<TypedNode>,
|
||||
/// The analyzed parameter pattern.
|
||||
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 upvalues: Vec<Rc<RefCell<Value>>>,
|
||||
pub positional_count: Option<u32>,
|
||||
@@ -18,8 +21,8 @@ pub struct Closure {
|
||||
impl Closure {
|
||||
#[inline]
|
||||
pub fn new(
|
||||
params: Rc<TypedNode>,
|
||||
body: Rc<TypedNode>,
|
||||
params: Rc<AnalyzedNode>,
|
||||
body: Rc<AnalyzedNode>,
|
||||
exec: Rc<ExecNode>,
|
||||
upvalues: Vec<Rc<RefCell<Value>>>,
|
||||
positional_count: Option<u32>,
|
||||
@@ -85,11 +88,14 @@ impl VMObserver for TracingObserver {
|
||||
const ACTIVE: bool = true;
|
||||
fn before_eval(&mut self, _vm: &VM, node: &ExecNode) {
|
||||
let pad = self.pad();
|
||||
let metrics = &node.ty.original.ty;
|
||||
self.logs.push(format!(
|
||||
"{}{} [{}]: {{",
|
||||
"{}{} [{} | P:{:?}{}]: {{",
|
||||
pad,
|
||||
node.kind.display_name(),
|
||||
node.ty.ty
|
||||
node.ty.ty,
|
||||
metrics.purity,
|
||||
if metrics.is_recursive { " | REC" } else { "" }
|
||||
));
|
||||
self.indent += 1;
|
||||
}
|
||||
@@ -166,8 +172,13 @@ macro_rules! dispatch_eval {
|
||||
BoundKind::Lambda { params, upvalues, body, positional_count } => {
|
||||
let mut captured = Vec::with_capacity(upvalues.len());
|
||||
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(params.ty.original.clone(), body.ty.original.clone(), body.clone(), captured, *positional_count);
|
||||
let closure = Closure::new(
|
||||
params.ty.original.clone(),
|
||||
body.ty.original.clone(),
|
||||
body.clone(),
|
||||
captured,
|
||||
*positional_count
|
||||
);
|
||||
Ok(Value::Object(Rc::new(closure)))
|
||||
},
|
||||
BoundKind::Call { callee, args } => {
|
||||
@@ -361,7 +372,6 @@ impl VM {
|
||||
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 {
|
||||
while let Value::TailCallRequest(payload) = result {
|
||||
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)"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user