1use llvm_ir::{Context, FunctionId, InstrKind, Module, ValueRef};
4use std::collections::{BTreeSet, HashSet};
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
8pub enum CallEdgeKind {
9 Direct,
11 Indirect,
13 External,
15}
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
19pub struct CallEdge {
20 pub to: Option<FunctionId>,
22 pub kind: CallEdgeKind,
24}
25
26pub struct CallGraph {
28 direct_callees: Vec<Vec<FunctionId>>,
30 direct_callers: Vec<Vec<FunctionId>>,
32 edges: Vec<Vec<CallEdge>>,
34}
35
36impl CallGraph {
37 pub fn build(_ctx: &Context, module: &Module) -> Self {
39 let n = module.functions.len();
40 let mut direct_callees_sets: Vec<BTreeSet<FunctionId>> = vec![BTreeSet::new(); n];
41 let mut direct_callers_sets: Vec<BTreeSet<FunctionId>> = vec![BTreeSet::new(); n];
42 let mut edges: Vec<Vec<CallEdge>> = vec![Vec::new(); n];
43
44 for (src_idx, func) in module.functions.iter().enumerate() {
45 for instr in &func.instructions {
46 let InstrKind::Call { callee, .. } = &instr.kind else {
47 continue;
48 };
49 let edge = match callee {
50 ValueRef::Global(gid) => {
51 let fid = FunctionId(gid.0);
52 if (fid.0 as usize) < n {
53 let kind = if module.functions[fid.0 as usize].is_declaration {
54 CallEdgeKind::External
55 } else {
56 CallEdgeKind::Direct
57 };
58 direct_callees_sets[src_idx].insert(fid);
59 direct_callers_sets[fid.0 as usize].insert(FunctionId(src_idx as u32));
60 CallEdge {
61 to: Some(fid),
62 kind,
63 }
64 } else {
65 CallEdge {
66 to: None,
67 kind: CallEdgeKind::External,
68 }
69 }
70 }
71 _ => CallEdge {
72 to: None,
73 kind: CallEdgeKind::Indirect,
74 },
75 };
76 edges[src_idx].push(edge);
77 }
78 }
79
80 let direct_callees = direct_callees_sets
81 .into_iter()
82 .map(|s| s.into_iter().collect())
83 .collect();
84 let direct_callers = direct_callers_sets
85 .into_iter()
86 .map(|s| s.into_iter().collect())
87 .collect();
88 Self {
89 direct_callees,
90 direct_callers,
91 edges,
92 }
93 }
94
95 pub fn callees(&self, f: FunctionId) -> &[FunctionId] {
97 &self.direct_callees[f.0 as usize]
98 }
99
100 pub fn callers(&self, f: FunctionId) -> &[FunctionId] {
102 &self.direct_callers[f.0 as usize]
103 }
104
105 pub fn edges(&self, f: FunctionId) -> &[CallEdge] {
107 &self.edges[f.0 as usize]
108 }
109
110 pub fn sccs(&self) -> Vec<Vec<FunctionId>> {
112 let n = self.direct_callees.len();
113 let mut order = Vec::with_capacity(n);
114 let mut seen = vec![false; n];
115 for i in 0..n {
116 if !seen[i] {
117 self.dfs_post(i, &mut seen, &mut order);
118 }
119 }
120
121 let mut rev_adj: Vec<Vec<usize>> = vec![Vec::new(); n];
122 for src in 0..n {
123 for &dst in &self.direct_callees[src] {
124 rev_adj[dst.0 as usize].push(src);
125 }
126 }
127
128 let mut comp = vec![usize::MAX; n];
129 let mut comp_nodes: Vec<Vec<usize>> = Vec::new();
130 for &u in order.iter().rev() {
131 if comp[u] != usize::MAX {
132 continue;
133 }
134 let id = comp_nodes.len();
135 let mut nodes = Vec::new();
136 let mut stack = vec![u];
137 comp[u] = id;
138 while let Some(x) = stack.pop() {
139 nodes.push(x);
140 for &p in &rev_adj[x] {
141 if comp[p] == usize::MAX {
142 comp[p] = id;
143 stack.push(p);
144 }
145 }
146 }
147 comp_nodes.push(nodes);
148 }
149
150 let c = comp_nodes.len();
151 let mut comp_succ: Vec<HashSet<usize>> = vec![HashSet::new(); c];
152 let mut indeg = vec![0usize; c];
153 for src in 0..n {
154 for &dst in &self.direct_callees[src] {
155 let a = comp[src];
156 let b = comp[dst.0 as usize];
157 if a != b && comp_succ[a].insert(b) {
158 indeg[b] += 1;
159 }
160 }
161 }
162
163 let mut q: Vec<usize> = (0..c).filter(|&i| indeg[i] == 0).collect();
165 q.sort_unstable();
166 let mut topo = Vec::with_capacity(c);
167 while let Some(x) = q.pop() {
168 topo.push(x);
169 let mut succs: Vec<usize> = comp_succ[x].iter().copied().collect();
170 succs.sort_unstable();
171 for y in succs {
172 indeg[y] -= 1;
173 if indeg[y] == 0 {
174 q.push(y);
175 }
176 }
177 }
178
179 topo.reverse();
180 topo.into_iter()
181 .map(|cid| {
182 let mut ids: Vec<FunctionId> = comp_nodes[cid]
183 .iter()
184 .map(|&x| FunctionId(x as u32))
185 .collect();
186 ids.sort_unstable_by_key(|f| f.0);
187 ids
188 })
189 .collect()
190 }
191
192 fn dfs_post(&self, u: usize, seen: &mut [bool], order: &mut Vec<usize>) {
193 if seen[u] {
194 return;
195 }
196 seen[u] = true;
197 for &v in &self.direct_callees[u] {
198 self.dfs_post(v.0 as usize, seen, order);
199 }
200 order.push(u);
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207 use llvm_ir::{Builder, GlobalId, Linkage};
208
209 fn build_graph_module() -> (Context, Module) {
210 let mut ctx = Context::new();
211 let mut module = Module::new("m");
212 let mut b = Builder::new(&mut ctx, &mut module);
213 let i64_ty = b.ctx.i64_ty;
214 let fn_ty = b.ctx.mk_fn_type(i64_ty, vec![i64_ty], false);
215
216 b.add_function("a", i64_ty, vec![i64_ty], vec!["x".into()], false, Linkage::External);
217 let a_entry = b.add_block("a.entry");
218 b.position_at_end(a_entry);
219 let x = b.get_arg(0);
220 let c1 = b.build_call("c1", i64_ty, fn_ty, ValueRef::Global(GlobalId(1)), vec![x]);
221 let c2 = b.build_call("c2", i64_ty, fn_ty, ValueRef::Global(GlobalId(2)), vec![x]);
222 let sum = b.build_add("sum", c1, c2);
223 b.build_ret(sum);
224
225 b.add_function("b", i64_ty, vec![i64_ty], vec!["x".into()], false, Linkage::External);
226 let b_entry = b.add_block("b.entry");
227 b.position_at_end(b_entry);
228 let bx = b.get_arg(0);
229 let bcall = b.build_call("r", i64_ty, fn_ty, ValueRef::Global(GlobalId(2)), vec![bx]);
230 b.build_ret(bcall);
231
232 b.add_function("c", i64_ty, vec![i64_ty], vec!["x".into()], false, Linkage::External);
233 let c_entry = b.add_block("c.entry");
234 b.position_at_end(c_entry);
235 let cx = b.get_arg(0);
236 b.build_ret(cx);
237
238 (ctx, module)
239 }
240
241 #[test]
242 fn call_graph_builds_direct_edges_for_dag() {
243 let (ctx, module) = build_graph_module();
244 let cg = CallGraph::build(&ctx, &module);
245
246 assert_eq!(cg.callees(FunctionId(0)), &[FunctionId(1), FunctionId(2)]);
247 assert_eq!(cg.callees(FunctionId(1)), &[FunctionId(2)]);
248 assert!(cg.callees(FunctionId(2)).is_empty());
249 assert_eq!(cg.callers(FunctionId(2)), &[FunctionId(0), FunctionId(1)]);
250 }
251
252 #[test]
253 fn call_graph_sccs_group_cycle_together() {
254 let mut ctx = Context::new();
255 let mut module = Module::new("m");
256 let mut b = Builder::new(&mut ctx, &mut module);
257 let i64_ty = b.ctx.i64_ty;
258 let fn_ty = b.ctx.mk_fn_type(i64_ty, vec![i64_ty], false);
259 for name in ["a", "b", "c"] {
260 b.add_function(name, i64_ty, vec![i64_ty], vec!["x".into()], false, Linkage::External);
261 let entry = b.add_block(format!("{name}.entry"));
262 b.position_at_end(entry);
263 let x = b.get_arg(0);
264 let callee = match name {
265 "a" => ValueRef::Global(GlobalId(1)),
266 "b" => ValueRef::Global(GlobalId(0)),
267 _ => ValueRef::Global(GlobalId(2)),
268 };
269 let r = b.build_call("r", i64_ty, fn_ty, callee, vec![x]);
270 b.build_ret(r);
271 }
272
273 let cg = CallGraph::build(&ctx, &module);
274 let sccs = cg.sccs();
275 assert!(
276 sccs.iter()
277 .any(|s| s.len() == 2 && s.contains(&FunctionId(0)) && s.contains(&FunctionId(1)))
278 );
279 }
280
281 #[test]
282 fn call_graph_classifies_indirect_and_external_calls() {
283 let mut ctx = Context::new();
284 let mut module = Module::new("m");
285 let mut b = Builder::new(&mut ctx, &mut module);
286 let i64_ty = b.ctx.i64_ty;
287 let fn_ty = b.ctx.mk_fn_type(i64_ty, vec![i64_ty], false);
288 b.add_declaration("ext", i64_ty, vec![i64_ty], false);
289
290 b.add_function("f", i64_ty, vec![i64_ty], vec!["x".into()], false, Linkage::External);
291 let entry = b.add_block("entry");
292 b.position_at_end(entry);
293 let x = b.get_arg(0);
294 let _ext = b.build_call("ext", i64_ty, fn_ty, ValueRef::Global(GlobalId(0)), vec![x]);
295 let _ind = b.build_call("ind", i64_ty, fn_ty, x, vec![x]);
296 b.build_ret(x);
297
298 let cg = CallGraph::build(&ctx, &module);
299 let edges = cg.edges(FunctionId(1));
300 assert!(edges.iter().any(|e| e.kind == CallEdgeKind::External));
301 assert!(edges.iter().any(|e| e.kind == CallEdgeKind::Indirect));
302 }
303}