Skip to main content

aver/ir/
last_use.rs

1//! Slot-based last-use annotation pass.
2//!
3//! After the resolver transforms `Ident(name)` → `Resolved { slot, name, last_use: AnnotBool(false) }`,
4//! this pass walks each function body backwards and sets `last_use = true` on every
5//! `Resolved` node where the slot is not referenced again afterwards.
6//!
7//! This enables backends to move instead of copy:
8//! - VM emits `MOVE_LOCAL` (clears slot, sole ownership) instead of `LOAD_LOCAL`
9//! - Rust codegen skips `.clone()` for last-use locals
10//! - Collection builtins (Map.set, Vector.set) mutate in-place via `CALL_BUILTIN_OWNED`
11
12use std::collections::HashSet;
13
14use crate::ast::*;
15
16/// Annotate the entire program — walk all FnDefs after resolution.
17pub fn annotate_program_last_use(items: &mut [TopLevel]) {
18    for item in items.iter_mut() {
19        if let TopLevel::FnDef(fd) = item
20            && fd.resolution.is_some()
21        {
22            let mut body = fd.body.as_ref().clone();
23            annotate_body(&mut body);
24            fd.body = std::sync::Arc::new(body);
25        }
26    }
27}
28
29/// Annotate a function body.
30fn annotate_body(body: &mut FnBody) {
31    let stmts = body.stmts_mut();
32    annotate_stmts(stmts, &HashSet::new());
33}
34
35/// Annotate a block of statements with last-use info.
36///
37/// `live_after` — slots known to be read after this block.
38/// Walk backwards: each statement sees what comes after it.
39fn annotate_stmts(stmts: &mut [Stmt], parent_live: &HashSet<u16>) {
40    let n = stmts.len();
41    // Compute live-after for each statement (backwards).
42    let mut live_after_sets: Vec<HashSet<u16>> = vec![HashSet::new(); n];
43    let mut suffix_live = parent_live.clone();
44
45    for i in (0..n).rev() {
46        live_after_sets[i] = suffix_live.clone();
47        // Add slots referenced in this statement to suffix_live
48        let stmt_slots = collect_slots_stmt(&stmts[i]);
49        suffix_live.extend(stmt_slots);
50        // A binding defines a slot — remove it from suffix (born here, not from outer scope).
51        if let Stmt::Binding(name, _, _) = &stmts[i] {
52            // Find the slot this binding defines by looking at any Resolved in the body
53            // that has this name. Alternatively, we can check the resolution metadata.
54            // Simpler: scan the RHS won't have it, but the next use will.
55            // Actually, the binding name is resolved to a slot. We need to find it.
56            // The slot is assigned by the resolver, but we don't have the mapping here.
57            // Instead, we note: the binding introduces `name`. Any Resolved { slot, name: n, .. }
58            // where n == name in subsequent code is the same slot. We can find it by scanning
59            // forward — but that's complex. Instead, we track binding names and remove matching
60            // slots from suffix_live.
61            remove_slot_for_name(&mut suffix_live, name, stmts);
62        }
63    }
64
65    // Now walk forward, annotating each statement's expressions.
66    for i in 0..n {
67        let live = &live_after_sets[i];
68        match &mut stmts[i] {
69            Stmt::Binding(_, _, expr) => annotate_expr(&mut expr.node, live),
70            Stmt::Expr(expr) => annotate_expr(&mut expr.node, live),
71        }
72    }
73}
74
75/// Find the slot for a binding name by scanning statements for a matching Resolved.
76fn remove_slot_for_name(live: &mut HashSet<u16>, name: &str, stmts: &[Stmt]) {
77    // Scan all expressions in the block for a Resolved with this name
78    for stmt in stmts {
79        let expr = match stmt {
80            Stmt::Binding(_, _, e) => &e.node,
81            Stmt::Expr(e) => &e.node,
82        };
83        if let Some(slot) = find_slot_for_name(expr, name) {
84            live.remove(&slot);
85            return;
86        }
87    }
88}
89
90/// Find the slot number for a given variable name in an expression tree.
91fn find_slot_for_name(expr: &Expr, target_name: &str) -> Option<u16> {
92    match expr {
93        Expr::Resolved { slot, name, .. } if name == target_name => Some(*slot),
94        Expr::FnCall(fn_expr, args) => {
95            find_slot_for_name(&fn_expr.node, target_name).or_else(|| {
96                args.iter()
97                    .find_map(|a| find_slot_for_name(&a.node, target_name))
98            })
99        }
100        Expr::BinOp(_, left, right) => find_slot_for_name(&left.node, target_name)
101            .or_else(|| find_slot_for_name(&right.node, target_name)),
102        Expr::Neg(inner) => find_slot_for_name(&inner.node, target_name),
103        Expr::Match { subject, arms } => {
104            find_slot_for_name(&subject.node, target_name).or_else(|| {
105                arms.iter()
106                    .find_map(|arm| find_slot_for_name(&arm.body.node, target_name))
107            })
108        }
109        Expr::Attr(obj, _) => find_slot_for_name(&obj.node, target_name),
110        Expr::ErrorProp(inner) => find_slot_for_name(&inner.node, target_name),
111        Expr::Constructor(_, Some(inner)) => find_slot_for_name(&inner.node, target_name),
112        Expr::InterpolatedStr(parts) => parts.iter().find_map(|p| match p {
113            StrPart::Parsed(e) => find_slot_for_name(&e.node, target_name),
114            _ => None,
115        }),
116        Expr::List(items) | Expr::Tuple(items) | Expr::IndependentProduct(items, _) => items
117            .iter()
118            .find_map(|e| find_slot_for_name(&e.node, target_name)),
119        Expr::TailCall(boxed) => boxed
120            .args
121            .iter()
122            .find_map(|a| find_slot_for_name(&a.node, target_name)),
123        Expr::MapLiteral(entries) => entries.iter().find_map(|(k, v)| {
124            find_slot_for_name(&k.node, target_name)
125                .or_else(|| find_slot_for_name(&v.node, target_name))
126        }),
127        Expr::RecordCreate { fields, .. } => fields
128            .iter()
129            .find_map(|(_, e)| find_slot_for_name(&e.node, target_name)),
130        Expr::RecordUpdate { base, updates, .. } => find_slot_for_name(&base.node, target_name)
131            .or_else(|| {
132                updates
133                    .iter()
134                    .find_map(|(_, e)| find_slot_for_name(&e.node, target_name))
135            }),
136        _ => None,
137    }
138}
139
140/// Collect all slots referenced in an expression.
141fn collect_slots(expr: &Expr) -> HashSet<u16> {
142    let mut slots = HashSet::new();
143    collect_slots_inner(expr, &mut slots);
144    slots
145}
146
147fn collect_slots_inner(expr: &Expr, slots: &mut HashSet<u16>) {
148    match expr {
149        Expr::Resolved { slot, .. } => {
150            slots.insert(*slot);
151        }
152        Expr::Ident(_) | Expr::Literal(_) => {}
153        Expr::Attr(obj, _) => collect_slots_inner(&obj.node, slots),
154        Expr::FnCall(fn_expr, args) => {
155            collect_slots_inner(&fn_expr.node, slots);
156            for a in args {
157                collect_slots_inner(&a.node, slots);
158            }
159        }
160        Expr::BinOp(_, left, right) => {
161            collect_slots_inner(&left.node, slots);
162            collect_slots_inner(&right.node, slots);
163        }
164        Expr::Neg(inner) => collect_slots_inner(&inner.node, slots),
165        Expr::Match { subject, arms } => {
166            collect_slots_inner(&subject.node, slots);
167            for arm in arms {
168                collect_slots_inner(&arm.body.node, slots);
169            }
170        }
171        Expr::Constructor(_, Some(inner)) => collect_slots_inner(&inner.node, slots),
172        Expr::Constructor(_, None) => {}
173        Expr::ErrorProp(inner) => collect_slots_inner(&inner.node, slots),
174        Expr::InterpolatedStr(parts) => {
175            for part in parts {
176                if let StrPart::Parsed(expr) = part {
177                    collect_slots_inner(&expr.node, slots);
178                }
179            }
180        }
181        Expr::List(elements) => {
182            for e in elements {
183                collect_slots_inner(&e.node, slots);
184            }
185        }
186        Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
187            for e in items {
188                collect_slots_inner(&e.node, slots);
189            }
190        }
191        Expr::MapLiteral(entries) => {
192            for (k, v) in entries {
193                collect_slots_inner(&k.node, slots);
194                collect_slots_inner(&v.node, slots);
195            }
196        }
197        Expr::RecordCreate { fields, .. } => {
198            for (_, expr) in fields {
199                collect_slots_inner(&expr.node, slots);
200            }
201        }
202        Expr::RecordUpdate { base, updates, .. } => {
203            collect_slots_inner(&base.node, slots);
204            for (_, expr) in updates {
205                collect_slots_inner(&expr.node, slots);
206            }
207        }
208        Expr::TailCall(boxed) => {
209            for a in &boxed.args {
210                collect_slots_inner(&a.node, slots);
211            }
212        }
213    }
214}
215
216/// Collect slots in a statement.
217fn collect_slots_stmt(stmt: &Stmt) -> HashSet<u16> {
218    match stmt {
219        Stmt::Binding(_, _, expr) => collect_slots(&expr.node),
220        Stmt::Expr(expr) => collect_slots(&expr.node),
221    }
222}
223
224/// Annotate an expression tree: set `last_use = true` on Resolved nodes
225/// whose slot is not in `live_after`.
226///
227/// For compound expressions (FnCall args, BinOp operands), we compute
228/// intra-expression liveness: later sub-expressions make earlier ones
229/// non-last-use if they share a slot.
230fn annotate_expr(expr: &mut Expr, live_after: &HashSet<u16>) {
231    match expr {
232        Expr::Resolved { slot, last_use, .. } => {
233            *last_use = AnnotBool(!live_after.contains(slot));
234        }
235        Expr::Ident(_) | Expr::Literal(_) => {}
236        Expr::Attr(obj, _) => annotate_expr(&mut obj.node, live_after),
237        Expr::FnCall(fn_expr, args) => {
238            // Intra-expression liveness: later args make earlier args non-last-use.
239            // Walk args right-to-left, accumulating live slots.
240            let mut cumulative_live = live_after.clone();
241            for arg in args.iter_mut().rev() {
242                annotate_expr(&mut arg.node, &cumulative_live);
243                let arg_slots = collect_slots(&arg.node);
244                cumulative_live.extend(arg_slots);
245            }
246            annotate_expr(&mut fn_expr.node, &cumulative_live);
247        }
248        Expr::BinOp(_, left, right) => {
249            // Right operand evaluated after left — right's slots affect left's liveness.
250            let mut left_live = live_after.clone();
251            left_live.extend(collect_slots(&right.node));
252            annotate_expr(&mut left.node, &left_live);
253            annotate_expr(&mut right.node, live_after);
254        }
255        Expr::Neg(inner) => annotate_expr(&mut inner.node, live_after),
256        Expr::Match { subject, arms } => {
257            // Subject is evaluated before any arm — all arm slots are "after" subject.
258            let mut subject_live = live_after.clone();
259            for arm in arms.iter() {
260                subject_live.extend(collect_slots(&arm.body.node));
261            }
262            annotate_expr(&mut subject.node, &subject_live);
263
264            // Each arm is independent (only one executes at runtime).
265            for arm in arms.iter_mut() {
266                annotate_expr(&mut arm.body.node, live_after);
267            }
268        }
269        Expr::Constructor(_, Some(inner)) => annotate_expr(&mut inner.node, live_after),
270        Expr::Constructor(_, None) => {}
271        Expr::ErrorProp(inner) => annotate_expr(&mut inner.node, live_after),
272        Expr::InterpolatedStr(parts) => {
273            // Parts evaluated left-to-right.
274            let mut cumulative_live = live_after.clone();
275            for part in parts.iter_mut().rev() {
276                if let StrPart::Parsed(expr) = part {
277                    annotate_expr(&mut expr.node, &cumulative_live);
278                    cumulative_live.extend(collect_slots(&expr.node));
279                }
280            }
281        }
282        Expr::List(elements) => {
283            let mut cumulative_live = live_after.clone();
284            for e in elements.iter_mut().rev() {
285                annotate_expr(&mut e.node, &cumulative_live);
286                cumulative_live.extend(collect_slots(&e.node));
287            }
288        }
289        Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
290            let mut cumulative_live = live_after.clone();
291            for e in items.iter_mut().rev() {
292                annotate_expr(&mut e.node, &cumulative_live);
293                cumulative_live.extend(collect_slots(&e.node));
294            }
295        }
296        Expr::MapLiteral(entries) => {
297            let mut cumulative_live = live_after.clone();
298            for (k, v) in entries.iter_mut().rev() {
299                annotate_expr(&mut v.node, &cumulative_live);
300                cumulative_live.extend(collect_slots(&v.node));
301                annotate_expr(&mut k.node, &cumulative_live);
302                cumulative_live.extend(collect_slots(&k.node));
303            }
304        }
305        Expr::RecordCreate { fields, .. } => {
306            let mut cumulative_live = live_after.clone();
307            for (_, e) in fields.iter_mut().rev() {
308                annotate_expr(&mut e.node, &cumulative_live);
309                cumulative_live.extend(collect_slots(&e.node));
310            }
311        }
312        Expr::RecordUpdate { base, updates, .. } => {
313            let mut cumulative_live = live_after.clone();
314            for (_, e) in updates.iter_mut().rev() {
315                annotate_expr(&mut e.node, &cumulative_live);
316                cumulative_live.extend(collect_slots(&e.node));
317            }
318            annotate_expr(&mut base.node, &cumulative_live);
319        }
320        Expr::TailCall(boxed) => {
321            // Tail call args evaluated left-to-right, nothing after.
322            let mut cumulative_live = live_after.clone();
323            for a in boxed.args.iter_mut().rev() {
324                annotate_expr(&mut a.node, &cumulative_live);
325                cumulative_live.extend(collect_slots(&a.node));
326            }
327        }
328    }
329}