logicaffeine_compile/analysis/
callgraph.rs1use std::collections::{HashMap, HashSet};
2
3use logicaffeine_base::{Interner, Symbol};
4use logicaffeine_language::ast::{Expr, Stmt};
5use logicaffeine_language::ast::stmt::ClosureBody;
6
7pub struct CallGraph {
13 pub edges: HashMap<Symbol, HashSet<Symbol>>,
15 pub native_fns: HashSet<Symbol>,
17 pub sccs: Vec<Vec<Symbol>>,
19}
20
21impl CallGraph {
22 pub fn build(stmts: &[Stmt<'_>], _interner: &Interner) -> Self {
27 let mut edges: HashMap<Symbol, HashSet<Symbol>> = HashMap::new();
28 let mut native_fns: HashSet<Symbol> = HashSet::new();
29
30 for stmt in stmts {
31 if let Stmt::FunctionDef { name, body, is_native, .. } = stmt {
32 edges.entry(*name).or_default();
33 if *is_native {
34 native_fns.insert(*name);
35 } else {
36 let callees = edges.entry(*name).or_default();
37 collect_calls_from_stmts(body, callees);
38 }
39 }
40 }
41
42 let sccs = compute_sccs(&edges);
43
44 Self { edges, native_fns, sccs }
45 }
46
47 pub fn reachable_from(&self, fn_sym: Symbol) -> HashSet<Symbol> {
51 let mut visited = HashSet::new();
52 let mut stack = Vec::new();
53
54 if let Some(callees) = self.edges.get(&fn_sym) {
55 for &c in callees {
56 if c != fn_sym {
57 stack.push(c);
58 }
59 }
60 }
61
62 while let Some(f) = stack.pop() {
63 if visited.insert(f) {
64 if let Some(callees) = self.edges.get(&f) {
65 for &c in callees {
66 if !visited.contains(&c) {
67 stack.push(c);
68 }
69 }
70 }
71 }
72 }
73
74 visited
75 }
76
77 pub fn is_recursive(&self, fn_sym: Symbol) -> bool {
80 if self.edges.get(&fn_sym).map(|s| s.contains(&fn_sym)).unwrap_or(false) {
82 return true;
83 }
84 for scc in &self.sccs {
86 if scc.len() > 1 && scc.contains(&fn_sym) {
87 return true;
88 }
89 }
90 false
91 }
92}
93
94pub(crate) fn calls_in_stmts(stmts: &[Stmt<'_>]) -> HashSet<Symbol> {
103 let mut calls = HashSet::new();
104 collect_calls_from_stmts(stmts, &mut calls);
105 calls
106}
107
108fn collect_calls_from_stmts(stmts: &[Stmt<'_>], calls: &mut HashSet<Symbol>) {
109 for stmt in stmts {
110 collect_calls_from_stmt(stmt, calls);
111 }
112}
113
114fn collect_calls_from_stmt(stmt: &Stmt<'_>, calls: &mut HashSet<Symbol>) {
115 match stmt {
116 Stmt::Call { function, args } => {
117 calls.insert(*function);
118 for arg in args {
119 collect_calls_from_expr(arg, calls);
120 }
121 }
122 Stmt::Let { value, .. } => collect_calls_from_expr(value, calls),
123 Stmt::Set { value, .. } => collect_calls_from_expr(value, calls),
124 Stmt::Return { value: Some(v) } => collect_calls_from_expr(v, calls),
125 Stmt::If { cond, then_block, else_block } => {
126 collect_calls_from_expr(cond, calls);
127 collect_calls_from_stmts(then_block, calls);
128 if let Some(else_b) = else_block {
129 collect_calls_from_stmts(else_b, calls);
130 }
131 }
132 Stmt::While { cond, body, .. } => {
133 collect_calls_from_expr(cond, calls);
134 collect_calls_from_stmts(body, calls);
135 }
136 Stmt::Repeat { iterable, body, .. } => {
137 collect_calls_from_expr(iterable, calls);
138 collect_calls_from_stmts(body, calls);
139 }
140 Stmt::Push { value, collection } => {
141 collect_calls_from_expr(value, calls);
142 collect_calls_from_expr(collection, calls);
143 }
144 Stmt::Pop { collection, .. } => collect_calls_from_expr(collection, calls),
145 Stmt::Add { value, collection } => {
146 collect_calls_from_expr(value, calls);
147 collect_calls_from_expr(collection, calls);
148 }
149 Stmt::Remove { value, collection } => {
150 collect_calls_from_expr(value, calls);
151 collect_calls_from_expr(collection, calls);
152 }
153 Stmt::SetIndex { collection, index, value } => {
154 collect_calls_from_expr(collection, calls);
155 collect_calls_from_expr(index, calls);
156 collect_calls_from_expr(value, calls);
157 }
158 Stmt::SetField { object, value, .. } => {
159 collect_calls_from_expr(object, calls);
160 collect_calls_from_expr(value, calls);
161 }
162 Stmt::Inspect { target, arms, .. } => {
163 collect_calls_from_expr(target, calls);
164 for arm in arms {
165 collect_calls_from_stmts(arm.body, calls);
166 }
167 }
168 Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => {
169 collect_calls_from_stmts(tasks, calls);
170 }
171 Stmt::Zone { body, .. } => collect_calls_from_stmts(body, calls),
172 _ => {}
173 }
174}
175
176fn collect_calls_from_expr(expr: &Expr<'_>, calls: &mut HashSet<Symbol>) {
177 match expr {
178 Expr::Call { function, args } => {
179 calls.insert(*function);
180 for arg in args {
181 collect_calls_from_expr(arg, calls);
182 }
183 }
184 Expr::Closure { body, .. } => match body {
185 ClosureBody::Expression(e) => collect_calls_from_expr(e, calls),
186 ClosureBody::Block(stmts) => collect_calls_from_stmts(stmts, calls),
187 },
188 Expr::BinaryOp { left, right, .. } => {
189 collect_calls_from_expr(left, calls);
190 collect_calls_from_expr(right, calls);
191 }
192 Expr::Index { collection, index } => {
193 collect_calls_from_expr(collection, calls);
194 collect_calls_from_expr(index, calls);
195 }
196 Expr::Slice { collection, start, end } => {
197 collect_calls_from_expr(collection, calls);
198 collect_calls_from_expr(start, calls);
199 collect_calls_from_expr(end, calls);
200 }
201 Expr::Length { collection } => collect_calls_from_expr(collection, calls),
202 Expr::Contains { collection, value } => {
203 collect_calls_from_expr(collection, calls);
204 collect_calls_from_expr(value, calls);
205 }
206 Expr::Union { left, right } | Expr::Intersection { left, right } => {
207 collect_calls_from_expr(left, calls);
208 collect_calls_from_expr(right, calls);
209 }
210 Expr::FieldAccess { object, .. } => collect_calls_from_expr(object, calls),
211 Expr::List(items) | Expr::Tuple(items) => {
212 for item in items {
213 collect_calls_from_expr(item, calls);
214 }
215 }
216 Expr::Range { start, end } => {
217 collect_calls_from_expr(start, calls);
218 collect_calls_from_expr(end, calls);
219 }
220 Expr::Copy { expr } | Expr::Give { value: expr } => {
221 collect_calls_from_expr(expr, calls);
222 }
223 Expr::OptionSome { value } => collect_calls_from_expr(value, calls),
224 Expr::WithCapacity { value, capacity } => {
225 collect_calls_from_expr(value, calls);
226 collect_calls_from_expr(capacity, calls);
227 }
228 Expr::CallExpr { callee, args } => {
229 collect_calls_from_expr(callee, calls);
230 for arg in args {
231 collect_calls_from_expr(arg, calls);
232 }
233 }
234 _ => {}
235 }
236}
237
238fn compute_sccs(edges: &HashMap<Symbol, HashSet<Symbol>>) -> Vec<Vec<Symbol>> {
243 let nodes: Vec<Symbol> = edges.keys().copied().collect();
244
245 let mut visited: HashSet<Symbol> = HashSet::new();
247 let mut finish_order: Vec<Symbol> = Vec::new();
248
249 for &v in &nodes {
250 if !visited.contains(&v) {
251 dfs_finish(v, edges, &mut visited, &mut finish_order);
252 }
253 }
254
255 let mut rev_edges: HashMap<Symbol, HashSet<Symbol>> = HashMap::new();
257 for (&src, callees) in edges {
258 for &dst in callees {
259 rev_edges.entry(dst).or_default().insert(src);
260 }
261 }
262
263 let mut visited2: HashSet<Symbol> = HashSet::new();
265 let mut sccs: Vec<Vec<Symbol>> = Vec::new();
266
267 for &v in finish_order.iter().rev() {
268 if !visited2.contains(&v) {
269 let mut scc = Vec::new();
270 dfs_collect(v, &rev_edges, &mut visited2, &mut scc);
271 sccs.push(scc);
272 }
273 }
274
275 sccs
276}
277
278fn dfs_finish(
279 v: Symbol,
280 edges: &HashMap<Symbol, HashSet<Symbol>>,
281 visited: &mut HashSet<Symbol>,
282 finish_order: &mut Vec<Symbol>,
283) {
284 if !visited.insert(v) {
285 return;
286 }
287 if let Some(callees) = edges.get(&v) {
288 for &callee in callees {
289 dfs_finish(callee, edges, visited, finish_order);
290 }
291 }
292 finish_order.push(v);
293}
294
295fn dfs_collect(
296 v: Symbol,
297 edges: &HashMap<Symbol, HashSet<Symbol>>,
298 visited: &mut HashSet<Symbol>,
299 scc: &mut Vec<Symbol>,
300) {
301 if !visited.insert(v) {
302 return;
303 }
304 scc.push(v);
305 if let Some(callees) = edges.get(&v) {
306 for &callee in callees {
307 dfs_collect(callee, edges, visited, scc);
308 }
309 }
310}