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