Skip to main content

rucc_opt/
purity.rs

1//! What a call is allowed to do, which is the question every pass asks before it moves one.
2//!
3//! Design: section 41.3 of `spec/optimizer/41-correctness.md`. Documents 08, 17, 20 and 34 all
4//! depend on classifying calls and all of them left the completeness argument to that section.
5//!
6//! # Not a boolean, and not a lattice anybody may extend casually
7//!
8//! GCC's version is the nineteen `ECF_` bits at `gcc/tree-core.h:46`. The ones that decide
9//! anything are `ECF_CONST`, which is a result that depends only on the arguments, and `ECF_PURE`,
10//! which reads memory but does not write it, and then `ECF_LOOPING_CONST_OR_PURE`, which is the
11//! one worth noticing: a function's result can depend only on its arguments while the function
12//! still fails to return, and deleting a call to one of those is not the same decision. GCC keeps
13//! the two properties apart and so does [`Purity`].
14//!
15//! # Opaque is the default and it is the most conservative answer
16//!
17//! There is no `Unknown` here. Where nothing is known the answer is [`Purity::Opaque`], which
18//! permits everything, so a classifier that has not been taught about something produces a missed
19//! optimization rather than a wrong program. The library table below can only ever strengthen an
20//! answer, which means a name missing from it costs nothing and a wrong entry in it is a
21//! miscompilation. That is the bar for adding one.
22//!
23//! # Exhaustive over what is being called
24//!
25//! [`Facts::purity_of`] matches on [`Callee`] with no wildcard arm. Adding a new kind of callee to
26//! the IR is then a compile error here until somebody says what it can do, which is the one thing
27//! Rust offers a compiler over C++ in this file and is not worth giving away to save four lines.
28//!
29//! # What the user wrote and what the compiler worked out are separate
30//!
31//! A person writing `__attribute__((const))` on a function that is not const is asserting
32//! something, and the compiler honours the assertion. [`infer`] works out its own answer for the
33//! functions it can see, and that answer lives in a different field of [`Facts`], because keeping
34//! them apart is what makes it possible to check one against the other later. The two are combined
35//! with [`Purity::stronger`] at the point of use and nowhere else.
36//!
37//! # Working it out, which is section 34.2
38//!
39//! [`infer`] is the analysis. It reads bodies over the call graph, so a function whose body says
40//! nothing can still come out `const` because everything it calls is. Section 34.5 says how the
41//! cycles go: start every function at [`Purity::Const`], lower it when the body contradicts that,
42//! and do not read the answer for anything in a component until the component has stopped moving.
43//! Starting at the other end and raising would answer `opaque` for a pair of functions that call
44//! each other and do nothing else, which is the case the optimism is for.
45//!
46//! What a body is allowed to do is a whitelist, for the reason [`crate::alias::keeps_address`] is
47//! one: an opcode nobody has thought about here is [`Purity::Opaque`], so adding one to the IR
48//! costs a missed optimization rather than a wrong program. Two of the entries in it are worth
49//! naming. A load or a store of a local this function never let out of its hands is not an access
50//! of anything the caller can see, which is what lets an ordinary function with a temporary in it
51//! come out `const` on a compiler that has no SROA yet. And a cycle in the control flow graph is
52//! the looping half of 34.2: a function with one is `const, may not return` rather than `const`,
53//! because deleting a call to it would change whether the program terminates. Proving a particular
54//! loop finite is [`crate::scev`]'s and nothing here asks it yet.
55
56use std::collections::{HashMap, HashSet};
57
58use rucc_base::{Interner, Symbol};
59use rucc_ir::{AttrSet, Extra, Flags, Func, Inst, InstData, MemOrder, Module, Opcode, Value};
60
61use crate::alias::{Escapes, Origin, origin};
62use crate::callgraph::{CallGraph, Node};
63use crate::cfg::Cfg;
64
65/// What a call can do.
66///
67/// Five, because there are two questions with two answers each and then everything else. Does the
68/// result depend on memory, does the call come back, and if either answer is not known then the
69/// call is [`Purity::Opaque`] and no pass may assume anything at all.
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
71pub enum Purity {
72    /// Reads no memory, writes none, and comes back. `__attribute__((const))`, GCC's `ECF_CONST`.
73    Const,
74    /// Reads no memory and writes none, and may not come back. GCC's `ECF_CONST` together with
75    /// `ECF_LOOPING_CONST_OR_PURE`.
76    LoopingConst,
77    /// Reads memory, writes none, and comes back. `__attribute__((pure))`, GCC's `ECF_PURE`.
78    Pure,
79    /// Reads memory, writes none, and may not come back.
80    LoopingPure,
81    /// Anything, which is what a call is until something says otherwise.
82    Opaque,
83}
84
85impl Purity {
86    /// The five, for a test that walks them.
87    pub const ALL: [Self; 5] =
88        [Self::Const, Self::LoopingConst, Self::Pure, Self::LoopingPure, Self::Opaque];
89
90    /// How it reads in a dump.
91    #[must_use]
92    pub const fn as_str(self) -> &'static str {
93        match self {
94            Self::Const => "const",
95            Self::LoopingConst => "const, may not return",
96            Self::Pure => "pure",
97            Self::LoopingPure => "pure, may not return",
98            Self::Opaque => "opaque",
99        }
100    }
101
102    /// Whether the call may read memory the caller cares about.
103    #[must_use]
104    pub const fn reads_memory(self) -> bool {
105        match self {
106            Self::Const | Self::LoopingConst => false,
107            Self::Pure | Self::LoopingPure | Self::Opaque => true,
108        }
109    }
110
111    /// Whether the call may write memory.
112    ///
113    /// Only an opaque call may. That is what the other four have in common and it is most of what
114    /// makes them worth telling apart from the rest.
115    #[must_use]
116    pub const fn writes_memory(self) -> bool {
117        matches!(self, Self::Opaque)
118    }
119
120    /// Whether control is known to come back from the call.
121    ///
122    /// Not known and known not to are the same answer here, because both of them stop the same
123    /// transformations. Which of the two it is belongs to `noreturn`, which is an attribute on the
124    /// function rather than a level of this.
125    #[must_use]
126    pub const fn terminates(self) -> bool {
127        matches!(self, Self::Const | Self::Pure)
128    }
129
130    /// Whether the result is a function of the arguments and nothing else.
131    ///
132    /// This is what lets two calls with the same arguments become one call with no question asked
133    /// about what happened to memory in between. A [`Purity::Pure`] call can be folded the same way
134    /// when the caller can show nothing wrote memory between the two, which is a question for the
135    /// alias analysis and not for this.
136    #[must_use]
137    pub const fn depends_only_on_arguments(self) -> bool {
138        !self.reads_memory() && !self.writes_memory()
139    }
140
141    /// Whether a call whose result nothing reads may be removed.
142    ///
143    /// Both halves are needed. A call that writes memory does something even when its result is
144    /// thrown away, and a call that may not come back does something by not coming back, which is
145    /// why the looping levels exist at all.
146    #[must_use]
147    pub const fn can_be_deleted_when_unused(self) -> bool {
148        !self.writes_memory() && self.terminates()
149    }
150
151    /// The strongest thing true of both, for a caller that has two sources and believes each.
152    ///
153    /// [`Purity::Opaque`] is nothing known, so it gives way to whatever the other source says. Two
154    /// sources that each know half give the whole: a declaration saying the result comes out of the
155    /// arguments and an analysis saying the loop inside terminates add up to [`Purity::Const`].
156    #[must_use]
157    pub const fn stronger(self, other: Self) -> Self {
158        match (self, other) {
159            (Self::Opaque, it) | (it, Self::Opaque) => it,
160            (one, two) => Self::of(
161                one.reads_memory() && two.reads_memory(),
162                one.terminates() || two.terminates(),
163            ),
164        }
165    }
166
167    /// The strongest thing true of either, for a caller that has to cover both.
168    ///
169    /// Which is what a call site with more than one possible callee needs, and what a caller
170    /// summarising a whole function needs.
171    #[must_use]
172    pub const fn weaker(self, other: Self) -> Self {
173        match (self, other) {
174            (Self::Opaque, _) | (_, Self::Opaque) => Self::Opaque,
175            (one, two) => Self::of(
176                one.reads_memory() || two.reads_memory(),
177                one.terminates() && two.terminates(),
178            ),
179        }
180    }
181
182    /// The level with those two answers, which is the four that are not opaque.
183    const fn of(reads: bool, terminates: bool) -> Self {
184        match (reads, terminates) {
185            (false, true) => Self::Const,
186            (false, false) => Self::LoopingConst,
187            (true, true) => Self::Pure,
188            (true, false) => Self::LoopingPure,
189        }
190    }
191}
192
193impl std::fmt::Display for Purity {
194    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195        f.write_str(self.as_str())
196    }
197}
198
199/// What is being called.
200///
201/// The thing [`Facts::purity_of`] is exhaustive over. The closed intrinsics are not here because
202/// they are opcodes rather than calls and each carries its own meaning in the opcode.
203#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
204pub enum Callee {
205    /// A named function, which may or may not be one this module defines.
206    Direct(Symbol),
207    /// A call through an address.
208    Indirect,
209    /// A target-specific intrinsic, named on the instruction, which is the open half of the
210    /// intrinsic set and is where the vector builtins land.
211    Intrinsic(Symbol),
212    /// Inline assembly, including `asm goto`.
213    Asm,
214}
215
216impl Callee {
217    /// What this instruction calls, and `None` for an instruction that calls nothing.
218    #[must_use]
219    pub fn of(func: &Func, inst: Inst) -> Option<Self> {
220        let data = &func[inst];
221        match data.opcode {
222            Opcode::Call | Opcode::TailCall | Opcode::CallIndirect => match data.extra {
223                Extra::Call(at) => Some(match func[at].callee {
224                    Some(name) => Self::Direct(name),
225                    None => Self::Indirect,
226                }),
227                _ => Some(Self::Indirect),
228            },
229            Opcode::TargetIntrinsic => match data.extra {
230                Extra::Symbol(name) => Some(Self::Intrinsic(name)),
231                _ => Some(Self::Asm),
232            },
233            Opcode::InlineAsm => Some(Self::Asm),
234            // A call through an address whose arguments are a block rather than operands.
235            Opcode::Apply => Some(Self::Indirect),
236            _ => None,
237        }
238    }
239}
240
241/// What is known about the functions a call could reach.
242///
243/// Built once from the module, because the attributes belong to the callee and there is one callee
244/// and many call sites. A caller with no module has [`Facts::nothing`], which answers
245/// [`Purity::Opaque`] to everything and is correct.
246#[derive(Debug, Clone, Default)]
247pub struct Facts {
248    declared: HashMap<Symbol, AttrSet>,
249    inferred: HashMap<Symbol, Purity>,
250    from_the_library: HashMap<Symbol, Purity>,
251}
252
253impl Facts {
254    /// Nothing known about anything, which is what a pass holding one function has.
255    #[must_use]
256    pub fn nothing() -> Self {
257        Self::default()
258    }
259
260    /// What the module says about each of its functions.
261    ///
262    /// The interner is here for the library table, which is written in text because that is what
263    /// the C standard names. Nothing after this call needs it.
264    #[must_use]
265    pub fn of_module(module: &Module, names: &Interner) -> Self {
266        let mut facts = Self::default();
267        let mut defined = HashSet::new();
268        for id in module.funcs() {
269            let func = &module[id];
270            facts.declared.insert(func.name, func.attrs.set);
271            if !func.is_declaration() {
272                defined.insert(func.name);
273            }
274        }
275        // A name this module defines is not the library's, whatever it is spelled, because the
276        // definition in hand is the function that will be called.
277        for &name in facts.declared.keys() {
278            if defined.contains(&name) {
279                continue;
280            }
281            if let Some(purity) = library_purity(names.resolve(name)) {
282                facts.from_the_library.insert(name, purity);
283            }
284        }
285        facts
286    }
287
288    /// Turns off the whole library table, which is `-fno-builtin` and `-ffreestanding`.
289    ///
290    /// A freestanding program has no C library for the name to be the name of, and a program that
291    /// means its own thing by `strlen` is the reason the flag exists.
292    pub fn without_the_library(&mut self) {
293        self.from_the_library.clear();
294    }
295
296    /// Takes one name away from the table, which is `-fno-builtin-<name>`.
297    ///
298    /// What a build that means its own `memcpy` and the library's everything else writes, which is
299    /// what the kernel does for a handful of names.
300    pub fn not_the_library_name(&mut self, name: Symbol) {
301        self.from_the_library.remove(&name);
302    }
303
304    /// Records what document 34's analysis worked out about a function.
305    ///
306    /// A separate field from the declaration on purpose. The two are combined where they are read
307    /// and are never written over each other, so that a later build can check one against the other
308    /// and report the function whose attribute was a lie.
309    pub fn record_inferred(&mut self, name: Symbol, purity: Purity) {
310        self.inferred.insert(name, purity);
311    }
312
313    /// What the user declared about this function, on its own.
314    #[must_use]
315    pub fn declared(&self, name: Symbol) -> Purity {
316        match self.declared.get(&name) {
317            Some(&set) => from_attributes(set),
318            None => Purity::Opaque,
319        }
320    }
321
322    /// What analysis worked out about this function, on its own.
323    #[must_use]
324    pub fn inferred(&self, name: Symbol) -> Purity {
325        self.inferred.get(&name).copied().unwrap_or(Purity::Opaque)
326    }
327
328    /// What this call can do.
329    ///
330    /// The match has no wildcard arm and adding a kind of callee should keep it that way.
331    #[must_use]
332    pub fn purity_of(&self, callee: Callee) -> Purity {
333        match callee {
334            Callee::Direct(name) => self.of_name(name),
335            // The address could be anything with its own definition, including a function this
336            // module never saw. Document 34's call graph narrows this and until then it does not.
337            Callee::Indirect => Purity::Opaque,
338            // The open half of the intrinsic set is named rather than enumerated, so nothing here
339            // knows what one does. The closed half are opcodes and never reach this.
340            Callee::Intrinsic(_) => Purity::Opaque,
341            // A template the compiler does not read, with a clobber list it has to believe.
342            Callee::Asm => Purity::Opaque,
343        }
344    }
345
346    /// Everything known about a named function, from all three sources.
347    fn of_name(&self, name: Symbol) -> Purity {
348        self.what_was_said_about(name).stronger(self.inferred(name))
349    }
350
351    /// What the declaration and the library say, leaving out what an analysis worked out.
352    ///
353    /// Which is the question [`infer`] has to ask about a callee, because it is in the middle of
354    /// working the third source out and a half finished answer is not one anything may read.
355    /// Section 34.5 of `spec/optimizer/34-ipa.md`: the result is only sound after the fixpoint.
356    fn what_was_said_about(&self, name: Symbol) -> Purity {
357        match self.from_the_library.get(&name) {
358            Some(&known) => self.declared(name).stronger(known),
359            None => self.declared(name),
360        }
361    }
362}
363
364/// Works out what each function in this module does, and writes it into the facts.
365///
366/// Section 34.2, which is `gcc/ipa-pure-const.cc` at 2,415 lines. The header of that file says
367/// when it may run and the same applies here: "This must be run after inlining decisions have been
368/// made since otherwise, the local sets will not contain information that is consistent with post
369/// inlined state." There is no inliner yet, so today it runs after nothing, and when there is one
370/// this has to move below it.
371///
372/// The graph is the caller's because it is the module's rather than this analysis's, and the three
373/// analyses section 34.6 lists after this one want the same graph. Building it here would mean
374/// building it four times.
375///
376/// Only an answer that is not [`Purity::Opaque`] is recorded. Opaque is what [`Facts::inferred`]
377/// says about a name it has never heard of, so writing it down would put four thousand entries in
378/// a map to say the thing an empty map already says.
379pub fn infer(module: &Module, graph: &CallGraph, facts: &mut Facts) {
380    // Optimistic, per section 34.5: "Starting optimistic (`const`) and lowering on contradiction
381    // gives the right answer". It is only the cycles that notice. Outside one, every callee of a
382    // node has settled before the node is asked, so what the starting value was never came up.
383    let answers = graph.solve(
384        |_| Purity::Const,
385        |node, answers| match graph.trusted_body(node) {
386            // The flag is the cheap half of the same answer the walk below would reach. A body
387            // that calls through an address, or contains inline assembly, or contains a target
388            // intrinsic nobody has written down the meaning of, is opaque either way, and this
389            // saves walking it to find that out.
390            Some(id) if !graph.reaches_unknown(node) => {
391                let purity = what_the_body_does(&module[id], graph, answers, facts);
392                // Recursion is the other way a function fails to come back, and it is not a cycle
393                // in any one body's control flow graph, so the walk below cannot see it. A
394                // function that calls itself, directly or round a ring of other functions, has no
395                // more of a promise to terminate than one with a loop in it.
396                match in_a_cycle(graph, node) {
397                    true => purity.weaker(Purity::LoopingConst),
398                    false => purity,
399                }
400            }
401            // Either there is no body, or there is one this link may replace with another
402            // object's, which is section 34.1's gate and is the graph's to answer.
403            _ => Purity::Opaque,
404        },
405    );
406    for node in graph.nodes() {
407        let purity = answers[node.index()];
408        if purity != Purity::Opaque {
409            facts.record_inferred(graph.name(node), purity);
410        }
411    }
412}
413
414/// What one body adds up to, given what everything it calls came out as.
415///
416/// [`Purity::Const`] is the identity of [`Purity::weaker`], so an instruction that says nothing
417/// answers with it and the combining needs no special case for the instructions that are most of
418/// a function.
419fn what_the_body_does(func: &Func, graph: &CallGraph, answers: &[Purity], facts: &Facts) -> Purity {
420    let mut so_far = Purity::Const;
421    // Built when the first access wants it and not before, because most of what this walks over
422    // is a function that reaches its first opaque instruction and stops.
423    let mut escapes: Option<Escapes> = None;
424    for block in func.blocks() {
425        for inst in func.insts(block) {
426            let data = func[inst];
427            so_far = so_far.weaker(if let Some(callee) = Callee::of(func, inst) {
428                what_that_call_does(callee, graph, answers, facts)
429            } else if !data.opcode.has_effects() || data.opcode.is_terminator() {
430                // Arithmetic, and the branches and returns that hold the function together.
431                // `asm goto` is a terminator and does not reach here, because it is a callee.
432                Purity::Const
433            } else {
434                match data.opcode {
435                    // Moving the stack pointer. The caller cannot name the storage and it is
436                    // gone by the time control is back with them.
437                    Opcode::Alloca => Purity::Const,
438                    Opcode::Load if plain(func, data) => {
439                        let escapes = escapes.get_or_insert_with(|| Escapes::of(func));
440                        match ours(func, escapes, func[data.args][0]) {
441                            true => Purity::Const,
442                            false => Purity::Pure,
443                        }
444                    }
445                    Opcode::Store if plain(func, data) => {
446                        let escapes = escapes.get_or_insert_with(|| Escapes::of(func));
447                        match ours(func, escapes, func[data.args][1]) {
448                            true => Purity::Const,
449                            false => Purity::Opaque,
450                        }
451                    }
452                    // Everything else, which is where a `volatile` access, an atomic one of
453                    // either kind, a `memcpy`, a `va_arg` and every opcode added after this was
454                    // written all land. A whitelist, for the reason
455                    // [`crate::alias::keeps_address`] is one.
456                    _ => Purity::Opaque,
457                }
458            });
459            if so_far == Purity::Opaque {
460                return Purity::Opaque;
461            }
462        }
463    }
464    // Last, because it costs a graph and most functions have already answered by here.
465    match has_a_cycle(func) {
466        true => so_far.weaker(Purity::LoopingConst),
467        false => so_far,
468    }
469}
470
471/// What a call site adds up to.
472///
473/// The declaration is combined with the worked out answer rather than replacing it, which is the
474/// case a person writing `__attribute__((const))` on a function that loops is asking for: the
475/// assertion is honoured, and the half of the answer the assertion said nothing about is the half
476/// the body was read for.
477fn what_that_call_does(
478    callee: Callee,
479    graph: &CallGraph,
480    answers: &[Purity],
481    facts: &Facts,
482) -> Purity {
483    let Callee::Direct(name) = callee else {
484        // An address, inline assembly, or an intrinsic named rather than enumerated. The same
485        // three [`Facts::purity_of`] gives up on, and for the same reasons.
486        return Purity::Opaque;
487    };
488    let said = facts.what_was_said_about(name);
489    match graph.node(name) {
490        Some(node) => said.stronger(answers[node.index()]),
491        // Every name a body calls has a node, so this is unreachable with a graph built from the
492        // module being read. A caller that hands over somebody else's graph gets the conservative
493        // answer rather than a panic.
494        None => said,
495    }
496}
497
498/// Whether this access is one that nothing outside the program's own data flow can tell happened.
499///
500/// A `volatile` access is one the program asked for by name. An atomic access is part of an order
501/// other threads can see, at every strength, which is the same line [`crate::dce`] draws and for
502/// the same reason.
503fn plain(func: &Func, data: InstData) -> bool {
504    if data.flags.contains(Flags::VOLATILE) {
505        return false;
506    }
507    match data.extra {
508        Extra::Mem(mem) => func[mem].order == MemOrder::NotAtomic,
509        _ => false,
510    }
511}
512
513/// Whether that address is in a local this function never let out of its hands.
514///
515/// Storage like that is not memory as far as the caller is concerned. Nothing outside the function
516/// had the address to read it with before the function started and nothing has it afterwards, so
517/// what the function put there is not a write anybody can see and what it took back out is not a
518/// read of anything that could have changed.
519fn ours(func: &Func, escapes: &Escapes, pointer: Value) -> bool {
520    matches!(origin(func, pointer).0, Origin::Local(local) if !escapes.escaped(local))
521}
522
523/// Whether this function can end up calling itself.
524///
525/// Which is a component with more than one function in it, or a component of one that has an edge
526/// to itself. The condensation has already worked this out and the second case is the one it does
527/// not record, because a single function is a component whether or not it calls itself.
528fn in_a_cycle(graph: &CallGraph, node: Node) -> bool {
529    graph.components()[graph.component_of(node)].len() > 1 || graph.calls(node).contains(&node)
530}
531
532/// Whether control can arrive at a block it has already been at.
533///
534/// A reverse postorder is a topological order exactly when the graph is acyclic, so an edge that
535/// arrives at a block ranked no later than the one it leaves is an edge that closes a cycle, and a
536/// function with no such edge has none. That is cheaper than the loop forest and is all this
537/// wants: one cycle anywhere is enough to stop the function promising to come back, whether or not
538/// it is a loop in the sense [`crate::loops`] means.
539///
540/// A block the entry cannot reach has no rank and is skipped. Control never arrives there, so a
541/// cycle among such blocks is a cycle the program cannot go round.
542fn has_a_cycle(func: &Func) -> bool {
543    let cfg = Cfg::new(func);
544    func.blocks().any(|block| {
545        let Some(from) = cfg.rank(block) else { return false };
546        cfg.successors(block).iter().any(|&to| cfg.rank(to).is_some_and(|to| to <= from))
547    })
548}
549
550/// What an attribute set says on its own.
551///
552/// `noreturn` is what turns either level into its looping one. A call that does not come back does
553/// something by not coming back, however little it touches, and that is the case
554/// `ECF_LOOPING_CONST_OR_PURE` exists for.
555fn from_attributes(set: AttrSet) -> Purity {
556    let terminates = !set.contains(AttrSet::NORETURN);
557    if set.contains(AttrSet::READNONE) {
558        return Purity::of(false, terminates);
559    }
560    if set.contains(AttrSet::READONLY) {
561        return Purity::of(true, terminates);
562    }
563    Purity::Opaque
564}
565
566/// What the C standard library functions do, for the ones where the answer is not arguable.
567///
568/// Only entries that strengthen the answer are here, so a name that is missing costs a missed
569/// optimization and a name that is wrong costs a wrong program. Nothing that writes memory, sets
570/// `errno`, touches a stream or allocates belongs in here, which rules out most of the library and
571/// all of `<math.h>`, since a math function sets `errno` unless the command line says it does not.
572///
573/// Sorted, and a test checks that it is sorted and says each name once.
574const LIBRARY: &[(&str, Purity)] = &[
575    ("abs", Purity::Const),
576    ("imaxabs", Purity::Const),
577    ("labs", Purity::Const),
578    ("llabs", Purity::Const),
579    ("memchr", Purity::Pure),
580    ("memcmp", Purity::Pure),
581    ("strchr", Purity::Pure),
582    ("strcmp", Purity::Pure),
583    ("strcspn", Purity::Pure),
584    ("strlen", Purity::Pure),
585    ("strncmp", Purity::Pure),
586    ("strnlen", Purity::Pure),
587    ("strpbrk", Purity::Pure),
588    ("strrchr", Purity::Pure),
589    ("strspn", Purity::Pure),
590    ("strstr", Purity::Pure),
591];
592
593/// What the library says about a name, under either spelling.
594///
595/// The `__builtin_` prefix is the program saying which function it means, so it reaches the same
596/// entry. Whether the plain spelling is allowed to is decided before this is called.
597fn library_purity(name: &str) -> Option<Purity> {
598    let name = name.strip_prefix("__builtin_").unwrap_or(name);
599    LIBRARY.binary_search_by_key(&name, |&(named, _)| named).ok().map(|at| LIBRARY[at].1)
600}
601
602#[cfg(test)]
603mod tests {
604    use rucc_base::Interner;
605    use rucc_ir::{
606        AsmInfo, AttrSet, BlockCallList, Builder, CallInfo, Extra, Flags, Func, InstData, IntPred,
607        MemInfo, MemOrder, Module, Opcode, Pic, Restrict, Signature, Type, Value,
608    };
609    use rucc_target::{TargetInfo, Triple};
610
611    use super::{CallGraph, Callee, Facts, LIBRARY, Purity, infer};
612
613    /// A module with those functions in it, declared unless they are asked to have a body.
614    fn module(named: &[(&str, bool, AttrSet)]) -> (Interner, Module) {
615        let mut names = Interner::new();
616        let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
617        let mut module = Module::new(names.intern("t.c"), &target);
618        for &(name, defined, attrs) in named {
619            let mut func = Func::new(names.intern(name), Signature::new());
620            func.attrs.set = attrs;
621            if defined {
622                let block = func.create_block();
623                let mut build = Builder::new(&mut func, block);
624                let zero = build.iconst(Type::int(32), 0);
625                build.ret(&[zero]);
626            }
627            module.add_func(func);
628        }
629        (names, module)
630    }
631
632    /// What the module says about a name.
633    fn purity(names: &mut Interner, module: &Module, name: &str) -> Purity {
634        let facts = Facts::of_module(module, names);
635        let symbol = names.intern(name);
636        facts.purity_of(Callee::Direct(symbol))
637    }
638
639    #[test]
640    fn a_function_nobody_promised_anything_about_is_opaque() {
641        let (mut names, module) = module(&[("f", true, AttrSet::NONE)]);
642        assert_eq!(purity(&mut names, &module, "f"), Purity::Opaque);
643    }
644
645    #[test]
646    fn a_name_this_module_never_heard_of_is_opaque_as_well() {
647        let (mut names, module) = module(&[("f", true, AttrSet::NONE)]);
648        let facts = Facts::of_module(&module, &names);
649        assert_eq!(facts.purity_of(Callee::Direct(names.intern("g"))), Purity::Opaque);
650    }
651
652    #[test]
653    fn the_const_attribute_is_honoured_because_the_user_asserted_it() {
654        let (mut names, module) = module(&[("f", false, AttrSet::READNONE)]);
655        let purity = purity(&mut names, &module, "f");
656        assert_eq!(purity, Purity::Const);
657        assert!(purity.depends_only_on_arguments());
658        assert!(purity.can_be_deleted_when_unused());
659    }
660
661    #[test]
662    fn the_pure_attribute_reads_memory_and_writes_none() {
663        let (mut names, module) = module(&[("f", false, AttrSet::READONLY)]);
664        let purity = purity(&mut names, &module, "f");
665        assert_eq!(purity, Purity::Pure);
666        assert!(purity.reads_memory());
667        assert!(!purity.writes_memory());
668        assert!(!purity.depends_only_on_arguments());
669        assert!(purity.can_be_deleted_when_unused());
670    }
671
672    #[test]
673    fn a_const_function_that_does_not_come_back_may_not_be_deleted() {
674        // Which is the whole reason the looping levels are in the enum. Its result depends only
675        // on its arguments and the call still does something, which is not come back.
676        let (mut names, module) =
677            module(&[("f", false, AttrSet::READNONE.union(AttrSet::NORETURN))]);
678        let purity = purity(&mut names, &module, "f");
679        assert_eq!(purity, Purity::LoopingConst);
680        assert!(purity.depends_only_on_arguments());
681        assert!(!purity.can_be_deleted_when_unused());
682    }
683
684    #[test]
685    fn nothing_that_is_not_a_direct_call_is_anything_but_opaque() {
686        let (mut names, module) = module(&[("f", true, AttrSet::READNONE)]);
687        let facts = Facts::of_module(&module, &names);
688        // Even though the module holds a const function of that name, none of these is known to
689        // be it, and each is opaque for its own reason.
690        assert_eq!(facts.purity_of(Callee::Indirect), Purity::Opaque);
691        assert_eq!(facts.purity_of(Callee::Asm), Purity::Opaque);
692        let vector = names.intern("__builtin_ia32_paddb");
693        assert_eq!(facts.purity_of(Callee::Intrinsic(vector)), Purity::Opaque);
694    }
695
696    #[test]
697    fn the_library_names_are_known_under_both_spellings() {
698        let (mut names, module) = module(&[
699            ("strlen", false, AttrSet::NONE),
700            ("abs", false, AttrSet::NONE),
701            ("__builtin_strlen", false, AttrSet::NONE),
702            ("printf", false, AttrSet::NONE),
703        ]);
704        assert_eq!(purity(&mut names, &module, "strlen"), Purity::Pure);
705        assert_eq!(purity(&mut names, &module, "__builtin_strlen"), Purity::Pure);
706        assert_eq!(purity(&mut names, &module, "abs"), Purity::Const);
707        // Everything else in the library, which is most of it, is opaque and stays that way.
708        assert_eq!(purity(&mut names, &module, "printf"), Purity::Opaque);
709    }
710
711    #[test]
712    fn a_module_that_defines_strlen_means_its_own() {
713        let (mut names, module) = module(&[("strlen", true, AttrSet::NONE)]);
714        assert_eq!(purity(&mut names, &module, "strlen"), Purity::Opaque);
715    }
716
717    #[test]
718    fn no_builtin_takes_the_table_away_and_the_named_form_takes_one_entry() {
719        let (mut names, module) =
720            module(&[("strlen", false, AttrSet::NONE), ("abs", false, AttrSet::NONE)]);
721        let mut facts = Facts::of_module(&module, &names);
722        let strlen = names.intern("strlen");
723        let abs = names.intern("abs");
724        facts.not_the_library_name(strlen);
725        assert_eq!(facts.purity_of(Callee::Direct(strlen)), Purity::Opaque);
726        assert_eq!(facts.purity_of(Callee::Direct(abs)), Purity::Const);
727        facts.without_the_library();
728        assert_eq!(facts.purity_of(Callee::Direct(abs)), Purity::Opaque);
729    }
730
731    #[test]
732    fn what_the_user_wrote_and_what_analysis_worked_out_are_kept_apart() {
733        let (mut names, module) =
734            module(&[("f", true, AttrSet::READNONE.union(AttrSet::NORETURN))]);
735        let mut facts = Facts::of_module(&module, &names);
736        let f = names.intern("f");
737        assert_eq!(facts.declared(f), Purity::LoopingConst);
738        assert_eq!(facts.inferred(f), Purity::Opaque);
739        // Document 34 gets to say the loop inside it terminates. The declaration said the result
740        // comes out of the arguments. Together that is const, and each is still readable on its
741        // own, which is what makes checking one against the other possible later.
742        facts.record_inferred(f, Purity::Pure);
743        assert_eq!(facts.declared(f), Purity::LoopingConst);
744        assert_eq!(facts.inferred(f), Purity::Pure);
745        assert_eq!(facts.purity_of(Callee::Direct(f)), Purity::Const);
746    }
747
748    #[test]
749    fn what_an_instruction_calls_is_read_off_the_instruction() {
750        let mut names = Interner::new();
751        let mut func = Func::new(names.intern("caller"), Signature::new());
752        let block = func.create_block();
753        let mut build = Builder::new(&mut func, block);
754        let signature = build.func().add_signature(Signature::new());
755        let direct = build.call(names.intern("f"), signature, &[]);
756        let varargs = build.func().push_abis(&[]);
757        let info = build.func().add_call(CallInfo { callee: None, signature, varargs });
758        let indirect = build.inst(
759            InstData { extra: Extra::Call(info), ..InstData::new(Opcode::CallIndirect) },
760            &[],
761        );
762        let asm = build.inline_asm(
763            AsmInfo {
764                template: names.intern("nop"),
765                constraints: names.intern(""),
766                clobbers: names.intern(""),
767                targets: BlockCallList::EMPTY,
768            },
769            &[],
770            &[],
771            Flags::NONE,
772        );
773        let nothing = build.ret(&[]);
774
775        let f = names.intern("f");
776        assert_eq!(Callee::of(&func, nothing), None);
777        assert_eq!(Callee::of(&func, direct), Some(Callee::Direct(f)));
778        assert_eq!(Callee::of(&func, indirect), Some(Callee::Indirect));
779        assert_eq!(Callee::of(&func, asm), Some(Callee::Asm));
780    }
781
782    #[test]
783    fn the_two_ways_of_combining_are_the_lattice_they_claim_to_be() {
784        for one in Purity::ALL {
785            assert_eq!(one.stronger(one), one, "{one} is not idempotent");
786            assert_eq!(one.weaker(one), one, "{one} is not idempotent");
787            assert_eq!(one.stronger(Purity::Opaque), one, "opaque should say nothing");
788            assert_eq!(one.weaker(Purity::Opaque), Purity::Opaque, "opaque covers everything");
789            for two in Purity::ALL {
790                assert_eq!(one.stronger(two), two.stronger(one), "{one} and {two} disagree");
791                assert_eq!(one.weaker(two), two.weaker(one), "{one} and {two} disagree");
792                // Whatever comes out of the weaker of the two permits whatever either permitted.
793                let both = one.weaker(two);
794                assert!(both.reads_memory() >= one.reads_memory());
795                assert!(both.writes_memory() >= one.writes_memory());
796                assert!(both.terminates() <= one.terminates());
797            }
798        }
799    }
800
801    #[test]
802    fn only_an_opaque_call_may_write_memory() {
803        for purity in Purity::ALL {
804            assert_eq!(purity.writes_memory(), purity == Purity::Opaque, "{purity}");
805            assert_eq!(purity.can_be_deleted_when_unused(), purity.terminates(), "{purity}");
806        }
807    }
808
809    #[test]
810    fn the_library_table_is_sorted_says_each_name_once_and_writes_no_memory() {
811        // Sorted because the lookup is a binary search, and the rest because an entry here is
812        // believed without being checked against anything.
813        for pair in LIBRARY.windows(2) {
814            assert!(pair[0].0 < pair[1].0, "{} and {} are out of order", pair[0].0, pair[1].0);
815        }
816        for &(name, purity) in LIBRARY {
817            assert!(!purity.writes_memory(), "{name} would not be worth an entry");
818            assert!(purity.terminates(), "{name} is in the table to be deletable");
819            assert!(!name.starts_with("__builtin_"), "{name} is reached under both spellings");
820        }
821    }
822
823    /// A four byte access of ordinary memory, which is what every test below uses.
824    fn access() -> MemInfo {
825        MemInfo {
826            size: 4,
827            align: 4,
828            owns: 4,
829            order: MemOrder::NotAtomic,
830            tbaa: None,
831            restrict: Restrict::NONE,
832        }
833    }
834
835    /// The address of a file scope variable, which is memory this function did not make.
836    fn somewhere(build: &mut Builder<'_>, names: &mut Interner) -> Value {
837        let name = names.intern("v");
838        build.value(
839            InstData { extra: Extra::Symbol(name), ..InstData::new(Opcode::GlobalAddr) },
840            Type::PTR,
841        )
842    }
843
844    /// Four bytes of stack.
845    fn stack(build: &mut Builder<'_>) -> Value {
846        let mem = build.func().add_mem(access());
847        build.value(InstData { extra: Extra::Mem(mem), ..InstData::new(Opcode::Alloca) }, Type::PTR)
848    }
849
850    /// A call to that name with no arguments and nothing done with what came back.
851    fn calls(build: &mut Builder<'_>, names: &mut Interner, name: &str) {
852        let name = names.intern(name);
853        let signature = build.func().add_signature(Signature::new());
854        build.call(name, signature, &[]);
855    }
856
857    /// One function's body, which is how a test says what its function does.
858    type Body = fn(&mut Interner, &mut Func);
859
860    /// A module of functions with bodies, with the purity worked out over it.
861    ///
862    /// Each body is a plain function pointer rather than a closure, so that a test says what its
863    /// function does in one place and two tests can share a body without sharing a module.
864    struct Worked {
865        names: Interner,
866        facts: Facts,
867    }
868
869    impl Worked {
870        /// Builds the module, then runs the analysis over it as the pipeline would.
871        fn out(bodies: &[(&str, Body)]) -> Self {
872            Self::linked(Pic::Executable, bodies)
873        }
874
875        /// The same, for a link where an exported definition may be replaced at load time.
876        fn linked(pic: Pic, bodies: &[(&str, Body)]) -> Self {
877            let mut names = Interner::new();
878            let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
879            let mut module = Module::new(names.intern("t.c"), &target);
880            for &(name, body) in bodies {
881                let mut func = Func::new(names.intern(name), Signature::new());
882                body(&mut names, &mut func);
883                module.add_func(func);
884            }
885            let mut facts = Facts::of_module(&module, &names);
886            infer(&module, &CallGraph::of(&module, pic), &mut facts);
887            Self { names, facts }
888        }
889
890        /// What the analysis worked out on its own, which is the field these tests are about.
891        fn about(&mut self, name: &str) -> Purity {
892            let name = self.names.intern(name);
893            self.facts.inferred(name)
894        }
895
896        /// Everything known about it from all three sources, which is what a call site sees.
897        fn at_a_call_site(&mut self, name: &str) -> Purity {
898            let name = self.names.intern(name);
899            self.facts.purity_of(Callee::Direct(name))
900        }
901    }
902
903    /// Adds two constants and hands back the answer.
904    fn only_arithmetic(_: &mut Interner, func: &mut Func) {
905        let block = func.create_block();
906        let mut build = Builder::new(func, block);
907        let a = build.iconst(Type::int(32), 2);
908        let b = build.iconst(Type::int(32), 3);
909        let sum = build.binary(Opcode::Add, a, b, Flags::NONE);
910        build.ret(&[sum]);
911    }
912
913    /// Nothing at all, which is a body and not a declaration.
914    fn empty(_: &mut Interner, func: &mut Func) {
915        let block = func.create_block();
916        Builder::new(func, block).ret(&[]);
917    }
918
919    /// No body, which is what a declaration is.
920    fn declared(_: &mut Interner, _: &mut Func) {}
921
922    #[test]
923    fn a_function_that_only_computes_is_const() {
924        assert_eq!(Worked::out(&[("f", only_arithmetic)]).about("f"), Purity::Const);
925    }
926
927    #[test]
928    fn a_function_that_reads_memory_it_did_not_make_is_pure() {
929        fn reads(names: &mut Interner, func: &mut Func) {
930            let block = func.create_block();
931            let mut build = Builder::new(func, block);
932            let at = somewhere(&mut build, names);
933            let value = build.load(Type::int(32), at, access(), Flags::NONE);
934            build.ret(&[value]);
935        }
936        let mut worked = Worked::out(&[("f", reads)]);
937        assert_eq!(worked.about("f"), Purity::Pure);
938        assert!(worked.about("f").can_be_deleted_when_unused());
939    }
940
941    #[test]
942    fn a_function_that_writes_memory_it_did_not_make_is_opaque() {
943        fn writes(names: &mut Interner, func: &mut Func) {
944            let block = func.create_block();
945            let mut build = Builder::new(func, block);
946            let at = somewhere(&mut build, names);
947            let zero = build.iconst(Type::int(32), 0);
948            build.store(zero, at, access(), Flags::NONE);
949            build.ret(&[]);
950        }
951        assert_eq!(Worked::out(&[("f", writes)]).about("f"), Purity::Opaque);
952    }
953
954    #[test]
955    fn a_local_this_function_kept_to_itself_is_not_memory() {
956        // The case the compiler meets everywhere until there is an SROA, which is an ordinary
957        // function with a temporary in it. Writing to the temporary and reading it back is not
958        // an access of anything the caller had the address of.
959        fn temporary(_: &mut Interner, func: &mut Func) {
960            let block = func.create_block();
961            let mut build = Builder::new(func, block);
962            let at = stack(&mut build);
963            let zero = build.iconst(Type::int(32), 0);
964            build.store(zero, at, access(), Flags::NONE);
965            let back = build.load(Type::int(32), at, access(), Flags::NONE);
966            build.ret(&[back]);
967        }
968        assert_eq!(Worked::out(&[("f", temporary)]).about("f"), Purity::Const);
969    }
970
971    #[test]
972    fn a_local_whose_address_the_function_hands_back_is_memory_like_any_other() {
973        fn handed_back(_: &mut Interner, func: &mut Func) {
974            let block = func.create_block();
975            let mut build = Builder::new(func, block);
976            let at = stack(&mut build);
977            let zero = build.iconst(Type::int(32), 0);
978            build.store(zero, at, access(), Flags::NONE);
979            build.ret(&[at]);
980        }
981        assert_eq!(Worked::out(&[("f", handed_back)]).about("f"), Purity::Opaque);
982    }
983
984    #[test]
985    fn a_volatile_read_is_opaque_however_private_the_storage_is() {
986        // An access the program asked for by name happens whether or not anybody wanted the
987        // value, so which object it is of decides nothing.
988        fn volatile(_: &mut Interner, func: &mut Func) {
989            let block = func.create_block();
990            let mut build = Builder::new(func, block);
991            let at = stack(&mut build);
992            let value = build.load(Type::int(32), at, access(), Flags::VOLATILE);
993            build.ret(&[value]);
994        }
995        assert_eq!(Worked::out(&[("f", volatile)]).about("f"), Purity::Opaque);
996    }
997
998    #[test]
999    fn a_caller_is_what_the_function_it_calls_is() {
1000        fn calls_g(names: &mut Interner, func: &mut Func) {
1001            let block = func.create_block();
1002            let mut build = Builder::new(func, block);
1003            calls(&mut build, names, "g");
1004            build.ret(&[]);
1005        }
1006        let mut worked = Worked::out(&[("f", calls_g), ("g", only_arithmetic)]);
1007        assert_eq!(worked.about("g"), Purity::Const);
1008        assert_eq!(worked.about("f"), Purity::Const);
1009    }
1010
1011    #[test]
1012    fn a_caller_of_something_nobody_can_see_is_opaque() {
1013        fn calls_g(names: &mut Interner, func: &mut Func) {
1014            let block = func.create_block();
1015            let mut build = Builder::new(func, block);
1016            calls(&mut build, names, "g");
1017            build.ret(&[]);
1018        }
1019        let mut worked = Worked::out(&[("f", calls_g), ("g", declared)]);
1020        assert_eq!(worked.about("g"), Purity::Opaque);
1021        assert_eq!(worked.about("f"), Purity::Opaque);
1022    }
1023
1024    #[test]
1025    fn what_the_library_says_about_a_callee_reaches_the_caller() {
1026        fn calls_strlen(names: &mut Interner, func: &mut Func) {
1027            let block = func.create_block();
1028            let mut build = Builder::new(func, block);
1029            calls(&mut build, names, "strlen");
1030            build.ret(&[]);
1031        }
1032        let mut worked = Worked::out(&[("f", calls_strlen), ("strlen", declared)]);
1033        assert_eq!(worked.about("f"), Purity::Pure);
1034    }
1035
1036    #[test]
1037    fn two_functions_that_call_each_other_and_do_nothing_else_are_not_opaque() {
1038        // The case the optimism of section 34.5 is for. Starting each of them at opaque and
1039        // raising would leave both there, because each is waiting on the other. Starting at const
1040        // and lowering settles at the looping level, which is right: neither body does anything,
1041        // and a pair of functions calling each other round a ring need not ever come back.
1042        fn calls_g(names: &mut Interner, func: &mut Func) {
1043            let block = func.create_block();
1044            let mut build = Builder::new(func, block);
1045            calls(&mut build, names, "g");
1046            build.ret(&[]);
1047        }
1048        fn calls_f(names: &mut Interner, func: &mut Func) {
1049            let block = func.create_block();
1050            let mut build = Builder::new(func, block);
1051            calls(&mut build, names, "f");
1052            build.ret(&[]);
1053        }
1054        let mut worked = Worked::out(&[("f", calls_g), ("g", calls_f)]);
1055        assert_eq!(worked.about("f"), Purity::LoopingConst);
1056        assert_eq!(worked.about("g"), Purity::LoopingConst);
1057        assert!(worked.about("f").depends_only_on_arguments());
1058        assert!(!worked.about("f").can_be_deleted_when_unused());
1059    }
1060
1061    #[test]
1062    fn a_function_that_calls_itself_may_not_come_back() {
1063        fn calls_itself(names: &mut Interner, func: &mut Func) {
1064            let block = func.create_block();
1065            let mut build = Builder::new(func, block);
1066            calls(&mut build, names, "f");
1067            build.ret(&[]);
1068        }
1069        assert_eq!(Worked::out(&[("f", calls_itself)]).about("f"), Purity::LoopingConst);
1070    }
1071
1072    #[test]
1073    fn a_function_with_a_loop_in_it_may_not_come_back() {
1074        // Nothing here proves the loop finite, and until something does, deleting a call to this
1075        // would be deleting the program's chance to hang.
1076        fn loops(_: &mut Interner, func: &mut Func) {
1077            let entry = func.create_block();
1078            let head = func.create_block();
1079            let done = func.create_block();
1080            let mut build = Builder::new(func, entry);
1081            build.jump(head, &[]);
1082            let mut build = Builder::new(func, head);
1083            let zero = build.iconst(Type::int(32), 0);
1084            let cond = build.icmp(IntPred::Eq, zero, zero);
1085            build.br_if(cond, head, &[], done, &[]);
1086            Builder::new(func, done).ret(&[]);
1087        }
1088        let mut worked = Worked::out(&[("f", loops)]);
1089        assert_eq!(worked.about("f"), Purity::LoopingConst);
1090        assert!(!worked.about("f").can_be_deleted_when_unused());
1091    }
1092
1093    #[test]
1094    fn a_branch_that_joins_again_is_not_a_loop() {
1095        // The other half of the test above, because a reverse postorder rank that was compared
1096        // the wrong way round would call every `if` a loop and nothing would ever be const.
1097        fn branches(_: &mut Interner, func: &mut Func) {
1098            let entry = func.create_block();
1099            let arm = func.create_block();
1100            let join = func.create_block();
1101            let mut build = Builder::new(func, entry);
1102            let zero = build.iconst(Type::int(32), 0);
1103            let cond = build.icmp(IntPred::Eq, zero, zero);
1104            build.br_if(cond, arm, &[], join, &[]);
1105            Builder::new(func, arm).jump(join, &[]);
1106            Builder::new(func, join).ret(&[]);
1107        }
1108        assert_eq!(Worked::out(&[("f", branches)]).about("f"), Purity::Const);
1109    }
1110
1111    #[test]
1112    fn a_declaration_has_nothing_worked_out_about_it() {
1113        assert_eq!(Worked::out(&[("f", declared)]).about("f"), Purity::Opaque);
1114    }
1115
1116    #[test]
1117    fn a_body_this_link_may_replace_has_nothing_worked_out_about_it() {
1118        // Section 34.1's gate, which the call graph answers and this one only obeys. The body in
1119        // hand does nothing, and in a library the definition that runs may be another object's.
1120        let mut worked = Worked::linked(Pic::Library, &[("f", only_arithmetic)]);
1121        assert_eq!(worked.about("f"), Purity::Opaque);
1122        let mut worked = Worked::linked(Pic::Executable, &[("f", only_arithmetic)]);
1123        assert_eq!(worked.about("f"), Purity::Const);
1124    }
1125
1126    #[test]
1127    fn nothing_is_written_down_for_a_function_that_came_out_opaque() {
1128        // Opaque is what an empty map already says, so recording it would put an entry in for
1129        // every function in the module to say the thing the absence of an entry says.
1130        let mut worked = Worked::out(&[("f", declared)]);
1131        assert!(worked.facts.inferred.is_empty());
1132        assert_eq!(worked.about("f"), Purity::Opaque);
1133    }
1134
1135    #[test]
1136    fn what_the_user_declared_and_what_the_body_says_are_both_kept() {
1137        // A person writing `__attribute__((const))` on a function with a loop in it is asserting
1138        // that the loop finishes. The assertion is honoured at the call site and the worked out
1139        // answer is still there to be checked against it later, which is what two fields are for.
1140        let mut names = Interner::new();
1141        let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1142        let mut module = Module::new(names.intern("t.c"), &target);
1143        let mut func = Func::new(names.intern("f"), Signature::new());
1144        func.attrs.set = AttrSet::READNONE;
1145        let entry = func.create_block();
1146        let head = func.create_block();
1147        Builder::new(&mut func, entry).jump(head, &[]);
1148        Builder::new(&mut func, head).jump(head, &[]);
1149        module.add_func(func);
1150        let mut facts = Facts::of_module(&module, &names);
1151        infer(&module, &CallGraph::of(&module, Pic::Executable), &mut facts);
1152        let name = names.intern("f");
1153        assert_eq!(facts.inferred(name), Purity::LoopingConst);
1154        assert_eq!(facts.declared(name), Purity::Const);
1155        assert_eq!(facts.purity_of(Callee::Direct(name)), Purity::Const);
1156    }
1157
1158    #[test]
1159    fn an_answer_travels_as_far_up_the_chain_as_it_holds() {
1160        fn calls_g(names: &mut Interner, func: &mut Func) {
1161            let block = func.create_block();
1162            let mut build = Builder::new(func, block);
1163            calls(&mut build, names, "g");
1164            build.ret(&[]);
1165        }
1166        fn calls_h(names: &mut Interner, func: &mut Func) {
1167            let block = func.create_block();
1168            let mut build = Builder::new(func, block);
1169            calls(&mut build, names, "h");
1170            build.ret(&[]);
1171        }
1172        let mut worked = Worked::out(&[("f", calls_g), ("g", calls_h), ("h", empty)]);
1173        assert_eq!(worked.about("h"), Purity::Const);
1174        assert_eq!(worked.about("g"), Purity::Const);
1175        assert_eq!(worked.about("f"), Purity::Const);
1176        assert_eq!(worked.at_a_call_site("f"), Purity::Const);
1177    }
1178
1179    #[test]
1180    fn a_reader_under_a_writer_makes_the_caller_opaque_and_not_pure() {
1181        // Order does not come into it: one opaque callee anywhere in a body is the whole answer,
1182        // and the walk stops at the first one rather than combining the rest.
1183        fn writes(names: &mut Interner, func: &mut Func) {
1184            let block = func.create_block();
1185            let mut build = Builder::new(func, block);
1186            let at = somewhere(&mut build, names);
1187            let zero = build.iconst(Type::int(32), 0);
1188            build.store(zero, at, access(), Flags::NONE);
1189            build.ret(&[]);
1190        }
1191        fn calls_both(names: &mut Interner, func: &mut Func) {
1192            let block = func.create_block();
1193            let mut build = Builder::new(func, block);
1194            calls(&mut build, names, "g");
1195            calls(&mut build, names, "h");
1196            build.ret(&[]);
1197        }
1198        let mut worked = Worked::out(&[("f", calls_both), ("g", only_arithmetic), ("h", writes)]);
1199        assert_eq!(worked.about("f"), Purity::Opaque);
1200    }
1201}