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