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::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);
}
_ => {}
}
}
+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.
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,
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, 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();
+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::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,
},