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