rucc_opt/ipa.rs
1//! What both interprocedural transformations need: which functions are theirs to change.
2//!
3//! Design: `spec/optimizer/34-ipa.md` section 34.6. The two transformations M4 builds, the constant
4//! propagation in [`crate::ipcp`] and the parameter removal in [`crate::ipasra`], ask the same
5//! question before they touch anything, and the spec asks it of both in the same words: any
6//! function whose address is taken or which is externally visible cannot be changed at all.
7//!
8//! It is one gate here rather than one in each of them because a gate two passes each carry a copy
9//! of is a gate two passes can come to disagree about. The disagreement that would follow is a
10//! function one of them rewrote the body of and the other rewrote the calls to, with neither of
11//! them wrong on its own.
12//!
13//! Nothing in here transforms anything. It reads the module and the call graph and says what is
14//! reachable from where, which is what makes it safe for a pass to ask again after it has changed
15//! something.
16
17use std::collections::{HashMap, HashSet};
18
19use rucc_ir::{Extra, Func, FuncId, Inst, Linkage, Module, Opcode, Value};
20
21use crate::CallGraph;
22
23/// The functions this unit can see every call to, with the parameters lined up.
24///
25/// Five things, and the first three are one thing said three ways. Internal linkage means no other
26/// object can name it. No address taken means nothing in this one can reach it except by naming it.
27/// A body this unit may read is [`CallGraph::trusted_body`], which is section 34.1's gate, and
28/// without it there is nothing to put a constant into. Then the signature has to have a fixed
29/// number of parameters, and the entry block has to have one value per parameter, which is what
30/// makes the position of an argument at a call the position of a parameter in the body.
31pub fn closed(module: &Module, graph: &CallGraph) -> Vec<FuncId> {
32 let mut closed = Vec::new();
33 for node in graph.nodes() {
34 if graph.address_taken(node) {
35 continue;
36 }
37 let Some(id) = graph.trusted_body(node) else { continue };
38 let func = &module[id];
39 if func.linkage != Linkage::Internal || func.signature().variadic {
40 continue;
41 }
42 let Some(entry) = func.entry() else { continue };
43 if func[entry].params.len() != func.signature().params.len() {
44 continue;
45 }
46 closed.push(id);
47 }
48 // Module order, for the reason the return above gives.
49 closed.sort_unstable_by_key(|id| id.raw());
50 closed
51}
52
53/// The components of the call graph with callers before callees, each holding only closed nodes.
54///
55/// [`CallGraph::components`] is callees first, so this is that read backwards. A component with no
56/// closed function in it is left out rather than walked over, since the round inside it would
57/// compute nothing.
58pub fn order(graph: &CallGraph, closed: &[FuncId]) -> Vec<Vec<FuncId>> {
59 let inside: HashSet<FuncId> = closed.iter().copied().collect();
60 let mut order = Vec::new();
61 for part in graph.components().iter().rev() {
62 let part: Vec<FuncId> = part
63 .iter()
64 .filter_map(|&node| graph.trusted_body(node))
65 .filter(|id| inside.contains(id))
66 .collect();
67 if !part.is_empty() {
68 order.push(part);
69 }
70 }
71 order
72}
73
74/// Every direct call in the module to one of the closed functions, by the function called.
75///
76/// Every call, not only the ones from closed functions. A call from anywhere is a call, and what
77/// the caller is only matters when the argument is the caller's own parameter, which is the one
78/// place below that asks.
79///
80/// A call whose argument count does not match what the callee takes is a prototype disagreeing with
81/// a definition, which a translation unit may contain. The positions would not line up, so the call
82/// is not read and the callee is struck out instead of being read from the rest of its calls, since
83/// what that one passes is exactly what is not known.
84pub fn sites(module: &Module, closed: &[FuncId]) -> HashMap<FuncId, Sites> {
85 let mut where_defined: HashMap<_, FuncId> = HashMap::new();
86 for &id in closed {
87 where_defined.insert(module[id].name, id);
88 }
89 let mut sites: HashMap<FuncId, Sites> = HashMap::new();
90 for id in module.funcs() {
91 let func = &module[id];
92 if func.is_declaration() {
93 continue;
94 }
95 for block in func.blocks() {
96 for inst in func.insts(block) {
97 if !matches!(func[inst].opcode, Opcode::Call | Opcode::TailCall) {
98 continue;
99 }
100 let Extra::Call(at) = func[inst].extra else { continue };
101 let Some(callee) = func[at].callee else { continue };
102 let Some(&target) = where_defined.get(&callee) else { continue };
103 let entry = sites.entry(target).or_default();
104 if module[target].signature().params.len() != func[func[inst].args].len() {
105 entry.ragged = true;
106 continue;
107 }
108 entry.calls.push((id, inst));
109 }
110 }
111 }
112 sites
113}
114
115/// Where one function is called from.
116#[derive(Debug, Default)]
117pub struct Sites {
118 /// The caller and the instruction, for every call whose arguments line up.
119 pub calls: Vec<(FuncId, Inst)>,
120 /// Whether some call passed a number of arguments the function does not take.
121 ///
122 /// One of those and nothing is claimed about any parameter, because the call is real and what
123 /// it passed is what cannot be read.
124 pub ragged: bool,
125}
126
127/// Every value the body reads, as an operand or as an argument on an edge.
128pub fn operands(func: &Func) -> HashSet<Value> {
129 let mut read = HashSet::new();
130 for block in func.blocks() {
131 for inst in func.insts(block) {
132 read.extend(func[func[inst].args].iter().copied());
133 for edge in func.successors(inst) {
134 read.extend(func[edge.args].iter().copied());
135 }
136 }
137 }
138 read
139}