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, Extra, Flags, Func, Inst, MemOrder, 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    // A `volatile` access is one the program asked for by name, and an atomic one is part of an
527    // order other threads can see, so neither is only a read or only a write of memory. Summarized
528    // as one, a callee that reads a `volatile` looks like it only reads, and `licm` moves the call
529    // out of the loop and makes it once (#2241). This is the line [`crate::purity`] draws, and a
530    // callee that crosses it did everything.
531    if data.flags.contains(Flags::VOLATILE)
532        || matches!(data.extra, Extra::Mem(mem) if func[mem].order != MemOrder::NotAtomic)
533    {
534        summary.touch_everything();
535        return;
536    }
537    let args = &func[data.args];
538    let mut through = |at: usize, effect: Effect| match behind(func, entry, escapes, args[at]) {
539        Behind::Param(at) => {
540            summary.params[at].effect = summary.params[at].effect.and_then(effect);
541        }
542        Behind::Private => {}
543        Behind::Outside => summary.outside = summary.outside.and_then(effect),
544    };
545    match data.opcode {
546        // Storage this call made, which no caller has a name for.
547        Opcode::Alloca => {}
548        Opcode::Load | Opcode::AtomicLoad | Opcode::Prefetch => through(0, Effect::Reads),
549        Opcode::Store | Opcode::AtomicStore => through(1, Effect::Writes),
550        Opcode::AtomicRmw | Opcode::Cmpxchg | Opcode::Memset => through(0, Effect::Writes),
551        Opcode::Memcpy | Opcode::Memmove => {
552            through(0, Effect::Writes);
553            through(1, Effect::Reads);
554        }
555        // An opcode with effects that is not named here is one this was not written for, and the
556        // answer that cannot be wrong is that it did everything. A whitelist for section 8.6's
557        // reason: the next opcode added to the IR should make this pass say less, not miscompile.
558        _ => summary.touch_everything(),
559    }
560}
561
562/// Which parameters this instruction lets the address of out of the function.
563///
564/// The same walk [`Escapes`] does for a local, over the entry block's parameters instead, and a
565/// whitelist for the same reason. A call is not on the list, so a parameter handed to one would
566/// escape here, which is why [`add_call`] handles a call on its own and this is not asked about
567/// one.
568fn add_operand_escapes(func: &Func, entry: Block, summary: &mut Summary, inst: Inst) {
569    let data = func[inst];
570    for (at, &arg) in func[data.args].iter().enumerate() {
571        if keeps_address(data.opcode, at) {
572            continue;
573        }
574        if let Some(at) = param_behind(func, entry, arg) {
575            summary.params[at].escapes = true;
576        }
577    }
578}
579
580/// What a branch hands to a block parameter, which is where an address stops being one the walk
581/// above can follow back to anything, so the answer for it has to be given up here instead.
582fn add_block_escapes(func: &Func, entry: Block, summary: &mut Summary, inst: Inst) {
583    for call in func.successors(inst) {
584        for &arg in &func[call.args] {
585            if let Some(at) = param_behind(func, entry, arg) {
586                summary.params[at].escapes = true;
587            }
588        }
589    }
590}
591
592/// What the object behind an address is, as far as a summary cares.
593#[derive(Clone, Copy, Debug, PartialEq, Eq)]
594enum Behind {
595    /// The object the parameter in this position points at.
596    Param(usize),
597    /// Storage this function made that nothing outside it can reach.
598    Private,
599    /// Anything else, which the caller has to be told about as a whole.
600    Outside,
601}
602
603fn behind(func: &Func, entry: Block, escapes: &Escapes, pointer: Value) -> Behind {
604    match origin(func, pointer).0 {
605        Origin::Local(local) if !escapes.escaped(local) => Behind::Private,
606        Origin::Unknown(value) => match param_of(func, entry, value) {
607            Some(at) => Behind::Param(at),
608            None => Behind::Outside,
609        },
610        _ => Behind::Outside,
611    }
612}
613
614/// Which of this function's parameters an address came from, when it came from one.
615fn param_behind(func: &Func, entry: Block, pointer: Value) -> Option<usize> {
616    let Origin::Unknown(value) = origin(func, pointer).0 else { return None };
617    param_of(func, entry, value)
618}
619
620fn param_of(func: &Func, entry: Block, value: Value) -> Option<usize> {
621    match func[value].def {
622        Def::Param { block, index } if block == entry => Some(index as usize),
623        _ => None,
624    }
625}
626
627#[cfg(test)]
628mod tests {
629    use rucc_base::Interner;
630    use rucc_ir::{
631        Builder, Extra, Flags, InstData, MemInfo, MemOrder, Pic, Restrict, Signature, Type,
632    };
633    use rucc_target::{TargetInfo, Triple};
634
635    use super::{
636        AttrSet, CallGraph, Effect, Func, Module, Opcode, Summaries, Summary, Touch, Value,
637        summarize,
638    };
639
640    /// A four byte access of ordinary memory, which is what every test below uses.
641    fn access() -> MemInfo {
642        MemInfo {
643            size: 4,
644            align: 4,
645            owns: 4,
646            order: MemOrder::NotAtomic,
647            tbaa: None,
648            restrict: Restrict::NONE,
649        }
650    }
651
652    /// The address of a file scope variable, which is memory no caller handed over.
653    fn somewhere(build: &mut Builder<'_>, names: &mut Interner) -> Value {
654        let name = names.intern("v");
655        build.value(
656            InstData { extra: Extra::Symbol(name), ..InstData::new(Opcode::GlobalAddr) },
657            Type::PTR,
658        )
659    }
660
661    /// Four bytes of stack.
662    fn stack(build: &mut Builder<'_>) -> Value {
663        let mem = build.func().add_mem(access());
664        build.value(InstData { extra: Extra::Mem(mem), ..InstData::new(Opcode::Alloca) }, Type::PTR)
665    }
666
667    /// Reads four bytes from there.
668    fn reads(build: &mut Builder<'_>, addr: Value) -> Value {
669        build.load(Type::int(32), addr, access(), Flags::NONE)
670    }
671
672    /// Writes four zero bytes there.
673    fn writes(build: &mut Builder<'_>, addr: Value) {
674        let zero = build.iconst(Type::int(32), 0);
675        build.store(zero, addr, access(), Flags::NONE);
676    }
677
678    /// Calls that name with those pointers and throws away whatever came back.
679    fn calls(build: &mut Builder<'_>, names: &mut Interner, name: &str, args: &[Value]) {
680        let name = names.intern(name);
681        let params = vec![Type::PTR; args.len()];
682        let signature = build.func().add_signature(Signature::new().with_params(&params));
683        build.call(name, signature, args);
684    }
685
686    /// One function's body, given the values its parameters arrived as.
687    type Body = fn(&mut Interner, &mut Builder<'_>, &[Value]);
688
689    /// A module with the summaries worked out over it, which is what every test asks.
690    struct Worked {
691        names: Interner,
692        summaries: Summaries,
693    }
694
695    impl Worked {
696        /// Each function is its name, how many pointer parameters it takes, and its body.
697        fn out(bodies: &[(&str, usize, AttrSet, Option<Body>)]) -> Self {
698            let mut names = Interner::new();
699            let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
700            let mut module = Module::new(names.intern("t.c"), &target);
701            for &(name, arity, attrs, body) in bodies {
702                let params = vec![Type::PTR; arity];
703                let mut func = Func::new(names.intern(name), Signature::new().with_params(&params));
704                func.attrs.set = attrs;
705                if let Some(body) = body {
706                    let entry = func.create_block();
707                    let args: Vec<Value> =
708                        (0..arity).map(|_| func.append_param(entry, Type::PTR)).collect();
709                    let mut build = Builder::new(&mut func, entry);
710                    body(&mut names, &mut build, &args);
711                }
712                module.add_func(func);
713            }
714            let mut summaries = Summaries::of_module(&module);
715            summarize(&module, &CallGraph::of(&module, Pic::Executable), &mut summaries);
716            Self { names, summaries }
717        }
718
719        /// What was worked out about that name.
720        fn about(&mut self, name: &str) -> Summary {
721            let name = self.names.intern(name);
722            self.summaries.of(name).expect("a defined function has a summary").clone()
723        }
724    }
725
726    /// Does nothing but come back.
727    fn nothing(_: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
728        build.ret(&[]);
729    }
730
731    #[test]
732    fn a_body_that_goes_nowhere_near_memory_says_so() {
733        let mut worked = Worked::out(&[("f", 2, AttrSet::NONE, Some(nothing))]);
734        let f = worked.about("f");
735        assert!(f.touches_nothing());
736        assert!(f.writes_nothing());
737        assert!(f.only_through_arguments());
738        assert_eq!(f.arity(), 2);
739        assert_eq!(f.param(0), Touch::nothing());
740        assert_eq!(f.param(1), Touch::nothing());
741    }
742
743    #[test]
744    fn a_load_through_one_parameter_is_a_read_of_that_one() {
745        fn body(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
746            let value = reads(build, args[0]);
747            build.ret(&[value]);
748        }
749        let mut worked = Worked::out(&[("f", 2, AttrSet::NONE, Some(body))]);
750        let f = worked.about("f");
751        assert_eq!(f.param(0).effect, Effect::Reads);
752        assert_eq!(f.param(1).effect, Effect::Nothing);
753        assert_eq!(f.outside(), Effect::Nothing);
754        assert!(f.writes_nothing());
755        assert!(f.only_through_arguments());
756        assert!(!f.param(0).escapes, "dereferencing an address is not keeping it");
757    }
758
759    /// A `volatile` read is not only a read, so the summary says the function did everything and
760    /// no caller moves or merges a call to it (#2241).
761    #[test]
762    fn a_volatile_load_is_everything() {
763        fn body(names: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
764            let global = somewhere(build, names);
765            let value = build.load(Type::int(32), global, access(), Flags::VOLATILE);
766            build.ret(&[value]);
767        }
768        let mut worked = Worked::out(&[("f", 1, AttrSet::NONE, Some(body))]);
769        let f = worked.about("f");
770        assert_eq!(f.outside(), Effect::Writes);
771        assert!(!f.writes_nothing());
772        assert_eq!(f.param(0), Touch::everything());
773    }
774
775    /// An acquire load is part of an order another thread can see, which is the spin wait that has
776    /// to see a flag change, so it is everything as well.
777    #[test]
778    fn an_atomic_load_is_everything() {
779        fn body(names: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
780            let global = somewhere(build, names);
781            let order = MemInfo { order: MemOrder::Acquire, ..access() };
782            let value = build.atomic_load(Type::int(32), global, order, Flags::NONE);
783            build.ret(&[value]);
784        }
785        let mut worked = Worked::out(&[("f", 0, AttrSet::NONE, Some(body))]);
786        let f = worked.about("f");
787        assert_eq!(f.outside(), Effect::Writes);
788        assert!(!f.writes_nothing());
789    }
790
791    #[test]
792    fn a_store_through_one_parameter_is_a_write_of_that_one() {
793        fn body(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
794            writes(build, args[1]);
795            build.ret(&[]);
796        }
797        let mut worked = Worked::out(&[("f", 2, AttrSet::NONE, Some(body))]);
798        let f = worked.about("f");
799        assert_eq!(f.param(0).effect, Effect::Nothing);
800        assert_eq!(f.param(1).effect, Effect::Writes);
801        assert!(!f.writes_nothing());
802        assert!(f.only_through_arguments(), "the only thing it wrote, it was handed");
803    }
804
805    #[test]
806    fn a_global_is_not_anybody_s_parameter() {
807        fn body(names: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
808            let global = somewhere(build, names);
809            writes(build, global);
810            build.ret(&[]);
811        }
812        let mut worked = Worked::out(&[("f", 1, AttrSet::NONE, Some(body))]);
813        let f = worked.about("f");
814        assert_eq!(f.outside(), Effect::Writes);
815        assert_eq!(f.param(0), Touch::nothing());
816        assert!(!f.only_through_arguments());
817    }
818
819    #[test]
820    fn what_a_function_did_to_its_own_stack_is_nobody_else_s_business() {
821        fn body(_: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
822            let local = stack(build);
823            writes(build, local);
824            let value = reads(build, local);
825            build.ret(&[value]);
826        }
827        let mut worked = Worked::out(&[("f", 1, AttrSet::NONE, Some(body))]);
828        assert!(worked.about("f").touches_nothing());
829    }
830
831    #[test]
832    fn a_copy_writes_the_one_it_writes_and_reads_the_one_it_reads() {
833        fn body(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
834            let mem = build.func().add_mem(access());
835            let list = build.func().push_values(&[args[0], args[1]]);
836            build.inst(
837                InstData { args: list, extra: Extra::Mem(mem), ..InstData::new(Opcode::Memcpy) },
838                &[],
839            );
840            build.ret(&[]);
841        }
842        let mut worked = Worked::out(&[("f", 2, AttrSet::NONE, Some(body))]);
843        let f = worked.about("f");
844        assert_eq!(f.param(0).effect, Effect::Writes);
845        assert_eq!(f.param(1).effect, Effect::Reads);
846        assert!(f.only_through_arguments());
847        assert!(!f.param(0).escapes);
848        assert!(!f.param(1).escapes);
849    }
850
851    #[test]
852    fn an_opcode_this_was_not_written_for_did_everything() {
853        // The whitelist, which is the part of this that has to stay wrong in the safe direction
854        // when somebody adds an opcode to the IR and not to the list.
855        fn body(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
856            let mem = build.func().add_mem(access());
857            let list = build.func().push_values(&[args[0]]);
858            build.inst(
859                InstData { args: list, extra: Extra::Mem(mem), ..InstData::new(Opcode::VaStart) },
860                &[],
861            );
862            build.ret(&[]);
863        }
864        let mut worked = Worked::out(&[("f", 1, AttrSet::NONE, Some(body))]);
865        let f = worked.about("f");
866        assert_eq!(f.outside(), Effect::Writes);
867        assert_eq!(f.param(0), Touch::everything());
868    }
869
870    #[test]
871    fn what_the_callee_does_to_what_it_was_handed_is_what_the_caller_does() {
872        fn callee(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
873            writes(build, args[0]);
874            build.ret(&[]);
875        }
876        fn caller(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
877            calls(build, names, "callee", &[args[1]]);
878            build.ret(&[]);
879        }
880        let mut worked = Worked::out(&[
881            ("callee", 1, AttrSet::NONE, Some(callee)),
882            ("caller", 2, AttrSet::NONE, Some(caller)),
883        ]);
884        let caller = worked.about("caller");
885        // The second one, because that is the one that was passed along, and this is the whole
886        // point of the analysis: the call did not write memory, it wrote that one.
887        assert_eq!(caller.param(0), Touch::nothing());
888        assert_eq!(caller.param(1).effect, Effect::Writes);
889        assert_eq!(caller.outside(), Effect::Nothing);
890        assert!(caller.only_through_arguments());
891    }
892
893    #[test]
894    fn a_parameter_handed_to_something_that_does_not_keep_it_has_not_got_out() {
895        // The one place the walk does better than [`Escapes`] would on its own, which marks a
896        // local as gone the moment it is an argument of anything.
897        fn callee(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
898            let value = reads(build, args[0]);
899            build.ret(&[value]);
900        }
901        fn caller(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
902            calls(build, names, "callee", &[args[0]]);
903            build.ret(&[]);
904        }
905        let mut worked = Worked::out(&[
906            ("callee", 1, AttrSet::NONE, Some(callee)),
907            ("caller", 1, AttrSet::NONE, Some(caller)),
908        ]);
909        assert!(!worked.about("callee").param(0).escapes);
910        assert!(!worked.about("caller").param(0).escapes);
911    }
912
913    #[test]
914    fn a_parameter_written_down_somewhere_has_got_out() {
915        fn body(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
916            let global = somewhere(build, names);
917            build.store(args[0], global, access(), Flags::NONE);
918            build.ret(&[]);
919        }
920        let mut worked = Worked::out(&[("f", 1, AttrSet::NONE, Some(body))]);
921        let f = worked.about("f");
922        assert!(f.param(0).escapes);
923        // And what it did to the bytes behind that address is still nothing.
924        assert_eq!(f.param(0).effect, Effect::Nothing);
925        assert_eq!(f.outside(), Effect::Writes);
926    }
927
928    #[test]
929    fn a_parameter_a_caller_cannot_be_told_about_travels_up_as_a_write_of_everything() {
930        fn callee(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
931            let global = somewhere(build, names);
932            build.store(args[0], global, access(), Flags::NONE);
933            build.ret(&[]);
934        }
935        fn caller(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
936            calls(build, names, "callee", &[args[0]]);
937            build.ret(&[]);
938        }
939        let mut worked = Worked::out(&[
940            ("callee", 1, AttrSet::NONE, Some(callee)),
941            ("caller", 1, AttrSet::NONE, Some(caller)),
942        ]);
943        let caller = worked.about("caller");
944        assert!(caller.param(0).escapes, "the callee kept it, so the caller let it go");
945        assert_eq!(caller.outside(), Effect::Writes);
946    }
947
948    #[test]
949    fn what_a_callee_did_to_a_local_it_was_only_lent_stays_inside() {
950        // Section 34.6's upgrade to the escape analysis, read from the other end. Without it the
951        // address of `place` is gone the moment it is an argument, so what the callee wrote
952        // through it is a write of memory this function's own callers would have to be told
953        // about, and every one of them loses every load across this call.
954        fn callee(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
955            writes(build, args[0]);
956            build.ret(&[]);
957        }
958        fn caller(names: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
959            let place = stack(build);
960            calls(build, names, "callee", &[place]);
961            build.ret(&[]);
962        }
963        let mut worked = Worked::out(&[
964            ("callee", 1, AttrSet::NONE, Some(callee)),
965            ("caller", 0, AttrSet::NONE, Some(caller)),
966        ]);
967        assert_eq!(worked.about("callee").param(0).effect, Effect::Writes);
968        assert!(worked.about("caller").touches_nothing());
969    }
970
971    #[test]
972    fn a_local_the_callee_wrote_down_is_one_this_function_lost() {
973        // The same shape with the one difference that matters, which is that the callee keeps the
974        // address rather than only using it. Everything after the call has to give up on it.
975        fn callee(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
976            let global = somewhere(build, names);
977            build.store(args[0], global, access(), Flags::NONE);
978            build.ret(&[]);
979        }
980        fn caller(names: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
981            let place = stack(build);
982            calls(build, names, "callee", &[place]);
983            writes(build, place);
984            build.ret(&[]);
985        }
986        let mut worked = Worked::out(&[
987            ("callee", 1, AttrSet::NONE, Some(callee)),
988            ("caller", 0, AttrSet::NONE, Some(caller)),
989        ]);
990        assert!(worked.about("callee").param(0).escapes);
991        assert_eq!(worked.about("caller").outside(), Effect::Writes);
992    }
993
994    #[test]
995    fn a_call_through_an_address_did_everything_to_everything() {
996        fn body(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
997            calls(build, names, "unknown", &[args[0]]);
998            build.ret(&[]);
999        }
1000        let mut worked = Worked::out(&[
1001            ("unknown", 1, AttrSet::NONE, None),
1002            ("f", 1, AttrSet::NONE, Some(body)),
1003        ]);
1004        let f = worked.about("f");
1005        assert_eq!(f.outside(), Effect::Writes);
1006        assert_eq!(f.param(0), Touch::everything());
1007    }
1008
1009    #[test]
1010    fn two_functions_that_call_each_other_and_touch_nothing_touch_nothing() {
1011        // The reason the analysis starts optimistic. Reading these bodies once each, starting at
1012        // the answer that cannot be wrong, would have each of them writing everything because the
1013        // other one does, and neither would ever come back down.
1014        fn ping(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
1015            calls(build, names, "pong", &[args[0]]);
1016            build.ret(&[]);
1017        }
1018        fn pong(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
1019            calls(build, names, "ping", &[args[0]]);
1020            build.ret(&[]);
1021        }
1022        let mut worked = Worked::out(&[
1023            ("ping", 1, AttrSet::NONE, Some(ping)),
1024            ("pong", 1, AttrSet::NONE, Some(pong)),
1025        ]);
1026        assert!(worked.about("ping").touches_nothing());
1027        assert!(worked.about("pong").touches_nothing());
1028    }
1029
1030    #[test]
1031    fn a_write_inside_a_cycle_is_still_found() {
1032        fn ping(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
1033            calls(build, names, "pong", &[args[0]]);
1034            build.ret(&[]);
1035        }
1036        fn pong(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
1037            writes(build, args[0]);
1038            calls(build, names, "ping", &[args[0]]);
1039            build.ret(&[]);
1040        }
1041        let mut worked = Worked::out(&[
1042            ("ping", 1, AttrSet::NONE, Some(ping)),
1043            ("pong", 1, AttrSet::NONE, Some(pong)),
1044        ]);
1045        assert_eq!(worked.about("ping").param(0).effect, Effect::Writes);
1046        assert_eq!(worked.about("pong").param(0).effect, Effect::Writes);
1047        assert!(worked.about("ping").only_through_arguments());
1048    }
1049
1050    #[test]
1051    fn a_declaration_is_whatever_it_promised_and_nothing_more() {
1052        let mut worked = Worked::out(&[
1053            ("plain", 1, AttrSet::NONE, None),
1054            ("none", 1, AttrSet::READNONE, None),
1055            ("only", 1, AttrSet::READONLY, None),
1056            ("args", 1, AttrSet::ARGMEM_ONLY, None),
1057        ]);
1058        let names = worked.names.intern("plain");
1059        assert!(worked.summaries.of(names).is_none(), "nobody promised anything about it");
1060        assert!(worked.about("none").touches_nothing());
1061        let only = worked.about("only");
1062        assert!(only.writes_nothing());
1063        assert_eq!(only.outside(), Effect::Reads);
1064        assert_eq!(only.param(0).effect, Effect::Reads);
1065        let args = worked.about("args");
1066        assert!(args.only_through_arguments());
1067        assert!(!args.writes_nothing());
1068        assert_eq!(args.param(0), Touch::everything());
1069    }
1070
1071    #[test]
1072    fn no_attribute_promises_the_address_was_not_kept() {
1073        // The one that has to be got right, because the escape bit is what takes a local out of
1074        // the escaped set and a `const` function is allowed to hand its argument straight back.
1075        let mut worked = Worked::out(&[
1076            ("none", 1, AttrSet::READNONE, None),
1077            ("only", 1, AttrSet::READONLY, None),
1078            ("args", 1, AttrSet::ARGMEM_ONLY, None),
1079        ]);
1080        for name in ["none", "only", "args"] {
1081            assert!(worked.about(name).param(0).escapes, "{name} promised no such thing");
1082        }
1083    }
1084
1085    #[test]
1086    fn a_promise_the_body_does_not_keep_is_still_a_promise() {
1087        // Somebody wrote the attribute and the callers were told to believe it. What the walk
1088        // found is recorded next to it rather than over it, so that the build that reports the
1089        // function whose attribute was a lie has both halves to report.
1090        fn body(names: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
1091            let global = somewhere(build, names);
1092            writes(build, global);
1093            build.ret(&[]);
1094        }
1095        let mut worked = Worked::out(&[("f", 1, AttrSet::READNONE, Some(body))]);
1096        assert!(worked.about("f").touches_nothing());
1097    }
1098
1099    #[test]
1100    fn an_entry_block_that_does_not_match_the_signature_gets_no_answer() {
1101        // Every mapping in the walk rests on position `n` of the signature being position `n` of
1102        // the entry block and position `n` of the argument list. A function where that does not
1103        // hold is one this cannot say anything about without risking saying it about the wrong
1104        // object.
1105        let mut names = Interner::new();
1106        let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1107        let mut module = Module::new(names.intern("t.c"), &target);
1108        let params = vec![Type::PTR; 2];
1109        let mut func = Func::new(names.intern("f"), Signature::new().with_params(&params));
1110        let entry = func.create_block();
1111        let only = func.append_param(entry, Type::PTR);
1112        let mut build = Builder::new(&mut func, entry);
1113        build.ret(&[only]);
1114        module.add_func(func);
1115
1116        let mut summaries = Summaries::of_module(&module);
1117        summarize(&module, &CallGraph::of(&module, Pic::Executable), &mut summaries);
1118        let f = summaries.of(names.intern("f")).expect("a defined function has a summary");
1119        assert_eq!(*f, Summary::everything(2));
1120    }
1121
1122    #[test]
1123    fn a_position_no_parameter_stands_for_is_a_position_anything_happened_to() {
1124        // Which is what a variadic call hands over, and what a call that disagrees with its
1125        // callee about how many arguments there are hands over.
1126        let summary = Summary::nothing(1);
1127        assert_eq!(summary.param(0), Touch::nothing());
1128        assert_eq!(summary.param(1), Touch::everything());
1129        assert_eq!(summary.param(9), Touch::everything());
1130    }
1131
1132    #[test]
1133    fn the_two_ways_of_combining_are_the_lattice_they_claim_to_be() {
1134        let all = [Effect::Nothing, Effect::Reads, Effect::Writes];
1135        for one in all {
1136            assert_eq!(one.and_then(one), one, "{one:?} is not idempotent");
1137            assert_eq!(one.as_well_as(one), one, "{one:?} is not idempotent");
1138            assert_eq!(one.and_then(Effect::Nothing), one, "nothing happening changes nothing");
1139            assert_eq!(one.as_well_as(Effect::Writes), one, "writing promises nothing");
1140            for two in all {
1141                assert_eq!(one.and_then(two), two.and_then(one), "{one:?} and {two:?} disagree");
1142                assert_eq!(one.as_well_as(two), two.as_well_as(one), "{one:?} and {two:?}");
1143                // Whatever the two of them did together covers whatever either of them did.
1144                let both = one.and_then(two);
1145                assert!(both.reads() >= one.reads());
1146                assert!(both.writes() >= one.writes());
1147            }
1148        }
1149        assert_eq!(Effect::Nothing.name(), "nothing");
1150        assert_eq!(Effect::Reads.name(), "reads");
1151        assert_eq!(Effect::Writes.name(), "writes");
1152    }
1153
1154    #[test]
1155    fn a_read_is_a_read_and_only_a_write_is_a_write() {
1156        assert!(!Effect::Nothing.reads());
1157        assert!(!Effect::Nothing.writes());
1158        assert!(Effect::Reads.reads());
1159        assert!(!Effect::Reads.writes());
1160        assert!(Effect::Writes.reads(), "a written byte is one the call could have looked at");
1161        assert!(Effect::Writes.writes());
1162    }
1163
1164    #[test]
1165    fn nothing_known_about_anything_is_a_thing_this_can_be() {
1166        let mut names = Interner::new();
1167        let summaries = Summaries::nothing();
1168        assert!(summaries.is_empty());
1169        assert_eq!(summaries.len(), 0);
1170        assert!(summaries.of(names.intern("f")).is_none());
1171    }
1172
1173    #[test]
1174    fn only_a_direct_call_has_a_summary_at_the_call_site() {
1175        let mut worked = Worked::out(&[("callee", 1, AttrSet::READNONE, None)]);
1176        let func = Worked::caller(&mut worked.names);
1177        let direct = func.1;
1178        assert!(worked.summaries.at(&func.0, direct).is_some_and(Summary::touches_nothing));
1179        assert!(worked.summaries.at(&func.0, func.2).is_none(), "through an address");
1180        assert!(worked.summaries.at(&func.0, func.3).is_none(), "not a call at all");
1181    }
1182
1183    impl Worked {
1184        /// A function calling `callee` directly, then through an address, then returning.
1185        fn caller(names: &mut Interner) -> (Func, super::Inst, super::Inst, super::Inst) {
1186            let mut func = Func::new(names.intern("caller"), Signature::new());
1187            let block = func.create_block();
1188            let mut build = Builder::new(&mut func, block);
1189            let signature = build.func().add_signature(Signature::new());
1190            let direct = build.call(names.intern("callee"), signature, &[]);
1191            let varargs = build.func().push_abis(&[]);
1192            let info =
1193                build.func().add_call(rucc_ir::CallInfo { callee: None, signature, varargs });
1194            let indirect = build.inst(
1195                InstData { extra: Extra::Call(info), ..InstData::new(Opcode::CallIndirect) },
1196                &[],
1197            );
1198            let end = build.ret(&[]);
1199            (func, direct, indirect, end)
1200        }
1201    }
1202}