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
+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::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)"),
}
}
}