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