Skip to main content

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}