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