Skip to main content

logicaffeine_compile/analysis/
liveness.rs

1use std::collections::{HashMap, HashSet};
2
3use logicaffeine_base::Symbol;
4use logicaffeine_language::ast::{Expr, Stmt};
5use logicaffeine_language::ast::stmt::{ClosureBody, Pattern};
6
7/// Liveness analysis result for a whole program.
8///
9/// For each user-defined function, stores a per-statement `live_after` set:
10/// `live_after[i]` is the set of variables live immediately *after* top-level
11/// statement `i` in that function's body.
12///
13/// Used by codegen to decide when an argument can be *moved* into a call
14/// instead of cloned: if a variable is not live after the call statement,
15/// the old value is never read again and ownership can be transferred.
16pub struct LivenessResult {
17    functions: HashMap<Symbol, FunctionLiveness>,
18}
19
20/// Per-function liveness table.
21pub struct FunctionLiveness {
22    /// `live_after[i]` = variables live after top-level statement `i`.
23    pub live_after: Vec<HashSet<Symbol>>,
24}
25
26impl LivenessResult {
27    /// Compute liveness for every `FunctionDef` in `stmts`.
28    ///
29    /// Algorithm: backward dataflow over the top-level statement list of each
30    /// function body.  `Return` is treated as a terminator — variables used in
31    /// subsequent (dead-code) statements do not affect liveness before the
32    /// `Return`.
33    pub fn analyze(stmts: &[Stmt<'_>]) -> Self {
34        let mut functions = HashMap::new();
35        for stmt in stmts {
36            if let Stmt::FunctionDef { name, body, .. } = stmt {
37                functions.insert(*name, analyze_function(body));
38            }
39        }
40        Self { functions }
41    }
42
43    /// Returns `true` if `var` is live immediately after top-level statement
44    /// `stmt_idx` in function `fn_sym`.
45    ///
46    /// Returns `false` for unknown functions, out-of-bounds indices, or when
47    /// the variable is definitely dead.
48    pub fn is_live_after(&self, fn_sym: Symbol, stmt_idx: usize, var: Symbol) -> bool {
49        self.functions
50            .get(&fn_sym)
51            .and_then(|fl| fl.live_after.get(stmt_idx))
52            .map(|s| s.contains(&var))
53            .unwrap_or(false)
54    }
55
56    /// Returns the live-after set for statement `stmt_idx` in `fn_sym`.
57    ///
58    /// Returns a reference to an empty set when the function or index is
59    /// unknown.
60    pub fn live_after(&self, fn_sym: Symbol, stmt_idx: usize) -> &HashSet<Symbol> {
61        static EMPTY: std::sync::OnceLock<HashSet<Symbol>> = std::sync::OnceLock::new();
62        self.functions
63            .get(&fn_sym)
64            .and_then(|fl| fl.live_after.get(stmt_idx))
65            .unwrap_or_else(|| EMPTY.get_or_init(HashSet::new))
66    }
67}
68
69// =============================================================================
70// Per-function analysis
71// =============================================================================
72
73fn analyze_function(body: &[Stmt<'_>]) -> FunctionLiveness {
74    let n = body.len();
75    let mut live_after = vec![HashSet::<Symbol>::new(); n];
76    let mut current: HashSet<Symbol> = HashSet::new();
77
78    for i in (0..n).rev() {
79        if is_terminator(&body[i]) {
80            // Return (or similar terminator): nothing is live after it,
81            // and dead code that follows does not influence pre-Return liveness.
82            live_after[i] = HashSet::new();
83            current = gen_stmt(&body[i]);
84        } else {
85            live_after[i] = current.clone();
86            current = live_before_stmt(&body[i], &current);
87        }
88    }
89
90    FunctionLiveness { live_after }
91}
92
93fn is_terminator(stmt: &Stmt<'_>) -> bool {
94    matches!(stmt, Stmt::Return { .. })
95}
96
97// =============================================================================
98// Live-before computation
99// =============================================================================
100
101/// Variables generated (used) by a statement, ignoring control flow.
102/// Used only for terminators (Return).
103fn gen_stmt(stmt: &Stmt<'_>) -> HashSet<Symbol> {
104    let mut out = HashSet::new();
105    match stmt {
106        Stmt::Return { value: Some(v) } => gen_expr(v, &mut out),
107        _ => {}
108    }
109    out
110}
111
112/// Compute the live-before set for a single statement given what is live after it.
113fn live_before_stmt(stmt: &Stmt<'_>, live_out: &HashSet<Symbol>) -> HashSet<Symbol> {
114    match stmt {
115        Stmt::Return { .. } => gen_stmt(stmt),
116
117        Stmt::Let { var, value, .. } => {
118            let mut result = live_out.clone();
119            result.remove(var);
120            gen_expr(value, &mut result);
121            result
122        }
123
124        Stmt::Set { target, value } => {
125            let mut result = live_out.clone();
126            result.remove(target);
127            gen_expr(value, &mut result);
128            result
129        }
130
131        Stmt::Call { args, .. } => {
132            let mut result = live_out.clone();
133            for a in args.iter() {
134                gen_expr(a, &mut result);
135            }
136            result
137        }
138
139        Stmt::Push { value, collection } => {
140            let mut result = live_out.clone();
141            gen_expr(value, &mut result);
142            gen_expr(collection, &mut result);
143            result
144        }
145
146        Stmt::Pop { collection, into } => {
147            let mut result = live_out.clone();
148            if let Some(v) = into {
149                result.remove(v);
150            }
151            gen_expr(collection, &mut result);
152            result
153        }
154
155        Stmt::Add { value, collection } | Stmt::Remove { value, collection } => {
156            let mut result = live_out.clone();
157            gen_expr(value, &mut result);
158            gen_expr(collection, &mut result);
159            result
160        }
161
162        Stmt::SetIndex { collection, index, value } => {
163            let mut result = live_out.clone();
164            gen_expr(collection, &mut result);
165            gen_expr(index, &mut result);
166            gen_expr(value, &mut result);
167            result
168        }
169
170        Stmt::SetField { object, value, .. } => {
171            let mut result = live_out.clone();
172            gen_expr(object, &mut result);
173            gen_expr(value, &mut result);
174            result
175        }
176
177        Stmt::If { cond, then_block, else_block } => {
178            let then_lb = live_before_block(then_block, live_out);
179            let else_lb = match else_block {
180                Some(b) => live_before_block(b, live_out),
181                None => live_out.clone(),
182            };
183            let mut result = HashSet::new();
184            gen_expr(cond, &mut result);
185            result.extend(then_lb);
186            result.extend(else_lb);
187            result
188        }
189
190        Stmt::While { cond, body, .. } => {
191            // Fixed-point: loop_live = live_out ∪ gen(cond) ∪ body_live_before(loop_live)
192            let mut loop_live: HashSet<Symbol> = live_out.clone();
193            gen_expr(cond, &mut loop_live);
194            loop {
195                let body_before = live_before_block(body, &loop_live);
196                let mut new_live = live_out.clone();
197                gen_expr(cond, &mut new_live);
198                new_live.extend(body_before);
199                if new_live == loop_live {
200                    break;
201                }
202                loop_live = new_live;
203            }
204            loop_live
205        }
206
207        Stmt::Repeat { pattern, iterable, body } => {
208            let body_before = live_before_block(body, live_out);
209            let pattern_syms: HashSet<Symbol> = match pattern {
210                Pattern::Identifier(s) => [*s].into_iter().collect(),
211                Pattern::Tuple(syms) => syms.iter().copied().collect(),
212            };
213            let mut result = live_out.clone();
214            gen_expr(iterable, &mut result);
215            for sym in body_before {
216                if !pattern_syms.contains(&sym) {
217                    result.insert(sym);
218                }
219            }
220            result
221        }
222
223        Stmt::Inspect { target, arms, .. } => {
224            let mut result = HashSet::new();
225            for arm in arms.iter() {
226                let arm_lb = live_before_block(arm.body, live_out);
227                result.extend(arm_lb);
228            }
229            gen_expr(target, &mut result);
230            result
231        }
232
233        Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => {
234            live_before_block(tasks, live_out)
235        }
236
237        Stmt::Zone { body, .. } => live_before_block(body, live_out),
238
239        _ => live_out.clone(),
240    }
241}
242
243/// Compute live-before for a block of statements.
244fn live_before_block(stmts: &[Stmt<'_>], live_out: &HashSet<Symbol>) -> HashSet<Symbol> {
245    let mut current = live_out.clone();
246    for stmt in stmts.iter().rev() {
247        if is_terminator(stmt) {
248            current = gen_stmt(stmt);
249        } else {
250            current = live_before_stmt(stmt, &current);
251        }
252    }
253    current
254}
255
256// =============================================================================
257// Gen set for expressions
258// =============================================================================
259
260/// Collects all variable identifiers referenced (used) in an expression.
261///
262/// Function names in `Expr::Call { function, .. }` are NOT collected since
263/// they are global names, not local variables.
264fn gen_expr(expr: &Expr<'_>, out: &mut HashSet<Symbol>) {
265    match expr {
266        Expr::Identifier(sym) => {
267            out.insert(*sym);
268        }
269        Expr::BinaryOp { left, right, .. } => {
270            gen_expr(left, out);
271            gen_expr(right, out);
272        }
273        Expr::Call { args, .. } => {
274            for a in args.iter() {
275                gen_expr(a, out);
276            }
277        }
278        Expr::CallExpr { callee, args } => {
279            gen_expr(callee, out);
280            for a in args.iter() {
281                gen_expr(a, out);
282            }
283        }
284        Expr::Length { collection } => gen_expr(collection, out),
285        Expr::Index { collection, index } => {
286            gen_expr(collection, out);
287            gen_expr(index, out);
288        }
289        Expr::Slice { collection, start, end } => {
290            gen_expr(collection, out);
291            gen_expr(start, out);
292            gen_expr(end, out);
293        }
294        Expr::Contains { collection, value } => {
295            gen_expr(collection, out);
296            gen_expr(value, out);
297        }
298        Expr::Union { left, right } | Expr::Intersection { left, right } => {
299            gen_expr(left, out);
300            gen_expr(right, out);
301        }
302        Expr::ManifestOf { zone } => gen_expr(zone, out),
303        Expr::ChunkAt { index, zone } => {
304            gen_expr(index, out);
305            gen_expr(zone, out);
306        }
307        Expr::FieldAccess { object, .. } => gen_expr(object, out),
308        Expr::List(items) | Expr::Tuple(items) => {
309            for i in items.iter() {
310                gen_expr(i, out);
311            }
312        }
313        Expr::Range { start, end } => {
314            gen_expr(start, out);
315            gen_expr(end, out);
316        }
317        Expr::Copy { expr } | Expr::Give { value: expr } | Expr::Not { operand: expr } => gen_expr(expr, out),
318        Expr::OptionSome { value } => gen_expr(value, out),
319        Expr::WithCapacity { value, capacity } => {
320            gen_expr(value, out);
321            gen_expr(capacity, out);
322        }
323        Expr::New { init_fields, .. } => {
324            for (_, v) in init_fields.iter() {
325                gen_expr(v, out);
326            }
327        }
328        Expr::NewVariant { fields, .. } => {
329            for (_, v) in fields.iter() {
330                gen_expr(v, out);
331            }
332        }
333        Expr::Closure { body, .. } => match body {
334            ClosureBody::Expression(e) => gen_expr(e, out),
335            ClosureBody::Block(stmts) => {
336                for s in stmts.iter() {
337                    gen_stmt_exprs(s, out);
338                }
339            }
340        },
341        Expr::InterpolatedString(parts) => {
342            for part in parts.iter() {
343                if let logicaffeine_language::ast::stmt::StringPart::Expr { value, .. } = part {
344                    gen_expr(value, out);
345                }
346            }
347        }
348        Expr::Literal(_) | Expr::OptionNone | Expr::Escape { .. } => {}
349    }
350}
351
352/// Collects all variable identifiers referenced in a statement's expressions.
353/// Used for closure bodies to conservatively compute the gen set.
354fn gen_stmt_exprs(stmt: &Stmt<'_>, out: &mut HashSet<Symbol>) {
355    match stmt {
356        Stmt::Let { value, .. } => gen_expr(value, out),
357        Stmt::Set { value, .. } => gen_expr(value, out),
358        Stmt::Return { value: Some(v) } => gen_expr(v, out),
359        Stmt::Call { args, .. } => {
360            for a in args.iter() { gen_expr(a, out); }
361        }
362        Stmt::Push { value, collection } => {
363            gen_expr(value, out);
364            gen_expr(collection, out);
365        }
366        Stmt::If { cond, then_block, else_block } => {
367            gen_expr(cond, out);
368            for s in then_block.iter() { gen_stmt_exprs(s, out); }
369            if let Some(b) = else_block {
370                for s in b.iter() { gen_stmt_exprs(s, out); }
371            }
372        }
373        Stmt::While { cond, body, .. } => {
374            gen_expr(cond, out);
375            for s in body.iter() { gen_stmt_exprs(s, out); }
376        }
377        _ => {}
378    }
379}