Skip to main content

rucc_opt/
modref.rs

1//! What a function does to memory, one answer for each pointer parameter.
2//!
3//! Section 34.3 of `spec/optimizer/34-ipa.md`, and the analysis section 34.6 asks for right after
4//! [`crate::purity`]. Purity answers one question about the whole of memory: does this function
5//! read it, does it write it. That is enough to delete a call whose result nobody reads and it is
6//! not enough to move a load past one, because a loop that calls `helper(other)` and reloads
7//! `mine[i]` does not want to know whether `helper` wrote something. It wants to know whether
8//! `helper` wrote *this*, and the answer to that is per parameter.
9//!
10//! # What a summary holds, which is section 34.6's deliverable and no more
11//!
12//! For each pointer parameter: does the function read through it, does it write through it, does
13//! the address go somewhere the caller cannot see. And one answer for everything else, which is
14//! the globals and every address the body did not get as an argument.
15//!
16//! No offsets, no sizes, no access trees, no aggregate granularity. A parameter is a whole object
17//! here or it is nothing. `gcc/ipa-modref.cc` keeps far more than this over five and a half
18//! thousand lines, and the part that pays for itself in a loop is the part that is here.
19//!
20//! # Working it out
21//!
22//! [`summarize`] is the analysis and it goes the way [`crate::purity::infer`] goes, for section
23//! 34.5's reason: start every function at "touches nothing", read the bodies over the condensation
24//! callee before caller, and lower an answer when the body contradicts it. Starting at the other
25//! end and raising would answer "writes everything" for a pair of functions that call each other
26//! and touch nothing, which is the case the optimism is for.
27//!
28//! The escape analysis underneath is the same walk [`Escapes`] does and it runs with what this
29//! module has worked out so far, which is section 34.6's upgrade to it: an address handed to a
30//! call is an address gone to [`Escapes::of`], and that is most of what a C program does with the
31//! address of a local, so a callee whose summary says it keeps nothing takes a whole class of
32//! locals back out of the escaped set. Both directions of that are used here. A local this
33//! function only lent out stays private, so what the callee did to it never reaches the summary,
34//! and a parameter handed on takes the callee's own answer for that position rather than the
35//! blanket one.
36//!
37//! One thing is deliberately not as precise as it could be, and it is written down in
38//! tamnd/rucc#1557 rather than done here: a parameter is a whole object, so a callee that writes
39//! one field of a structure is a callee that wrote the structure.
40
41use std::collections::HashMap;
42
43use rucc_base::Symbol;
44use rucc_ir::{AttrSet, Block, Def, Func, Inst, Module, Opcode, Value};
45
46use crate::alias::{Escapes, Origin, keeps_address, origin};
47use crate::callgraph::{CallGraph, Node};
48use crate::purity::Callee;
49
50/// How many instructions of one body the walk will read before it gives up on that body.
51///
52/// `gcc/params.opt:300` gives `ipa-max-aa-steps` an `Init(25000)` for the same job, and the same
53/// number is used here because the thing it is protecting is the same: a generated function with
54/// a hundred thousand instructions in it should cost a compile that is linear in the module and
55/// not one that is quadratic in the worst function. A body over the limit gets the answer that
56/// cannot be wrong, which costs its callers precision and costs nobody a correct program.
57const MAX_STEPS: usize = 25_000;
58
59/// What a function does to one part of memory.
60///
61/// Ordered weakest first, so that adding up what a body does is a maximum and narrowing what was
62/// promised against what was worked out is a minimum. There is no fourth value for writing without
63/// reading: a summary that claimed it would have to be believed by a load as well as by a store,
64/// and nothing at this granularity has earned that.
65#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
66pub enum Effect {
67    /// Never touched.
68    #[default]
69    Nothing,
70    /// Read and not written.
71    Reads,
72    /// Written, and read as far as anything here can tell.
73    Writes,
74}
75
76impl Effect {
77    /// Both of these happened.
78    #[must_use]
79    pub fn and_then(self, other: Self) -> Self {
80        self.max(other)
81    }
82
83    /// Both of these are true of the same function, so the tighter one is.
84    #[must_use]
85    pub fn as_well_as(self, other: Self) -> Self {
86        self.min(other)
87    }
88
89    /// Whether the bytes can have been read.
90    #[must_use]
91    pub fn reads(self) -> bool {
92        self != Self::Nothing
93    }
94
95    /// Whether the bytes can have been written.
96    #[must_use]
97    pub fn writes(self) -> bool {
98        self == Self::Writes
99    }
100
101    /// What this reads as in a remark.
102    #[must_use]
103    pub fn name(self) -> &'static str {
104        match self {
105            Self::Nothing => "nothing",
106            Self::Reads => "reads",
107            Self::Writes => "writes",
108        }
109    }
110}
111
112/// What a function does to the object one parameter points at.
113#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
114pub struct Touch {
115    /// What happens to the bytes.
116    pub effect: Effect,
117    /// Whether the address itself ends up somewhere the caller cannot see, which is what stops
118    /// the caller reasoning about the object after the call returns.
119    pub escapes: bool,
120}
121
122impl Touch {
123    /// Never touched and never kept.
124    #[must_use]
125    pub fn nothing() -> Self {
126        Self::default()
127    }
128
129    /// Written and kept, which is what an unknown callee does to what it is handed.
130    #[must_use]
131    pub fn everything() -> Self {
132        Self { effect: Effect::Writes, escapes: true }
133    }
134
135    /// Both of these happened.
136    #[must_use]
137    pub fn and_then(self, other: Self) -> Self {
138        Self { effect: self.effect.and_then(other.effect), escapes: self.escapes || other.escapes }
139    }
140
141    /// Both of these are true of the same parameter, so the tighter one is.
142    #[must_use]
143    pub fn as_well_as(self, other: Self) -> Self {
144        Self {
145            effect: self.effect.as_well_as(other.effect),
146            escapes: self.escapes && other.escapes,
147        }
148    }
149}
150
151/// What a function does to memory.
152#[derive(Clone, Debug, PartialEq, Eq)]
153pub struct Summary {
154    outside: Effect,
155    params: Box<[Touch]>,
156}
157
158impl Summary {
159    /// A function that touches no memory at all, taking this many parameters.
160    #[must_use]
161    pub fn nothing(arity: usize) -> Self {
162        Self { outside: Effect::Nothing, params: vec![Touch::nothing(); arity].into() }
163    }
164
165    /// A function nothing is known about, taking this many parameters.
166    #[must_use]
167    pub fn everything(arity: usize) -> Self {
168        Self { outside: Effect::Writes, params: vec![Touch::everything(); arity].into() }
169    }
170
171    /// A function that does this to what it was not handed and that to each of its parameters.
172    ///
173    /// The general one, for a caller that has worked the answer out rather than read it off an
174    /// attribute. The common shapes have their own names above and below this.
175    #[must_use]
176    pub fn doing(outside: Effect, params: &[Touch]) -> Self {
177        Self { outside, params: params.into() }
178    }
179
180    /// A function that reads whatever it likes and writes nothing, taking this many parameters.
181    ///
182    /// `readonly`, which is `__attribute__((pure))` written as an effect. The addresses are kept
183    /// rather than dropped: a function that does not write cannot have put one anywhere a later
184    /// call could find it, but it can hand one back, and returning it is not writing.
185    #[must_use]
186    pub fn reading(arity: usize) -> Self {
187        Self::doing(Effect::Reads, &vec![Touch { effect: Effect::Reads, escapes: true }; arity])
188    }
189
190    /// A function that does what it likes to what it was handed and nothing to anything else,
191    /// taking this many parameters.
192    ///
193    /// `argmemonly`. It says where and not what, so everything that can happen to an argument is
194    /// taken to have happened to every one of them.
195    #[must_use]
196    pub fn through_arguments(arity: usize) -> Self {
197        Self::doing(Effect::Nothing, &vec![Touch::everything(); arity])
198    }
199
200    /// What it does to memory it was not handed: the globals, and anything reached through an
201    /// address that did not arrive as an argument.
202    #[must_use]
203    pub fn outside(&self) -> Effect {
204        self.outside
205    }
206
207    /// What it does to the object the parameter in this position points at.
208    ///
209    /// A position past the end is an argument no parameter stands for, which is a variadic call,
210    /// and the answer for one of those is that anything may have happened to it.
211    #[must_use]
212    pub fn param(&self, index: usize) -> Touch {
213        self.params.get(index).copied().unwrap_or_else(Touch::everything)
214    }
215
216    /// How many parameters it has an answer for.
217    #[must_use]
218    pub fn arity(&self) -> usize {
219        self.params.len()
220    }
221
222    /// Whether everything it touches, it reached through an argument.
223    ///
224    /// This is `__attribute__((access))`'s promise and the IR's `argmemonly`, worked out rather
225    /// than declared, and it is what lets [`crate::alias`] ask about the arguments one at a time
226    /// instead of giving up.
227    #[must_use]
228    pub fn only_through_arguments(&self) -> bool {
229        self.outside == Effect::Nothing
230    }
231
232    /// Whether it writes nothing anywhere, which is `readonly` worked out rather than declared.
233    #[must_use]
234    pub fn writes_nothing(&self) -> bool {
235        !self.outside.writes() && self.params.iter().all(|touch| !touch.effect.writes())
236    }
237
238    /// Whether it touches nothing anywhere, which is `readnone`.
239    #[must_use]
240    pub fn touches_nothing(&self) -> bool {
241        self.outside == Effect::Nothing
242            && self.params.iter().all(|touch| touch.effect == Effect::Nothing)
243    }
244
245    /// Both of these are true of the same function, so the tighter one is.
246    #[must_use]
247    fn as_well_as(&self, other: &Self) -> Self {
248        let arity = self.params.len().max(other.params.len());
249        let params = (0..arity).map(|at| self.param(at).as_well_as(other.param(at))).collect();
250        Self { outside: self.outside.as_well_as(other.outside), params }
251    }
252
253    /// Everything that happens to what is behind this pointer, whatever the pointer is.
254    fn touch_everything(&mut self) {
255        self.outside = Effect::Writes;
256        for touch in &mut self.params {
257            *touch = Touch::everything();
258        }
259    }
260}
261
262/// What is known about each function in the module.
263///
264/// Built once, because a summary belongs to the callee and there is one callee and many call
265/// sites. A pass holding one function and no module has [`Summaries::nothing`], which answers
266/// `None` to everything and leaves every caller with the conservative answer.
267#[derive(Clone, Debug, Default)]
268pub struct Summaries {
269    known: HashMap<Symbol, Summary>,
270}
271
272impl Summaries {
273    /// Nothing known about anything.
274    #[must_use]
275    pub fn nothing() -> Self {
276        Self::default()
277    }
278
279    /// What the attributes on each of the module's functions promise, before anything is read.
280    ///
281    /// The three that say something about memory are the three [`crate::alias`] already reads, and
282    /// what they promise is the floor the analysis starts from rather than something it can
283    /// contradict. A function with none of them gets no entry, which is not the same as an entry
284    /// saying it does everything: the difference is what lets [`summarize`] tell a promise it has
285    /// to keep from an absence it may fill in.
286    #[must_use]
287    pub fn of_module(module: &Module) -> Self {
288        let mut summaries = Self::default();
289        for id in module.funcs() {
290            let func = &module[id];
291            let arity = func.signature().params.len();
292            if let Some(summary) = from_attributes(func.attrs.set, arity) {
293                summaries.known.insert(func.name, summary);
294            }
295        }
296        summaries
297    }
298
299    /// What is known about this function, or nothing.
300    #[must_use]
301    pub fn of(&self, name: Symbol) -> Option<&Summary> {
302        self.known.get(&name)
303    }
304
305    /// What is known about what this call reaches.
306    ///
307    /// Only a direct call has an answer. A call through an address, inline assembly and a target
308    /// intrinsic are all names this cannot put to a body.
309    #[must_use]
310    pub fn at(&self, func: &Func, call: Inst) -> Option<&Summary> {
311        match Callee::of(func, call)? {
312            Callee::Direct(name) => self.of(name),
313            Callee::Indirect | Callee::Intrinsic(_) | Callee::Asm => None,
314        }
315    }
316
317    /// Writes down what the analysis worked out.
318    ///
319    /// Narrowed against whatever the attributes promised rather than written over it, because a
320    /// promise the body does not keep is still a promise the caller was told to rely on, and the
321    /// build that reports the function whose attribute was a lie wants both halves.
322    pub fn record(&mut self, name: Symbol, summary: Summary) {
323        let merged = match self.known.get(&name) {
324            Some(said) => said.as_well_as(&summary),
325            None => summary,
326        };
327        self.known.insert(name, merged);
328    }
329
330    /// How many functions there is an answer for.
331    #[must_use]
332    pub fn len(&self) -> usize {
333        self.known.len()
334    }
335
336    /// Whether nothing is known about anything.
337    #[must_use]
338    pub fn is_empty(&self) -> bool {
339        self.known.is_empty()
340    }
341}
342
343/// What the attributes a person wrote already promise, which is where the analysis starts.
344///
345/// None of the three says anything about whether the address is kept, so every parameter of a
346/// declared summary escapes. `const` is the one to watch: it promises the result comes out of the
347/// arguments and it does not promise the function did not hand one of them back, and a caller that
348/// took the escape bit at face value would go on to treat a local it had passed in as one nothing
349/// else can reach. Where there is a body the walk overrules this, because the walk looks.
350fn from_attributes(set: AttrSet, arity: usize) -> Option<Summary> {
351    if set.contains(AttrSet::READNONE) {
352        let params = vec![Touch { effect: Effect::Nothing, escapes: true }; arity];
353        return Some(Summary { outside: Effect::Nothing, params: params.into() });
354    }
355    // `readonly` writes nothing anywhere, so the effect is a read wherever it reaches. The
356    // addresses are left alone: a function that does not write cannot have put one anywhere a
357    // later call could find it, but it can hand one back, and returning it is not writing.
358    if set.contains(AttrSet::READONLY) {
359        return Some(Summary::reading(arity));
360    }
361    // `argmemonly` says where, not what, so everything that can happen to an argument may have.
362    if set.contains(AttrSet::ARGMEM_ONLY) {
363        return Some(Summary::through_arguments(arity));
364    }
365    None
366}
367
368/// Works out what every function in the module does to memory.
369///
370/// Callee before caller over the condensation, so a caller is read once its callees have settled,
371/// and round a cycle until nothing moves. The answers go into `summaries`, narrowed against
372/// whatever the attributes already promised.
373pub fn summarize(module: &Module, graph: &CallGraph, summaries: &mut Summaries) {
374    let arity = |node: Node| match graph.func(node) {
375        Some(id) => module[id].signature().params.len(),
376        None => 0,
377    };
378    let answers = graph.solve(
379        |node| Summary::nothing(arity(node)),
380        |node, answers| match graph.trusted_body(node) {
381            Some(id) => what_the_body_does(&module[id], graph, answers, summaries),
382            // A declaration, an ifunc, or a definition this link may replace with another
383            // object's. What the attributes promised still holds, and nothing else does.
384            None => match summaries.of(graph.name(node)) {
385                Some(said) => said.clone(),
386                None => Summary::everything(arity(node)),
387            },
388        },
389    );
390    for node in graph.nodes() {
391        if graph.trusted_body(node).is_none() {
392            continue;
393        }
394        summaries.record(graph.name(node), answers[node.index()].clone());
395    }
396}
397
398/// What one body does, given what everything it calls does.
399fn what_the_body_does(
400    func: &Func,
401    graph: &CallGraph,
402    answers: &[Summary],
403    said: &Summaries,
404) -> Summary {
405    let arity = func.signature().params.len();
406    let Some(entry) = func.entry() else { return Summary::everything(arity) };
407    // The parameter in position `n` of the signature is the parameter in position `n` of the entry
408    // block, and the argument in position `n` of a direct call to it. Every mapping below rests on
409    // that, so a function where it does not hold gets no answer rather than a wrong one.
410    if func[entry].params.len() != arity {
411        return Summary::everything(arity);
412    }
413    // What each call in this body reaches, worked out before anything else because the escape
414    // analysis below reads it. A call that keeps nothing it is handed is a call that did not let
415    // this function's own locals out, which is section 34.6's upgrade, and the answers are still
416    // moving while this asks, which is why it is these rather than a finished [`Summaries`].
417    let mut callees: HashMap<Inst, Summary> = HashMap::new();
418    let mut steps = 0;
419    for block in func.blocks() {
420        for inst in func.insts(block) {
421            steps += 1;
422            if steps > MAX_STEPS {
423                return Summary::everything(arity);
424            }
425            if let Some(summary) = what_that_call_does(func, inst, graph, answers, said) {
426                callees.insert(inst, summary);
427            }
428        }
429    }
430    let escapes = Escapes::with(func, |inst, index| {
431        callees.get(&inst).is_some_and(|summary| !summary.param(index).escapes)
432    });
433    let mut summary = Summary::nothing(arity);
434    for block in func.blocks() {
435        for inst in func.insts(block) {
436            // Before the call check, because an `asm goto` is a call by [`Callee::of`] and still
437            // hands operands to the blocks it can land in.
438            add_block_escapes(func, entry, &mut summary, inst);
439            if let Some(callee) = callees.get(&inst) {
440                add_call(func, entry, &escapes, &mut summary, inst, callee);
441                continue;
442            }
443            add_access(func, entry, &escapes, &mut summary, inst);
444            add_operand_escapes(func, entry, &mut summary, inst);
445        }
446    }
447    summary
448}
449
450/// The summary of what this instruction calls, for an instruction that is a call.
451fn what_that_call_does(
452    func: &Func,
453    inst: Inst,
454    graph: &CallGraph,
455    answers: &[Summary],
456    said: &Summaries,
457) -> Option<Summary> {
458    let callee = Callee::of(func, inst)?;
459    // An argument's position is a parameter's position only for the two forms where the operands
460    // are the arguments and nothing else. `call_indirect` puts the address it calls first, and it
461    // has no name to look up anyway.
462    let direct = matches!(func[inst].opcode, Opcode::Call | Opcode::TailCall);
463    let Callee::Direct(name) = callee else {
464        // Through an address, inline assembly, or an intrinsic whose name is all there is of it.
465        return Some(Summary::everything(0));
466    };
467    if !direct {
468        return Some(Summary::everything(0));
469    }
470    // Mid flight for anything in this component and settled for everything below it, which is
471    // what [`CallGraph::solve`] promises and is why nothing may read one of these until the
472    // component it is in has stopped moving.
473    let walked = graph.node(name).map(|node| answers[node.index()].clone());
474    let promised = said.of(name).cloned();
475    Some(match (walked, promised) {
476        (Some(walked), Some(promised)) => walked.as_well_as(&promised),
477        (Some(only), None) | (None, Some(only)) => only,
478        (None, None) => Summary::everything(0),
479    })
480}
481
482/// What a call does to this function's own memory, which is what it does to each thing it is given.
483fn add_call(
484    func: &Func,
485    entry: Block,
486    escapes: &Escapes,
487    summary: &mut Summary,
488    inst: Inst,
489    callee: &Summary,
490) {
491    summary.outside = summary.outside.and_then(callee.outside());
492    let args = &func[func[inst].args];
493    for (at, &arg) in args.iter().enumerate() {
494        if !func[arg].ty.is_ptr() {
495            continue;
496        }
497        let touch = callee.param(at);
498        if touch == Touch::nothing() {
499            continue;
500        }
501        match behind(func, entry, escapes, arg) {
502            // The callee's own answer for the address it was given is this function's answer for
503            // the parameter that address came from, escape bit and all. This is the one place a
504            // parameter does better than [`Escapes`] would: handing it to a call that does not let
505            // it out has not let it out.
506            Behind::Param(at) => summary.params[at] = summary.params[at].and_then(touch),
507            // A local nobody outside this function can reach, so whatever the callee did to it
508            // stayed inside this function and none of it is in the summary.
509            Behind::Private => {}
510            Behind::Outside => summary.outside = summary.outside.and_then(touch.effect),
511        }
512    }
513}
514
515/// What an instruction that is not a call does to memory.
516fn add_access(func: &Func, entry: Block, escapes: &Escapes, summary: &mut Summary, inst: Inst) {
517    let data = func[inst];
518    if !data.opcode.has_effects() || data.opcode.is_terminator() {
519        return;
520    }
521    // Neither the storage nor the addresses these reach are the program's, which is the whole of
522    // [`Opcode::touches_only_planes`]. Nothing a summary is read for can be one of them.
523    if data.opcode.touches_only_planes() {
524        return;
525    }
526    let args = &func[data.args];
527    let mut through = |at: usize, effect: Effect| match behind(func, entry, escapes, args[at]) {
528        Behind::Param(at) => {
529            summary.params[at].effect = summary.params[at].effect.and_then(effect);
530        }
531        Behind::Private => {}
532        Behind::Outside => summary.outside = summary.outside.and_then(effect),
533    };
534    match data.opcode {
535        // Storage this call made, which no caller has a name for.
536        Opcode::Alloca => {}
537        Opcode::Load | Opcode::AtomicLoad | Opcode::Prefetch => through(0, Effect::Reads),
538        Opcode::Store | Opcode::AtomicStore => through(1, Effect::Writes),
539        Opcode::AtomicRmw | Opcode::Cmpxchg | Opcode::Memset => through(0, Effect::Writes),
540        Opcode::Memcpy | Opcode::Memmove => {
541            through(0, Effect::Writes);
542            through(1, Effect::Reads);
543        }
544        // An opcode with effects that is not named here is one this was not written for, and the
545        // answer that cannot be wrong is that it did everything. A whitelist for section 8.6's
546        // reason: the next opcode added to the IR should make this pass say less, not miscompile.
547        _ => summary.touch_everything(),
548    }
549}
550
551/// Which parameters this instruction lets the address of out of the function.
552///
553/// The same walk [`Escapes`] does for a local, over the entry block's parameters instead, and a
554/// whitelist for the same reason. A call is not on the list, so a parameter handed to one would
555/// escape here, which is why [`add_call`] handles a call on its own and this is not asked about
556/// one.
557fn add_operand_escapes(func: &Func, entry: Block, summary: &mut Summary, inst: Inst) {
558    let data = func[inst];
559    for (at, &arg) in func[data.args].iter().enumerate() {
560        if keeps_address(data.opcode, at) {
561            continue;
562        }
563        if let Some(at) = param_behind(func, entry, arg) {
564            summary.params[at].escapes = true;
565        }
566    }
567}
568
569/// What a branch hands to a block parameter, which is where an address stops being one the walk
570/// above can follow back to anything, so the answer for it has to be given up here instead.
571fn add_block_escapes(func: &Func, entry: Block, summary: &mut Summary, inst: Inst) {
572    for call in func.successors(inst) {
573        for &arg in &func[call.args] {
574            if let Some(at) = param_behind(func, entry, arg) {
575                summary.params[at].escapes = true;
576            }
577        }
578    }
579}
580
581/// What the object behind an address is, as far as a summary cares.
582#[derive(Clone, Copy, Debug, PartialEq, Eq)]
583enum Behind {
584    /// The object the parameter in this position points at.
585    Param(usize),
586    /// Storage this function made that nothing outside it can reach.
587    Private,
588    /// Anything else, which the caller has to be told about as a whole.
589    Outside,
590}
591
592fn behind(func: &Func, entry: Block, escapes: &Escapes, pointer: Value) -> Behind {
593    match origin(func, pointer).0 {
594        Origin::Local(local) if !escapes.escaped(local) => Behind::Private,
595        Origin::Unknown(value) => match param_of(func, entry, value) {
596            Some(at) => Behind::Param(at),
597            None => Behind::Outside,
598        },
599        _ => Behind::Outside,
600    }
601}
602
603/// Which of this function's parameters an address came from, when it came from one.
604fn param_behind(func: &Func, entry: Block, pointer: Value) -> Option<usize> {
605    let Origin::Unknown(value) = origin(func, pointer).0 else { return None };
606    param_of(func, entry, value)
607}
608
609fn param_of(func: &Func, entry: Block, value: Value) -> Option<usize> {
610    match func[value].def {
611        Def::Param { block, index } if block == entry => Some(index as usize),
612        _ => None,
613    }
614}
615
616#[cfg(test)]
617mod tests {
618    use rucc_base::Interner;
619    use rucc_ir::{
620        Builder, Extra, Flags, InstData, MemInfo, MemOrder, Pic, Restrict, Signature, Type,
621    };
622    use rucc_target::{TargetInfo, Triple};
623
624    use super::{
625        AttrSet, CallGraph, Effect, Func, Module, Opcode, Summaries, Summary, Touch, Value,
626        summarize,
627    };
628
629    /// A four byte access of ordinary memory, which is what every test below uses.
630    fn access() -> MemInfo {
631        MemInfo {
632            size: 4,
633            align: 4,
634            owns: 4,
635            order: MemOrder::NotAtomic,
636            tbaa: None,
637            restrict: Restrict::NONE,
638        }
639    }
640
641    /// The address of a file scope variable, which is memory no caller handed over.
642    fn somewhere(build: &mut Builder<'_>, names: &mut Interner) -> Value {
643        let name = names.intern("v");
644        build.value(
645            InstData { extra: Extra::Symbol(name), ..InstData::new(Opcode::GlobalAddr) },
646            Type::PTR,
647        )
648    }
649
650    /// Four bytes of stack.
651    fn stack(build: &mut Builder<'_>) -> Value {
652        let mem = build.func().add_mem(access());
653        build.value(InstData { extra: Extra::Mem(mem), ..InstData::new(Opcode::Alloca) }, Type::PTR)
654    }
655
656    /// Reads four bytes from there.
657    fn reads(build: &mut Builder<'_>, addr: Value) -> Value {
658        build.load(Type::int(32), addr, access(), Flags::NONE)
659    }
660
661    /// Writes four zero bytes there.
662    fn writes(build: &mut Builder<'_>, addr: Value) {
663        let zero = build.iconst(Type::int(32), 0);
664        build.store(zero, addr, access(), Flags::NONE);
665    }
666
667    /// Calls that name with those pointers and throws away whatever came back.
668    fn calls(build: &mut Builder<'_>, names: &mut Interner, name: &str, args: &[Value]) {
669        let name = names.intern(name);
670        let params = vec![Type::PTR; args.len()];
671        let signature = build.func().add_signature(Signature::new().with_params(&params));
672        build.call(name, signature, args);
673    }
674
675    /// One function's body, given the values its parameters arrived as.
676    type Body = fn(&mut Interner, &mut Builder<'_>, &[Value]);
677
678    /// A module with the summaries worked out over it, which is what every test asks.
679    struct Worked {
680        names: Interner,
681        summaries: Summaries,
682    }
683
684    impl Worked {
685        /// Each function is its name, how many pointer parameters it takes, and its body.
686        fn out(bodies: &[(&str, usize, AttrSet, Option<Body>)]) -> Self {
687            let mut names = Interner::new();
688            let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
689            let mut module = Module::new(names.intern("t.c"), &target);
690            for &(name, arity, attrs, body) in bodies {
691                let params = vec![Type::PTR; arity];
692                let mut func = Func::new(names.intern(name), Signature::new().with_params(&params));
693                func.attrs.set = attrs;
694                if let Some(body) = body {
695                    let entry = func.create_block();
696                    let args: Vec<Value> =
697                        (0..arity).map(|_| func.append_param(entry, Type::PTR)).collect();
698                    let mut build = Builder::new(&mut func, entry);
699                    body(&mut names, &mut build, &args);
700                }
701                module.add_func(func);
702            }
703            let mut summaries = Summaries::of_module(&module);
704            summarize(&module, &CallGraph::of(&module, Pic::Executable), &mut summaries);
705            Self { names, summaries }
706        }
707
708        /// What was worked out about that name.
709        fn about(&mut self, name: &str) -> Summary {
710            let name = self.names.intern(name);
711            self.summaries.of(name).expect("a defined function has a summary").clone()
712        }
713    }
714
715    /// Does nothing but come back.
716    fn nothing(_: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
717        build.ret(&[]);
718    }
719
720    #[test]
721    fn a_body_that_goes_nowhere_near_memory_says_so() {
722        let mut worked = Worked::out(&[("f", 2, AttrSet::NONE, Some(nothing))]);
723        let f = worked.about("f");
724        assert!(f.touches_nothing());
725        assert!(f.writes_nothing());
726        assert!(f.only_through_arguments());
727        assert_eq!(f.arity(), 2);
728        assert_eq!(f.param(0), Touch::nothing());
729        assert_eq!(f.param(1), Touch::nothing());
730    }
731
732    #[test]
733    fn a_load_through_one_parameter_is_a_read_of_that_one() {
734        fn body(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
735            let value = reads(build, args[0]);
736            build.ret(&[value]);
737        }
738        let mut worked = Worked::out(&[("f", 2, AttrSet::NONE, Some(body))]);
739        let f = worked.about("f");
740        assert_eq!(f.param(0).effect, Effect::Reads);
741        assert_eq!(f.param(1).effect, Effect::Nothing);
742        assert_eq!(f.outside(), Effect::Nothing);
743        assert!(f.writes_nothing());
744        assert!(f.only_through_arguments());
745        assert!(!f.param(0).escapes, "dereferencing an address is not keeping it");
746    }
747
748    #[test]
749    fn a_store_through_one_parameter_is_a_write_of_that_one() {
750        fn body(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
751            writes(build, args[1]);
752            build.ret(&[]);
753        }
754        let mut worked = Worked::out(&[("f", 2, AttrSet::NONE, Some(body))]);
755        let f = worked.about("f");
756        assert_eq!(f.param(0).effect, Effect::Nothing);
757        assert_eq!(f.param(1).effect, Effect::Writes);
758        assert!(!f.writes_nothing());
759        assert!(f.only_through_arguments(), "the only thing it wrote, it was handed");
760    }
761
762    #[test]
763    fn a_global_is_not_anybody_s_parameter() {
764        fn body(names: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
765            let global = somewhere(build, names);
766            writes(build, global);
767            build.ret(&[]);
768        }
769        let mut worked = Worked::out(&[("f", 1, AttrSet::NONE, Some(body))]);
770        let f = worked.about("f");
771        assert_eq!(f.outside(), Effect::Writes);
772        assert_eq!(f.param(0), Touch::nothing());
773        assert!(!f.only_through_arguments());
774    }
775
776    #[test]
777    fn what_a_function_did_to_its_own_stack_is_nobody_else_s_business() {
778        fn body(_: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
779            let local = stack(build);
780            writes(build, local);
781            let value = reads(build, local);
782            build.ret(&[value]);
783        }
784        let mut worked = Worked::out(&[("f", 1, AttrSet::NONE, Some(body))]);
785        assert!(worked.about("f").touches_nothing());
786    }
787
788    #[test]
789    fn a_copy_writes_the_one_it_writes_and_reads_the_one_it_reads() {
790        fn body(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
791            let mem = build.func().add_mem(access());
792            let list = build.func().push_values(&[args[0], args[1]]);
793            build.inst(
794                InstData { args: list, extra: Extra::Mem(mem), ..InstData::new(Opcode::Memcpy) },
795                &[],
796            );
797            build.ret(&[]);
798        }
799        let mut worked = Worked::out(&[("f", 2, AttrSet::NONE, Some(body))]);
800        let f = worked.about("f");
801        assert_eq!(f.param(0).effect, Effect::Writes);
802        assert_eq!(f.param(1).effect, Effect::Reads);
803        assert!(f.only_through_arguments());
804        assert!(!f.param(0).escapes);
805        assert!(!f.param(1).escapes);
806    }
807
808    #[test]
809    fn an_opcode_this_was_not_written_for_did_everything() {
810        // The whitelist, which is the part of this that has to stay wrong in the safe direction
811        // when somebody adds an opcode to the IR and not to the list.
812        fn body(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
813            let mem = build.func().add_mem(access());
814            let list = build.func().push_values(&[args[0]]);
815            build.inst(
816                InstData { args: list, extra: Extra::Mem(mem), ..InstData::new(Opcode::VaStart) },
817                &[],
818            );
819            build.ret(&[]);
820        }
821        let mut worked = Worked::out(&[("f", 1, AttrSet::NONE, Some(body))]);
822        let f = worked.about("f");
823        assert_eq!(f.outside(), Effect::Writes);
824        assert_eq!(f.param(0), Touch::everything());
825    }
826
827    #[test]
828    fn what_the_callee_does_to_what_it_was_handed_is_what_the_caller_does() {
829        fn callee(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
830            writes(build, args[0]);
831            build.ret(&[]);
832        }
833        fn caller(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
834            calls(build, names, "callee", &[args[1]]);
835            build.ret(&[]);
836        }
837        let mut worked = Worked::out(&[
838            ("callee", 1, AttrSet::NONE, Some(callee)),
839            ("caller", 2, AttrSet::NONE, Some(caller)),
840        ]);
841        let caller = worked.about("caller");
842        // The second one, because that is the one that was passed along, and this is the whole
843        // point of the analysis: the call did not write memory, it wrote that one.
844        assert_eq!(caller.param(0), Touch::nothing());
845        assert_eq!(caller.param(1).effect, Effect::Writes);
846        assert_eq!(caller.outside(), Effect::Nothing);
847        assert!(caller.only_through_arguments());
848    }
849
850    #[test]
851    fn a_parameter_handed_to_something_that_does_not_keep_it_has_not_got_out() {
852        // The one place the walk does better than [`Escapes`] would on its own, which marks a
853        // local as gone the moment it is an argument of anything.
854        fn callee(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
855            let value = reads(build, args[0]);
856            build.ret(&[value]);
857        }
858        fn caller(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
859            calls(build, names, "callee", &[args[0]]);
860            build.ret(&[]);
861        }
862        let mut worked = Worked::out(&[
863            ("callee", 1, AttrSet::NONE, Some(callee)),
864            ("caller", 1, AttrSet::NONE, Some(caller)),
865        ]);
866        assert!(!worked.about("callee").param(0).escapes);
867        assert!(!worked.about("caller").param(0).escapes);
868    }
869
870    #[test]
871    fn a_parameter_written_down_somewhere_has_got_out() {
872        fn body(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
873            let global = somewhere(build, names);
874            build.store(args[0], global, access(), Flags::NONE);
875            build.ret(&[]);
876        }
877        let mut worked = Worked::out(&[("f", 1, AttrSet::NONE, Some(body))]);
878        let f = worked.about("f");
879        assert!(f.param(0).escapes);
880        // And what it did to the bytes behind that address is still nothing.
881        assert_eq!(f.param(0).effect, Effect::Nothing);
882        assert_eq!(f.outside(), Effect::Writes);
883    }
884
885    #[test]
886    fn a_parameter_a_caller_cannot_be_told_about_travels_up_as_a_write_of_everything() {
887        fn callee(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
888            let global = somewhere(build, names);
889            build.store(args[0], global, access(), Flags::NONE);
890            build.ret(&[]);
891        }
892        fn caller(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
893            calls(build, names, "callee", &[args[0]]);
894            build.ret(&[]);
895        }
896        let mut worked = Worked::out(&[
897            ("callee", 1, AttrSet::NONE, Some(callee)),
898            ("caller", 1, AttrSet::NONE, Some(caller)),
899        ]);
900        let caller = worked.about("caller");
901        assert!(caller.param(0).escapes, "the callee kept it, so the caller let it go");
902        assert_eq!(caller.outside(), Effect::Writes);
903    }
904
905    #[test]
906    fn what_a_callee_did_to_a_local_it_was_only_lent_stays_inside() {
907        // Section 34.6's upgrade to the escape analysis, read from the other end. Without it the
908        // address of `place` is gone the moment it is an argument, so what the callee wrote
909        // through it is a write of memory this function's own callers would have to be told
910        // about, and every one of them loses every load across this call.
911        fn callee(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
912            writes(build, args[0]);
913            build.ret(&[]);
914        }
915        fn caller(names: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
916            let place = stack(build);
917            calls(build, names, "callee", &[place]);
918            build.ret(&[]);
919        }
920        let mut worked = Worked::out(&[
921            ("callee", 1, AttrSet::NONE, Some(callee)),
922            ("caller", 0, AttrSet::NONE, Some(caller)),
923        ]);
924        assert_eq!(worked.about("callee").param(0).effect, Effect::Writes);
925        assert!(worked.about("caller").touches_nothing());
926    }
927
928    #[test]
929    fn a_local_the_callee_wrote_down_is_one_this_function_lost() {
930        // The same shape with the one difference that matters, which is that the callee keeps the
931        // address rather than only using it. Everything after the call has to give up on it.
932        fn callee(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
933            let global = somewhere(build, names);
934            build.store(args[0], global, access(), Flags::NONE);
935            build.ret(&[]);
936        }
937        fn caller(names: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
938            let place = stack(build);
939            calls(build, names, "callee", &[place]);
940            writes(build, place);
941            build.ret(&[]);
942        }
943        let mut worked = Worked::out(&[
944            ("callee", 1, AttrSet::NONE, Some(callee)),
945            ("caller", 0, AttrSet::NONE, Some(caller)),
946        ]);
947        assert!(worked.about("callee").param(0).escapes);
948        assert_eq!(worked.about("caller").outside(), Effect::Writes);
949    }
950
951    #[test]
952    fn a_call_through_an_address_did_everything_to_everything() {
953        fn body(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
954            calls(build, names, "unknown", &[args[0]]);
955            build.ret(&[]);
956        }
957        let mut worked = Worked::out(&[
958            ("unknown", 1, AttrSet::NONE, None),
959            ("f", 1, AttrSet::NONE, Some(body)),
960        ]);
961        let f = worked.about("f");
962        assert_eq!(f.outside(), Effect::Writes);
963        assert_eq!(f.param(0), Touch::everything());
964    }
965
966    #[test]
967    fn two_functions_that_call_each_other_and_touch_nothing_touch_nothing() {
968        // The reason the analysis starts optimistic. Reading these bodies once each, starting at
969        // the answer that cannot be wrong, would have each of them writing everything because the
970        // other one does, and neither would ever come back down.
971        fn ping(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
972            calls(build, names, "pong", &[args[0]]);
973            build.ret(&[]);
974        }
975        fn pong(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
976            calls(build, names, "ping", &[args[0]]);
977            build.ret(&[]);
978        }
979        let mut worked = Worked::out(&[
980            ("ping", 1, AttrSet::NONE, Some(ping)),
981            ("pong", 1, AttrSet::NONE, Some(pong)),
982        ]);
983        assert!(worked.about("ping").touches_nothing());
984        assert!(worked.about("pong").touches_nothing());
985    }
986
987    #[test]
988    fn a_write_inside_a_cycle_is_still_found() {
989        fn ping(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
990            calls(build, names, "pong", &[args[0]]);
991            build.ret(&[]);
992        }
993        fn pong(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
994            writes(build, args[0]);
995            calls(build, names, "ping", &[args[0]]);
996            build.ret(&[]);
997        }
998        let mut worked = Worked::out(&[
999            ("ping", 1, AttrSet::NONE, Some(ping)),
1000            ("pong", 1, AttrSet::NONE, Some(pong)),
1001        ]);
1002        assert_eq!(worked.about("ping").param(0).effect, Effect::Writes);
1003        assert_eq!(worked.about("pong").param(0).effect, Effect::Writes);
1004        assert!(worked.about("ping").only_through_arguments());
1005    }
1006
1007    #[test]
1008    fn a_declaration_is_whatever_it_promised_and_nothing_more() {
1009        let mut worked = Worked::out(&[
1010            ("plain", 1, AttrSet::NONE, None),
1011            ("none", 1, AttrSet::READNONE, None),
1012            ("only", 1, AttrSet::READONLY, None),
1013            ("args", 1, AttrSet::ARGMEM_ONLY, None),
1014        ]);
1015        let names = worked.names.intern("plain");
1016        assert!(worked.summaries.of(names).is_none(), "nobody promised anything about it");
1017        assert!(worked.about("none").touches_nothing());
1018        let only = worked.about("only");
1019        assert!(only.writes_nothing());
1020        assert_eq!(only.outside(), Effect::Reads);
1021        assert_eq!(only.param(0).effect, Effect::Reads);
1022        let args = worked.about("args");
1023        assert!(args.only_through_arguments());
1024        assert!(!args.writes_nothing());
1025        assert_eq!(args.param(0), Touch::everything());
1026    }
1027
1028    #[test]
1029    fn no_attribute_promises_the_address_was_not_kept() {
1030        // The one that has to be got right, because the escape bit is what takes a local out of
1031        // the escaped set and a `const` function is allowed to hand its argument straight back.
1032        let mut worked = Worked::out(&[
1033            ("none", 1, AttrSet::READNONE, None),
1034            ("only", 1, AttrSet::READONLY, None),
1035            ("args", 1, AttrSet::ARGMEM_ONLY, None),
1036        ]);
1037        for name in ["none", "only", "args"] {
1038            assert!(worked.about(name).param(0).escapes, "{name} promised no such thing");
1039        }
1040    }
1041
1042    #[test]
1043    fn a_promise_the_body_does_not_keep_is_still_a_promise() {
1044        // Somebody wrote the attribute and the callers were told to believe it. What the walk
1045        // found is recorded next to it rather than over it, so that the build that reports the
1046        // function whose attribute was a lie has both halves to report.
1047        fn body(names: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
1048            let global = somewhere(build, names);
1049            writes(build, global);
1050            build.ret(&[]);
1051        }
1052        let mut worked = Worked::out(&[("f", 1, AttrSet::READNONE, Some(body))]);
1053        assert!(worked.about("f").touches_nothing());
1054    }
1055
1056    #[test]
1057    fn an_entry_block_that_does_not_match_the_signature_gets_no_answer() {
1058        // Every mapping in the walk rests on position `n` of the signature being position `n` of
1059        // the entry block and position `n` of the argument list. A function where that does not
1060        // hold is one this cannot say anything about without risking saying it about the wrong
1061        // object.
1062        let mut names = Interner::new();
1063        let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1064        let mut module = Module::new(names.intern("t.c"), &target);
1065        let params = vec![Type::PTR; 2];
1066        let mut func = Func::new(names.intern("f"), Signature::new().with_params(&params));
1067        let entry = func.create_block();
1068        let only = func.append_param(entry, Type::PTR);
1069        let mut build = Builder::new(&mut func, entry);
1070        build.ret(&[only]);
1071        module.add_func(func);
1072
1073        let mut summaries = Summaries::of_module(&module);
1074        summarize(&module, &CallGraph::of(&module, Pic::Executable), &mut summaries);
1075        let f = summaries.of(names.intern("f")).expect("a defined function has a summary");
1076        assert_eq!(*f, Summary::everything(2));
1077    }
1078
1079    #[test]
1080    fn a_position_no_parameter_stands_for_is_a_position_anything_happened_to() {
1081        // Which is what a variadic call hands over, and what a call that disagrees with its
1082        // callee about how many arguments there are hands over.
1083        let summary = Summary::nothing(1);
1084        assert_eq!(summary.param(0), Touch::nothing());
1085        assert_eq!(summary.param(1), Touch::everything());
1086        assert_eq!(summary.param(9), Touch::everything());
1087    }
1088
1089    #[test]
1090    fn the_two_ways_of_combining_are_the_lattice_they_claim_to_be() {
1091        let all = [Effect::Nothing, Effect::Reads, Effect::Writes];
1092        for one in all {
1093            assert_eq!(one.and_then(one), one, "{one:?} is not idempotent");
1094            assert_eq!(one.as_well_as(one), one, "{one:?} is not idempotent");
1095            assert_eq!(one.and_then(Effect::Nothing), one, "nothing happening changes nothing");
1096            assert_eq!(one.as_well_as(Effect::Writes), one, "writing promises nothing");
1097            for two in all {
1098                assert_eq!(one.and_then(two), two.and_then(one), "{one:?} and {two:?} disagree");
1099                assert_eq!(one.as_well_as(two), two.as_well_as(one), "{one:?} and {two:?}");
1100                // Whatever the two of them did together covers whatever either of them did.
1101                let both = one.and_then(two);
1102                assert!(both.reads() >= one.reads());
1103                assert!(both.writes() >= one.writes());
1104            }
1105        }
1106        assert_eq!(Effect::Nothing.name(), "nothing");
1107        assert_eq!(Effect::Reads.name(), "reads");
1108        assert_eq!(Effect::Writes.name(), "writes");
1109    }
1110
1111    #[test]
1112    fn a_read_is_a_read_and_only_a_write_is_a_write() {
1113        assert!(!Effect::Nothing.reads());
1114        assert!(!Effect::Nothing.writes());
1115        assert!(Effect::Reads.reads());
1116        assert!(!Effect::Reads.writes());
1117        assert!(Effect::Writes.reads(), "a written byte is one the call could have looked at");
1118        assert!(Effect::Writes.writes());
1119    }
1120
1121    #[test]
1122    fn nothing_known_about_anything_is_a_thing_this_can_be() {
1123        let mut names = Interner::new();
1124        let summaries = Summaries::nothing();
1125        assert!(summaries.is_empty());
1126        assert_eq!(summaries.len(), 0);
1127        assert!(summaries.of(names.intern("f")).is_none());
1128    }
1129
1130    #[test]
1131    fn only_a_direct_call_has_a_summary_at_the_call_site() {
1132        let mut worked = Worked::out(&[("callee", 1, AttrSet::READNONE, None)]);
1133        let func = Worked::caller(&mut worked.names);
1134        let direct = func.1;
1135        assert!(worked.summaries.at(&func.0, direct).is_some_and(Summary::touches_nothing));
1136        assert!(worked.summaries.at(&func.0, func.2).is_none(), "through an address");
1137        assert!(worked.summaries.at(&func.0, func.3).is_none(), "not a call at all");
1138    }
1139
1140    impl Worked {
1141        /// A function calling `callee` directly, then through an address, then returning.
1142        fn caller(names: &mut Interner) -> (Func, super::Inst, super::Inst, super::Inst) {
1143            let mut func = Func::new(names.intern("caller"), Signature::new());
1144            let block = func.create_block();
1145            let mut build = Builder::new(&mut func, block);
1146            let signature = build.func().add_signature(Signature::new());
1147            let direct = build.call(names.intern("callee"), signature, &[]);
1148            let varargs = build.func().push_abis(&[]);
1149            let info =
1150                build.func().add_call(rucc_ir::CallInfo { callee: None, signature, varargs });
1151            let indirect = build.inst(
1152                InstData { extra: Extra::Call(info), ..InstData::new(Opcode::CallIndirect) },
1153                &[],
1154            );
1155            let end = build.ret(&[]);
1156            (func, direct, indirect, end)
1157        }
1158    }
1159}