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. Document 34's analysis will work out its own
33//! answer for the functions it can see, and that answer lives in a different field of [`Facts`],
34//! because keeping them apart is what makes it possible to check one against the other later. The
35//! two are combined with [`Purity::stronger`] at the point of use and nowhere else.
36
37use std::collections::{HashMap, HashSet};
38
39use rucc_base::{Interner, Symbol};
40use rucc_ir::{AttrSet, Extra, Func, Inst, Module, Opcode};
41
42/// What a call can do.
43///
44/// Five, because there are two questions with two answers each and then everything else. Does the
45/// result depend on memory, does the call come back, and if either answer is not known then the
46/// call is [`Purity::Opaque`] and no pass may assume anything at all.
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
48pub enum Purity {
49    /// Reads no memory, writes none, and comes back. `__attribute__((const))`, GCC's `ECF_CONST`.
50    Const,
51    /// Reads no memory and writes none, and may not come back. GCC's `ECF_CONST` together with
52    /// `ECF_LOOPING_CONST_OR_PURE`.
53    LoopingConst,
54    /// Reads memory, writes none, and comes back. `__attribute__((pure))`, GCC's `ECF_PURE`.
55    Pure,
56    /// Reads memory, writes none, and may not come back.
57    LoopingPure,
58    /// Anything, which is what a call is until something says otherwise.
59    Opaque,
60}
61
62impl Purity {
63    /// The five, for a test that walks them.
64    pub const ALL: [Self; 5] =
65        [Self::Const, Self::LoopingConst, Self::Pure, Self::LoopingPure, Self::Opaque];
66
67    /// How it reads in a dump.
68    #[must_use]
69    pub const fn as_str(self) -> &'static str {
70        match self {
71            Self::Const => "const",
72            Self::LoopingConst => "const, may not return",
73            Self::Pure => "pure",
74            Self::LoopingPure => "pure, may not return",
75            Self::Opaque => "opaque",
76        }
77    }
78
79    /// Whether the call may read memory the caller cares about.
80    #[must_use]
81    pub const fn reads_memory(self) -> bool {
82        match self {
83            Self::Const | Self::LoopingConst => false,
84            Self::Pure | Self::LoopingPure | Self::Opaque => true,
85        }
86    }
87
88    /// Whether the call may write memory.
89    ///
90    /// Only an opaque call may. That is what the other four have in common and it is most of what
91    /// makes them worth telling apart from the rest.
92    #[must_use]
93    pub const fn writes_memory(self) -> bool {
94        matches!(self, Self::Opaque)
95    }
96
97    /// Whether control is known to come back from the call.
98    ///
99    /// Not known and known not to are the same answer here, because both of them stop the same
100    /// transformations. Which of the two it is belongs to `noreturn`, which is an attribute on the
101    /// function rather than a level of this.
102    #[must_use]
103    pub const fn terminates(self) -> bool {
104        matches!(self, Self::Const | Self::Pure)
105    }
106
107    /// Whether the result is a function of the arguments and nothing else.
108    ///
109    /// This is what lets two calls with the same arguments become one call with no question asked
110    /// about what happened to memory in between. A [`Purity::Pure`] call can be folded the same way
111    /// when the caller can show nothing wrote memory between the two, which is a question for the
112    /// alias analysis and not for this.
113    #[must_use]
114    pub const fn depends_only_on_arguments(self) -> bool {
115        !self.reads_memory() && !self.writes_memory()
116    }
117
118    /// Whether a call whose result nothing reads may be removed.
119    ///
120    /// Both halves are needed. A call that writes memory does something even when its result is
121    /// thrown away, and a call that may not come back does something by not coming back, which is
122    /// why the looping levels exist at all.
123    #[must_use]
124    pub const fn can_be_deleted_when_unused(self) -> bool {
125        !self.writes_memory() && self.terminates()
126    }
127
128    /// The strongest thing true of both, for a caller that has two sources and believes each.
129    ///
130    /// [`Purity::Opaque`] is nothing known, so it gives way to whatever the other source says. Two
131    /// sources that each know half give the whole: a declaration saying the result comes out of the
132    /// arguments and an analysis saying the loop inside terminates add up to [`Purity::Const`].
133    #[must_use]
134    pub const fn stronger(self, other: Self) -> Self {
135        match (self, other) {
136            (Self::Opaque, it) | (it, Self::Opaque) => it,
137            (one, two) => Self::of(
138                one.reads_memory() && two.reads_memory(),
139                one.terminates() || two.terminates(),
140            ),
141        }
142    }
143
144    /// The strongest thing true of either, for a caller that has to cover both.
145    ///
146    /// Which is what a call site with more than one possible callee needs, and what a caller
147    /// summarising a whole function needs.
148    #[must_use]
149    pub const fn weaker(self, other: Self) -> Self {
150        match (self, other) {
151            (Self::Opaque, _) | (_, Self::Opaque) => Self::Opaque,
152            (one, two) => Self::of(
153                one.reads_memory() || two.reads_memory(),
154                one.terminates() && two.terminates(),
155            ),
156        }
157    }
158
159    /// The level with those two answers, which is the four that are not opaque.
160    const fn of(reads: bool, terminates: bool) -> Self {
161        match (reads, terminates) {
162            (false, true) => Self::Const,
163            (false, false) => Self::LoopingConst,
164            (true, true) => Self::Pure,
165            (true, false) => Self::LoopingPure,
166        }
167    }
168}
169
170impl std::fmt::Display for Purity {
171    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172        f.write_str(self.as_str())
173    }
174}
175
176/// What is being called.
177///
178/// The thing [`Facts::purity_of`] is exhaustive over. The closed intrinsics are not here because
179/// they are opcodes rather than calls and each carries its own meaning in the opcode.
180#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
181pub enum Callee {
182    /// A named function, which may or may not be one this module defines.
183    Direct(Symbol),
184    /// A call through an address.
185    Indirect,
186    /// A target-specific intrinsic, named on the instruction, which is the open half of the
187    /// intrinsic set and is where the vector builtins land.
188    Intrinsic(Symbol),
189    /// Inline assembly, including `asm goto`.
190    Asm,
191}
192
193impl Callee {
194    /// What this instruction calls, and `None` for an instruction that calls nothing.
195    #[must_use]
196    pub fn of(func: &Func, inst: Inst) -> Option<Self> {
197        let data = &func[inst];
198        match data.opcode {
199            Opcode::Call | Opcode::TailCall | Opcode::CallIndirect => match data.extra {
200                Extra::Call(at) => Some(match func[at].callee {
201                    Some(name) => Self::Direct(name),
202                    None => Self::Indirect,
203                }),
204                _ => Some(Self::Indirect),
205            },
206            Opcode::TargetIntrinsic => match data.extra {
207                Extra::Symbol(name) => Some(Self::Intrinsic(name)),
208                _ => Some(Self::Asm),
209            },
210            Opcode::InlineAsm => Some(Self::Asm),
211            _ => None,
212        }
213    }
214}
215
216/// What is known about the functions a call could reach.
217///
218/// Built once from the module, because the attributes belong to the callee and there is one callee
219/// and many call sites. A caller with no module has [`Facts::nothing`], which answers
220/// [`Purity::Opaque`] to everything and is correct.
221#[derive(Debug, Clone, Default)]
222pub struct Facts {
223    declared: HashMap<Symbol, AttrSet>,
224    inferred: HashMap<Symbol, Purity>,
225    from_the_library: HashMap<Symbol, Purity>,
226}
227
228impl Facts {
229    /// Nothing known about anything, which is what a pass holding one function has.
230    #[must_use]
231    pub fn nothing() -> Self {
232        Self::default()
233    }
234
235    /// What the module says about each of its functions.
236    ///
237    /// The interner is here for the library table, which is written in text because that is what
238    /// the C standard names. Nothing after this call needs it.
239    #[must_use]
240    pub fn of_module(module: &Module, names: &Interner) -> Self {
241        let mut facts = Self::default();
242        let mut defined = HashSet::new();
243        for id in module.funcs() {
244            let func = &module[id];
245            facts.declared.insert(func.name, func.attrs.set);
246            if !func.is_declaration() {
247                defined.insert(func.name);
248            }
249        }
250        // A name this module defines is not the library's, whatever it is spelled, because the
251        // definition in hand is the function that will be called.
252        for &name in facts.declared.keys() {
253            if defined.contains(&name) {
254                continue;
255            }
256            if let Some(purity) = library_purity(names.resolve(name)) {
257                facts.from_the_library.insert(name, purity);
258            }
259        }
260        facts
261    }
262
263    /// Turns off the whole library table, which is `-fno-builtin` and `-ffreestanding`.
264    ///
265    /// A freestanding program has no C library for the name to be the name of, and a program that
266    /// means its own thing by `strlen` is the reason the flag exists.
267    pub fn without_the_library(&mut self) {
268        self.from_the_library.clear();
269    }
270
271    /// Takes one name away from the table, which is `-fno-builtin-<name>`.
272    ///
273    /// What a build that means its own `memcpy` and the library's everything else writes, which is
274    /// what the kernel does for a handful of names.
275    pub fn not_the_library_name(&mut self, name: Symbol) {
276        self.from_the_library.remove(&name);
277    }
278
279    /// Records what document 34's analysis worked out about a function.
280    ///
281    /// A separate field from the declaration on purpose. The two are combined where they are read
282    /// and are never written over each other, so that a later build can check one against the other
283    /// and report the function whose attribute was a lie.
284    pub fn record_inferred(&mut self, name: Symbol, purity: Purity) {
285        self.inferred.insert(name, purity);
286    }
287
288    /// What the user declared about this function, on its own.
289    #[must_use]
290    pub fn declared(&self, name: Symbol) -> Purity {
291        match self.declared.get(&name) {
292            Some(&set) => from_attributes(set),
293            None => Purity::Opaque,
294        }
295    }
296
297    /// What analysis worked out about this function, on its own.
298    #[must_use]
299    pub fn inferred(&self, name: Symbol) -> Purity {
300        self.inferred.get(&name).copied().unwrap_or(Purity::Opaque)
301    }
302
303    /// What this call can do.
304    ///
305    /// The match has no wildcard arm and adding a kind of callee should keep it that way.
306    #[must_use]
307    pub fn purity_of(&self, callee: Callee) -> Purity {
308        match callee {
309            Callee::Direct(name) => self.of_name(name),
310            // The address could be anything with its own definition, including a function this
311            // module never saw. Document 34's call graph narrows this and until then it does not.
312            Callee::Indirect => Purity::Opaque,
313            // The open half of the intrinsic set is named rather than enumerated, so nothing here
314            // knows what one does. The closed half are opcodes and never reach this.
315            Callee::Intrinsic(_) => Purity::Opaque,
316            // A template the compiler does not read, with a clobber list it has to believe.
317            Callee::Asm => Purity::Opaque,
318        }
319    }
320
321    /// Everything known about a named function, from all three sources.
322    fn of_name(&self, name: Symbol) -> Purity {
323        let mut purity = self.declared(name).stronger(self.inferred(name));
324        if let Some(&known) = self.from_the_library.get(&name) {
325            purity = purity.stronger(known);
326        }
327        purity
328    }
329}
330
331/// What an attribute set says on its own.
332///
333/// `noreturn` is what turns either level into its looping one. A call that does not come back does
334/// something by not coming back, however little it touches, and that is the case
335/// `ECF_LOOPING_CONST_OR_PURE` exists for.
336fn from_attributes(set: AttrSet) -> Purity {
337    let terminates = !set.contains(AttrSet::NORETURN);
338    if set.contains(AttrSet::READNONE) {
339        return Purity::of(false, terminates);
340    }
341    if set.contains(AttrSet::READONLY) {
342        return Purity::of(true, terminates);
343    }
344    Purity::Opaque
345}
346
347/// What the C standard library functions do, for the ones where the answer is not arguable.
348///
349/// Only entries that strengthen the answer are here, so a name that is missing costs a missed
350/// optimization and a name that is wrong costs a wrong program. Nothing that writes memory, sets
351/// `errno`, touches a stream or allocates belongs in here, which rules out most of the library and
352/// all of `<math.h>`, since a math function sets `errno` unless the command line says it does not.
353///
354/// Sorted, and a test checks that it is sorted and says each name once.
355const LIBRARY: &[(&str, Purity)] = &[
356    ("abs", Purity::Const),
357    ("imaxabs", Purity::Const),
358    ("labs", Purity::Const),
359    ("llabs", Purity::Const),
360    ("memchr", Purity::Pure),
361    ("memcmp", Purity::Pure),
362    ("strchr", Purity::Pure),
363    ("strcmp", Purity::Pure),
364    ("strcspn", Purity::Pure),
365    ("strlen", Purity::Pure),
366    ("strncmp", Purity::Pure),
367    ("strnlen", Purity::Pure),
368    ("strpbrk", Purity::Pure),
369    ("strrchr", Purity::Pure),
370    ("strspn", Purity::Pure),
371    ("strstr", Purity::Pure),
372];
373
374/// What the library says about a name, under either spelling.
375///
376/// The `__builtin_` prefix is the program saying which function it means, so it reaches the same
377/// entry. Whether the plain spelling is allowed to is decided before this is called.
378fn library_purity(name: &str) -> Option<Purity> {
379    let name = name.strip_prefix("__builtin_").unwrap_or(name);
380    LIBRARY.binary_search_by_key(&name, |&(named, _)| named).ok().map(|at| LIBRARY[at].1)
381}
382
383#[cfg(test)]
384mod tests {
385    use rucc_base::Interner;
386    use rucc_ir::{
387        AsmInfo, AttrSet, BlockCallList, Builder, CallInfo, Extra, Flags, Func, InstData, Module,
388        Opcode, Signature, Type,
389    };
390    use rucc_target::{TargetInfo, Triple};
391
392    use super::{Callee, Facts, LIBRARY, Purity};
393
394    /// A module with those functions in it, declared unless they are asked to have a body.
395    fn module(named: &[(&str, bool, AttrSet)]) -> (Interner, Module) {
396        let mut names = Interner::new();
397        let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
398        let mut module = Module::new(names.intern("t.c"), &target);
399        for &(name, defined, attrs) in named {
400            let mut func = Func::new(names.intern(name), Signature::new());
401            func.attrs.set = attrs;
402            if defined {
403                let block = func.create_block();
404                let mut build = Builder::new(&mut func, block);
405                let zero = build.iconst(Type::int(32), 0);
406                build.ret(&[zero]);
407            }
408            module.add_func(func);
409        }
410        (names, module)
411    }
412
413    /// What the module says about a name.
414    fn purity(names: &mut Interner, module: &Module, name: &str) -> Purity {
415        let facts = Facts::of_module(module, names);
416        let symbol = names.intern(name);
417        facts.purity_of(Callee::Direct(symbol))
418    }
419
420    #[test]
421    fn a_function_nobody_promised_anything_about_is_opaque() {
422        let (mut names, module) = module(&[("f", true, AttrSet::NONE)]);
423        assert_eq!(purity(&mut names, &module, "f"), Purity::Opaque);
424    }
425
426    #[test]
427    fn a_name_this_module_never_heard_of_is_opaque_as_well() {
428        let (mut names, module) = module(&[("f", true, AttrSet::NONE)]);
429        let facts = Facts::of_module(&module, &names);
430        assert_eq!(facts.purity_of(Callee::Direct(names.intern("g"))), Purity::Opaque);
431    }
432
433    #[test]
434    fn the_const_attribute_is_honoured_because_the_user_asserted_it() {
435        let (mut names, module) = module(&[("f", false, AttrSet::READNONE)]);
436        let purity = purity(&mut names, &module, "f");
437        assert_eq!(purity, Purity::Const);
438        assert!(purity.depends_only_on_arguments());
439        assert!(purity.can_be_deleted_when_unused());
440    }
441
442    #[test]
443    fn the_pure_attribute_reads_memory_and_writes_none() {
444        let (mut names, module) = module(&[("f", false, AttrSet::READONLY)]);
445        let purity = purity(&mut names, &module, "f");
446        assert_eq!(purity, Purity::Pure);
447        assert!(purity.reads_memory());
448        assert!(!purity.writes_memory());
449        assert!(!purity.depends_only_on_arguments());
450        assert!(purity.can_be_deleted_when_unused());
451    }
452
453    #[test]
454    fn a_const_function_that_does_not_come_back_may_not_be_deleted() {
455        // Which is the whole reason the looping levels are in the enum. Its result depends only
456        // on its arguments and the call still does something, which is not come back.
457        let (mut names, module) =
458            module(&[("f", false, AttrSet::READNONE.union(AttrSet::NORETURN))]);
459        let purity = purity(&mut names, &module, "f");
460        assert_eq!(purity, Purity::LoopingConst);
461        assert!(purity.depends_only_on_arguments());
462        assert!(!purity.can_be_deleted_when_unused());
463    }
464
465    #[test]
466    fn nothing_that_is_not_a_direct_call_is_anything_but_opaque() {
467        let (mut names, module) = module(&[("f", true, AttrSet::READNONE)]);
468        let facts = Facts::of_module(&module, &names);
469        // Even though the module holds a const function of that name, none of these is known to
470        // be it, and each is opaque for its own reason.
471        assert_eq!(facts.purity_of(Callee::Indirect), Purity::Opaque);
472        assert_eq!(facts.purity_of(Callee::Asm), Purity::Opaque);
473        let vector = names.intern("__builtin_ia32_paddb");
474        assert_eq!(facts.purity_of(Callee::Intrinsic(vector)), Purity::Opaque);
475    }
476
477    #[test]
478    fn the_library_names_are_known_under_both_spellings() {
479        let (mut names, module) = module(&[
480            ("strlen", false, AttrSet::NONE),
481            ("abs", false, AttrSet::NONE),
482            ("__builtin_strlen", false, AttrSet::NONE),
483            ("printf", false, AttrSet::NONE),
484        ]);
485        assert_eq!(purity(&mut names, &module, "strlen"), Purity::Pure);
486        assert_eq!(purity(&mut names, &module, "__builtin_strlen"), Purity::Pure);
487        assert_eq!(purity(&mut names, &module, "abs"), Purity::Const);
488        // Everything else in the library, which is most of it, is opaque and stays that way.
489        assert_eq!(purity(&mut names, &module, "printf"), Purity::Opaque);
490    }
491
492    #[test]
493    fn a_module_that_defines_strlen_means_its_own() {
494        let (mut names, module) = module(&[("strlen", true, AttrSet::NONE)]);
495        assert_eq!(purity(&mut names, &module, "strlen"), Purity::Opaque);
496    }
497
498    #[test]
499    fn no_builtin_takes_the_table_away_and_the_named_form_takes_one_entry() {
500        let (mut names, module) =
501            module(&[("strlen", false, AttrSet::NONE), ("abs", false, AttrSet::NONE)]);
502        let mut facts = Facts::of_module(&module, &names);
503        let strlen = names.intern("strlen");
504        let abs = names.intern("abs");
505        facts.not_the_library_name(strlen);
506        assert_eq!(facts.purity_of(Callee::Direct(strlen)), Purity::Opaque);
507        assert_eq!(facts.purity_of(Callee::Direct(abs)), Purity::Const);
508        facts.without_the_library();
509        assert_eq!(facts.purity_of(Callee::Direct(abs)), Purity::Opaque);
510    }
511
512    #[test]
513    fn what_the_user_wrote_and_what_analysis_worked_out_are_kept_apart() {
514        let (mut names, module) =
515            module(&[("f", true, AttrSet::READNONE.union(AttrSet::NORETURN))]);
516        let mut facts = Facts::of_module(&module, &names);
517        let f = names.intern("f");
518        assert_eq!(facts.declared(f), Purity::LoopingConst);
519        assert_eq!(facts.inferred(f), Purity::Opaque);
520        // Document 34 gets to say the loop inside it terminates. The declaration said the result
521        // comes out of the arguments. Together that is const, and each is still readable on its
522        // own, which is what makes checking one against the other possible later.
523        facts.record_inferred(f, Purity::Pure);
524        assert_eq!(facts.declared(f), Purity::LoopingConst);
525        assert_eq!(facts.inferred(f), Purity::Pure);
526        assert_eq!(facts.purity_of(Callee::Direct(f)), Purity::Const);
527    }
528
529    #[test]
530    fn what_an_instruction_calls_is_read_off_the_instruction() {
531        let mut names = Interner::new();
532        let mut func = Func::new(names.intern("caller"), Signature::new());
533        let block = func.create_block();
534        let mut build = Builder::new(&mut func, block);
535        let signature = build.func().add_signature(Signature::new());
536        let direct = build.call(names.intern("f"), signature, &[]);
537        let varargs = build.func().push_abis(&[]);
538        let info = build.func().add_call(CallInfo { callee: None, signature, varargs });
539        let indirect = build.inst(
540            InstData { extra: Extra::Call(info), ..InstData::new(Opcode::CallIndirect) },
541            &[],
542        );
543        let asm = build.inline_asm(
544            AsmInfo {
545                template: names.intern("nop"),
546                constraints: names.intern(""),
547                clobbers: names.intern(""),
548                targets: BlockCallList::EMPTY,
549            },
550            &[],
551            &[],
552            Flags::NONE,
553        );
554        let nothing = build.ret(&[]);
555
556        let f = names.intern("f");
557        assert_eq!(Callee::of(&func, nothing), None);
558        assert_eq!(Callee::of(&func, direct), Some(Callee::Direct(f)));
559        assert_eq!(Callee::of(&func, indirect), Some(Callee::Indirect));
560        assert_eq!(Callee::of(&func, asm), Some(Callee::Asm));
561    }
562
563    #[test]
564    fn the_two_ways_of_combining_are_the_lattice_they_claim_to_be() {
565        for one in Purity::ALL {
566            assert_eq!(one.stronger(one), one, "{one} is not idempotent");
567            assert_eq!(one.weaker(one), one, "{one} is not idempotent");
568            assert_eq!(one.stronger(Purity::Opaque), one, "opaque should say nothing");
569            assert_eq!(one.weaker(Purity::Opaque), Purity::Opaque, "opaque covers everything");
570            for two in Purity::ALL {
571                assert_eq!(one.stronger(two), two.stronger(one), "{one} and {two} disagree");
572                assert_eq!(one.weaker(two), two.weaker(one), "{one} and {two} disagree");
573                // Whatever comes out of the weaker of the two permits whatever either permitted.
574                let both = one.weaker(two);
575                assert!(both.reads_memory() >= one.reads_memory());
576                assert!(both.writes_memory() >= one.writes_memory());
577                assert!(both.terminates() <= one.terminates());
578            }
579        }
580    }
581
582    #[test]
583    fn only_an_opaque_call_may_write_memory() {
584        for purity in Purity::ALL {
585            assert_eq!(purity.writes_memory(), purity == Purity::Opaque, "{purity}");
586            assert_eq!(purity.can_be_deleted_when_unused(), purity.terminates(), "{purity}");
587        }
588    }
589
590    #[test]
591    fn the_library_table_is_sorted_says_each_name_once_and_writes_no_memory() {
592        // Sorted because the lookup is a binary search, and the rest because an entry here is
593        // believed without being checked against anything.
594        for pair in LIBRARY.windows(2) {
595            assert!(pair[0].0 < pair[1].0, "{} and {} are out of order", pair[0].0, pair[1].0);
596        }
597        for &(name, purity) in LIBRARY {
598            assert!(!purity.writes_memory(), "{name} would not be worth an entry");
599            assert!(purity.terminates(), "{name} is in the table to be deletable");
600            assert!(!name.starts_with("__builtin_"), "{name} is reached under both spellings");
601        }
602    }
603}