Refactor: Collect global usage in optimizer

Introduce `collect_usage` to gather both local and global variable
usage. This enables dead code elimination for unused global definitions.
This commit is contained in:
Michael Schimmel
2026-02-22 02:07:00 +01:00
parent 7c997ca841
commit e5841976c4
2 changed files with 65 additions and 31 deletions
+1
View File
@@ -1,2 +1,3 @@
/target /target
Delphi Delphi
*.snap.new
+64 -31
View File
@@ -82,14 +82,14 @@ impl Optimizer {
} }
if let Some(globals_rc) = &self.globals { if let Some(globals_rc) = &self.globals {
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)
if self.is_inlinable_value(val) { && self.is_inlinable_value(val)
return Node { {
identity: node.identity, return Node {
ty: val.static_type(), identity: node.identity,
kind: BoundKind::Constant(val.clone()), ty: val.static_type(),
}; kind: BoundKind::Constant(val.clone()),
} };
} }
} }
sub.used_globals.insert(idx); sub.used_globals.insert(idx);
@@ -207,13 +207,16 @@ impl Optimizer {
BoundKind::Block { exprs } => { BoundKind::Block { exprs } => {
let mut used_in_block = HashSet::new(); let mut used_in_block = HashSet::new();
let mut used_globals_in_block = HashSet::new();
if !exprs.is_empty() { if !exprs.is_empty() {
for e in &exprs { for e in &exprs {
self.collect_local_usage(e, &mut used_in_block); self.collect_usage(e, &mut used_in_block, &mut used_globals_in_block);
} }
if let Some(last) = exprs.last() { if let Some(last) = exprs.last() {
if let BoundKind::Get { addr: Address::Local(slot), .. } = &last.kind { match &last.kind {
used_in_block.insert(*slot); BoundKind::Get { addr: Address::Local(slot), .. } => { used_in_block.insert(*slot); }
BoundKind::Get { addr: Address::Global(idx), .. } => { used_globals_in_block.insert(*idx); }
_ => {}
} }
} }
} }
@@ -229,6 +232,9 @@ impl Optimizer {
BoundKind::DefLocal { slot, value, .. } => { BoundKind::DefLocal { slot, value, .. } => {
!used_in_block.contains(slot) && !sub.captured_slots.contains(slot) && self.is_pure(value) !used_in_block.contains(slot) && !sub.captured_slots.contains(slot) && self.is_pure(value)
} }
BoundKind::DefGlobal { global_index, value, .. } => {
!used_globals_in_block.contains(global_index) && self.is_pure(value)
}
BoundKind::Set { addr: Address::Local(slot), value, .. } => { BoundKind::Set { addr: Address::Local(slot), value, .. } => {
!used_in_block.contains(slot) && !sub.captured_slots.contains(slot) && self.is_pure(value) !used_in_block.contains(slot) && !sub.captured_slots.contains(slot) && self.is_pure(value)
} }
@@ -340,10 +346,9 @@ impl Optimizer {
if let BoundKind::Constant(val) = &value.kind { if let BoundKind::Constant(val) = &value.kind {
sub.add_global(global_index, val.clone()); sub.add_global(global_index, val.clone());
} }
if self.is_pure(&value) { if self.is_pure(&value)
if let Some(purity_rc) = &self.global_purity { && let Some(purity_rc) = &self.global_purity {
purity_rc.borrow_mut().insert(global_index, true); purity_rc.borrow_mut().insert(global_index, true);
}
} }
(BoundKind::DefGlobal { name, global_index, value }, node.ty) (BoundKind::DefGlobal { name, global_index, value }, node.ty)
}, },
@@ -515,42 +520,62 @@ impl Optimizer {
} }
} }
fn collect_local_usage(&self, node: &TypedNode, used: &mut HashSet<u32>) { fn collect_usage(&self, node: &TypedNode, used_locals: &mut HashSet<u32>, used_globals: &mut HashSet<u32>) {
match &node.kind { match &node.kind {
BoundKind::Get { addr: Address::Local(slot), .. } | BoundKind::Set { addr: Address::Local(slot), .. } => { BoundKind::Constant(v) => {
used.insert(*slot); if let Value::Object(obj) = v
&& let Some(closure) = obj.as_any().downcast_ref::<Closure>()
{
self.collect_usage(&closure.function_node, used_locals, used_globals);
}
} }
BoundKind::Lambda { upvalues, .. } => { BoundKind::Get { addr, .. } | BoundKind::Set { addr, .. } => {
match addr {
Address::Local(slot) => { used_locals.insert(*slot); }
Address::Global(idx) => { used_globals.insert(*idx); }
_ => {}
}
if let BoundKind::Set { value, .. } = &node.kind {
self.collect_usage(value, used_locals, used_globals);
}
}
BoundKind::Lambda { params, body, upvalues, .. } => {
self.collect_usage(params, used_locals, used_globals);
self.collect_usage(body, used_locals, used_globals);
for addr in upvalues { for addr in upvalues {
if let Address::Local(slot) = addr { used.insert(*slot); } match addr {
Address::Local(slot) => { used_locals.insert(*slot); }
Address::Global(idx) => { used_globals.insert(*idx); }
_ => {}
}
} }
} }
BoundKind::Block { exprs } => { BoundKind::Block { exprs } => {
for e in exprs { self.collect_local_usage(e, used); } for e in exprs { self.collect_usage(e, used_locals, used_globals); }
} }
BoundKind::If { cond, then_br, else_br } => { BoundKind::If { cond, then_br, else_br } => {
self.collect_local_usage(cond, used); self.collect_usage(cond, used_locals, used_globals);
self.collect_local_usage(then_br, used); self.collect_usage(then_br, used_locals, used_globals);
if let Some(e) = else_br { self.collect_local_usage(e, used); } if let Some(e) = else_br { self.collect_usage(e, used_locals, used_globals); }
} }
BoundKind::Call { callee, args } => { BoundKind::Call { callee, args } => {
self.collect_local_usage(callee, used); self.collect_usage(callee, used_locals, used_globals);
self.collect_local_usage(args, used); self.collect_usage(args, used_locals, used_globals);
} }
BoundKind::Tuple { elements } => { BoundKind::Tuple { elements } => {
for e in elements { self.collect_local_usage(e, used); } for e in elements { self.collect_usage(e, used_locals, used_globals); }
} }
BoundKind::Record { fields } => { BoundKind::Record { fields } => {
for (k, v) in fields { for (k, v) in fields {
self.collect_local_usage(k, used); self.collect_usage(k, used_locals, used_globals);
self.collect_local_usage(v, used); self.collect_usage(v, used_locals, used_globals);
} }
} }
BoundKind::DefLocal { value, .. } | BoundKind::Set { value, .. } => { BoundKind::DefLocal { value, .. } | BoundKind::DefGlobal { value, .. } => {
self.collect_local_usage(value, used); self.collect_usage(value, used_locals, used_globals);
} }
BoundKind::Expansion { bound_expanded, .. } => { BoundKind::Expansion { bound_expanded, .. } => {
self.collect_local_usage(bound_expanded, used); self.collect_usage(bound_expanded, used_locals, used_globals);
} }
_ => {} _ => {}
} }
@@ -727,4 +752,12 @@ mod tests {
let dump = get_optimized_dump(source, true); let dump = get_optimized_dump(source, true);
insta::assert_snapshot!(dump); insta::assert_snapshot!(dump);
} }
#[test]
fn test_opt_global_unused_dce() {
let source = "(do (def f (fn [x] x)) 42)";
let dump = get_optimized_dump(source, true);
assert!(!dump.contains("DefGlobal"), "Unused global helper 'f' should be optimized away. Dump: \n{}", dump);
assert!(dump.contains("Constant: 42"));
}
} }