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
Delphi
*.snap.new
+59 -26
View File
@@ -82,8 +82,9 @@ impl Optimizer {
}
if let Some(globals_rc) = &self.globals {
let globals = globals_rc.borrow();
if let Some(val) = globals.get(idx as usize) {
if self.is_inlinable_value(val) {
if let Some(val) = globals.get(idx as usize)
&& self.is_inlinable_value(val)
{
return Node {
identity: node.identity,
ty: val.static_type(),
@@ -91,7 +92,6 @@ impl Optimizer {
};
}
}
}
sub.used_globals.insert(idx);
Address::Global(idx)
}
@@ -207,13 +207,16 @@ impl Optimizer {
BoundKind::Block { exprs } => {
let mut used_in_block = HashSet::new();
let mut used_globals_in_block = HashSet::new();
if !exprs.is_empty() {
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 BoundKind::Get { addr: Address::Local(slot), .. } = &last.kind {
used_in_block.insert(*slot);
match &last.kind {
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, .. } => {
!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, .. } => {
!used_in_block.contains(slot) && !sub.captured_slots.contains(slot) && self.is_pure(value)
}
@@ -340,11 +346,10 @@ impl Optimizer {
if let BoundKind::Constant(val) = &value.kind {
sub.add_global(global_index, val.clone());
}
if self.is_pure(&value) {
if let Some(purity_rc) = &self.global_purity {
if self.is_pure(&value)
&& let Some(purity_rc) = &self.global_purity {
purity_rc.borrow_mut().insert(global_index, true);
}
}
(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 {
BoundKind::Get { addr: Address::Local(slot), .. } | BoundKind::Set { addr: Address::Local(slot), .. } => {
used.insert(*slot);
BoundKind::Constant(v) => {
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 {
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 } => {
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 } => {
self.collect_local_usage(cond, used);
self.collect_local_usage(then_br, used);
if let Some(e) = else_br { self.collect_local_usage(e, used); }
self.collect_usage(cond, used_locals, used_globals);
self.collect_usage(then_br, used_locals, used_globals);
if let Some(e) = else_br { self.collect_usage(e, used_locals, used_globals); }
}
BoundKind::Call { callee, args } => {
self.collect_local_usage(callee, used);
self.collect_local_usage(args, used);
self.collect_usage(callee, used_locals, used_globals);
self.collect_usage(args, used_locals, used_globals);
}
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 } => {
for (k, v) in fields {
self.collect_local_usage(k, used);
self.collect_local_usage(v, used);
self.collect_usage(k, used_locals, used_globals);
self.collect_usage(v, used_locals, used_globals);
}
}
BoundKind::DefLocal { value, .. } | BoundKind::Set { value, .. } => {
self.collect_local_usage(value, used);
BoundKind::DefLocal { value, .. } | BoundKind::DefGlobal { value, .. } => {
self.collect_usage(value, used_locals, used_globals);
}
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);
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"));
}
}