use std::collections::HashMap;
use rucc_base::Symbol;
use rucc_ir::{AliasKind, Datum, Extra, Func, FuncId, Inst, Linkage, Module, Opcode, Pic};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Node(u32);
impl Node {
#[must_use]
pub const fn index(self) -> usize {
self.0 as usize
}
}
#[derive(Debug, Clone)]
struct Entry {
name: Symbol,
func: Option<FuncId>,
body: Option<FuncId>,
calls: Vec<Node>,
unknown: bool,
address_taken: bool,
}
#[derive(Debug, Clone, Default)]
pub struct CallGraph {
entries: Vec<Entry>,
by_name: HashMap<Symbol, Node>,
components: Vec<Vec<Node>>,
component_of: Vec<u32>,
}
impl CallGraph {
#[must_use]
pub fn of(module: &Module, pic: Pic) -> Self {
let mut graph = Self::default();
for id in module.funcs() {
let func = &module[id];
let body = (!func.is_declaration() && trusted(func, pic)).then_some(id);
let node = graph.intern(func.name);
let at = node.index();
graph.entries[at].func = Some(id);
graph.entries[at].body = body;
graph.entries[at].unknown = body.is_none();
}
for id in module.aliases() {
let alias = &module[id];
let node = graph.intern(alias.name);
match alias.kind {
AliasKind::Alias => {
let to = graph.intern(alias.target);
graph.entries[node.index()].calls.push(to);
}
AliasKind::IFunc => {
graph.entries[node.index()].unknown = true;
let resolver = graph.intern(alias.target);
graph.entries[resolver.index()].address_taken = true;
}
}
}
for id in module.funcs() {
let func = &module[id];
if func.is_declaration() {
continue;
}
let from = graph.by_name[&func.name];
for block in func.blocks() {
for inst in func.insts(block) {
graph.read(func, inst, from);
}
}
}
for id in module.globals() {
let init = module[id].init.map(|list| &module[list]).unwrap_or_default();
for datum in init {
if let Datum::Addr(reloc) | Datum::Away(reloc) = *datum {
graph.took_the_address_of(module[reloc].symbol);
}
}
}
graph.condense();
graph
}
pub fn nodes(&self) -> impl Iterator<Item = Node> + use<> {
(0..self.entries.len() as u32).map(Node)
}
#[must_use]
pub fn len(&self) -> usize {
self.entries.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
#[must_use]
pub fn node(&self, name: Symbol) -> Option<Node> {
self.by_name.get(&name).copied()
}
#[must_use]
pub fn name(&self, node: Node) -> Symbol {
self.entries[node.index()].name
}
#[must_use]
pub fn func(&self, node: Node) -> Option<FuncId> {
self.entries[node.index()].func
}
#[must_use]
pub fn trusted_body(&self, node: Node) -> Option<FuncId> {
self.entries[node.index()].body
}
#[must_use]
pub fn calls(&self, node: Node) -> &[Node] {
&self.entries[node.index()].calls
}
#[must_use]
pub fn reaches_unknown(&self, node: Node) -> bool {
self.entries[node.index()].unknown
}
#[must_use]
pub fn address_taken(&self, node: Node) -> bool {
self.entries[node.index()].address_taken
}
#[must_use]
pub fn components(&self) -> &[Vec<Node>] {
&self.components
}
#[must_use]
pub fn component_of(&self, node: Node) -> usize {
self.component_of[node.index()] as usize
}
pub fn solve<T, S, F>(&self, start: S, mut transfer: F) -> Vec<T>
where
T: Clone + PartialEq,
S: Fn(Node) -> T,
F: FnMut(Node, &[T]) -> T,
{
let mut answers: Vec<T> = self.nodes().map(&start).collect();
for part in &self.components {
if let [only] = part[..] {
if !self.entries[only.index()].calls.contains(&only) {
answers[only.index()] = transfer(only, &answers);
continue;
}
}
let ceiling = 1000 + part.len() * 64;
let mut rounds = 0usize;
loop {
let mut settled = true;
for &node in part {
let now = transfer(node, &answers);
if now != answers[node.index()] {
answers[node.index()] = now;
settled = false;
}
}
if settled {
break;
}
rounds += 1;
debug_assert!(rounds < ceiling, "the transfer function is not monotone");
}
}
answers
}
fn intern(&mut self, name: Symbol) -> Node {
if let Some(&node) = self.by_name.get(&name) {
return node;
}
let node = Node(self.entries.len() as u32);
self.entries.push(Entry {
name,
func: None,
body: None,
calls: Vec::new(),
unknown: true,
address_taken: false,
});
self.by_name.insert(name, node);
node
}
fn took_the_address_of(&mut self, name: Symbol) {
if let Some(&node) = self.by_name.get(&name) {
self.entries[node.index()].address_taken = true;
}
}
fn read(&mut self, func: &Func, inst: Inst, from: Node) {
let data = &func[inst];
match data.opcode {
Opcode::Call | Opcode::TailCall => {
let name = match data.extra {
Extra::Call(at) => func[at].callee,
_ => None,
};
let Some(name) = name else {
self.entries[from.index()].unknown = true;
return;
};
let to = self.intern(name);
let calls = &mut self.entries[from.index()].calls;
if !calls.contains(&to) {
calls.push(to);
}
}
Opcode::CallIndirect => self.entries[from.index()].unknown = true,
Opcode::InlineAsm | Opcode::TargetIntrinsic => {
self.entries[from.index()].unknown = true;
}
Opcode::GlobalAddr => {
if let Extra::Symbol(name) = data.extra {
self.took_the_address_of(name);
}
}
_ => {}
}
}
fn condense(&mut self) {
let count = self.entries.len();
let mut index = vec![u32::MAX; count];
let mut low = vec![0u32; count];
let mut on_stack = vec![false; count];
let mut stack: Vec<u32> = Vec::new();
let mut frames: Vec<(u32, usize)> = Vec::new();
let mut next = 0u32;
self.component_of = vec![u32::MAX; count];
for root in 0..count as u32 {
if index[root as usize] != u32::MAX {
continue;
}
index[root as usize] = next;
low[root as usize] = next;
next += 1;
stack.push(root);
on_stack[root as usize] = true;
frames.push((root, 0));
while let Some(&(node, at)) = frames.last() {
let edges = &self.entries[node as usize].calls;
if at < edges.len() {
let to = edges[at].0;
frames.last_mut().expect("the frame just read").1 += 1;
if index[to as usize] == u32::MAX {
index[to as usize] = next;
low[to as usize] = next;
next += 1;
stack.push(to);
on_stack[to as usize] = true;
frames.push((to, 0));
} else if on_stack[to as usize] {
low[node as usize] = low[node as usize].min(index[to as usize]);
}
continue;
}
frames.pop();
if low[node as usize] == index[node as usize] {
let mut part = Vec::new();
while let Some(top) = stack.pop() {
on_stack[top as usize] = false;
part.push(Node(top));
if top == node {
break;
}
}
part.sort_unstable();
let which = self.components.len() as u32;
for member in &part {
self.component_of[member.index()] = which;
}
self.components.push(part);
}
if let Some(&(above, _)) = frames.last() {
low[above as usize] = low[above as usize].min(low[node as usize]);
}
}
}
debug_assert!(
self.component_of.iter().all(|&which| which != u32::MAX),
"every node is in a component"
);
}
}
fn trusted(func: &Func, pic: Pic) -> bool {
!matches!(func.linkage, Linkage::Weak | Linkage::Common)
&& !pic.replaceable(func.linkage, func.visibility)
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_ir::{
Alias, AliasKind, AsmInfo, BlockCallList, Builder, CallInfo, Datum, Extra, Flags, Func,
Global, InstData, Linkage, Module, Opcode, Pic, Reloc, Signature, Type, Visibility,
};
use rucc_target::{TargetInfo, Triple};
use super::{CallGraph, Node};
fn blank() -> (Interner, Module) {
let mut names = Interner::new();
let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
let module = Module::new(names.intern("t.c"), &target);
(names, module)
}
fn calling(names: &mut Interner, module: &mut Module, name: &str, callees: &[&str]) {
let mut func = Func::new(names.intern(name), Signature::new());
let block = func.create_block();
let mut build = Builder::new(&mut func, block);
let signature = build.func().add_signature(Signature::new());
for callee in callees {
build.call(names.intern(callee), signature, &[]);
}
build.ret(&[]);
module.add_func(func);
}
fn declaring(names: &mut Interner, module: &mut Module, name: &str) {
module.add_func(Func::new(names.intern(name), Signature::new()));
}
fn node(graph: &CallGraph, names: &mut Interner, name: &str) -> Node {
let name = names.intern(name);
graph.node(name).unwrap_or_else(|| panic!("no node for {}", names.resolve(name)))
}
fn order(graph: &CallGraph, names: &Interner) -> Vec<Vec<String>> {
graph
.components()
.iter()
.map(|part| part.iter().map(|&it| names.resolve(graph.name(it)).to_string()).collect())
.collect()
}
#[test]
fn a_call_is_an_edge_and_the_callee_gets_a_node_of_its_own() {
let (mut names, mut module) = blank();
calling(&mut names, &mut module, "f", &["g"]);
calling(&mut names, &mut module, "g", &[]);
let graph = CallGraph::of(&module, Pic::Executable);
let (f, g) = (node(&graph, &mut names, "f"), node(&graph, &mut names, "g"));
assert_eq!(graph.calls(f), [g]);
assert_eq!(graph.calls(g), []);
}
#[test]
fn the_same_callee_twice_is_one_edge() {
let (mut names, mut module) = blank();
calling(&mut names, &mut module, "f", &["g", "h", "g"]);
calling(&mut names, &mut module, "g", &[]);
calling(&mut names, &mut module, "h", &[]);
let graph = CallGraph::of(&module, Pic::Executable);
let f = node(&graph, &mut names, "f");
let (g, h) = (node(&graph, &mut names, "g"), node(&graph, &mut names, "h"));
assert_eq!(graph.calls(f), [g, h], "the order the body calls them, each once");
}
#[test]
fn a_name_the_module_never_declared_still_gets_a_node() {
let (mut names, mut module) = blank();
calling(&mut names, &mut module, "f", &["witness"]);
let graph = CallGraph::of(&module, Pic::Executable);
let witness = node(&graph, &mut names, "witness");
assert_eq!(graph.calls(node(&graph, &mut names, "f")), [witness]);
assert_eq!(graph.func(witness), None);
assert_eq!(graph.trusted_body(witness), None);
assert!(graph.reaches_unknown(witness), "its body is somewhere this graph cannot see");
}
#[test]
fn a_declaration_has_a_function_and_no_body_and_reaches_the_unknown() {
let (mut names, mut module) = blank();
declaring(&mut names, &mut module, "printf");
let graph = CallGraph::of(&module, Pic::Executable);
let printf = node(&graph, &mut names, "printf");
assert!(graph.func(printf).is_some());
assert_eq!(graph.trusted_body(printf), None);
assert!(graph.reaches_unknown(printf));
}
#[test]
fn a_body_this_link_will_keep_is_one_an_analysis_may_read() {
let (mut names, mut module) = blank();
calling(&mut names, &mut module, "f", &[]);
let graph = CallGraph::of(&module, Pic::Executable);
let f = node(&graph, &mut names, "f");
assert!(graph.trusted_body(f).is_some());
assert!(!graph.reaches_unknown(f));
}
#[test]
fn a_weak_definition_is_not_a_body_this_analysis_may_read() {
let (mut names, mut module) = blank();
calling(&mut names, &mut module, "f", &[]);
let id = module.funcs().next().expect("the one function");
module[id].linkage = Linkage::Weak;
let graph = CallGraph::of(&module, Pic::Executable);
let f = node(&graph, &mut names, "f");
assert!(graph.func(f).is_some(), "the declaration is still there");
assert_eq!(graph.trusted_body(f), None, "another object may win over it");
assert!(graph.reaches_unknown(f));
}
#[test]
fn an_exported_definition_in_a_library_may_be_interposed_and_a_hidden_one_may_not() {
let (mut names, mut module) = blank();
calling(&mut names, &mut module, "f", &[]);
let id = module.funcs().next().expect("the one function");
let graph = CallGraph::of(&module, Pic::Library);
assert_eq!(graph.trusted_body(node(&graph, &mut names, "f")), None);
module[id].visibility = Visibility::Hidden;
let graph = CallGraph::of(&module, Pic::Library);
assert!(graph.trusted_body(node(&graph, &mut names, "f")).is_some());
}
#[test]
fn a_call_through_an_address_is_the_flag_and_not_an_edge() {
let (mut names, mut module) = blank();
let mut func = Func::new(names.intern("f"), Signature::new());
let block = func.create_block();
let mut build = Builder::new(&mut func, block);
let extra = Extra::Symbol(names.intern("g"));
let target =
build.value(InstData { extra, ..InstData::new(Opcode::GlobalAddr) }, Type::PTR);
let signature = build.func().add_signature(Signature::new());
let varargs = build.func().push_abis(&[]);
let info = build.func().add_call(CallInfo { callee: None, signature, varargs });
let args = build.func().push_values(&[target]);
build.inst(
InstData { args, extra: Extra::Call(info), ..InstData::new(Opcode::CallIndirect) },
&[],
);
build.ret(&[]);
module.add_func(func);
calling(&mut names, &mut module, "g", &[]);
let graph = CallGraph::of(&module, Pic::Executable);
let f = node(&graph, &mut names, "f");
assert_eq!(graph.calls(f), [], "nothing here names what is at the other end");
assert!(graph.reaches_unknown(f));
assert!(graph.address_taken(node(&graph, &mut names, "g")));
}
#[test]
fn a_function_named_in_an_image_has_had_its_address_taken() {
let (mut names, mut module) = blank();
calling(&mut names, &mut module, "handler", &[]);
let handler = names.intern("handler");
let reloc = module.add_reloc(Reloc { symbol: handler, addend: 0, size: 8 });
let mut table = Global::new(names.intern("table"), 8, 8);
table.init = Some(module.push_data(&[Datum::Addr(reloc)]));
module.add_global(table);
let graph = CallGraph::of(&module, Pic::Executable);
assert!(graph.address_taken(node(&graph, &mut names, "handler")));
}
#[test]
fn taking_the_address_of_a_variable_puts_nothing_in_the_graph() {
let (mut names, mut module) = blank();
let mut func = Func::new(names.intern("f"), Signature::new());
let block = func.create_block();
let mut build = Builder::new(&mut func, block);
let extra = Extra::Symbol(names.intern("counter"));
build.value(InstData { extra, ..InstData::new(Opcode::GlobalAddr) }, Type::PTR);
build.ret(&[]);
module.add_func(func);
module.add_global(Global::new(names.intern("counter"), 4, 4));
let graph = CallGraph::of(&module, Pic::Executable);
assert_eq!(graph.len(), 1, "the one function and nothing else");
assert_eq!(graph.node(names.intern("counter")), None);
}
#[test]
fn an_alias_is_an_edge_to_what_it_aliases() {
let (mut names, mut module) = blank();
calling(&mut names, &mut module, "caller", &["shorthand"]);
calling(&mut names, &mut module, "real", &[]);
module.add_alias(Alias::new(names.intern("shorthand"), names.intern("real")));
let graph = CallGraph::of(&module, Pic::Executable);
let shorthand = node(&graph, &mut names, "shorthand");
let real = node(&graph, &mut names, "real");
assert_eq!(graph.calls(node(&graph, &mut names, "caller")), [shorthand]);
assert_eq!(graph.calls(shorthand), [real], "a call to the alias is a call to the body");
}
#[test]
fn an_ifunc_reaches_the_unknown_and_its_resolver_has_had_its_address_taken() {
let (mut names, mut module) = blank();
calling(&mut names, &mut module, "resolve", &[]);
let mut alias = Alias::new(names.intern("memcpy"), names.intern("resolve"));
alias.kind = AliasKind::IFunc;
module.add_alias(alias);
let graph = CallGraph::of(&module, Pic::Executable);
let memcpy = node(&graph, &mut names, "memcpy");
assert_eq!(graph.calls(memcpy), [], "what it resolves to is not a name this module has");
assert!(graph.reaches_unknown(memcpy));
assert!(graph.address_taken(node(&graph, &mut names, "resolve")));
assert!(!graph.address_taken(memcpy));
}
#[test]
fn inline_assembly_reaches_the_unknown() {
let (mut names, mut module) = blank();
let mut func = Func::new(names.intern("f"), Signature::new());
let block = func.create_block();
let mut build = Builder::new(&mut func, block);
build.inline_asm(
AsmInfo {
template: names.intern("nop"),
constraints: names.intern(""),
clobbers: names.intern(""),
targets: BlockCallList::EMPTY,
},
&[],
&[],
Flags::NONE,
);
build.ret(&[]);
module.add_func(func);
let graph = CallGraph::of(&module, Pic::Executable);
assert!(graph.reaches_unknown(node(&graph, &mut names, "f")));
}
#[test]
fn a_target_intrinsic_reaches_the_unknown() {
let (mut names, mut module) = blank();
let mut func = Func::new(names.intern("f"), Signature::new());
let block = func.create_block();
let mut build = Builder::new(&mut func, block);
let extra = Extra::Symbol(names.intern("x86.pause"));
build.inst(InstData { extra, ..InstData::new(Opcode::TargetIntrinsic) }, &[]);
build.ret(&[]);
module.add_func(func);
let graph = CallGraph::of(&module, Pic::Executable);
assert!(graph.reaches_unknown(node(&graph, &mut names, "f")));
}
#[test]
fn a_chain_of_callers_comes_out_callee_before_caller() {
let (mut names, mut module) = blank();
calling(&mut names, &mut module, "top", &["middle"]);
calling(&mut names, &mut module, "middle", &["bottom"]);
calling(&mut names, &mut module, "bottom", &[]);
let graph = CallGraph::of(&module, Pic::Executable);
assert_eq!(order(&graph, &names), [["bottom"], ["middle"], ["top"]]);
}
#[test]
fn a_function_that_calls_itself_is_a_component_of_one_that_is_a_cycle() {
let (mut names, mut module) = blank();
calling(&mut names, &mut module, "spin", &["spin"]);
let graph = CallGraph::of(&module, Pic::Executable);
let spin = node(&graph, &mut names, "spin");
assert_eq!(graph.calls(spin), [spin]);
assert_eq!(order(&graph, &names), [["spin"]]);
}
#[test]
fn two_functions_that_call_each_other_are_one_component() {
let (mut names, mut module) = blank();
calling(&mut names, &mut module, "even", &["odd"]);
calling(&mut names, &mut module, "odd", &["even"]);
calling(&mut names, &mut module, "main", &["even"]);
let graph = CallGraph::of(&module, Pic::Executable);
assert_eq!(order(&graph, &names), [vec!["even", "odd"], vec!["main"]]);
let (even, odd) = (node(&graph, &mut names, "even"), node(&graph, &mut names, "odd"));
assert_eq!(graph.component_of(even), graph.component_of(odd));
}
#[test]
fn a_component_holds_its_nodes_in_the_graphs_own_order() {
let (mut names, mut module) = blank();
calling(&mut names, &mut module, "a", &["c"]);
calling(&mut names, &mut module, "b", &["a"]);
calling(&mut names, &mut module, "c", &["b"]);
let graph = CallGraph::of(&module, Pic::Executable);
assert_eq!(order(&graph, &names), [["a", "b", "c"]]);
let a = node(&graph, &mut names, "a");
assert_eq!(graph.components()[graph.component_of(a)][0], a);
}
#[test]
fn the_walk_settles_a_component_before_anything_that_calls_into_it() {
let (mut names, mut module) = blank();
calling(&mut names, &mut module, "top", &["middle"]);
calling(&mut names, &mut module, "middle", &["bottom"]);
calling(&mut names, &mut module, "bottom", &[]);
let graph = CallGraph::of(&module, Pic::Executable);
let depth = graph.solve(
|_| 0usize,
|node, answers| {
graph.calls(node).iter().map(|&it| answers[it.index()] + 1).max().unwrap_or(0)
},
);
assert_eq!(depth[node(&graph, &mut names, "bottom").index()], 0);
assert_eq!(depth[node(&graph, &mut names, "middle").index()], 1);
assert_eq!(depth[node(&graph, &mut names, "top").index()], 2);
}
#[test]
fn a_component_of_one_with_no_edge_to_itself_is_asked_once() {
let (mut names, mut module) = blank();
calling(&mut names, &mut module, "f", &["g"]);
calling(&mut names, &mut module, "g", &[]);
let graph = CallGraph::of(&module, Pic::Executable);
let mut asked = 0usize;
let answers: Vec<bool> = graph.solve(
|_| false,
|_, _| {
asked += 1;
true
},
);
assert_eq!(asked, 2, "one question each, with nothing to settle");
assert!(answers[node(&graph, &mut names, "f").index()]);
}
#[test]
fn a_cycle_is_iterated_until_nothing_moves() {
let (mut names, mut module) = blank();
calling(&mut names, &mut module, "even", &["odd"]);
calling(&mut names, &mut module, "odd", &["even"]);
let graph = CallGraph::of(&module, Pic::Executable);
let odd = node(&graph, &mut names, "odd");
let settled = graph.solve(
|_| true,
|node, answers| node != odd && graph.calls(node).iter().all(|&it| answers[it.index()]),
);
assert!(!settled[odd.index()]);
assert!(!settled[node(&graph, &mut names, "even").index()], "through the cycle");
}
#[test]
fn an_empty_module_is_an_empty_graph() {
let (_, module) = blank();
let graph = CallGraph::of(&module, Pic::Executable);
assert!(graph.is_empty());
assert_eq!(graph.len(), 0);
assert!(graph.components().is_empty());
let answers: Vec<usize> = graph.solve(|_| 0, |_, _| 0);
assert!(answers.is_empty());
}
#[test]
fn the_graph_is_the_same_graph_every_time_it_is_built() {
let (mut names, mut module) = blank();
for name in ["one", "two", "three", "four", "five"] {
calling(&mut names, &mut module, name, &["helper", "one"]);
}
calling(&mut names, &mut module, "helper", &[]);
let first = CallGraph::of(&module, Pic::Executable);
let spelling = |graph: &CallGraph| {
graph
.nodes()
.map(|it| {
let calls: Vec<&str> =
graph.calls(it).iter().map(|&to| names.resolve(graph.name(to))).collect();
(names.resolve(graph.name(it)).to_string(), calls.join(" "))
})
.collect::<Vec<_>>()
};
for _ in 0..8 {
let again = CallGraph::of(&module, Pic::Executable);
assert_eq!(spelling(&first), spelling(&again));
assert_eq!(order(&first, &names), order(&again, &names));
}
}
#[test]
fn a_chain_deeper_than_a_recursive_walk_could_manage_still_comes_out_in_order() {
let (mut names, mut module) = blank();
let deep = 20_000;
for at in 0..deep {
let next = format!("f{}", at + 1);
calling(&mut names, &mut module, &format!("f{at}"), &[next.as_str()]);
}
let graph = CallGraph::of(&module, Pic::Executable);
assert_eq!(graph.components().len(), deep + 1, "the tail name gets one of its own");
let top = node(&graph, &mut names, "f0");
assert_eq!(graph.component_of(top), deep, "settled last, after everything under it");
}
}