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 rucc_ir::Func;
36
37use crate::machine::Machine;
38use crate::predict::Callees;
39use crate::{
40    Cfg, ControlDependence, Dominators, Frequencies, Frontiers, Liveness, Loops, PostDominators,
41    Pressure,
42};
43
44/// One analysis this cache holds.
45///
46/// The order matters and is checked by a test: an analysis is built out of analyses that come
47/// before it in this list and never out of one that comes after. That is what lets the
48/// invalidation walk settle in one pass over the list rather than in a loop to a fixed point.
49#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
50pub enum Analysis {
51    /// [`Cfg`], which everything else here is built on.
52    Cfg,
53    /// [`Dominators`].
54    Dominators,
55    /// [`PostDominators`].
56    PostDominators,
57    /// [`Loops`].
58    Loops,
59    /// [`Frontiers`].
60    Frontiers,
61    /// [`ControlDependence`].
62    ControlDependence,
63    /// [`Frequencies`], which carries the branch predictions it was worked out from.
64    Frequencies,
65    /// [`Liveness`].
66    Liveness,
67    /// [`Pressure`], which is the live counts split by register class.
68    Pressure,
69}
70
71impl Analysis {
72    /// Every analysis this cache holds, in dependency order.
73    pub const EVERY: &'static [Analysis] = &[
74        Analysis::Cfg,
75        Analysis::Dominators,
76        Analysis::PostDominators,
77        Analysis::Loops,
78        Analysis::Frontiers,
79        Analysis::ControlDependence,
80        Analysis::Frequencies,
81        Analysis::Liveness,
82        Analysis::Pressure,
83    ];
84
85    /// What it is called in a message to somebody debugging a pass.
86    #[must_use]
87    pub const fn name(self) -> &'static str {
88        match self {
89            Self::Cfg => "the control flow graph",
90            Self::Dominators => "the dominator tree",
91            Self::PostDominators => "the post-dominator tree",
92            Self::Loops => "the loop forest",
93            Self::Frontiers => "the dominance frontiers",
94            Self::ControlDependence => "the control dependence relation",
95            Self::Frequencies => "the block frequencies",
96            Self::Liveness => "the liveness",
97            Self::Pressure => "the register pressure",
98        }
99    }
100
101    /// The analyses this one is built out of, which cannot outlive it.
102    ///
103    /// Section 4.4 of the design has a table of these and the entry for almost every row is
104    /// "any CFG change", which is why [`Analysis::Cfg`] is what the other three name.
105    #[must_use]
106    pub const fn needs(self) -> &'static [Analysis] {
107        match self {
108            Self::Cfg => &[],
109            Self::Dominators | Self::PostDominators => &[Analysis::Cfg],
110            Self::Loops | Self::Frontiers => &[Analysis::Cfg, Analysis::Dominators],
111            Self::ControlDependence => &[Analysis::Cfg, Analysis::PostDominators],
112            Self::Frequencies => &[Analysis::Cfg, Analysis::Dominators, Analysis::Loops],
113            Self::Liveness => &[Analysis::Cfg],
114            Self::Pressure => &[Analysis::Cfg, Analysis::Liveness],
115        }
116    }
117
118    /// Which bit of a [`Preserved`] set this one is.
119    const fn bit(self) -> u16 {
120        1 << (self as u16)
121    }
122}
123
124/// What a pass leaves standing.
125///
126/// A set rather than the three cases the design writes, because [`Preserved::ALL`] and
127/// [`Preserved::NONE`] are the full set and the empty one and a named set is what is between
128/// them. A pass that adds an analysis to this list is saying the code it produced answers the
129/// same questions the code it was given did, which is a claim about a pass and not about a run,
130/// so it is stated once on the pass rather than returned from each call.
131#[derive(Clone, Copy, Debug, PartialEq, Eq)]
132pub struct Preserved(u16);
133
134impl Preserved {
135    /// Everything, which is what a pass that does not change the shape of a function says.
136    pub const ALL: Preserved = Preserved(u16::MAX);
137
138    /// Nothing, which is what a pass that moves an edge says, however small the move was.
139    pub const NONE: Preserved = Preserved(0);
140
141    /// This set and that analysis.
142    #[must_use]
143    pub const fn and(self, analysis: Analysis) -> Self {
144        Self(self.0 | analysis.bit())
145    }
146
147    /// This set without that analysis.
148    ///
149    /// The way a pass says "everything except one thing", which is what a pass that rewrites
150    /// operands and moves no edge has to say: the shape of the function is what it was and the
151    /// liveness is not, because a value read in one more place is live in one more place.
152    #[must_use]
153    pub const fn without(self, analysis: Analysis) -> Self {
154        Self(self.0 & !analysis.bit())
155    }
156
157    /// Whether the pass said this one survived.
158    #[must_use]
159    pub const fn keeps(self, analysis: Analysis) -> bool {
160        self.0 & analysis.bit() != 0
161    }
162}
163
164/// The analyses of one function, computed when asked for and kept until something breaks them.
165///
166/// Empty to start with. Nothing here is computed by existing, which matters because most
167/// functions are walked by a pass that wants none of it.
168#[derive(Clone, Debug)]
169pub struct Analyses {
170    machine: Machine,
171    cfg: Option<Cfg>,
172    doms: Option<Dominators>,
173    post: Option<PostDominators>,
174    loops: Option<Loops>,
175    frontiers: Option<Frontiers>,
176    control: Option<ControlDependence>,
177    frequencies: Option<Frequencies>,
178    live: Option<Liveness>,
179    pressure: Option<Pressure>,
180}
181
182impl Analyses {
183    /// An empty cache for a function being compiled for that machine.
184    ///
185    /// There is no `Default`, and the machine is why. A cache that could be made without one
186    /// would be made without one, and the pass that read it would be optimizing for a target
187    /// nobody chose. `Machine::unknown` is how a caller says it has no target, and saying it is
188    /// the point.
189    #[must_use]
190    pub fn new(machine: Machine) -> Self {
191        Self {
192            machine,
193            cfg: None,
194            doms: None,
195            post: None,
196            loops: None,
197            frontiers: None,
198            control: None,
199            frequencies: None,
200            live: None,
201            pressure: None,
202        }
203    }
204
205    /// The machine this function is being compiled for.
206    ///
207    /// Not an analysis, and here because this is the one thing a pass is handed besides the
208    /// function and its fuel. See [`crate::Machine`] for why that is where it went.
209    #[must_use]
210    pub const fn machine(&self) -> Machine {
211        self.machine
212    }
213
214    /// The control flow graph, computed if it is not already here.
215    pub fn cfg(&mut self, func: &Func) -> &Cfg {
216        self.cfg.get_or_insert_with(|| Cfg::new(func))
217    }
218
219    /// The dominator tree, computed if it is not already here.
220    ///
221    /// The graph comes out of the cache as well, so a caller that wants both pays for it once.
222    /// Each of these is written against the field rather than through the method above it,
223    /// because two fields of one structure can be borrowed at the same time and two calls that
224    /// each take all of `self` cannot.
225    pub fn dominators(&mut self, func: &Func) -> &Dominators {
226        let cfg: &Cfg = self.cfg.get_or_insert_with(|| Cfg::new(func));
227        self.doms.get_or_insert_with(|| Dominators::new(cfg))
228    }
229
230    /// The post-dominator tree, computed if it is not already here.
231    ///
232    /// # Panics
233    ///
234    /// Panics through [`PostDominators::new`], on a function with a block that control reaches
235    /// and that has no path to any exit even after the fake edges have been added.
236    pub fn post_dominators(&mut self, func: &Func) -> &PostDominators {
237        let cfg: &Cfg = self.cfg.get_or_insert_with(|| Cfg::new(func));
238        self.post.get_or_insert_with(|| PostDominators::new(cfg))
239    }
240
241    /// The loop forest, computed if it is not already here.
242    pub fn loops(&mut self, func: &Func) -> &Loops {
243        let cfg: &Cfg = self.cfg.get_or_insert_with(|| Cfg::new(func));
244        let doms: &Dominators = self.doms.get_or_insert_with(|| Dominators::new(cfg));
245        self.loops.get_or_insert_with(|| Loops::new(cfg, doms))
246    }
247
248    /// The dominance frontier of every block, computed if it is not already here.
249    pub fn frontiers(&mut self, func: &Func) -> &Frontiers {
250        let cfg: &Cfg = self.cfg.get_or_insert_with(|| Cfg::new(func));
251        let doms: &Dominators = self.doms.get_or_insert_with(|| Dominators::new(cfg));
252        self.frontiers.get_or_insert_with(|| Frontiers::new(cfg, doms))
253    }
254
255    /// Which branches decide whether each block runs, computed if it is not already here.
256    ///
257    /// # Panics
258    ///
259    /// Panics through [`PostDominators::new`], for the reason above it.
260    pub fn control_dependence(&mut self, func: &Func) -> &ControlDependence {
261        let cfg: &Cfg = self.cfg.get_or_insert_with(|| Cfg::new(func));
262        let post: &PostDominators = self.post.get_or_insert_with(|| PostDominators::new(cfg));
263        self.control.get_or_insert_with(|| ControlDependence::new(cfg, post))
264    }
265
266    /// How often each block runs and which way each branch goes, computed if it is not here.
267    ///
268    /// Predicted rather than measured, and every number out of it says so. A function pass is
269    /// given one function and not the module around it, so nothing is known here about what any
270    /// callee does. Section 11.2's two predictors that would like to know, which are the ones
271    /// about a call that never returns and a call to something cold, still fire on what the IR
272    /// says: the front end puts an unreachable after a call that does not come back. A module
273    /// pass that wants the rest of the answer builds its own with [`Callees::of_module`].
274    pub fn frequencies(&mut self, func: &Func) -> &Frequencies {
275        let cfg: &Cfg = self.cfg.get_or_insert_with(|| Cfg::new(func));
276        let doms: &Dominators = self.doms.get_or_insert_with(|| Dominators::new(cfg));
277        let loops: &Loops = self.loops.get_or_insert_with(|| Loops::new(cfg, doms));
278        self.frequencies
279            .get_or_insert_with(|| Frequencies::of(func, cfg, loops, &Callees::nothing()))
280    }
281
282    /// What is live at the edges of every block, computed if it is not here.
283    pub fn live(&mut self, func: &Func) -> &Liveness {
284        let cfg: &Cfg = self.cfg.get_or_insert_with(|| Cfg::new(func));
285        self.live.get_or_insert_with(|| Liveness::of(func, cfg))
286    }
287
288    /// How many registers of each class the function needs where, computed if it is not here.
289    ///
290    /// Section 40.6's one function with four consumers. It is in the cache rather than at each of
291    /// them because four passes computing their own liveness is four chances for the numbers to
292    /// disagree, and two passes making opposite decisions off different counts of the same thing
293    /// is the failure that is hardest to see afterwards.
294    pub fn pressure(&mut self, func: &Func) -> &Pressure {
295        let cfg: &Cfg = self.cfg.get_or_insert_with(|| Cfg::new(func));
296        let live: &Liveness = self.live.get_or_insert_with(|| Liveness::of(func, cfg));
297        self.pressure.get_or_insert_with(|| Pressure::of(func, cfg, live))
298    }
299
300    /// Whether this one is here without computing it.
301    ///
302    /// For the debug check below and for tests. A pass has no business asking, because a pass
303    /// that behaves differently depending on what somebody else happened to leave in the cache
304    /// is a pass whose output depends on the pipeline around it.
305    #[must_use]
306    pub fn holds(&self, analysis: Analysis) -> bool {
307        match analysis {
308            Analysis::Cfg => self.cfg.is_some(),
309            Analysis::Dominators => self.doms.is_some(),
310            Analysis::PostDominators => self.post.is_some(),
311            Analysis::Loops => self.loops.is_some(),
312            Analysis::Frontiers => self.frontiers.is_some(),
313            Analysis::ControlDependence => self.control.is_some(),
314            Analysis::Frequencies => self.frequencies.is_some(),
315            Analysis::Liveness => self.live.is_some(),
316            Analysis::Pressure => self.pressure.is_some(),
317        }
318    }
319
320    /// Takes the pass at its word, and in a checked build sees whether it was telling the truth.
321    ///
322    /// Call it after every pass over the function, with what the pass said it preserved. What
323    /// comes back is the analyses the pass claimed to preserve and did not, which is empty when
324    /// `check` is off and is empty on an honest pass. Everything the pass did not preserve is
325    /// gone from the cache afterwards, and so is everything that was built on top of it.
326    ///
327    /// The check recomputes, which is why it is behind a flag and why the flag is the one that
328    /// already turns the IR verifier on. Both are the same kind of thing: a cost paid in a
329    /// build somebody is developing in, to catch the kind of mistake that produces a wrong
330    /// program rather than a slow one.
331    pub fn settle(&mut self, func: &Func, keeps: Preserved, check: bool) -> Vec<Analysis> {
332        let lied = if check { self.lies(func, keeps) } else { Vec::new() };
333        // What the pass said, minus what it was just caught being wrong about. A cache that
334        // keeps an answer it has proved stale is worse than one that never looked, because the
335        // complaint goes into a report somebody reads later and the stale answer goes into the
336        // next pass now.
337        let mut keeps = keeps;
338        for &analysis in &lied {
339            keeps = keeps.without(analysis);
340        }
341        // One pass over the list in dependency order. An analysis survives if the pass said so
342        // and everything it is built out of also survived, and because `needs` only ever names
343        // an earlier analysis, the answer for what it needs is already final by the time this
344        // gets here.
345        let mut alive = [false; Analysis::EVERY.len()];
346        for &analysis in Analysis::EVERY {
347            let kept =
348                keeps.keeps(analysis) && analysis.needs().iter().all(|&need| alive[need as usize]);
349            alive[analysis as usize] = kept;
350            if !kept {
351                self.drop(analysis);
352            }
353        }
354        lied
355    }
356
357    /// Throws every analysis away, whatever any pass said.
358    ///
359    /// For the caller that changed the function itself rather than through a pass, and for a
360    /// test that wants a cold cache. The machine is not thrown away, because it is not an
361    /// analysis and nothing a pass did to the function changed which target it is for.
362    pub fn clear(&mut self) {
363        *self = Self::new(self.machine);
364    }
365
366    /// Forgets one analysis and nothing else.
367    fn drop(&mut self, analysis: Analysis) {
368        match analysis {
369            Analysis::Cfg => self.cfg = None,
370            Analysis::Dominators => self.doms = None,
371            Analysis::PostDominators => self.post = None,
372            Analysis::Loops => self.loops = None,
373            Analysis::Frontiers => self.frontiers = None,
374            Analysis::ControlDependence => self.control = None,
375            Analysis::Frequencies => self.frequencies = None,
376            Analysis::Liveness => self.live = None,
377            Analysis::Pressure => self.pressure = None,
378        }
379    }
380
381    /// The analyses that are here, were claimed to be preserved, and do not match what the
382    /// function says now.
383    ///
384    /// Only the ones that are here, because an analysis nobody asked for is one nobody can have
385    /// been misled by, and recomputing it to check a claim about it would be the cache doing
386    /// work the compilation never wanted.
387    fn lies(&self, func: &Func, keeps: Preserved) -> Vec<Analysis> {
388        let wanted: Vec<Analysis> = Analysis::EVERY
389            .iter()
390            .copied()
391            .filter(|&it| self.holds(it) && keeps.keeps(it))
392            .collect();
393        if wanted.is_empty() {
394            return Vec::new();
395        }
396        // From the function rather than from anything cached, since what is cached is exactly
397        // what is under suspicion.
398        let cfg = Cfg::new(func);
399        let mut lied = Vec::new();
400        for analysis in wanted {
401            let same = match analysis {
402                Analysis::Cfg => self.cfg.as_ref() == Some(&cfg),
403                Analysis::Dominators => self.doms.as_ref() == Some(&Dominators::new(&cfg)),
404                Analysis::PostDominators => self.post.as_ref() == Some(&PostDominators::new(&cfg)),
405                Analysis::Loops => {
406                    self.loops.as_ref() == Some(&Loops::new(&cfg, &Dominators::new(&cfg)))
407                }
408                Analysis::Frontiers => {
409                    self.frontiers.as_ref() == Some(&Frontiers::new(&cfg, &Dominators::new(&cfg)))
410                }
411                Analysis::ControlDependence => {
412                    self.control.as_ref()
413                        == Some(&ControlDependence::new(&cfg, &PostDominators::new(&cfg)))
414                }
415                Analysis::Frequencies => {
416                    let doms = Dominators::new(&cfg);
417                    let loops = Loops::new(&cfg, &doms);
418                    let now = Frequencies::of(func, &cfg, &loops, &Callees::nothing());
419                    self.frequencies.as_ref() == Some(&now)
420                }
421                Analysis::Liveness => self.live.as_ref() == Some(&Liveness::of(func, &cfg)),
422                Analysis::Pressure => {
423                    let live = Liveness::of(func, &cfg);
424                    self.pressure.as_ref() == Some(&Pressure::of(func, &cfg, &live))
425                }
426            };
427            if !same {
428                lied.push(analysis);
429            }
430        }
431        lied
432    }
433}
434
435#[cfg(test)]
436mod tests {
437    use rucc_base::Interner;
438    use rucc_ir::{Block, Func, Signature};
439
440    use super::{Analysis, Preserved};
441    use crate::testing::graph;
442
443    /// A diamond with a loop around the join, which is a shape every analysis here has something
444    /// to say about.
445    fn func() -> Func {
446        graph(&[&[1, 2], &[3], &[3], &[4, 1], &[]])
447    }
448
449    #[test]
450    fn an_analysis_is_built_out_of_ones_that_come_before_it() {
451        // The invalidation walk depends on this and would silently keep a stale analysis if it
452        // stopped being true, which is the one bug this file exists to stop.
453        for &analysis in Analysis::EVERY {
454            for &need in analysis.needs() {
455                assert!(need < analysis, "{} is built out of a later analysis", analysis.name());
456            }
457        }
458    }
459
460    #[test]
461    fn every_analysis_is_in_the_list_once() {
462        for &analysis in Analysis::EVERY {
463            let found = Analysis::EVERY.iter().filter(|&&it| it == analysis).count();
464            assert_eq!(found, 1, "{} appears twice", analysis.name());
465        }
466        assert_eq!(Analysis::EVERY.len(), 9);
467    }
468
469    #[test]
470    fn all_keeps_everything_and_none_keeps_nothing() {
471        for &analysis in Analysis::EVERY {
472            assert!(Preserved::ALL.keeps(analysis));
473            assert!(!Preserved::NONE.keeps(analysis));
474        }
475    }
476
477    #[test]
478    fn a_named_set_holds_what_was_named_and_nothing_else() {
479        let keeps = Preserved::NONE.and(Analysis::Cfg).and(Analysis::Loops);
480        assert!(keeps.keeps(Analysis::Cfg));
481        assert!(keeps.keeps(Analysis::Loops));
482        assert!(!keeps.keeps(Analysis::Dominators));
483        assert!(!keeps.keeps(Analysis::PostDominators));
484    }
485
486    #[test]
487    fn nothing_is_computed_until_it_is_asked_for() {
488        let mut an = crate::machine::fixtures::analyses();
489        for &analysis in Analysis::EVERY {
490            assert!(!an.holds(analysis));
491        }
492        let func = func();
493        an.dominators(&func);
494        // The graph as well, because the tree is built out of it and building it twice is what
495        // the cache is here to stop.
496        assert!(an.holds(Analysis::Cfg));
497        assert!(an.holds(Analysis::Dominators));
498        assert!(!an.holds(Analysis::Loops));
499        assert!(!an.holds(Analysis::PostDominators));
500    }
501
502    #[test]
503    fn asking_twice_gives_the_same_answer_and_the_second_one_is_free() {
504        let func = func();
505        let mut an = crate::machine::fixtures::analyses();
506        let first = an.cfg(&func).clone();
507        let second = an.cfg(&func);
508        assert_eq!(&first, second);
509    }
510
511    #[test]
512    fn the_loop_forest_pulls_in_what_it_is_built_out_of() {
513        let func = func();
514        let mut an = crate::machine::fixtures::analyses();
515        an.loops(&func);
516        assert!(an.holds(Analysis::Cfg));
517        assert!(an.holds(Analysis::Dominators));
518        assert!(an.holds(Analysis::Loops));
519    }
520
521    #[test]
522    fn preserving_everything_keeps_everything() {
523        let func = func();
524        let mut an = crate::machine::fixtures::analyses();
525        an.loops(&func);
526        an.frontiers(&func);
527        an.control_dependence(&func);
528        an.frequencies(&func);
529        an.pressure(&func);
530        assert!(an.settle(&func, Preserved::ALL, true).is_empty());
531        for &analysis in Analysis::EVERY {
532            assert!(an.holds(analysis), "{} was thrown away", analysis.name());
533        }
534    }
535
536    #[test]
537    fn the_pressure_falls_with_the_liveness_it_was_counted_from() {
538        let func = func();
539        let mut an = crate::machine::fixtures::analyses();
540        an.pressure(&func);
541        assert!(an.holds(Analysis::Liveness), "it had to be computed to count anything");
542        let keeps = Preserved::NONE.and(Analysis::Cfg).and(Analysis::Pressure);
543        an.settle(&func, keeps, false);
544        assert!(an.holds(Analysis::Cfg));
545        assert!(!an.holds(Analysis::Liveness));
546        assert!(!an.holds(Analysis::Pressure), "a count outlived what it counted");
547    }
548
549    #[test]
550    fn preserving_nothing_empties_the_cache() {
551        let func = func();
552        let mut an = crate::machine::fixtures::analyses();
553        an.loops(&func);
554        an.frontiers(&func);
555        an.control_dependence(&func);
556        an.settle(&func, Preserved::NONE, false);
557        for &analysis in Analysis::EVERY {
558            assert!(!an.holds(analysis), "{} outlived the pass", analysis.name());
559        }
560    }
561
562    #[test]
563    fn losing_the_graph_loses_what_was_built_on_it() {
564        let func = func();
565        let mut an = crate::machine::fixtures::analyses();
566        an.loops(&func);
567        an.post_dominators(&func);
568        // A pass that says it kept the trees and the forest and not the graph they came out of.
569        // What it says about them is not wrong so much as meaningless, and taking it at its word
570        // is how a stale dominator tree reaches the pass after next.
571        let keeps = Preserved::NONE
572            .and(Analysis::Dominators)
573            .and(Analysis::PostDominators)
574            .and(Analysis::Loops);
575        an.settle(&func, keeps, false);
576        for &analysis in Analysis::EVERY {
577            assert!(!an.holds(analysis), "{} outlived the graph", analysis.name());
578        }
579    }
580
581    #[test]
582    fn losing_the_dominator_tree_loses_the_forest_and_leaves_the_graph() {
583        let func = func();
584        let mut an = crate::machine::fixtures::analyses();
585        an.loops(&func);
586        an.post_dominators(&func);
587        let keeps =
588            Preserved::NONE.and(Analysis::Cfg).and(Analysis::PostDominators).and(Analysis::Loops);
589        an.settle(&func, keeps, false);
590        assert!(an.holds(Analysis::Cfg));
591        assert!(an.holds(Analysis::PostDominators));
592        assert!(!an.holds(Analysis::Dominators), "the tree was not preserved");
593        assert!(!an.holds(Analysis::Loops), "the forest outlived the tree it needs");
594    }
595
596    #[test]
597    fn each_frontier_falls_with_the_tree_it_was_walked_on_and_not_the_other_one() {
598        // The two frontiers are the same algorithm, but they are not the same analysis. A pass
599        // that claims both and only keeps one of the two trees gets to keep one of them, and the
600        // other goes with the tree it was walked on whatever the pass said about it.
601        let func = func();
602        let mut an = crate::machine::fixtures::analyses();
603        an.frontiers(&func);
604        an.control_dependence(&func);
605        let keeps = Preserved::NONE
606            .and(Analysis::Cfg)
607            .and(Analysis::Dominators)
608            .and(Analysis::Frontiers)
609            .and(Analysis::ControlDependence);
610        an.settle(&func, keeps, false);
611        assert!(an.holds(Analysis::Frontiers), "the frontier stands on a tree that stood");
612        assert!(!an.holds(Analysis::ControlDependence), "the post-dominator tree went with it");
613    }
614
615    #[test]
616    fn the_frequencies_fall_with_the_loop_forest_they_were_worked_out_from() {
617        let func = func();
618        let mut an = crate::machine::fixtures::analyses();
619        an.frequencies(&func);
620        // Asking for them brings in the graph, the tree and the forest, because the series in
621        // section 11.3 is per loop and there is no loop without all three.
622        for analysis in [Analysis::Cfg, Analysis::Dominators, Analysis::Loops] {
623            assert!(an.holds(analysis), "{} was not pulled in", analysis.name());
624        }
625        let keeps =
626            Preserved::NONE.and(Analysis::Cfg).and(Analysis::Dominators).and(Analysis::Frequencies);
627        an.settle(&func, keeps, false);
628        assert!(!an.holds(Analysis::Loops), "the forest was not preserved");
629        assert!(!an.holds(Analysis::Frequencies), "a frequency outlived the loop it counted");
630    }
631
632    #[test]
633    fn a_pass_that_says_it_kept_the_graph_and_moved_an_edge_is_caught() {
634        let mut func = func();
635        let mut an = crate::machine::fixtures::analyses();
636        an.loops(&func);
637        // The edit a lying pass makes: block4 falls off the end of the diamond, and now it
638        // returns to nobody instead. The blocks are the same blocks and the graph is not the
639        // same graph.
640        let block = Block::from_usize(3);
641        let term = func.terminator(block).expect("the helper gives every block a terminator");
642        func.remove_inst(term);
643        let mut build = rucc_ir::Builder::new(&mut func, block);
644        build.ret(&[]);
645        let lied = an.settle(&func, Preserved::ALL, true);
646        assert_eq!(lied, vec![Analysis::Cfg, Analysis::Dominators, Analysis::Loops]);
647        // And it is thrown away anyway, because a cache that keeps what it just proved wrong is
648        // worse than one that never checked.
649        for &analysis in Analysis::EVERY {
650            assert!(!an.holds(analysis));
651        }
652    }
653
654    #[test]
655    fn a_lie_about_the_frontiers_is_caught_the_same_way() {
656        let mut func = func();
657        let mut an = crate::machine::fixtures::analyses();
658        an.frontiers(&func);
659        an.control_dependence(&func);
660        // The back edge goes away, so block1 stops being a join and block3 stops being a branch.
661        // Both frontiers move, and a pass that swears they did not is wrong about both.
662        let block = Block::from_usize(3);
663        let term = func.terminator(block).expect("the helper gives every block a terminator");
664        func.remove_inst(term);
665        let mut build = rucc_ir::Builder::new(&mut func, block);
666        build.ret(&[]);
667        let lied = an.settle(&func, Preserved::ALL, true);
668        assert!(lied.contains(&Analysis::Frontiers));
669        assert!(lied.contains(&Analysis::ControlDependence));
670    }
671
672    #[test]
673    fn the_check_costs_nothing_when_it_is_off() {
674        let mut func = func();
675        let mut an = crate::machine::fixtures::analyses();
676        an.cfg(&func);
677        let block = Block::from_usize(3);
678        let term = func.terminator(block).expect("the helper gives every block a terminator");
679        func.remove_inst(term);
680        let mut build = rucc_ir::Builder::new(&mut func, block);
681        build.ret(&[]);
682        assert!(an.settle(&func, Preserved::ALL, false).is_empty());
683        // Which is the trade the flag is: the lie is not caught, and the stale graph is still
684        // there, exactly as the pass claimed.
685        assert!(an.holds(Analysis::Cfg));
686    }
687
688    #[test]
689    fn an_analysis_nobody_asked_for_is_not_checked() {
690        let func = func();
691        let mut an = crate::machine::fixtures::analyses();
692        assert!(an.settle(&func, Preserved::ALL, true).is_empty());
693    }
694
695    #[test]
696    fn a_declaration_has_analyses_like_anything_else() {
697        // Because the pipeline hands the cache whatever the module holds, and a cache that
698        // panicked on a function with no body would put the check in every caller.
699        let mut names = Interner::new();
700        let func = Func::new(names.intern("declared"), Signature::new());
701        let mut an = crate::machine::fixtures::analyses();
702        assert!(an.cfg(&func).entry().is_none());
703        an.loops(&func);
704        an.post_dominators(&func);
705        assert!(an.settle(&func, Preserved::ALL, true).is_empty());
706    }
707
708    #[test]
709    fn clearing_takes_everything() {
710        let func = func();
711        let mut an = crate::machine::fixtures::analyses();
712        an.loops(&func);
713        an.clear();
714        for &analysis in Analysis::EVERY {
715            assert!(!an.holds(analysis));
716        }
717    }
718}