Skip to main content

rucc_opt/
analysis.rs

1//! The analysis cache, and what a pass has to say about what it left standing.
2//!
3//! Design: section 4.3 of `spec/optimizer/04-pass-manager.md`, which calls this the analysis
4//! manager and gives it four jobs and no more than four. Compute an analysis when somebody asks
5//! and keep the answer. Throw an answer away when a pass says it broke the thing the answer was
6//! about. Throw away everything built on top of that answer at the same time. Catch a pass that
7//! says it preserved something it did not.
8//!
9//! The type is called [`Analyses`] rather than `Manager` because this crate already has a pass
10//! manager in [`crate::pipeline`], and a bare `Manager` re-exported at the top of the crate would
11//! not say which of the two it was.
12//!
13//! # What is cached and what is not
14//!
15//! The nine here are the nine that own their data: [`Cfg`], [`Dominators`], [`PostDominators`],
16//! [`Loops`], [`Frontiers`], [`ControlDependence`], [`Frequencies`], [`Liveness`] and
17//! [`Pressure`]. Each is built from the function once and then answers questions without looking
18//! at it again, so each is a thing a cache can hold.
19//!
20//! The rest of the analyses in this crate are not here and do not belong here. [`crate::Alias`],
21//! [`crate::memssa`], [`crate::Scev`] and [`crate::range::query::Ranges`] all borrow the function
22//! they answer about, which means holding one across an edit is not something the cache would have
23//! to be careful about, it is something the compiler refuses. They are query engines built on top
24//! of the ones here, and the ones here are what they cost.
25//!
26//! # Why the cache is keyed by function elsewhere
27//!
28//! There is one of these per function, and [`crate::pipeline`] keeps a map from function to cache
29//! because it runs a pass over the whole module before the next pass starts. Under that order a
30//! cache that lived only as long as one function would be thrown away between every pass and every
31//! analysis would be recomputed for every pass that wanted it. Section 4.2 of the design says to
32//! turn the loop inside out in M4 and run every pass over one function before moving to the next,
33//! and the day that lands the map goes away and one of these lives on the stack of the loop.
34
35use std::cell::OnceCell;
36use std::sync::Arc;
37
38use rucc_ir::Func;
39
40use crate::image::Images;
41use crate::machine::Machine;
42use crate::modref::Summaries;
43use crate::outside::Outside;
44use crate::predict::Callees;
45use crate::purity::Facts;
46use crate::{
47    Cfg, ControlDependence, Dominators, Frequencies, Frontiers, Liveness, Loops, PostDominators,
48    Pressure,
49};
50
51/// One analysis this cache holds.
52///
53/// The order matters and is checked by a test: an analysis is built out of analyses that come
54/// before it in this list and never out of one that comes after. That is what lets the
55/// invalidation walk settle in one pass over the list rather than in a loop to a fixed point.
56#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
57pub enum Analysis {
58    /// [`Cfg`], which everything else here is built on.
59    Cfg,
60    /// [`Dominators`].
61    Dominators,
62    /// [`PostDominators`].
63    PostDominators,
64    /// [`Loops`].
65    Loops,
66    /// [`Frontiers`].
67    Frontiers,
68    /// [`ControlDependence`].
69    ControlDependence,
70    /// [`Frequencies`], which carries the branch predictions it was worked out from.
71    Frequencies,
72    /// [`Liveness`].
73    Liveness,
74    /// [`Pressure`], which is the live counts split by register class.
75    Pressure,
76}
77
78impl Analysis {
79    /// Every analysis this cache holds, in dependency order.
80    pub const EVERY: &'static [Analysis] = &[
81        Analysis::Cfg,
82        Analysis::Dominators,
83        Analysis::PostDominators,
84        Analysis::Loops,
85        Analysis::Frontiers,
86        Analysis::ControlDependence,
87        Analysis::Frequencies,
88        Analysis::Liveness,
89        Analysis::Pressure,
90    ];
91
92    /// What it is called in a message to somebody debugging a pass.
93    #[must_use]
94    pub const fn name(self) -> &'static str {
95        match self {
96            Self::Cfg => "the control flow graph",
97            Self::Dominators => "the dominator tree",
98            Self::PostDominators => "the post-dominator tree",
99            Self::Loops => "the loop forest",
100            Self::Frontiers => "the dominance frontiers",
101            Self::ControlDependence => "the control dependence relation",
102            Self::Frequencies => "the block frequencies",
103            Self::Liveness => "the liveness",
104            Self::Pressure => "the register pressure",
105        }
106    }
107
108    /// The analyses this one is built out of, which cannot outlive it.
109    ///
110    /// Section 4.4 of the design has a table of these and the entry for almost every row is
111    /// "any CFG change", which is why [`Analysis::Cfg`] is what the other three name.
112    #[must_use]
113    pub const fn needs(self) -> &'static [Analysis] {
114        match self {
115            Self::Cfg => &[],
116            Self::Dominators | Self::PostDominators => &[Analysis::Cfg],
117            Self::Loops | Self::Frontiers => &[Analysis::Cfg, Analysis::Dominators],
118            Self::ControlDependence => &[Analysis::Cfg, Analysis::PostDominators],
119            Self::Frequencies => &[Analysis::Cfg, Analysis::Dominators, Analysis::Loops],
120            Self::Liveness => &[Analysis::Cfg],
121            Self::Pressure => &[Analysis::Cfg, Analysis::Liveness],
122        }
123    }
124
125    /// Which bit of a [`Preserved`] set this one is.
126    const fn bit(self) -> u16 {
127        1 << (self as u16)
128    }
129}
130
131/// What a pass leaves standing.
132///
133/// A set rather than the three cases the design writes, because [`Preserved::ALL`] and
134/// [`Preserved::NONE`] are the full set and the empty one and a named set is what is between
135/// them. A pass that adds an analysis to this list is saying the code it produced answers the
136/// same questions the code it was given did, which is a claim about a pass and not about a run,
137/// so it is stated once on the pass rather than returned from each call.
138#[derive(Clone, Copy, Debug, PartialEq, Eq)]
139pub struct Preserved(u16);
140
141impl Preserved {
142    /// Everything, which is what a pass that does not change the shape of a function says.
143    pub const ALL: Preserved = Preserved(u16::MAX);
144
145    /// Nothing, which is what a pass that moves an edge says, however small the move was.
146    pub const NONE: Preserved = Preserved(0);
147
148    /// This set and that analysis.
149    #[must_use]
150    pub const fn and(self, analysis: Analysis) -> Self {
151        Self(self.0 | analysis.bit())
152    }
153
154    /// This set without that analysis.
155    ///
156    /// The way a pass says "everything except one thing", which is what a pass that rewrites
157    /// operands and moves no edge has to say: the shape of the function is what it was and the
158    /// liveness is not, because a value read in one more place is live in one more place.
159    #[must_use]
160    pub const fn without(self, analysis: Analysis) -> Self {
161        Self(self.0 & !analysis.bit())
162    }
163
164    /// Whether the pass said this one survived.
165    #[must_use]
166    pub const fn keeps(self, analysis: Analysis) -> bool {
167        self.0 & analysis.bit() != 0
168    }
169}
170
171/// The analyses of one function, computed when asked for and kept until something breaks them.
172///
173/// Empty to start with. Nothing here is computed by existing, which matters because most
174/// functions are walked by a pass that wants none of it.
175///
176/// Each answer is behind a [`OnceCell`] rather than an [`Option`] so that asking for one takes a
177/// shared borrow of the cache instead of an exclusive one. With an exclusive borrow a pass that
178/// wanted two answers at the same time could not have them, because the second call would end the
179/// borrow the first handed out, and the way every pass here got round that was to copy what it
180/// asked for. A copy of the graph or the loop forest is the size of the function, and a pass that
181/// makes one edit at a time was making one per edit. tamnd/rucc#1045.
182#[derive(Clone, Debug)]
183pub struct Analyses {
184    machine: Machine,
185    images: Arc<Images>,
186    outside: Arc<Outside>,
187    purity: Arc<Facts>,
188    modref: Arc<Summaries>,
189    cfg: OnceCell<Cfg>,
190    doms: OnceCell<Dominators>,
191    post: OnceCell<PostDominators>,
192    loops: OnceCell<Loops>,
193    frontiers: OnceCell<Frontiers>,
194    control: OnceCell<ControlDependence>,
195    frequencies: OnceCell<Frequencies>,
196    live: OnceCell<Liveness>,
197    pressure: OnceCell<Pressure>,
198}
199
200impl Analyses {
201    /// An empty cache for a function being compiled for that machine.
202    ///
203    /// There is no `Default`, and the machine is why. A cache that could be made without one
204    /// would be made without one, and the pass that read it would be optimizing for a target
205    /// nobody chose. `Machine::unknown` is how a caller says it has no target, and saying it is
206    /// the point.
207    #[must_use]
208    pub fn new(machine: Machine) -> Self {
209        Self {
210            machine,
211            images: Arc::default(),
212            outside: Arc::default(),
213            purity: Arc::default(),
214            modref: Arc::default(),
215            cfg: OnceCell::new(),
216            doms: OnceCell::new(),
217            post: OnceCell::new(),
218            loops: OnceCell::new(),
219            frontiers: OnceCell::new(),
220            control: OnceCell::new(),
221            frequencies: OnceCell::new(),
222            live: OnceCell::new(),
223            pressure: OnceCell::new(),
224        }
225    }
226
227    /// The same cache, reading loads out of those images.
228    ///
229    /// Separate from [`Analyses::new`] rather than a second argument to it, because a cache
230    /// without images is a cache that folds one load fewer and a cache without a machine is a
231    /// cache that optimizes for the wrong target. The empty table is the right default and the
232    /// unknown machine is not, so one of the two is worth making every caller say and the other
233    /// is worth letting most of them leave out.
234    ///
235    /// Counted rather than copied. There is one cache per function and one table per module, and
236    /// the table is the size of the module's read only data.
237    #[must_use]
238    pub fn reading(mut self, images: Arc<Images>) -> Self {
239        self.images = images;
240        self
241    }
242
243    /// The same cache, with those module facts in it for a pass that asks the alias oracle.
244    ///
245    /// Separate from [`Analyses::reading`] for the same reason that one is separate from
246    /// [`Analyses::new`]: the empty one is the right default, it answers `May` to everything it
247    /// would have used the module for, and a caller that has no module gets a slower compile
248    /// rather than a wrong one.
249    ///
250    /// Counted rather than copied, and there is one of these per module.
251    #[must_use]
252    pub fn about(mut self, outside: Arc<Outside>) -> Self {
253        self.outside = outside;
254        self
255    }
256
257    /// The same cache, knowing what the functions this one calls are allowed to do.
258    ///
259    /// Separate from the two above it for the third time and for the third version of the same
260    /// reason. `Facts::nothing` answers [`crate::Purity::Opaque`] to every call, which is what a
261    /// call is until something says otherwise, so a pass that is correct against the empty one is
262    /// correct against every one.
263    ///
264    /// Counted rather than copied, and there is one of these per module.
265    #[must_use]
266    pub fn calling(mut self, purity: Arc<Facts>) -> Self {
267        self.purity = purity;
268        self
269    }
270
271    /// The same cache, knowing what each of those functions does to memory it was handed.
272    ///
273    /// The per parameter half of the answer above, which is the half a loop wants: purity says
274    /// whether a call wrote memory and this says whether it wrote *this*. Separate for the same
275    /// reason again, since [`Summaries::nothing`] knows nothing about anything and a pass that is
276    /// correct against that one is correct against every one.
277    ///
278    /// Counted rather than copied, and there is one of these per module.
279    #[must_use]
280    pub fn touching(mut self, modref: Arc<Summaries>) -> Self {
281        self.modref = modref;
282        self
283    }
284
285    /// The machine this function is being compiled for.
286    ///
287    /// Not an analysis, and here because this is the one thing a pass is handed besides the
288    /// function and its fuel. See [`crate::Machine`] for why that is where it went.
289    #[must_use]
290    pub const fn machine(&self) -> Machine {
291        self.machine
292    }
293
294    /// What the module's read only globals were initialized to.
295    ///
296    /// Here for the same reason the machine is, which [`crate::image`] sets out: it is a fact
297    /// about the module that a pass handed one function cannot reach any other way.
298    #[must_use]
299    pub fn images(&self) -> &Images {
300        &self.images
301    }
302
303    /// The module facts an alias oracle asks for.
304    ///
305    /// Here for the reason the images are, and [`crate::outside`] sets out the rest of it: a pass
306    /// is handed `&mut module[id]`, so the module is the one thing it cannot borrow, and the
307    /// oracle wants four small things out of it.
308    #[must_use]
309    pub fn outside(&self) -> &Outside {
310        &self.outside
311    }
312
313    /// What each function this module calls is allowed to do.
314    ///
315    /// Here for the reason the two above are, and for one more of its own: what a call can do is a
316    /// fact about the callee, there is one callee and many call sites, and a pass holding the
317    /// caller is holding the one function in the module that does not have the answer in it.
318    #[must_use]
319    pub fn purity(&self) -> &Facts {
320        &self.purity
321    }
322
323    /// What each of those functions does to the memory behind each of its pointer parameters.
324    ///
325    /// Here for the reason the one above is. See [`crate::modref`] for what is in one and for
326    /// what an alias oracle does with it.
327    #[must_use]
328    pub fn modref(&self) -> &Summaries {
329        &self.modref
330    }
331
332    /// The control flow graph, computed if it is not already here.
333    pub fn cfg(&self, func: &Func) -> &Cfg {
334        self.cfg.get_or_init(|| Cfg::new(func))
335    }
336
337    /// The dominator tree, computed if it is not already here.
338    ///
339    /// The graph comes out of the cache as well, so a caller that wants both pays for it once.
340    /// Asking for it through the method above rather than reaching into the field is allowed here
341    /// because the two are different cells, and a cell being filled in only refuses a second ask
342    /// for itself.
343    pub fn dominators(&self, func: &Func) -> &Dominators {
344        self.doms.get_or_init(|| Dominators::new(self.cfg(func)))
345    }
346
347    /// The post-dominator tree, computed if it is not already here.
348    ///
349    /// # Panics
350    ///
351    /// Panics through [`PostDominators::new`], on a function with a block that control reaches
352    /// and that has no path to any exit even after the fake edges have been added.
353    pub fn post_dominators(&self, func: &Func) -> &PostDominators {
354        self.post.get_or_init(|| PostDominators::new(self.cfg(func)))
355    }
356
357    /// The loop forest, computed if it is not already here.
358    pub fn loops(&self, func: &Func) -> &Loops {
359        self.loops.get_or_init(|| Loops::new(self.cfg(func), self.dominators(func)))
360    }
361
362    /// The dominance frontier of every block, computed if it is not already here.
363    pub fn frontiers(&self, func: &Func) -> &Frontiers {
364        self.frontiers.get_or_init(|| Frontiers::new(self.cfg(func), self.dominators(func)))
365    }
366
367    /// Which branches decide whether each block runs, computed if it is not already here.
368    ///
369    /// # Panics
370    ///
371    /// Panics through [`PostDominators::new`], for the reason above it.
372    pub fn control_dependence(&self, func: &Func) -> &ControlDependence {
373        self.control
374            .get_or_init(|| ControlDependence::new(self.cfg(func), self.post_dominators(func)))
375    }
376
377    /// How often each block runs and which way each branch goes, computed if it is not here.
378    ///
379    /// Predicted rather than measured, and every number out of it says so. A function pass is
380    /// given one function and not the module around it, so nothing is known here about what any
381    /// callee does. Section 11.2's two predictors that would like to know, which are the ones
382    /// about a call that never returns and a call to something cold, still fire on what the IR
383    /// says: the front end puts an unreachable after a call that does not come back. A module
384    /// pass that wants the rest of the answer builds its own with [`Callees::of_module`].
385    pub fn frequencies(&self, func: &Func) -> &Frequencies {
386        self.frequencies.get_or_init(|| {
387            Frequencies::of(func, self.cfg(func), self.loops(func), &Callees::nothing())
388        })
389    }
390
391    /// What is live at the edges of every block, computed if it is not here.
392    pub fn live(&self, func: &Func) -> &Liveness {
393        self.live.get_or_init(|| Liveness::of(func, self.cfg(func)))
394    }
395
396    /// How many registers of each class the function needs where, computed if it is not here.
397    ///
398    /// Section 40.6's one function with four consumers. It is in the cache rather than at each of
399    /// them because four passes computing their own liveness is four chances for the numbers to
400    /// disagree, and two passes making opposite decisions off different counts of the same thing
401    /// is the failure that is hardest to see afterwards.
402    pub fn pressure(&self, func: &Func) -> &Pressure {
403        self.pressure.get_or_init(|| Pressure::of(func, self.cfg(func), self.live(func)))
404    }
405
406    /// Whether this one is here without computing it.
407    ///
408    /// For the debug check below and for tests. A pass has no business asking, because a pass
409    /// that behaves differently depending on what somebody else happened to leave in the cache
410    /// is a pass whose output depends on the pipeline around it.
411    #[must_use]
412    pub fn holds(&self, analysis: Analysis) -> bool {
413        match analysis {
414            Analysis::Cfg => self.cfg.get().is_some(),
415            Analysis::Dominators => self.doms.get().is_some(),
416            Analysis::PostDominators => self.post.get().is_some(),
417            Analysis::Loops => self.loops.get().is_some(),
418            Analysis::Frontiers => self.frontiers.get().is_some(),
419            Analysis::ControlDependence => self.control.get().is_some(),
420            Analysis::Frequencies => self.frequencies.get().is_some(),
421            Analysis::Liveness => self.live.get().is_some(),
422            Analysis::Pressure => self.pressure.get().is_some(),
423        }
424    }
425
426    /// Takes the pass at its word, and in a checked build sees whether it was telling the truth.
427    ///
428    /// Call it after every pass over the function, with what the pass said it preserved. What
429    /// comes back is the analyses the pass claimed to preserve and did not, which is empty when
430    /// `check` is off and is empty on an honest pass. Everything the pass did not preserve is
431    /// gone from the cache afterwards, and so is everything that was built on top of it.
432    ///
433    /// The check recomputes, which is why it is behind a flag and why the flag is the one that
434    /// already turns the IR verifier on. Both are the same kind of thing: a cost paid in a
435    /// build somebody is developing in, to catch the kind of mistake that produces a wrong
436    /// program rather than a slow one.
437    pub fn settle(&mut self, func: &Func, keeps: Preserved, check: bool) -> Vec<Analysis> {
438        let lied = if check { self.lies(func, keeps) } else { Vec::new() };
439        // What the pass said, minus what it was just caught being wrong about. A cache that
440        // keeps an answer it has proved stale is worse than one that never looked, because the
441        // complaint goes into a report somebody reads later and the stale answer goes into the
442        // next pass now.
443        let mut keeps = keeps;
444        for &analysis in &lied {
445            keeps = keeps.without(analysis);
446        }
447        let alive = Self::survivors(keeps);
448        for &analysis in Analysis::EVERY {
449            if !alive[analysis as usize] {
450                self.drop(analysis);
451            }
452        }
453        lied
454    }
455
456    /// What a claim leaves standing, once what each analysis is built out of is taken into
457    /// account.
458    ///
459    /// One pass over the list in dependency order. An analysis survives if the pass said so and
460    /// everything it is built out of also survived, and because `needs` only ever names an
461    /// earlier analysis, the answer for what it needs is already final by the time this gets
462    /// there.
463    ///
464    /// Both callers want the claim read this way rather than literally. A pass that says it broke
465    /// the liveness has broken the register pressure with it whether or not it mentions it, so
466    /// the cache has to throw the counts away, and the check below has no business complaining
467    /// about an answer that is on its way out either.
468    fn survivors(keeps: Preserved) -> [bool; Analysis::EVERY.len()] {
469        let mut alive = [false; Analysis::EVERY.len()];
470        for &analysis in Analysis::EVERY {
471            alive[analysis as usize] =
472                keeps.keeps(analysis) && analysis.needs().iter().all(|&need| alive[need as usize]);
473        }
474        alive
475    }
476
477    /// Throws every analysis away, whatever any pass said.
478    ///
479    /// For the caller that changed the function itself rather than through a pass, and for a
480    /// test that wants a cold cache. The machine is not thrown away, because it is not an
481    /// analysis and nothing a pass did to the function changed which target it is for. Neither are
482    /// the images, for the same reason: no pass writes to a `const` global. Neither are the module
483    /// facts, since a function pass cannot add a symbol or a type node. Neither is what the
484    /// functions are allowed to do, which was worked out for the whole module before the run: a
485    /// pass that makes one function do less can only leave that answer stale in the safe
486    /// direction, and a pass cannot make one do more. Neither is what they do to the memory they
487    /// are handed, which is the same argument once more.
488    pub fn clear(&mut self) {
489        *self = Self::new(self.machine)
490            .reading(Arc::clone(&self.images))
491            .about(Arc::clone(&self.outside))
492            .calling(Arc::clone(&self.purity))
493            .touching(Arc::clone(&self.modref));
494    }
495
496    /// Forgets one analysis and nothing else.
497    fn drop(&mut self, analysis: Analysis) {
498        match analysis {
499            Analysis::Cfg => {
500                self.cfg.take();
501            }
502            Analysis::Dominators => {
503                self.doms.take();
504            }
505            Analysis::PostDominators => {
506                self.post.take();
507            }
508            Analysis::Loops => {
509                self.loops.take();
510            }
511            Analysis::Frontiers => {
512                self.frontiers.take();
513            }
514            Analysis::ControlDependence => {
515                self.control.take();
516            }
517            Analysis::Frequencies => {
518                self.frequencies.take();
519            }
520            Analysis::Liveness => {
521                self.live.take();
522            }
523            Analysis::Pressure => {
524                self.pressure.take();
525            }
526        }
527    }
528
529    /// The analyses that are here, were claimed to be preserved, and do not match what the
530    /// function says now.
531    ///
532    /// Only the ones that are here, because an analysis nobody asked for is one nobody can have
533    /// been misled by, and recomputing it to check a claim about it would be the cache doing
534    /// work the compilation never wanted. Only the ones the claim leaves standing, too, for the
535    /// same reason: what is about to be thrown away cannot mislead anybody either.
536    fn lies(&self, func: &Func, keeps: Preserved) -> Vec<Analysis> {
537        let alive = Self::survivors(keeps);
538        let wanted: Vec<Analysis> = Analysis::EVERY
539            .iter()
540            .copied()
541            .filter(|&it| self.holds(it) && alive[it as usize])
542            .collect();
543        if wanted.is_empty() {
544            return Vec::new();
545        }
546        // From the function rather than from anything cached, since what is cached is exactly
547        // what is under suspicion.
548        let cfg = Cfg::new(func);
549        let mut lied = Vec::new();
550        for analysis in wanted {
551            let same = match analysis {
552                Analysis::Cfg => self.cfg.get() == Some(&cfg),
553                Analysis::Dominators => self.doms.get() == Some(&Dominators::new(&cfg)),
554                Analysis::PostDominators => self.post.get() == Some(&PostDominators::new(&cfg)),
555                Analysis::Loops => {
556                    self.loops.get() == Some(&Loops::new(&cfg, &Dominators::new(&cfg)))
557                }
558                Analysis::Frontiers => {
559                    self.frontiers.get() == Some(&Frontiers::new(&cfg, &Dominators::new(&cfg)))
560                }
561                Analysis::ControlDependence => {
562                    self.control.get()
563                        == Some(&ControlDependence::new(&cfg, &PostDominators::new(&cfg)))
564                }
565                Analysis::Frequencies => {
566                    let doms = Dominators::new(&cfg);
567                    let loops = Loops::new(&cfg, &doms);
568                    let now = Frequencies::of(func, &cfg, &loops, &Callees::nothing());
569                    self.frequencies.get() == Some(&now)
570                }
571                Analysis::Liveness => self.live.get() == Some(&Liveness::of(func, &cfg)),
572                Analysis::Pressure => {
573                    let live = Liveness::of(func, &cfg);
574                    self.pressure.get() == Some(&Pressure::of(func, &cfg, &live))
575                }
576            };
577            if !same {
578                lied.push(analysis);
579            }
580        }
581        lied
582    }
583}
584
585#[cfg(test)]
586mod tests {
587    use std::sync::Arc;
588
589    use rucc_base::Interner;
590    use rucc_ir::{Block, Func, Signature};
591
592    use super::{Analysis, Preserved};
593    use crate::modref::{Summaries, Summary};
594    use crate::purity::{Facts, Purity};
595    use crate::testing::graph;
596
597    /// A diamond with a loop around the join, which is a shape every analysis here has something
598    /// to say about.
599    fn func() -> Func {
600        graph(&[&[1, 2], &[3], &[3], &[4, 1], &[]])
601    }
602
603    #[test]
604    fn an_analysis_is_built_out_of_ones_that_come_before_it() {
605        // The invalidation walk depends on this and would silently keep a stale analysis if it
606        // stopped being true, which is the one bug this file exists to stop.
607        for &analysis in Analysis::EVERY {
608            for &need in analysis.needs() {
609                assert!(need < analysis, "{} is built out of a later analysis", analysis.name());
610            }
611        }
612    }
613
614    #[test]
615    fn every_analysis_is_in_the_list_once() {
616        for &analysis in Analysis::EVERY {
617            let found = Analysis::EVERY.iter().filter(|&&it| it == analysis).count();
618            assert_eq!(found, 1, "{} appears twice", analysis.name());
619        }
620        assert_eq!(Analysis::EVERY.len(), 9);
621    }
622
623    #[test]
624    fn all_keeps_everything_and_none_keeps_nothing() {
625        for &analysis in Analysis::EVERY {
626            assert!(Preserved::ALL.keeps(analysis));
627            assert!(!Preserved::NONE.keeps(analysis));
628        }
629    }
630
631    #[test]
632    fn a_named_set_holds_what_was_named_and_nothing_else() {
633        let keeps = Preserved::NONE.and(Analysis::Cfg).and(Analysis::Loops);
634        assert!(keeps.keeps(Analysis::Cfg));
635        assert!(keeps.keeps(Analysis::Loops));
636        assert!(!keeps.keeps(Analysis::Dominators));
637        assert!(!keeps.keeps(Analysis::PostDominators));
638    }
639
640    #[test]
641    fn nothing_is_computed_until_it_is_asked_for() {
642        let an = crate::machine::fixtures::analyses();
643        for &analysis in Analysis::EVERY {
644            assert!(!an.holds(analysis));
645        }
646        let func = func();
647        an.dominators(&func);
648        // The graph as well, because the tree is built out of it and building it twice is what
649        // the cache is here to stop.
650        assert!(an.holds(Analysis::Cfg));
651        assert!(an.holds(Analysis::Dominators));
652        assert!(!an.holds(Analysis::Loops));
653        assert!(!an.holds(Analysis::PostDominators));
654    }
655
656    #[test]
657    fn asking_twice_gives_the_same_answer_and_the_second_one_is_free() {
658        let func = func();
659        let an = crate::machine::fixtures::analyses();
660        let first = an.cfg(&func).clone();
661        let second = an.cfg(&func);
662        assert_eq!(&first, second);
663    }
664
665    #[test]
666    fn the_loop_forest_pulls_in_what_it_is_built_out_of() {
667        let func = func();
668        let an = crate::machine::fixtures::analyses();
669        an.loops(&func);
670        assert!(an.holds(Analysis::Cfg));
671        assert!(an.holds(Analysis::Dominators));
672        assert!(an.holds(Analysis::Loops));
673    }
674
675    #[test]
676    fn preserving_everything_keeps_everything() {
677        let func = func();
678        let mut an = crate::machine::fixtures::analyses();
679        an.loops(&func);
680        an.frontiers(&func);
681        an.control_dependence(&func);
682        an.frequencies(&func);
683        an.pressure(&func);
684        assert!(an.settle(&func, Preserved::ALL, true).is_empty());
685        for &analysis in Analysis::EVERY {
686            assert!(an.holds(analysis), "{} was thrown away", analysis.name());
687        }
688    }
689
690    #[test]
691    fn the_pressure_falls_with_the_liveness_it_was_counted_from() {
692        let func = func();
693        let mut an = crate::machine::fixtures::analyses();
694        an.pressure(&func);
695        assert!(an.holds(Analysis::Liveness), "it had to be computed to count anything");
696        let keeps = Preserved::NONE.and(Analysis::Cfg).and(Analysis::Pressure);
697        an.settle(&func, keeps, false);
698        assert!(an.holds(Analysis::Cfg));
699        assert!(!an.holds(Analysis::Liveness));
700        assert!(!an.holds(Analysis::Pressure), "a count outlived what it counted");
701    }
702
703    #[test]
704    fn a_claim_is_read_with_what_each_analysis_is_built_out_of() {
705        // So `.without(Analysis::Liveness)` is the whole claim a pass that moved a use has to
706        // make. The counts come off the liveness, so they went with it, and a pass that had to
707        // remember to say so twice would be a pass that eventually forgot.
708        let alive = super::Analyses::survivors(Preserved::ALL.without(Analysis::Liveness));
709        assert!(!alive[Analysis::Liveness as usize]);
710        assert!(!alive[Analysis::Pressure as usize], "a count survived what it was counted from");
711        assert!(alive[Analysis::Loops as usize], "the shape of the function did not change");
712    }
713
714    #[test]
715    fn preserving_nothing_empties_the_cache() {
716        let func = func();
717        let mut an = crate::machine::fixtures::analyses();
718        an.loops(&func);
719        an.frontiers(&func);
720        an.control_dependence(&func);
721        an.settle(&func, Preserved::NONE, false);
722        for &analysis in Analysis::EVERY {
723            assert!(!an.holds(analysis), "{} outlived the pass", analysis.name());
724        }
725    }
726
727    #[test]
728    fn losing_the_graph_loses_what_was_built_on_it() {
729        let func = func();
730        let mut an = crate::machine::fixtures::analyses();
731        an.loops(&func);
732        an.post_dominators(&func);
733        // A pass that says it kept the trees and the forest and not the graph they came out of.
734        // What it says about them is not wrong so much as meaningless, and taking it at its word
735        // is how a stale dominator tree reaches the pass after next.
736        let keeps = Preserved::NONE
737            .and(Analysis::Dominators)
738            .and(Analysis::PostDominators)
739            .and(Analysis::Loops);
740        an.settle(&func, keeps, false);
741        for &analysis in Analysis::EVERY {
742            assert!(!an.holds(analysis), "{} outlived the graph", analysis.name());
743        }
744    }
745
746    #[test]
747    fn losing_the_dominator_tree_loses_the_forest_and_leaves_the_graph() {
748        let func = func();
749        let mut an = crate::machine::fixtures::analyses();
750        an.loops(&func);
751        an.post_dominators(&func);
752        let keeps =
753            Preserved::NONE.and(Analysis::Cfg).and(Analysis::PostDominators).and(Analysis::Loops);
754        an.settle(&func, keeps, false);
755        assert!(an.holds(Analysis::Cfg));
756        assert!(an.holds(Analysis::PostDominators));
757        assert!(!an.holds(Analysis::Dominators), "the tree was not preserved");
758        assert!(!an.holds(Analysis::Loops), "the forest outlived the tree it needs");
759    }
760
761    #[test]
762    fn each_frontier_falls_with_the_tree_it_was_walked_on_and_not_the_other_one() {
763        // The two frontiers are the same algorithm, but they are not the same analysis. A pass
764        // that claims both and only keeps one of the two trees gets to keep one of them, and the
765        // other goes with the tree it was walked on whatever the pass said about it.
766        let func = func();
767        let mut an = crate::machine::fixtures::analyses();
768        an.frontiers(&func);
769        an.control_dependence(&func);
770        let keeps = Preserved::NONE
771            .and(Analysis::Cfg)
772            .and(Analysis::Dominators)
773            .and(Analysis::Frontiers)
774            .and(Analysis::ControlDependence);
775        an.settle(&func, keeps, false);
776        assert!(an.holds(Analysis::Frontiers), "the frontier stands on a tree that stood");
777        assert!(!an.holds(Analysis::ControlDependence), "the post-dominator tree went with it");
778    }
779
780    #[test]
781    fn the_frequencies_fall_with_the_loop_forest_they_were_worked_out_from() {
782        let func = func();
783        let mut an = crate::machine::fixtures::analyses();
784        an.frequencies(&func);
785        // Asking for them brings in the graph, the tree and the forest, because the series in
786        // section 11.3 is per loop and there is no loop without all three.
787        for analysis in [Analysis::Cfg, Analysis::Dominators, Analysis::Loops] {
788            assert!(an.holds(analysis), "{} was not pulled in", analysis.name());
789        }
790        let keeps =
791            Preserved::NONE.and(Analysis::Cfg).and(Analysis::Dominators).and(Analysis::Frequencies);
792        an.settle(&func, keeps, false);
793        assert!(!an.holds(Analysis::Loops), "the forest was not preserved");
794        assert!(!an.holds(Analysis::Frequencies), "a frequency outlived the loop it counted");
795    }
796
797    #[test]
798    fn a_pass_that_says_it_kept_the_graph_and_moved_an_edge_is_caught() {
799        let mut func = func();
800        let mut an = crate::machine::fixtures::analyses();
801        an.loops(&func);
802        // The edit a lying pass makes: block4 falls off the end of the diamond, and now it
803        // returns to nobody instead. The blocks are the same blocks and the graph is not the
804        // same graph.
805        let block = Block::from_usize(3);
806        let term = func.terminator(block).expect("the helper gives every block a terminator");
807        func.remove_inst(term);
808        let mut build = rucc_ir::Builder::new(&mut func, block);
809        build.ret(&[]);
810        let lied = an.settle(&func, Preserved::ALL, true);
811        assert_eq!(lied, vec![Analysis::Cfg, Analysis::Dominators, Analysis::Loops]);
812        // And it is thrown away anyway, because a cache that keeps what it just proved wrong is
813        // worse than one that never checked.
814        for &analysis in Analysis::EVERY {
815            assert!(!an.holds(analysis));
816        }
817    }
818
819    #[test]
820    fn a_lie_about_the_frontiers_is_caught_the_same_way() {
821        let mut func = func();
822        let mut an = crate::machine::fixtures::analyses();
823        an.frontiers(&func);
824        an.control_dependence(&func);
825        // The back edge goes away, so block1 stops being a join and block3 stops being a branch.
826        // Both frontiers move, and a pass that swears they did not is wrong about both.
827        let block = Block::from_usize(3);
828        let term = func.terminator(block).expect("the helper gives every block a terminator");
829        func.remove_inst(term);
830        let mut build = rucc_ir::Builder::new(&mut func, block);
831        build.ret(&[]);
832        let lied = an.settle(&func, Preserved::ALL, true);
833        assert!(lied.contains(&Analysis::Frontiers));
834        assert!(lied.contains(&Analysis::ControlDependence));
835    }
836
837    #[test]
838    fn the_check_costs_nothing_when_it_is_off() {
839        let mut func = func();
840        let mut an = crate::machine::fixtures::analyses();
841        an.cfg(&func);
842        let block = Block::from_usize(3);
843        let term = func.terminator(block).expect("the helper gives every block a terminator");
844        func.remove_inst(term);
845        let mut build = rucc_ir::Builder::new(&mut func, block);
846        build.ret(&[]);
847        assert!(an.settle(&func, Preserved::ALL, false).is_empty());
848        // Which is the trade the flag is: the lie is not caught, and the stale graph is still
849        // there, exactly as the pass claimed.
850        assert!(an.holds(Analysis::Cfg));
851    }
852
853    #[test]
854    fn an_analysis_nobody_asked_for_is_not_checked() {
855        let func = func();
856        let mut an = crate::machine::fixtures::analyses();
857        assert!(an.settle(&func, Preserved::ALL, true).is_empty());
858    }
859
860    #[test]
861    fn a_declaration_has_analyses_like_anything_else() {
862        // Because the pipeline hands the cache whatever the module holds, and a cache that
863        // panicked on a function with no body would put the check in every caller.
864        let mut names = Interner::new();
865        let func = Func::new(names.intern("declared"), Signature::new());
866        let mut an = crate::machine::fixtures::analyses();
867        assert!(an.cfg(&func).entry().is_none());
868        an.loops(&func);
869        an.post_dominators(&func);
870        assert!(an.settle(&func, Preserved::ALL, true).is_empty());
871    }
872
873    #[test]
874    fn clearing_keeps_what_the_whole_module_said() {
875        // Only the per function answers go. What the module said was worked out once before any
876        // pass ran and no function pass can have made it wrong, so a cache that dropped it would
877        // quietly hand the next pass an empty set of facts and the pass would find nothing.
878        let mut names = Interner::new();
879        let name = names.intern("f");
880        let mut facts = Facts::default();
881        facts.record_inferred(name, Purity::Const);
882        let mut modref = Summaries::nothing();
883        modref.record(name, Summary::nothing(1));
884        let mut an = crate::machine::fixtures::analyses()
885            .calling(Arc::new(facts))
886            .touching(Arc::new(modref));
887        an.clear();
888        assert_eq!(an.purity().inferred(name), Purity::Const);
889        assert!(an.modref().of(name).is_some_and(Summary::touches_nothing));
890    }
891
892    #[test]
893    fn clearing_takes_everything() {
894        let func = func();
895        let mut an = crate::machine::fixtures::analyses();
896        an.loops(&func);
897        an.clear();
898        for &analysis in Analysis::EVERY {
899            assert!(!an.holds(analysis));
900        }
901    }
902}