Files
RustAst/src/ast/compiler/optimizer/engine.rs
T
Michael Schimmel b87a6d7ada Refactor closure instantiation and inlining
This commit makes several changes related to how closures are handled:

- **Dumper:** Removes redundant introspection of closure ASTs, as this
  is no longer relevant.
- **Optimizer:** Updates `try_inline` to accept `ExecNode` for
  parameters and adds a check to ensure the `function_node` is a
  `Lambda` before attempting inlining.
- **Environment:** Simplifies closure creation by passing an `ExecNode`
  for parameters and ensuring the correct `stack_size` is used.
- **VM:** Adjusts `Closure` to store an `ExecNode` for parameters and
  updates `VM::unpack` to accept an `ExecNode`.
2026-03-13 18:00:58 +01:00

721 lines
27 KiB
Rust

use crate::ast::compiler::bound_nodes::{
Address, AnalyzedNode, BoundKind, GlobalAnalyzedRegistry, Node, UpvalueIdx,
};
use crate::ast::types::{Purity, Value};
use crate::ast::vm::Closure;
use std::cell::RefCell;
use std::rc::Rc;
use super::folder::Folder;
use super::inliner::Inliner;
use super::substitution_map::SubstitutionMap;
use super::utils::{PathTracker, UsageInfo};
pub struct Optimizer {
pub enabled: bool,
max_passes: usize,
pub globals: Option<Rc<RefCell<Vec<Value>>>>,
pub root_purity: Option<Rc<RefCell<Vec<Purity>>>>,
pub lambda_registry: Option<Rc<RefCell<GlobalAnalyzedRegistry>>>,
}
impl Optimizer {
pub fn new(enabled: bool) -> Self {
Self {
enabled,
max_passes: 5,
globals: None,
root_purity: None,
lambda_registry: None,
}
}
pub fn with_globals(mut self, globals: Rc<RefCell<Vec<Value>>>) -> Self {
self.globals = Some(globals);
self
}
pub fn with_purity(mut self, purity: Rc<RefCell<Vec<Purity>>>) -> Self {
self.root_purity = Some(purity);
self
}
pub fn with_registry(
mut self,
registry: Rc<RefCell<GlobalAnalyzedRegistry>>,
) -> Self {
self.lambda_registry = Some(registry);
self
}
pub fn optimize(&self, node: AnalyzedNode) -> AnalyzedNode {
if !self.enabled {
return node;
}
let mut current = Rc::new(node);
for _ in 0..self.max_passes {
let mut sub = SubstitutionMap::new();
let mut path = PathTracker::new();
let next = self.visit_node(current.clone(), &mut sub, &mut path);
if Rc::ptr_eq(&next, &current) {
break;
}
current = next;
}
// Unwrap the final Rc if we are at the end, or return a clone of the inner node.
// Since AnalyzedNode is small now (header + Rcs), cloning is cheap.
(*current).clone()
}
fn try_inline(
&self,
params: &AnalyzedNode,
arg_nodes: &[Rc<AnalyzedNode>],
body: &AnalyzedNode,
sub: &mut SubstitutionMap,
path: &mut PathTracker,
base_sub: Option<SubstitutionMap>,
) -> Option<Rc<AnalyzedNode>> {
let inliner = Inliner::new(&self.globals, &self.root_purity);
let mut inner_sub = base_sub.unwrap_or_else(|| sub.new_for_inlining());
if inliner.prepare_beta_reduction(params, arg_nodes, body, &mut inner_sub).is_some() {
let res = self.visit_node(Rc::new(body.clone()), &mut inner_sub, path);
// Sync back state to parent substitution map
sub.next_slot = inner_sub.next_slot;
sub.used.extend(inner_sub.used.iter().cloned());
sub.assigned.extend(inner_sub.assigned.iter().cloned());
sub.captured_slots.extend(inner_sub.captured_slots.iter().cloned());
Some(res)
} else {
None
}
}
fn visit_node(
&self,
node_rc: Rc<AnalyzedNode>,
sub: &mut SubstitutionMap,
path: &mut PathTracker,
) -> Rc<AnalyzedNode> {
let node = &*node_rc;
let folder = Folder::new(&self.globals);
let inliner = Inliner::new(&self.globals, &self.root_purity);
let (new_kind, metrics) = match &node.kind {
BoundKind::Get { addr, name } => {
let addr = *addr;
if !sub.assigned.contains(&addr) {
// 1. Try inlining from current value substitution map (locals/globals/upvalues)
if let Some(val) = sub.get_value(&addr)
&& inliner.is_inlinable_value(val, addr)
{
return Rc::new(folder.make_constant_node(val.clone(), node));
}
// 2. Try inlining from AST substitution map (pure expressions)
if let Some(inlined_node) = sub.ast_substitutions.get(&addr) {
return inlined_node.clone();
}
// 3. Fallback for Globals: check the actual VM environment
if let Address::Global(idx) = addr
&& let Some(globals_rc) = &self.globals
{
let globals = globals_rc.borrow();
if let Some(val) = globals.get(idx.0 as usize)
&& inliner.is_inlinable_value(val, addr)
{
return Rc::new(folder.make_constant_node(val.clone(), node));
}
}
}
sub.used.insert(addr);
(
BoundKind::Get {
addr: sub.map_address(addr),
name: name.clone(),
},
node.ty.clone(),
)
}
BoundKind::FieldAccessor(k) => (BoundKind::FieldAccessor(*k), node.ty.clone()),
BoundKind::GetField { rec, field } => {
let rec_opt = self.visit_node(rec.clone(), sub, path);
// Constant folding for Field Access
if let BoundKind::Constant(Value::Record(layout, values)) = &rec_opt.kind
&& let Some(idx) = layout.index_of(*field)
{
return Rc::new(folder.make_constant_node(values[idx].clone(), node));
}
if Rc::ptr_eq(&rec_opt, rec) {
return node_rc;
}
(
BoundKind::GetField {
rec: rec_opt,
field: *field,
},
node.ty.clone(),
)
}
BoundKind::Set { addr, value } => {
let value_opt = self.visit_node(value.clone(), sub, path);
if let BoundKind::Constant(val) = &value_opt.kind {
sub.add_value(*addr, val.clone());
} else {
sub.remove_value(addr);
}
if Rc::ptr_eq(&value_opt, value) {
return node_rc;
}
(
BoundKind::Set {
addr: sub.map_address(*addr),
value: value_opt,
},
node.ty.clone(),
)
}
BoundKind::Define {
name,
addr,
kind,
value,
captured_by,
} => {
let value_opt = self.visit_node(value.clone(), sub, path);
if let Address::Local(slot) = addr
&& !captured_by.is_empty()
{
sub.captured_slots.insert(*slot);
}
if let BoundKind::Constant(val) = &value_opt.kind {
sub.add_value(*addr, val.clone());
} else {
sub.remove_value(addr);
}
if let Address::Global(global_index) = addr
&& value_opt.ty.purity > Purity::Impure
&& let Some(purity_rc) = &self.root_purity
{
let mut pr = purity_rc.borrow_mut();
let idx = global_index.0 as usize;
if idx < pr.len() {
pr[idx] = value_opt.ty.purity;
}
}
if Rc::ptr_eq(&value_opt, value) {
return node_rc;
}
(
BoundKind::Define {
name: name.clone(),
addr: sub.map_address(*addr),
kind: *kind,
value: value_opt,
captured_by: captured_by.clone(),
},
node.ty.clone(),
)
}
BoundKind::Call { callee, args } => {
let callee_opt = self.visit_node(callee.clone(), sub, path);
let args_opt = self.visit_node(args.clone(), sub, path);
if self.enabled {
// Optimized Field Access Transformation
if let BoundKind::FieldAccessor(k) = &callee_opt.kind
&& let BoundKind::Tuple { elements } = &args_opt.kind
&& elements.len() == 1
{
let rec = elements[0].clone();
let metrics = node.ty.clone();
return Rc::new(Node {
identity: node.identity.clone(),
kind: BoundKind::GetField {
rec,
field: *k,
},
ty: metrics,
});
}
let mut arg_nodes = Vec::new();
self.flatten_tuple(args_opt.clone(), &mut arg_nodes);
if let BoundKind::Lambda {
params,
body,
upvalues,
positional_count,
} = &callee_opt.kind
&& upvalues.is_empty()
&& positional_count.is_some()
&& path.inlining_depth < 5
&& !callee_opt.ty.is_recursive
&& path.enter_lambda(&callee_opt.identity)
{
path.inlining_depth += 1;
let collapsed = self.try_inline(params, &arg_nodes, body, sub, path, None);
path.inlining_depth -= 1;
path.exit_lambda(&callee_opt.identity);
if let Some(res) = collapsed {
return res;
}
}
if let BoundKind::Get {
addr: Address::Global(idx),
..
} = &callee_opt.kind
&& let Some(registry_rc) = &self.lambda_registry
&& path.inlining_depth < 5
&& !path.inlining_stack.contains(idx)
{
let registry = registry_rc.borrow();
if let Some(lambda_node) = registry.get(idx)
&& let BoundKind::Lambda {
params,
body,
upvalues,
positional_count,
} = &lambda_node.kind
&& upvalues.is_empty()
&& positional_count.is_some()
&& !lambda_node.ty.is_recursive
{
path.inlining_stack.insert(*idx);
path.inlining_depth += 1;
let collapsed = self.try_inline(params, &arg_nodes, body, sub, path, None);
path.inlining_depth -= 1;
path.inlining_stack.remove(idx);
if let Some(res) = collapsed {
return res;
}
}
}
if let BoundKind::Constant(Value::Object(ref obj)) = callee_opt.kind
&& path.inlining_depth < 5
&& let Some(closure) = obj.as_any().downcast_ref::<Closure>()
&& (closure.upvalues.is_empty()
|| closure.function_node.ty.purity >= Purity::SideEffectFree)
&& !closure.function_node.ty.is_recursive
&& path.enter_lambda(&closure.function_node.identity)
{
let mut closure_sub = sub.new_for_inlining();
for (i, cell) in closure.upvalues.iter().enumerate() {
closure_sub.add_value(
Address::Upvalue(UpvalueIdx(i as u32)),
cell.borrow().clone(),
);
}
path.inlining_depth += 1;
if let BoundKind::Lambda { params, .. } = &closure.function_node.kind {
let collapsed = self.try_inline(
params,
&arg_nodes,
&closure.function_node,
sub,
path,
Some(closure_sub),
);
path.inlining_depth -= 1;
path.exit_lambda(&closure.function_node.identity);
if let Some(res) = collapsed {
return res;
}
} else {
path.inlining_depth -= 1;
path.exit_lambda(&closure.function_node.identity);
}
}
if let Some(folded) = folder.try_fold_pure(&callee_opt, &arg_nodes) {
return Rc::new(folded);
}
}
if Rc::ptr_eq(&callee_opt, callee) && Rc::ptr_eq(&args_opt, args) {
return node_rc;
}
(
BoundKind::Call {
callee: callee_opt,
args: args_opt,
},
node.ty.clone(),
)
}
BoundKind::Again { args } => {
let args_opt = self.visit_node(args.clone(), sub, path);
if Rc::ptr_eq(&args_opt, args) {
return node_rc;
}
(
BoundKind::Again {
args: args_opt,
},
node.ty.clone(),
)
}
BoundKind::If {
cond,
then_br,
else_br,
} => {
let cond_opt = self.visit_node(cond.clone(), sub, path);
if self.enabled
&& let BoundKind::Constant(ref val) = cond_opt.kind
{
if val.is_truthy() {
return self.visit_node(then_br.clone(), sub, path);
} else if let Some(else_node) = else_br {
return self.visit_node(else_node.clone(), sub, path);
} else {
return Rc::new(folder.make_nop_node(node));
}
}
let then_br_opt = self.visit_node(then_br.clone(), sub, path);
let else_br_opt = else_br
.as_ref()
.map(|e| self.visit_node(e.clone(), sub, path));
let unchanged = Rc::ptr_eq(&cond_opt, cond) && Rc::ptr_eq(&then_br_opt, then_br) && match (&else_br_opt, else_br) {
(Some(a), Some(b)) => Rc::ptr_eq(a, b),
(None, None) => true,
_ => false,
};
if unchanged {
return node_rc;
}
(
BoundKind::If {
cond: cond_opt,
then_br: then_br_opt,
else_br: else_br_opt,
},
node.ty.clone(),
)
}
BoundKind::Pipe {
inputs,
lambda,
out_type,
} => {
let mut o_inputs = Vec::with_capacity(inputs.len());
let mut inputs_changed = false;
for input in inputs {
let opt = self.visit_node(input.clone(), sub, path);
if !Rc::ptr_eq(&opt, input) {
inputs_changed = true;
}
o_inputs.push(opt);
}
let o_lambda = self.visit_node(lambda.clone(), sub, path);
if !inputs_changed && Rc::ptr_eq(&o_lambda, lambda) {
return node_rc;
}
(
BoundKind::Pipe {
inputs: o_inputs,
lambda: o_lambda,
out_type: out_type.clone(),
},
node.ty.clone(),
)
}
BoundKind::Block { exprs } => {
let mut info = UsageInfo::default();
if !exprs.is_empty() {
for e in exprs {
info.collect(e);
}
}
sub.assigned.extend(info.assigned.iter().cloned());
let mut new_exprs = Vec::with_capacity(exprs.len());
let last_idx = exprs.len().saturating_sub(1);
let mut block_changed = false;
for (i, e) in exprs.iter().enumerate() {
let is_last = i == last_idx;
if self.enabled && !is_last {
let removable = match &e.kind {
BoundKind::Define { addr, value, .. } => {
!info.is_used(addr)
&& (if let Address::Local(slot) = addr {
!sub.captured_slots.contains(slot)
} else {
true
})
&& (value.ty.purity >= Purity::SideEffectFree
|| matches!(value.kind, BoundKind::Lambda { .. }))
}
BoundKind::Set { addr, value, .. } => {
!info.is_used(addr)
&& (if let Address::Local(slot) = addr {
!sub.captured_slots.contains(slot)
} else {
true
})
&& value.ty.purity >= Purity::SideEffectFree
}
_ => e.ty.purity >= Purity::SideEffectFree,
};
if removable {
block_changed = true;
continue;
}
}
let opt = self.visit_node(e.clone(), sub, path);
if let BoundKind::Define { addr, value, .. } = &opt.kind
&& !info.assigned.contains(addr)
{
if let BoundKind::Constant(val) = &value.kind {
sub.add_value(*addr, val.clone());
} else if let BoundKind::Lambda { upvalues, .. } = &value.kind
&& upvalues.is_empty()
{
sub.add_ast_substitution(*addr, (**value).clone());
}
}
if self.enabled && matches!(opt.kind, BoundKind::Nop) && !is_last {
block_changed = true;
continue;
}
if !Rc::ptr_eq(&opt, e) {
block_changed = true;
}
new_exprs.push(opt);
}
if !block_changed {
return node_rc;
}
if self.enabled {
if new_exprs.is_empty() {
return Rc::new(folder.make_nop_node(node));
} else if new_exprs.len() == 1 {
return new_exprs.pop().unwrap();
}
}
(BoundKind::Block { exprs: new_exprs }, node.ty.clone())
}
BoundKind::Lambda {
params,
upvalues,
body,
positional_count,
} => {
let mut info = UsageInfo::default();
info.collect(node);
let mut new_upvalues = Vec::new();
let mut mapping = Vec::new();
let mut next_inner_subs = sub.new_inner();
next_inner_subs.assigned = info.assigned;
let mut upvalues_changed = false;
for (old_idx, capture_addr) in upvalues.iter().enumerate() {
let mut inlined_val = None;
if !sub.assigned.contains(capture_addr)
&& let Some(val) = sub.get_value(capture_addr)
{
inlined_val = Some(val.clone());
}
if let Address::Local(slot) = capture_addr {
sub.captured_slots.insert(*slot);
}
if let Some(val) = inlined_val {
next_inner_subs
.add_value(Address::Upvalue(UpvalueIdx(old_idx as u32)), val);
mapping.push(None);
upvalues_changed = true;
} else {
mapping.push(Some(new_upvalues.len() as u32));
let mapped = sub.map_address(*capture_addr);
if mapped != *capture_addr {
upvalues_changed = true;
}
new_upvalues.push(mapped);
}
}
let params_opt = self.visit_node(params.clone(), &mut next_inner_subs, path);
let body_opt = self.visit_node(body.clone(), &mut next_inner_subs, path);
let reindexed_body = if new_upvalues.len() != upvalues.len() {
sub.reindex_upvalues(body_opt, &mapping)
} else {
body_opt
};
if !upvalues_changed && Rc::ptr_eq(&params_opt, params) && Rc::ptr_eq(&reindexed_body, body) {
return node_rc;
}
(
BoundKind::Lambda {
params: params_opt,
upvalues: new_upvalues,
body: reindexed_body,
positional_count: *positional_count,
},
node.ty.clone(),
)
}
BoundKind::Destructure { pattern, value } => {
let val_opt = self.visit_node(value.clone(), sub, path);
let pat_opt = self.visit_node(pattern.clone(), sub, path);
let mut info = UsageInfo::default();
info.collect_pattern(&pat_opt);
for addr in info.assigned {
sub.remove_value(&addr);
}
if Rc::ptr_eq(&val_opt, value) && Rc::ptr_eq(&pat_opt, pattern) {
return node_rc;
}
(
BoundKind::Destructure {
pattern: pat_opt,
value: val_opt,
},
node.ty.clone(),
)
}
BoundKind::Tuple { elements } => {
let mut new_elements = Vec::with_capacity(elements.len());
let mut changed = false;
for e in elements {
let opt = self.visit_node(e.clone(), sub, path);
if !Rc::ptr_eq(&opt, e) {
changed = true;
}
new_elements.push(opt);
}
if !changed {
return node_rc;
}
(BoundKind::Tuple { elements: new_elements }, node.ty.clone())
}
BoundKind::Record { layout, values } => {
let mut mapped_values = Vec::with_capacity(values.len());
let mut changed = false;
for v in values {
let opt = self.visit_node(v.clone(), sub, path);
if !Rc::ptr_eq(&opt, v) {
changed = true;
}
mapped_values.push(opt);
}
if self.enabled
&& let Some(folded) = folder.try_fold_record(layout, &mapped_values, node)
{
return Rc::new(folded);
}
if !changed {
return node_rc;
}
(
BoundKind::Record {
layout: layout.clone(),
values: mapped_values,
},
node.ty.clone(),
)
}
BoundKind::Expansion {
original_call,
bound_expanded,
} => {
path.inlining_depth += 1;
let expanded_opt = self.visit_node(bound_expanded.clone(), sub, path);
path.inlining_depth -= 1;
if Rc::ptr_eq(&expanded_opt, bound_expanded) {
return node_rc;
}
(
BoundKind::Expansion {
original_call: original_call.clone(),
bound_expanded: expanded_opt,
},
node.ty.clone(),
)
}
k => (k.clone(), node.ty.clone()),
};
Rc::new(Node {
identity: node.identity.clone(),
kind: new_kind,
ty: metrics,
})
}
fn flatten_tuple(&self, node: Rc<AnalyzedNode>, into: &mut Vec<Rc<AnalyzedNode>>) {
match &node.kind {
BoundKind::Tuple { elements } => {
for el in elements {
self.flatten_tuple(el.clone(), into);
}
}
BoundKind::Nop => {}
_ => into.push(node),
}
}
}