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}