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 // A body that saves the registers it was called with reads its arguments where the
47 // convention put them rather than as parameters, so a parameter taken out or a constant
48 // put in its place would leave it reading something the caller no longer passes.
49 let saves = func
50 .blocks()
51 .any(|block| func.insts(block).any(|inst| func[inst].opcode == Opcode::ApplyArgs));
52 if saves {
53 continue;
54 }
55 closed.push(id);
56 }
57 // Module order, for the reason the return above gives.
58 closed.sort_unstable_by_key(|id| id.raw());
59 closed
60}
61
62/// The components of the call graph with callers before callees, each holding only closed nodes.
63///
64/// [`CallGraph::components`] is callees first, so this is that read backwards. A component with no
65/// closed function in it is left out rather than walked over, since the round inside it would
66/// compute nothing.
67pub fn order(graph: &CallGraph, closed: &[FuncId]) -> Vec<Vec<FuncId>> {
68 let inside: HashSet<FuncId> = closed.iter().copied().collect();
69 let mut order = Vec::new();
70 for part in graph.components().iter().rev() {
71 let part: Vec<FuncId> = part
72 .iter()
73 .filter_map(|&node| graph.trusted_body(node))
74 .filter(|id| inside.contains(id))
75 .collect();
76 if !part.is_empty() {
77 order.push(part);
78 }
79 }
80 order
81}
82
83/// Every direct call in the module to one of the closed functions, by the function called.
84///
85/// Every call, not only the ones from closed functions. A call from anywhere is a call, and what
86/// the caller is only matters when the argument is the caller's own parameter, which is the one
87/// place below that asks.
88///
89/// A call whose argument count does not match what the callee takes is a prototype disagreeing with
90/// a definition, which a translation unit may contain. The positions would not line up, so the call
91/// is not read and the callee is struck out instead of being read from the rest of its calls, since
92/// what that one passes is exactly what is not known.
93pub fn sites(module: &Module, closed: &[FuncId]) -> HashMap<FuncId, Sites> {
94 let mut where_defined: HashMap<_, FuncId> = HashMap::new();
95 for &id in closed {
96 where_defined.insert(module[id].name, id);
97 }
98 let mut sites: HashMap<FuncId, Sites> = HashMap::new();
99 for id in module.funcs() {
100 let func = &module[id];
101 if func.is_declaration() {
102 continue;
103 }
104 for block in func.blocks() {
105 for inst in func.insts(block) {
106 if !matches!(func[inst].opcode, Opcode::Call | Opcode::TailCall) {
107 continue;
108 }
109 let Extra::Call(at) = func[inst].extra else { continue };
110 let Some(callee) = func[at].callee else { continue };
111 let Some(&target) = where_defined.get(&callee) else { continue };
112 let entry = sites.entry(target).or_default();
113 if module[target].signature().params.len() != func[func[inst].args].len() {
114 entry.ragged = true;
115 continue;
116 }
117 entry.calls.push((id, inst));
118 }
119 }
120 }
121 sites
122}
123
124/// Where one function is called from.
125#[derive(Debug, Default)]
126pub struct Sites {
127 /// The caller and the instruction, for every call whose arguments line up.
128 pub calls: Vec<(FuncId, Inst)>,
129 /// Whether some call passed a number of arguments the function does not take.
130 ///
131 /// One of those and nothing is claimed about any parameter, because the call is real and what
132 /// it passed is what cannot be read.
133 pub ragged: bool,
134}
135
136/// Every value the body reads, as an operand or as an argument on an edge.
137pub fn operands(func: &Func) -> HashSet<Value> {
138 let mut read = HashSet::new();
139 for block in func.blocks() {
140 for inst in func.insts(block) {
141 read.extend(func[func[inst].args].iter().copied());
142 for edge in func.successors(inst) {
143 read.extend(func[edge.args].iter().copied());
144 }
145 }
146 }
147 read
148}