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        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}