Refactor AST inlining and global purity checks

Rename `ast_locals` to `ast_substitutions` for clarity.
Update `is_inlinable_value` to accept `Address` and check global purity
separately.
Simplify inlining logic in `optimize_node`.
This commit is contained in:
Michael Schimmel
2026-02-25 11:09:22 +01:00
parent 283cdf61a4
commit 6034216524
+39 -27
View File
@@ -129,22 +129,15 @@ impl Optimizer {
let (new_kind, metrics) = match node.kind { let (new_kind, metrics) = match node.kind {
BoundKind::Get { addr, ref name } => { BoundKind::Get { addr, ref name } => {
if !sub.assigned.contains(&addr) { if !sub.assigned.contains(&addr) {
// 1. Try inlining from current substitution map (locals/globals/upvalues) // 1. Try inlining from current value substitution map (locals/globals/upvalues)
if let Some(val) = sub.get_value(&addr) { if let Some(val) = sub.get_value(&addr)
// For globals, only inline if it's a known "safe" value && self.is_inlinable_value(val, addr)
let can_inline = match addr { {
Address::Global(idx) => self.is_inlinable_value(val, Some(idx)), return self.make_constant_node(val.clone(), &node);
_ => true,
};
if can_inline {
return self.make_constant_node(val.clone(), &node);
}
} }
// 2. Special case: Local-only AST inlining (for non-constant but pure expressions) // 2. Try inlining from AST substitution map (pure expressions)
if let Address::Local(slot) = addr if let Some(inlined_node) = sub.ast_substitutions.get(&addr) {
&& let Some(inlined_node) = sub.ast_locals.get(&slot)
{
return inlined_node.clone(); return inlined_node.clone();
} }
@@ -154,7 +147,7 @@ impl Optimizer {
{ {
let globals = globals_rc.borrow(); let globals = globals_rc.borrow();
if let Some(val) = globals.get(idx as usize) if let Some(val) = globals.get(idx as usize)
&& self.is_inlinable_value(val, Some(idx)) && self.is_inlinable_value(val, addr)
{ {
return self.make_constant_node(val.clone(), &node); return self.make_constant_node(val.clone(), &node);
} }
@@ -597,8 +590,9 @@ impl Optimizer {
} }
} }
fn is_inlinable_value(&self, val: &Value, _global_idx: Option<u32>) -> bool { fn is_inlinable_value(&self, val: &Value, addr: Address) -> bool {
match val { // 1. Basic check: is the type itself inlinable (Constants, pure Closures)?
let type_ok = match val {
Value::Int(_) Value::Int(_)
| Value::Float(_) | Value::Float(_)
| Value::Bool(_) | Value::Bool(_)
@@ -613,7 +607,27 @@ impl Optimizer {
} }
} }
_ => false, _ => false,
};
if !type_ok {
return false;
} }
// 2. For Globals: check if they are actually pure/stable
if let Address::Global(idx) = addr {
if let Some(purity_rc) = &self.global_purity {
let purity = purity_rc
.borrow()
.get(&idx)
.cloned()
.unwrap_or(Purity::Impure);
return purity >= Purity::Pure;
}
// If no purity info is available, we assume the global is unstable (safer)
return false;
}
true // Locals/Upvalues that reach here and passed the type check are inlinable
} }
fn make_constant_node(&self, val: Value, template: &AnalyzedNode) -> AnalyzedNode { fn make_constant_node(&self, val: Value, template: &AnalyzedNode) -> AnalyzedNode {
@@ -710,13 +724,13 @@ impl Optimizer {
let addr = Address::Local(slot); let addr = Address::Local(slot);
if (body_usage.is_used(&addr) || body_usage.is_assigned(&addr)) if (body_usage.is_used(&addr) || body_usage.is_assigned(&addr))
&& sub.get_value(&addr).is_none() && sub.get_value(&addr).is_none()
&& !sub.ast_locals.contains_key(&slot) && !sub.ast_substitutions.contains_key(&addr)
{ {
return None; return None;
} }
} }
if sub.values.is_empty() && sub.ast_locals.is_empty() && !arg_vals.is_empty() { if sub.values.is_empty() && sub.ast_substitutions.is_empty() && !arg_vals.is_empty() {
return None; return None;
} }
@@ -774,16 +788,14 @@ impl Optimizer {
sub.add_value(*addr, val.clone()); sub.add_value(*addr, val.clone());
} else if let BoundKind::Lambda { upvalues, .. } = &arg.kind } else if let BoundKind::Lambda { upvalues, .. } = &arg.kind
&& upvalues.is_empty() && upvalues.is_empty()
&& let Address::Local(slot) = addr
{ {
sub.add_ast_local(*slot, arg.clone()); sub.add_ast_substitution(*addr, arg.clone());
} else if let BoundKind::Get { } else if let BoundKind::Get {
addr: Address::Global(_), addr: Address::Global(_),
.. ..
} = &arg.kind } = &arg.kind
&& let Address::Local(slot) = addr
{ {
sub.add_ast_local(*slot, arg.clone()); sub.add_ast_substitution(*addr, arg.clone());
} }
} }
@@ -941,7 +953,7 @@ impl Optimizer {
struct SubstitutionMap { struct SubstitutionMap {
values: HashMap<Address, Value>, values: HashMap<Address, Value>,
ast_locals: HashMap<u32, AnalyzedNode>, ast_substitutions: HashMap<Address, AnalyzedNode>,
slot_mapping: HashMap<u32, u32>, slot_mapping: HashMap<u32, u32>,
assigned: HashSet<Address>, assigned: HashSet<Address>,
next_slot: u32, next_slot: u32,
@@ -953,7 +965,7 @@ impl SubstitutionMap {
fn new() -> Self { fn new() -> Self {
Self { Self {
values: HashMap::new(), values: HashMap::new(),
ast_locals: HashMap::new(), ast_substitutions: HashMap::new(),
slot_mapping: HashMap::new(), slot_mapping: HashMap::new(),
assigned: HashSet::new(), assigned: HashSet::new(),
next_slot: 0, next_slot: 0,
@@ -962,8 +974,8 @@ impl SubstitutionMap {
} }
} }
fn add_ast_local(&mut self, slot: u32, node: AnalyzedNode) { fn add_ast_substitution(&mut self, addr: Address, node: AnalyzedNode) {
self.ast_locals.insert(slot, node); self.ast_substitutions.insert(addr, node);
} }
fn map_slot(&mut self, old_slot: u32) -> u32 { fn map_slot(&mut self, old_slot: u32) -> u32 {