Skip to main content

rucc_opt/
analysis.rs

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