rusty_cpp/analysis/
liveness.rs

1// Liveness Analysis for Conservative Borrow Clearing
2//
3// This module implements conservative liveness analysis to determine when
4// variables have their "last use" and can be safely considered dead.
5//
6// Conservative Rules:
7// - Variables used in loops → live until loop exit
8// - Variables passed to functions → live (might be stored)
9// - Variables that escape scope → live (returned, stored, etc.)
10// - When uncertain → assume live (safe default)
11
12use crate::ir::{IrStatement, IrFunction};
13use std::collections::{HashMap, HashSet};
14use crate::debug_println;
15
16#[derive(Debug, Clone, PartialEq)]
17pub enum UseType {
18    Read,           // Simple read: int x = r
19    Write,          // Assignment: r = ...
20    Escape,         // Returned, might escape scope
21    FunctionArg,    // Passed to function (might be stored)
22    InLoop,         // Used inside loop body
23    InCondition,    // Used in if/else condition
24}
25
26#[derive(Debug, Clone)]
27pub struct UseInfo {
28    pub statement_idx: usize,
29    pub use_type: UseType,
30}
31
32pub struct LivenessAnalyzer {
33    // For each variable, track all its uses
34    uses: HashMap<String, Vec<UseInfo>>,
35    // Track current statement index
36    current_idx: usize,
37    // Track if we're in a loop
38    in_loop_depth: usize,
39    // Track if we're in a conditional
40    in_conditional_depth: usize,
41}
42
43impl LivenessAnalyzer {
44    pub fn new() -> Self {
45        Self {
46            uses: HashMap::new(),
47            current_idx: 0,
48            in_loop_depth: 0,
49            in_conditional_depth: 0,
50        }
51    }
52
53    /// Analyze a function and return map of variable -> last_use_statement_index
54    /// Conservative: only returns last use if we're CERTAIN the variable won't be used again
55    pub fn analyze(&mut self, function: &IrFunction) -> HashMap<String, usize> {
56        // Get the first basic block (main function body)
57        // For now, we only analyze the first block for simplicity
58        let first_block = function.cfg.node_weights().next();
59
60        if let Some(block) = first_block {
61            debug_println!("LIVENESS: Analyzing function with {} statements", block.statements.len());
62
63            // First pass: collect all uses
64            self.collect_uses(&block.statements);
65        } else {
66            debug_println!("LIVENESS: No basic blocks found in function");
67        }
68
69        // Second pass: determine last uses (conservatively)
70        let last_uses = self.compute_last_uses();
71
72        debug_println!("LIVENESS: Found {} variables with determinable last use", last_uses.len());
73        for (var, idx) in &last_uses {
74            debug_println!("LIVENESS:   '{}' last used at statement {}", var, idx);
75        }
76
77        last_uses
78    }
79
80    fn collect_uses(&mut self, statements: &[IrStatement]) {
81        for (idx, stmt) in statements.iter().enumerate() {
82            self.current_idx = idx;
83            self.collect_uses_from_statement(stmt);
84        }
85    }
86
87    fn collect_uses_from_statement(&mut self, stmt: &IrStatement) {
88        match stmt {
89            IrStatement::Borrow { from, to, .. } => {
90                // 'from' is being borrowed (read)
91                self.record_use(from, UseType::Read);
92                // 'to' is the new reference (written to)
93                self.record_use(to, UseType::Write);
94            }
95
96            IrStatement::Move { from, to } => {
97                // 'from' is being moved (read + consumed)
98                self.record_use(from, UseType::Read);
99                // 'to' is receiving the value (written to)
100                self.record_use(to, UseType::Write);
101            }
102
103            IrStatement::Assign { lhs, rhs } => {
104                // RHS is being read
105                match rhs {
106                    crate::ir::IrExpression::Variable(var) => {
107                        self.record_use(var, UseType::Read);
108                    }
109                    crate::ir::IrExpression::Move(var) => {
110                        self.record_use(var, UseType::Read);
111                    }
112                    crate::ir::IrExpression::Borrow(var, _) => {
113                        self.record_use(var, UseType::Read);
114                    }
115                    crate::ir::IrExpression::New(_) => {
116                        // Allocation, no variable read
117                    }
118                    crate::ir::IrExpression::Literal(_) => {
119                        // Literal value, no variable read
120                    }
121                }
122                // LHS is being assigned to
123                self.record_use(lhs, UseType::Write);
124            }
125
126            IrStatement::Return { value } => {
127                // Returned variable escapes scope - VERY conservative
128                if let Some(var) = value {
129                    self.record_use(var, UseType::Escape);
130                }
131            }
132
133            IrStatement::CallExpr { args, result, .. } => {
134                // All arguments might be stored by the function (conservative)
135                for arg in args {
136                    self.record_use(arg, UseType::FunctionArg);
137                }
138                // Result is written to
139                if let Some(res) = result {
140                    self.record_use(res, UseType::Write);
141                }
142            }
143
144            IrStatement::UseVariable { var, .. } => {
145                // Variable is being used (read)
146                let use_type = if self.in_loop_depth > 0 {
147                    UseType::InLoop
148                } else if self.in_conditional_depth > 0 {
149                    UseType::InCondition
150                } else {
151                    UseType::Read
152                };
153                self.record_use(var, use_type);
154            }
155
156            IrStatement::UseField { object, .. } => {
157                // Object is being accessed
158                let use_type = if self.in_loop_depth > 0 {
159                    UseType::InLoop
160                } else {
161                    UseType::Read
162                };
163                self.record_use(object, use_type);
164            }
165
166            IrStatement::MoveField { object, to, .. } => {
167                // Object field is being moved
168                self.record_use(object, UseType::Read);
169                self.record_use(to, UseType::Write);
170            }
171
172            IrStatement::BorrowField { object, to, .. } => {
173                // Object field is being borrowed
174                self.record_use(object, UseType::Read);
175                self.record_use(to, UseType::Write);
176            }
177
178            IrStatement::EnterLoop => {
179                self.in_loop_depth += 1;
180            }
181
182            IrStatement::ExitLoop => {
183                if self.in_loop_depth > 0 {
184                    self.in_loop_depth -= 1;
185                }
186            }
187
188            IrStatement::If { then_branch, else_branch } => {
189                self.in_conditional_depth += 1;
190
191                // Analyze then branch
192                self.collect_uses(then_branch);
193
194                // Analyze else branch
195                if let Some(else_stmts) = else_branch {
196                    self.collect_uses(else_stmts);
197                }
198
199                self.in_conditional_depth -= 1;
200            }
201
202            IrStatement::PackExpansion { pack_name, operation } => {
203                // Phase 4: Pack expansion uses the pack variable
204                let use_type = if operation == "move" || operation == "forward" {
205                    UseType::Read  // Moving/forwarding reads the pack
206                } else {
207                    UseType::Read  // Regular use also reads
208                };
209                self.record_use(pack_name, use_type);
210            }
211
212            // These don't use variables
213            IrStatement::EnterScope |
214            IrStatement::ExitScope |
215            IrStatement::EnterUnsafe |
216            IrStatement::ExitUnsafe |
217            IrStatement::Drop(_) |
218            IrStatement::ImplicitDrop { .. } |
219            IrStatement::LambdaCapture { .. } |
220            IrStatement::VarDecl { .. } => {}
221        }
222    }
223
224    fn record_use(&mut self, var: &str, use_type: UseType) {
225        // Skip special variables (temporaries, field accesses)
226        if var.starts_with('_') || var.contains('.') {
227            return;
228        }
229
230        debug_println!("LIVENESS: Recording use of '{}' at index {} (type: {:?})",
231                      var, self.current_idx, use_type);
232
233        let use_info = UseInfo {
234            statement_idx: self.current_idx,
235            use_type,
236        };
237
238        self.uses.entry(var.to_string()).or_default().push(use_info);
239    }
240
241    fn compute_last_uses(&self) -> HashMap<String, usize> {
242        let mut last_uses = HashMap::new();
243
244        for (var, uses) in &self.uses {
245            // Conservative rules: DON'T compute last use if:
246
247            // 1. Variable escapes scope
248            if uses.iter().any(|u| matches!(u.use_type, UseType::Escape)) {
249                debug_println!("LIVENESS: '{}' escapes scope - not clearing", var);
250                continue;
251            }
252
253            // 2. Variable used in loop
254            if uses.iter().any(|u| matches!(u.use_type, UseType::InLoop)) {
255                debug_println!("LIVENESS: '{}' used in loop - not clearing", var);
256                continue;
257            }
258
259            // 3. Variable passed to function (might be stored)
260            if uses.iter().any(|u| matches!(u.use_type, UseType::FunctionArg)) {
261                debug_println!("LIVENESS: '{}' passed to function - not clearing", var);
262                continue;
263            }
264
265            // Conservative: find the LAST read/use
266            let last_read_or_use = uses.iter()
267                .filter(|u| matches!(u.use_type, UseType::Read | UseType::InCondition))
268                .map(|u| u.statement_idx)
269                .max();
270
271            if let Some(last_idx) = last_read_or_use {
272                debug_println!("LIVENESS: '{}' has last use (read) at statement {}", var, last_idx);
273                last_uses.insert(var.clone(), last_idx);
274            }
275            // Note: Variables that are only written (never read) don't get a last use
276            // Their borrows stay active until scope end (handled by drop order tracking)
277        }
278
279        last_uses
280    }
281}
282
283#[cfg(test)]
284mod tests {
285    use super::*;
286
287    #[test]
288    fn test_liveness_simple_sequence() {
289        // This is a unit test for the liveness analyzer
290        // Will be expanded once integrated with IR
291        let mut analyzer = LivenessAnalyzer::new();
292        assert_eq!(analyzer.uses.len(), 0);
293    }
294
295    #[test]
296    fn test_conservative_loop() {
297        // Variables used in loops should NOT have determinable last use
298        let mut analyzer = LivenessAnalyzer::new();
299
300        // Simulate: r used in loop
301        analyzer.in_loop_depth = 1;
302        analyzer.current_idx = 5;
303        analyzer.record_use("r", UseType::InLoop);
304
305        let last_uses = analyzer.compute_last_uses();
306
307        // Should NOT have last use for r (used in loop)
308        assert!(!last_uses.contains_key("r"));
309    }
310
311    #[test]
312    fn test_escaped_variable() {
313        // Variables that escape should NOT have determinable last use
314        let mut analyzer = LivenessAnalyzer::new();
315
316        analyzer.current_idx = 5;
317        analyzer.record_use("r", UseType::Escape);
318
319        let last_uses = analyzer.compute_last_uses();
320
321        // Should NOT have last use for r (escaped)
322        assert!(!last_uses.contains_key("r"));
323    }
324
325    #[test]
326    fn test_function_argument() {
327        // Variables passed to functions should NOT have determinable last use
328        let mut analyzer = LivenessAnalyzer::new();
329
330        analyzer.current_idx = 5;
331        analyzer.record_use("r", UseType::FunctionArg);
332
333        let last_uses = analyzer.compute_last_uses();
334
335        // Should NOT have last use for r (passed to function)
336        assert!(!last_uses.contains_key("r"));
337    }
338
339    #[test]
340    fn test_simple_read_sequence() {
341        // Simple read sequence should have determinable last use
342        let mut analyzer = LivenessAnalyzer::new();
343
344        analyzer.current_idx = 2;
345        analyzer.record_use("r", UseType::Write);  // Created
346        analyzer.current_idx = 3;
347        analyzer.record_use("r", UseType::Read);   // Used
348        analyzer.current_idx = 4;
349        analyzer.record_use("r", UseType::Read);   // Last use
350
351        let last_uses = analyzer.compute_last_uses();
352
353        // Should have last use at index 4
354        assert_eq!(last_uses.get("r"), Some(&4));
355    }
356}