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}