Skip to main content

rucc_opt/
pipeline.rs

1//! The pipelines, one per optimization level, and the manager that runs one.
2//!
3//! Section 9.1 of `spec/09-optimizer.md` says the pipelines are written out rather than assembled
4//! from flags, and gives the reason: the prior art ran the same pipeline at every level and named
5//! that as a limitation. A level here is a list of pass names, and the list is the definition of
6//! the level rather than something that emerges from which flags happen to be set.
7//!
8//! Section 9.10 says the manager is deliberately boring. There is no adaptive ordering and no
9//! scheduling heuristic, because document 03's determinism rule needs the same input to produce
10//! the same output on every host and predictability is worth more than the last percent.
11//!
12//! What the manager does beyond running the list is the four things that make a pass debuggable:
13//! it counts each pass's transformations against its fuel, it collects what each pass said it did
14//! and did not do, it dumps the IR around whichever passes were asked for, and it verifies any
15//! function a pass changed.
16//!
17//! That last one is section 41.4 of `spec/optimizer/41-correctness.md`, which reads GCC's
18//! `execute_function_todo` and takes six things from it. Three of them are already true here by
19//! construction and are worth naming so that nobody looks for them. GCC verifies what the IR
20//! currently is, by consulting `curr_properties`, because its IR passes through GENERIC, GIMPLE
21//! with and without a CFG, GIMPLE in SSA, and RTL. rucc has one IR, it is in SSA from the moment
22//! the lowering walk builds it, and it always has a CFG, so the applicable set never varies and a
23//! bitmask saying so would have nothing to say. GCC guards the verifiers with `!seen_error()`,
24//! because after a user error the IR is legitimately malformed and an internal error raised over
25//! it hides the real diagnostic. Here the optimizer is not reached at all after a parse, check or
26//! lowering error, which is the same guard placed one level up where it cannot be forgotten. And
27//! GCC asserts that a verifier did not change the dominator state. Here a verifier takes the
28//! module by shared reference, so that is a type error rather than an assertion.
29//!
30//! What is left of the six is the part below: verify what changed, not everything, and say which
31//! function it was.
32
33use std::collections::HashMap;
34use std::fmt::Write as _;
35
36use rucc_base::{Interner, Symbol};
37use rucc_ir::{FuncId, Module, Pic};
38use rucc_session::OptLevel;
39
40use crate::{
41    Analyses, Fuel, Gates, Machine, Pass, Preserved, Stats, extents, heap, nofree, params, pass,
42};
43
44/// The passes that read a summary [`nofree::annotate`], [`extents::annotate`],
45/// [`params::annotate`] or [`heap::annotate`] writes onto the IR.
46///
47/// A list rather than one name because there will be more of them: section 7.5 asks for three more
48/// summary fields and section 7.3's lifetime elimination is the next thing to want this one. A pass
49/// that reads a summary and is not named here reads whatever the last build left, which is nothing,
50/// so the cost of forgetting to add a name is a missed optimization.
51///
52/// The five after the first are `crate::discharge`'s measurement runs, and leaving them out was a
53/// missed optimization of exactly that kind: a run measuring what an object says about itself, with
54/// no table saying how big any global is, answers that objects say nothing, and the number looks
55/// like a result rather than like a list with a name missing from it.
56const READS_SUMMARIES: &[&str] = &[
57    "discharge",
58    "discharge-objects",
59    "discharge-dominance",
60    "discharge-summaries",
61    "discharge-narrow",
62    "discharge-every",
63];
64
65/// `-O0`. Two passes, and neither of them is an optimization. Section 9.1 gives this level SSA
66/// construction, which the lowering walk in `spec/08-ir.md` already does, and mem2reg for the
67/// allocas that are left, which is the next pass to be written.
68///
69/// `expect` is here because what it takes out is a node the front end writes for every
70/// `__builtin_expect` in the program and gcc writes for none of them. Left standing it would be an
71/// instruction in the output of a level whose whole contract is that it emits what it was given.
72///
73/// `simplify-cfg` is here because a branch on a condition that is a constant is not a missed
74/// optimization, it is a call to a function the program never calls, and a program that calls a
75/// function it never calls is one that does not link. That is issue 359, gcc removes the code at
76/// every level including this one, and a `-O0` that emitted it would be a `-O0` some correct
77/// programs cannot be built at. Nothing else runs, and no analysis beyond the graph the pass
78/// reads reachability out of is computed.
79const O0: &[&str] = &["expect", "simplify-cfg"];
80
81/// `-O1`. Section 9.1 asks for one e-graph round, conservative inlining, simplify-CFG, SROA,
82/// GVN, DCE, LICM and the loop canonicalizations. Folding, control flow simplification and dead
83/// code elimination are the part of that which exists, with the peephole among them. They run in
84/// that order because folding and the peephole are what make most of the dead code there is to
85/// eliminate, because a constant a fold produced is a branch condition the control flow pass can
86/// then read, and because the comparison that branch was on is dead once it has.
87///
88/// The peephole runs on both sides of `narrow`, which is the one place in this list where a pass
89/// is named twice, so the reason is worth stating. The rewrite table is written at a width, and
90/// the widths below `int` are unreachable from C source: the integer promotions mean an addition
91/// of two `char` values arrives here as an `add.i32`, so a rule about `add.i8` matches nothing
92/// that a front end can produce. `narrow` is what puts the width back, and it is therefore the
93/// only producer the narrow half of the table has. Running the peephole only before it left
94/// sixty nine of the first hundred and twenty five rules unable to fire on any program, which is
95/// issue 505 and is what the corpus measured. Running it only after it would give up the smaller
96/// trees the peephole hands `narrow`, since a subtree `narrow` redoes has to have one reader and
97/// an identity left standing is a second one. Both sides costs one more walk over each function
98/// and is what the pass is for.
99///
100/// `phiopt` comes after `thread` and the order between them is not arbitrary. Both look at a
101/// diamond whose arms carry a value to a join. Where the join then branches on that value,
102/// threading removes a branch and costs nothing, and if-conversion would have turned the same
103/// shape into a `select` the join branches on instead, which is strictly worse. Threading first
104/// leaves if-conversion the diamonds whose value is used rather than tested, which are the ones it
105/// is for.
106///
107/// `prune` is between `phiopt` and `simplify-cfg` and both sides of that are load bearing. It reads
108/// document 10's ranges off the graph to find a branch that can only go one way and a switch case
109/// nothing can reach, so it has to run after the two passes that change the graph most. What it
110/// leaves is a jump where a branch was and a block nothing reaches, and `simplify-cfg` is the pass
111/// that takes those out, so it has to run before it rather than after.
112///
113/// `canon` is where document 26's loop pipeline opens, so it goes after the value level passes and
114/// before the cleanup. It gives every loop a preheader, one latch, exits of its own and loop closed
115/// form, which is what lets the loop passes that follow it write `insert at the end of the
116/// preheader` rather than each making one. On its own it generates nothing: the blocks it adds are
117/// empty and the parameters it adds have one argument each, and `simplify-cfg` runs straight after
118/// it and takes both back out to a fixed point. That is section 26.7's arrangement, and it is why
119/// the position matters more than the pass does until the loop passes land on top of it.
120///
121/// `header-copy` is section 26.7's third step and `canon` runs again after it, which is the same
122/// section's instruction to re-canonicalize the loops it changed. It has to: what the copy leaves
123/// is a loop entered from a block that branches two ways, and a block that branches two ways is not
124/// a preheader. Nothing between the two needs the properties, so the second run is bookkeeping
125/// against the loop passes that come later rather than something this level's output depends on,
126/// and `simplify-cfg` after it takes out the blocks and parameters both runs added that nothing
127/// used.
128///
129/// `licm` comes after the copy and the canonicalization behind it, and section 27.1 says why it has
130/// to. What it may move in front of a loop depends on what runs on every entry to the loop, and
131/// after that pair that is the whole body rather than the header alone. Running it before the
132/// copy would leave it the header, which is most of the pass's value gone. It is also the reason
133/// the copy exists, so the two are one arrangement read from either end.
134///
135/// It is in the three speed levels and not in `-Os` or `-Oz`. Moving a computation out of a loop
136/// does not remove one, so there are no bytes in it for a level whose cost model is size, and the
137/// one thing it can cost is a spill inside the loop, which is bytes. That trade is worth making for
138/// time and there is nothing on the other side of it for space.
139///
140/// `unroll` runs after `licm` and only at the two speed levels. After, because what it does is copy
141/// the body, and a computation licm has already moved in front of the loop is one the copies do not
142/// each get their own of. It needs the same shape licm does and for the same reason, a loop that
143/// tests at the bottom with a preheader in front of it, so it sits at the end of the same run of
144/// loop passes rather than anywhere of its own. `simplify-cfg` straight after it is what turns the
145/// chain of copies into one block, since each copy now ends in a jump to the next and a block with
146/// one way in and one way out is a block that goes away.
147///
148/// `number` goes straight after that `simplify-cfg` and immediately before `load-forward`, and the
149/// two are one arrangement rather than two passes that happen to be adjacent. On its own it removes
150/// an instruction here and there, because the arithmetic a person writes is not usually written
151/// twice. What it is really for is the arithmetic the front end writes underneath: a subscript
152/// lowered twice is the same multiply and add twice, and giving the two one name is what turns a
153/// store and a load that `load-forward` was refusing into a store and a load of the same address.
154/// Running it the other way round would leave the pass after it nothing it did not already have.
155///
156/// `load-forward` goes next, and the position is the whole of what
157/// the pass is worth. It is the block local half of document 16, so what it can find is bounded by
158/// how much code is in one block, and `simplify-cfg` merging the straight line chains is what makes
159/// the blocks the largest they are ever going to be. At the two speed levels that position is also
160/// just after `unroll`, which is where the case the pass was written for lives: the body
161/// copies now sit in one block, and a copy that stored to an array slot and read it straight back
162/// is a store and a load of the same address with nothing in between.
163///
164/// `fold` runs a second time after it, and it is not there out of habit. What forwarding leaves
165/// behind is a value that arrived as a constant through memory, `grid[i] = 3` read back as a load
166/// that is now the literal three, and nothing else this late in the list would fold the arithmetic
167/// on top of it. The first `fold` ran before any of this existed.
168///
169/// `simplify` runs after that second `fold` for the same reason the second `fold` runs at all.
170/// Folding does not remove an instruction whose answer is a constant somebody still adds, it only
171/// writes the constant down, and an index folded to zero leaves a `ptr_add x, 0` behind. That is
172/// an identity the peephole takes and nothing else in the list is about. The unrolled body is
173/// where they come from: the copy that runs first subscripts the array at zero, so the multiply
174/// that worked its offset out is a multiply by zero, and until now the last thing any level did to
175/// that arithmetic was fold it. The add of zero reached the selector and was written out as an
176/// `addq $0`. Over the corpus at `-O2` the run is worth 3000 bytes across 1830 programs, 224 of
177/// them smaller and 4 larger, with every result unchanged.
178///
179/// `hoist` is the first of the two check passes and it runs where it does because of what is above
180/// it. It needs a loop that tests at the bottom, which is what `header-copy` makes, and it needs a
181/// preheader to put a check in, which is what the `canon` after it puts back. Running it before
182/// `discharge` rather than after is deliberate as well: what it leaves in the preheader is a check
183/// over the whole range the loop sweeps, and that is a fact `discharge` can then use on anything
184/// else in front of the loop that is about the same bytes.
185///
186/// `discharge` is second to last, between `hoist` and `dce`, and both neighbours are the reason. It
187/// reads the dominator tree to find a safety check whose bytes an earlier check already covered, so
188/// it wants the graph after the block merging rather than before, when a straight run of code is
189/// still several blocks and a fact does not reach the check it would cover. What it leaves behind
190/// is the `cap_of` the check it removed was reading, which nothing now reads, so `dce` after it is
191/// what makes the function smaller rather than shorter by one instruction. It is in every level
192/// except `-O0`, which keeps every check on purpose: document 14 measures against a build where
193/// nothing was discharged, and that build is `-O0`.
194const O1: &[&str] = &[
195    "expect",
196    "fold",
197    "simplify",
198    "narrow",
199    "simplify",
200    "thread",
201    "phiopt",
202    "prune",
203    "canon",
204    "header-copy",
205    "canon",
206    "licm",
207    "simplify-cfg",
208    "number",
209    "load-forward",
210    "fold",
211    "simplify",
212    "hoist",
213    "discharge",
214    "dead-plane",
215    "coalesce",
216    "dce",
217];
218
219/// `-O2`. The level the code quality claim is about. Section 9.1 asks for two e-graph rounds
220/// around the loop pipeline, the full inlining cost model, Memory SSA and the full alias
221/// analysis stack, and then the scalar and machine passes on top.
222///
223/// `short-circuit` is the one pass here that `-O1` does not have, and section 22.5 is where the
224/// level comes from. It folds the two branches of an `a && b` into one, which costs the right
225/// operand's work on the path that was skipping it and buys a branch the machine no longer has to
226/// guess. That is a trade worth making when the aim is speed and the branch is hard to call, and
227/// it is not one to make by default, which is what `-O1` is.
228///
229/// It runs before `thread` and `phiopt` rather than after, and the order is not arbitrary. Both of
230/// those look at edges, and the collapse removes a block and turns two edges into one, so running
231/// it first hands them a smaller graph with nothing lost. The other way round, threading is free
232/// to give the second branch's block another predecessor, and a block two edges reach is one the
233/// collapse will not touch, so a chain that was foldable stops being foldable.
234///
235/// `canon` and `licm` run a second time after `split`, and that pair is the only thing here that
236/// looks at what `split` wrote. A guard goes in the preheader of the loop being split, which for an
237/// inner loop is a block inside the loops around it, and the guard asks the runtime how big each
238/// object is. On a matrix multiply that is four queries per entry to the innermost loop, two of
239/// them about a pointer that has not changed since it was allocated, and the pass that would take
240/// those out ran seven passes ago. `spec/safe-memory/13-performance.md` section 13.1 measured the
241/// cost and tamnd/rucc#893 is the rest of it.
242///
243/// `ivopts` goes last of the loop passes, because it is the one that decides what the loop's
244/// variables finally are and everything above it is still moving code around. It is followed by
245/// `simplify-cfg` and the pair cannot be separated. Section 28.4 has a loop stop asking its counter
246/// anything, and the counter goes on being incremented round the loop until the parameter carrying
247/// it is taken away. `crate::dce` says in its own documentation that it cannot do that, because the
248/// only reader left is the addition feeding the parameter back and a use count never reaches zero
249/// on a cycle. `crate::simplify_cfg` can, and says it was written for this. Without it the loop
250/// pays for the new pointer and keeps the old counter as well, which over the corpus is about half
251/// of what choosing badly costs.
252const O2: &[&str] = &[
253    "expect",
254    "fold",
255    "simplify",
256    "narrow",
257    "simplify",
258    "switch-conv",
259    "short-circuit",
260    "thread",
261    "phiopt",
262    "prune",
263    "canon",
264    "header-copy",
265    "canon",
266    "licm",
267    "unroll",
268    "simplify-cfg",
269    "number",
270    "load-forward",
271    "fold",
272    "simplify",
273    "hoist",
274    "split",
275    "canon",
276    "licm",
277    "ivopts",
278    "simplify-cfg",
279    "discharge",
280    "dead-plane",
281    "coalesce",
282    "dce",
283];
284
285/// `-O3`. `-O2` plus loop vectorization, larger inlining and unrolling thresholds, interchange
286/// and distribution where the dependence analysis is confident, and function specialization.
287const O3: &[&str] = &[
288    "expect",
289    "fold",
290    "simplify",
291    "narrow",
292    "simplify",
293    "switch-conv",
294    "short-circuit",
295    "thread",
296    "phiopt",
297    "prune",
298    "canon",
299    "header-copy",
300    "canon",
301    "licm",
302    "unroll",
303    "simplify-cfg",
304    "number",
305    "load-forward",
306    "fold",
307    "simplify",
308    "hoist",
309    "split",
310    "canon",
311    "licm",
312    "ivopts",
313    "simplify-cfg",
314    "discharge",
315    "dead-plane",
316    "coalesce",
317    "dce",
318];
319
320/// `-Os`. `-O2`'s passes under a size cost model: inlining only where it shrinks, no unrolling
321/// and no vectorization.
322///
323/// The second peephole is here rather than cut for size, because every rule it can fire replaces
324/// a term with a strictly smaller one. Tier one of `spec/optimizer/13-rewrite-rules.md` is
325/// defined that way, so a level that wants smaller code wants more of it and not less.
326///
327/// `short-circuit` is the pass this level drops from `-O2`, for the mirror of that reason. What it
328/// removes is a branch, which is time, and what it adds is the right operand's instructions on a
329/// path that did not run them and an and on top. The code comes out no smaller and usually a byte
330/// or two larger, so a level whose cost model is size has nothing to gain from it.
331///
332/// `hoist` is dropped here as well, and the reason is the same trade read the other way.
333/// It takes a check out of a loop body and puts one in the preheader, plus the address arithmetic
334/// the new check needs, so the loop runs faster and the function is a few instructions larger. That
335/// is a speed transformation with a size cost, which is what `-Os` and `-Oz` are for declining.
336///
337/// `header-copy-small` is the same pass `-O1` and above run under section 26.6's smaller budget.
338/// The copy is code growth and this level pays for it once per loop, so five instructions is what
339/// it will pay. What it gets back is a body that is one region and an exit test at the bottom,
340/// which is slightly smaller in the steady state, so the trade is worth making at a limit that
341/// keeps the header small and not at one that copies twenty instructions to save two.
342const OS: &[&str] = &[
343    "expect",
344    "fold",
345    "simplify",
346    "narrow",
347    "simplify",
348    "switch-conv",
349    "thread",
350    "phiopt",
351    "prune",
352    "canon",
353    "header-copy-small",
354    "canon",
355    "simplify-cfg",
356    "number",
357    "load-forward",
358    "fold",
359    "simplify",
360    "discharge",
361    "dead-plane",
362    "coalesce",
363    "dce",
364];
365
366/// `-Oz`. `-Os` and additionally the outliner, with instruction selection preferring the smaller
367/// encoding wherever there is a choice.
368///
369/// Header copying is the pass this level drops from `-Os`, which section 26.6 asks for by name. It
370/// is the one loop canonicalization that makes the function bigger, `-Oz` is the level that would
371/// rather have the branch than the bytes, and every reason to want the do-while form here is a
372/// speed reason.
373const OZ: &[&str] = &[
374    "expect",
375    "fold",
376    "simplify",
377    "narrow",
378    "simplify",
379    "switch-conv",
380    "thread",
381    "phiopt",
382    "prune",
383    "canon",
384    "simplify-cfg",
385    "number",
386    "load-forward",
387    "fold",
388    "simplify",
389    "discharge",
390    "dead-plane",
391    "coalesce",
392    "dce",
393];
394
395/// The passes this level runs, before the command line adds to or removes from them.
396#[must_use]
397pub const fn for_level(level: OptLevel) -> &'static [&'static str] {
398    match level {
399        OptLevel::O0 => O0,
400        OptLevel::O1 => O1,
401        OptLevel::O2 => O2,
402        OptLevel::O3 => O3,
403        OptLevel::Os => OS,
404        OptLevel::Oz => OZ,
405    }
406}
407
408/// Which passes the IR is written out around.
409///
410/// Empty by default, which is the whole point: a dump is a debugging aid and writing files
411/// nobody asked for is not one.
412#[derive(Debug, Clone, Default, PartialEq, Eq)]
413pub struct Dumps {
414    /// Every pass, on both sides.
415    all: bool,
416    /// The passes to write out before.
417    before: Vec<String>,
418    /// The passes to write out after.
419    after: Vec<String>,
420}
421
422impl Dumps {
423    /// Adds one `-fdump-ir=` argument.
424    ///
425    /// # Errors
426    ///
427    /// When the argument is not `all`, `before-<pass>` or `after-<pass>`, or when it names a
428    /// pass this compiler does not have. A misspelled pass name that quietly dumped nothing
429    /// would look exactly like a pass that did not run.
430    pub fn add(&mut self, spec: &str) -> Result<(), String> {
431        if spec == "all" {
432            self.all = true;
433            return Ok(());
434        }
435        let (side, name) = match spec.split_once('-') {
436            Some(("before", name)) => (&mut self.before, name),
437            Some(("after", name)) => (&mut self.after, name),
438            _ => {
439                return Err(format!(
440                    "`{spec}` is not a dump this compiler makes, which are `all`, \
441                     `before-<pass>` and `after-<pass>`"
442                ));
443            }
444        };
445        if pass::find(name).is_none() {
446            return Err(format!("`{name}` is not a pass this compiler has, see --print-pipeline"));
447        }
448        side.push(name.to_owned());
449        Ok(())
450    }
451
452    /// Whether anything is dumped at all.
453    #[must_use]
454    pub fn is_empty(&self) -> bool {
455        !self.all && self.before.is_empty() && self.after.is_empty()
456    }
457
458    /// Whether the IR is written out before this pass runs.
459    #[must_use]
460    pub fn wants_before(&self, name: &str) -> bool {
461        self.all || self.before.iter().any(|it| it == name)
462    }
463
464    /// Whether the IR is written out after this pass runs.
465    #[must_use]
466    pub fn wants_after(&self, name: &str) -> bool {
467        self.all || self.after.iter().any(|it| it == name)
468    }
469}
470
471/// What the command line asked the optimizer for.
472#[derive(Debug, Clone, PartialEq, Eq)]
473pub struct Options {
474    /// Which pipeline to start from.
475    pub level: OptLevel,
476    /// The passes `-f<name>` added and `-fno-<name>` removed, in the order they were given, so
477    /// that the last mention of a pass is the one that decides.
478    pub toggles: Vec<(String, bool)>,
479    /// What `-fpass-fuel=<pass>=<n>` limited, by pass name.
480    pub fuel: HashMap<String, u32>,
481    /// What `-fpass-fuel-global=<n>` limited the whole pipeline to, across every pass.
482    ///
483    /// This is the outer search of the two in section 4.5 of
484    /// `spec/optimizer/04-pass-manager.md`. Halving this finds the pass, and halving
485    /// `-fpass-fuel` for that pass finds the rewrite inside it. Two searches of twenty
486    /// compilations each beat one search over a space nobody knows the shape of.
487    pub global_fuel: Option<u32>,
488    /// What `-fdisable-<pass>` and `-fenable-<pass>` said about which functions a pass runs on.
489    pub gates: Gates,
490    /// What `-fdump-ir=` asked to see.
491    pub dumps: Dumps,
492    /// Whether the verifier runs after every pass that changed anything.
493    pub verify: bool,
494    /// Which definitions in this module something else may replace at load time.
495    ///
496    /// The analyses that read a body and write down what they found have to stop at a name like
497    /// that, because the body they read is not the one that will run. [`Pic::Library`] is the
498    /// answer when the object may end up in a shared library and the exported names in it are
499    /// interposable, which is what `-fPIC` alone means and is gcc's default.
500    ///
501    /// [`Pic::Executable`] is the answer for everything else, and that includes
502    /// `-fno-semantic-interposition`, where the build has promised that the definition here is the
503    /// one that runs. It is a promise and not a deduction, and it is the one every distribution
504    /// makes, because a library that cannot inline its own functions into each other pays for the
505    /// possibility of an interposition that never happens.
506    ///
507    /// This is not the same value the code generator is given. How an address is reached does not
508    /// change under that promise, and gcc does not change it either: a variable a shared library
509    /// exports is still read out of the global offset table, because the promise is about which
510    /// definition runs rather than about how many copies of the variable there are.
511    pub interposition: Pic,
512}
513
514impl Default for Options {
515    /// The default level with nothing added to it, and the verifier on in a debug build, which
516    /// is what section 9.10 asks for.
517    fn default() -> Self {
518        Self {
519            level: OptLevel::default(),
520            toggles: Vec::new(),
521            fuel: HashMap::new(),
522            global_fuel: None,
523            gates: Gates::default(),
524            dumps: Dumps::default(),
525            verify: cfg!(debug_assertions),
526            interposition: Pic::Executable,
527        }
528    }
529}
530
531impl Options {
532    /// The options a level asks for on its own.
533    #[must_use]
534    pub fn for_level(level: OptLevel) -> Self {
535        Self { level, ..Self::default() }
536    }
537
538    /// The passes the level and the `-f` flags chose, in order, before the gates are consulted.
539    ///
540    /// A pass named by `-f<name>` that the level did not choose is appended, because the only
541    /// place it could go that does not need an ordering rule nobody wrote down is the end.
542    #[must_use]
543    pub fn chosen(&self) -> Vec<&'static str> {
544        let mut names: Vec<&str> = for_level(self.level).to_vec();
545        for (name, on) in &self.toggles {
546            let name = name.as_str();
547            match *on {
548                true if !names.contains(&name) => names.push(name),
549                true => {}
550                // A pass that says it is required stays, since turning it off is a compile that
551                // fails rather than one that optimizes less. See [`Pass::required`].
552                false => names.retain(|it| *it != name || required(it)),
553            }
554        }
555        names.into_iter().filter_map(pass::find).map(Pass::name).collect()
556    }
557}
558
559/// Whether the pass of that name is one `-fno-<name>` does not turn off.
560fn required(name: &str) -> bool {
561    pass::find(name).is_some_and(|pass| pass.required())
562}
563
564impl Options {
565    /// The passes that will run, in order, over at least one function.
566    ///
567    /// A pass `-fenable-<name>` reached that the level did not choose is appended after them,
568    /// for the same reason and in the same place. It runs only over the functions the gate names,
569    /// which is the whole point of the flag: a pass being in this list is not the same question as
570    /// a pass running on the function somebody is looking at.
571    #[must_use]
572    pub fn passes(&self) -> Vec<&'static dyn Pass> {
573        let mut names = self.chosen();
574        for name in self.gates.enabled() {
575            // Through the pass list rather than straight from the gate, because the name the
576            // pass holds outlives this call and the one the gate holds does not.
577            let Some(found) = pass::find(name) else { continue };
578            if !names.contains(&found.name()) {
579                names.push(found.name());
580            }
581        }
582        names.into_iter().filter_map(pass::find).collect()
583    }
584}
585
586/// One written out copy of the IR.
587#[derive(Debug, Clone, PartialEq, Eq)]
588pub struct Dump {
589    /// What to call it, which is a number, a side and a pass name, as in `01-after-fold`. The
590    /// number is there so that a directory listing is in the order the passes ran.
591    pub name: String,
592    /// The module, in the textual form from `spec/08-ir.md`.
593    pub text: String,
594}
595
596/// What one pass had to say about one function.
597///
598/// One of these per pass per function with a body, whether or not the pass said anything, because
599/// a pass that reports nothing being visible as a pass that reports nothing is the point of the
600/// record. Section 42.2 of `spec/optimizer/42-measurement.md` has the argument.
601#[derive(Debug, Clone, PartialEq, Eq)]
602pub struct Remark {
603    /// Which pass, by the name a `-f` flag spells.
604    pub pass: &'static str,
605    /// Which function, by the name in the source.
606    pub func: Symbol,
607    /// What it said.
608    pub stats: Stats,
609}
610
611/// What running the pipeline produced beyond the changed module.
612#[derive(Debug, Clone, Default, PartialEq, Eq)]
613pub struct Report {
614    /// The dumps asked for, in the order they were taken. The manager does not write files,
615    /// because nothing below the driver in `spec/18-package-layout.md` knows what a file is.
616    pub dumps: Vec<Dump>,
617    /// A pass that left the IR in a state the verifier refuses, named, with what it said.
618    pub broke: Vec<String>,
619    /// How much fuel each pass spent, which is the number a bisection halves.
620    pub spent: Vec<(&'static str, u32)>,
621    /// What every pass said about every function, in the order the passes ran and then in the
622    /// order the module holds its functions. This is what `-fopt-info` prints.
623    pub remarks: Vec<Remark>,
624}
625
626impl Report {
627    /// Everything one pass said across the whole module, added up.
628    ///
629    /// The counts of an event are addable across functions because an event names a site in a
630    /// pass rather than a fact about a program, which is the reason [`crate::stats::Event::what`]
631    /// is a fixed string.
632    #[must_use]
633    pub fn totals(&self, pass: &str) -> Stats {
634        let mut total = Stats::new();
635        for remark in self.remarks.iter().filter(|it| it.pass == pass) {
636            total.merge(&remark.stats);
637        }
638        total
639    }
640}
641
642/// Runs the pipeline over the module.
643///
644/// Every pass sees every function with a body, one at a time, and a pass runs over the whole
645/// module before the next one starts. That order is what makes the dumps readable: a dump is
646/// the state of the program between two passes rather than between two functions.
647pub fn run(module: &mut Module, names: &Interner, opts: &Options) -> Report {
648    let mut report = Report::default();
649    let chosen = opts.chosen();
650    // One cache per function, kept across passes because a pass runs over the whole module
651    // before the next one starts. A cache that lived only as long as one function would be
652    // thrown away between every pass and would never answer a second question. Section 4.2 of
653    // `spec/optimizer/04-pass-manager.md` is the plan for turning the loop inside out, and the
654    // day that happens this map becomes a local in the inner loop.
655    let mut cached: HashMap<FuncId, Analyses> = HashMap::new();
656    // The machine, once for the module, because every function in it is compiled for the same
657    // target at the same goal. It goes into each function's cache rather than into a parameter of
658    // its own, per `crate::machine`.
659    let machine = Machine::of(module, opts.level);
660    // What the whole pipeline has left, which every pass draws its own allowance out of and
661    // gives the unspent part of back. A pass past the end of it is given nothing rather than
662    // skipped, so it still runs, still reports, and still transforms nothing.
663    let mut budget = opts.global_fuel;
664    // What each pass has left of what `-fpass-fuel` gave it. One allowance across every place
665    // the list names that pass, rather than one allowance each, because the number in the flag
666    // is meant to be the number of rewrites that happened. A peephole that runs twice under
667    // `-fpass-fuel=simplify=5` and rewrites ten things would make the bisection in section 4.5
668    // of `spec/optimizer/04-pass-manager.md` step over the rewrite it was looking for.
669    let mut allowance = opts.fuel.clone();
670    let passes = opts.passes();
671    // Before anything runs, because each of these is a fact about the module and every pass after
672    // this sees one function. Only when a pass in this run reads them: a flag nothing looks at
673    // would show up in every `-O0` dump and mean nothing to anybody reading one.
674    if passes.iter().any(|pass| READS_SUMMARIES.contains(&pass.name())) {
675        nofree::annotate(module, names, opts.interposition);
676        extents::annotate(module, opts.interposition);
677        params::annotate(module, opts.interposition);
678        heap::annotate(module, names);
679    }
680    for (index, pass) in passes.into_iter().enumerate() {
681        let name = pass.name();
682        if opts.dumps.wants_before(name) {
683            report.dumps.push(dump(index, "before", name, module, names));
684        }
685        let mut fuel = match (allowance.get(name).copied(), budget) {
686            // Whichever limit is tighter, because two limits that disagree mean the one that
687            // stops first, and a bisection that started with the global one has to stay inside
688            // it while the per pass one is halved.
689            (Some(count), Some(left)) => Fuel::of(count.min(left)),
690            (Some(count), None) => Fuel::of(count),
691            (None, Some(left)) => Fuel::of(left),
692            (None, None) => Fuel::unlimited(),
693        };
694        // What the level and the `-f` flags decided, which is what a gate overrides for the
695        // functions it names and leaves alone for the ones it does not.
696        let default = chosen.contains(&name);
697        for id in module.funcs() {
698            if module[id].is_declaration() {
699                continue;
700            }
701            if !opts.gates.allows(name, default, id.raw(), names.resolve(module[id].name)) {
702                // No remark either. A pass that did not run on a function has nothing to say
703                // about it, and a record saying it found nothing would read as a pass that
704                // looked.
705                continue;
706            }
707            let an = cached.entry(id).or_insert_with(|| Analyses::new(machine));
708            let stats = pass.run(&mut module[id], an, &mut fuel);
709            // A pass that changed nothing preserved everything, whatever it says about itself,
710            // so the cheap case does not need every pass to have a second opinion about it.
711            // A pass that did change something is taken at its word, and in a checked build the
712            // word is checked.
713            let keeps = if stats.changed() { pass.preserves() } else { Preserved::ALL };
714            for broken in an.settle(&module[id], keeps, opts.verify) {
715                let func = names.resolve(module[id].name);
716                report.broke.push(format!(
717                    "the {name} pass said it preserved {} of {func} and did not",
718                    broken.name()
719                ));
720            }
721            // Here rather than after the pass, and this function rather than the module. A pass
722            // is a function pass, so the only thing it can have broken is the function it was
723            // given, and walking the other ones again after every one of them is the quadratic
724            // walk `rucc_ir::verify_func` exists to avoid. Doing it here is also what lets the
725            // message name the function, which the module walk could not, and it puts the
726            // failure next to the pass that caused it rather than at the end of the module.
727            if stats.changed() && opts.verify {
728                if let Err(errors) = rucc_ir::verify_func(module, &module[id], names) {
729                    let func = names.resolve(module[id].name);
730                    for error in errors {
731                        report
732                            .broke
733                            .push(format!("the {name} pass left invalid IR in {func}, {error}"));
734                    }
735                }
736            }
737            // The record is the only place the manager learns that anything happened, which is
738            // why the pass cannot leave recording until later. See `crate::stats`.
739            report.remarks.push(Remark { pass: name, func: module[id].name, stats });
740        }
741        // Added to rather than pushed, so a pass the list names twice is one line here with what
742        // both of its runs spent. That is the number a bisection halves, and two lines under one
743        // name would be two numbers where the flag takes one.
744        match report.spent.iter_mut().find(|(it, _)| *it == name) {
745            Some((_, total)) => *total += fuel.spent(),
746            None => report.spent.push((name, fuel.spent())),
747        }
748        if let Some(left) = &mut budget {
749            // Never below zero, because the allowance the pass was given was at most this.
750            *left -= fuel.spent();
751        }
752        if let Some(left) = allowance.get_mut(name) {
753            // Same, and for the same reason.
754            *left -= fuel.spent();
755        }
756        if opts.dumps.wants_after(name) {
757            report.dumps.push(dump(index, "after", name, module, names));
758        }
759    }
760    report
761}
762
763/// The module written out, under a name that sorts in the order the passes ran.
764fn dump(index: usize, side: &str, name: &str, module: &Module, names: &Interner) -> Dump {
765    Dump { name: format!("{index:02}-{side}-{name}"), text: rucc_ir::print(module, names) }
766}
767
768/// Renders what `--print-pipeline` prints.
769///
770/// One line per pass, numbered from one, with what the pass does after it. A level that runs
771/// nothing says so rather than printing an empty list, because an empty answer and a broken
772/// command look the same.
773#[must_use]
774pub fn print(opts: &Options) -> String {
775    let mut out = String::new();
776    let _ = writeln!(out, "level: {}", opts.level);
777    // Only when it was asked for, so the listing of a compilation nobody is bisecting is the
778    // same listing it has always been. A run under a budget is a run whose output is not the
779    // one the level asked for, and the listing is where that has to be visible.
780    if let Some(count) = opts.global_fuel {
781        let _ = writeln!(out, "global fuel: {count}");
782    }
783    let passes = opts.passes();
784    if passes.is_empty() {
785        let _ = writeln!(out, "no passes");
786        return out;
787    }
788    for (index, pass) in passes.iter().enumerate() {
789        let _ = write!(out, "{}: {}, {}", index + 1, pass.name(), pass.describe());
790        // Only when a gate mentions the pass, so the listing of a compilation nobody is
791        // debugging is the same listing it has always been.
792        if let Some(note) = opts.gates.note(pass.name()) {
793            let _ = write!(out, " [{note}]");
794        }
795        out.push('\n');
796    }
797    out
798}
799
800#[cfg(test)]
801mod tests {
802    use rucc_base::Interner;
803    use rucc_ir::{
804        Builder, Extra, Flags, Func, IntPred, MemInfo, MemOrder, Module, Opcode, Restrict,
805        Signature, Type,
806    };
807    use rucc_session::OptLevel;
808    use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
809
810    use super::{Dumps, Options, for_level};
811    use crate::stats::Kind;
812    use crate::{Pass, pass};
813
814    /// A module with one function whose body has something to fold in it.
815    fn module() -> (Interner, Module) {
816        let mut names = Interner::new();
817        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
818        let mut module = Module::new(names.intern("test.c"), &target);
819        let func = foldable(&mut names, "f");
820        module.add_func(func);
821        (names, module)
822    }
823
824    /// A module with two of them, called `f` and `g`, in that order, so `f` is function 0.
825    fn two_functions() -> (Interner, Module) {
826        let mut names = Interner::new();
827        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
828        let mut module = Module::new(names.intern("test.c"), &target);
829        for name in ["f", "g"] {
830            let func = foldable(&mut names, name);
831            module.add_func(func);
832        }
833        (names, module)
834    }
835
836    /// A module with one function holding two identities the peephole takes, on a value that
837    /// arrives as a parameter so that folding cannot get to them first.
838    fn identities() -> (Interner, Module) {
839        let mut names = Interner::new();
840        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
841        let mut module = Module::new(names.intern("test.c"), &target);
842        let i32_ = Type::int(32);
843        let mut func = Func::new(
844            names.intern("h"),
845            Signature::new().with_params(&[i32_]).with_returns(&[i32_]),
846        );
847        let entry = func.create_block();
848        let x = func.append_param(entry, i32_);
849        let mut build = Builder::new(&mut func, entry);
850        let zero = build.iconst(i32_, 0);
851        let one = build.iconst(i32_, 1);
852        let sum = build.binary(Opcode::Add, x, zero, Flags::NONE);
853        let product = build.binary(Opcode::Mul, sum, one, Flags::NONE);
854        build.ret(&[product]);
855        module.add_func(func);
856        (names, module)
857    }
858
859    /// A function that returns a sign extension of a constant, which folding rewrites.
860    fn foldable(names: &mut Interner, name: &str) -> Func {
861        let mut func =
862            Func::new(names.intern(name), Signature::new().with_returns(&[Type::int(64)]));
863        let block = func.create_block();
864        let mut build = Builder::new(&mut func, block);
865        let narrow = build.iconst(Type::int(32), 7);
866        let wide = build.unary(Opcode::SExt, narrow, Type::int(64));
867        build.ret(&[wide]);
868        func
869    }
870
871    /// Whether the pass said anything about the function, which it only does when it ran on it.
872    fn spoke_about(report: &super::Report, pass: &str, func: &str, names: &Interner) -> bool {
873        report.remarks.iter().any(|it| it.pass == pass && names.resolve(it.func) == func)
874    }
875
876    /// A module with a loop short enough for the unroller to flatten, over an array a parameter
877    /// points at.
878    ///
879    /// Four iterations, which is a trip count the unroller takes whole. The copy that runs first
880    /// subscripts the array at zero, so what works its offset out is a multiply by zero, and
881    /// folding that is what leaves the addition this is here to look for.
882    fn a_short_loop() -> (Interner, Module) {
883        let mut names = Interner::new();
884        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
885        let mut module = Module::new(names.intern("test.c"), &target);
886        let (i32_, i64_) = (Type::int(32), Type::int(64));
887        let signature = Signature::new().with_params(&[Type::PTR]).with_returns(&[i32_]);
888        let mut func = Func::new(names.intern("sum"), signature);
889        let entry = func.create_block();
890        let head = func.create_block();
891        let body = func.create_block();
892        let exit = func.create_block();
893        let p = func.append_param(entry, Type::PTR);
894        let i = func.append_param(head, i32_);
895        let acc = func.append_param(head, i32_);
896
897        let mut build = Builder::new(&mut func, entry);
898        let zero = build.iconst(i32_, 0);
899        build.jump(head, &[zero, zero]);
900
901        let mut build = Builder::new(&mut func, head);
902        let four = build.iconst(i32_, 4);
903        let more = build.icmp(IntPred::Slt, i, four);
904        build.br_if(more, body, &[], exit, &[]);
905
906        let mut build = Builder::new(&mut func, body);
907        let wide = build.unary(Opcode::SExt, i, i64_);
908        let scale = build.iconst(i64_, 4);
909        let offset = build.binary(Opcode::Mul, wide, scale, Flags::NSW);
910        let at = build.binary(Opcode::PtrAdd, p, offset, Flags::NONE);
911        let read = build.load(i32_, at, plain(), Flags::NONE);
912        let total = build.binary(Opcode::Add, acc, read, Flags::NONE);
913        let one = build.iconst(i32_, 1);
914        let next = build.binary(Opcode::Add, i, one, Flags::NSW);
915        build.jump(head, &[next, total]);
916
917        let mut build = Builder::new(&mut func, exit);
918        build.ret(&[acc]);
919        module.add_func(func);
920        (names, module)
921    }
922
923    /// Memory with nothing said about it, which is what a plain subscript reads through.
924    fn plain() -> MemInfo {
925        MemInfo {
926            size: 4,
927            align: 4,
928            order: MemOrder::NotAtomic,
929            tbaa: None,
930            owns: 0,
931            restrict: Restrict::NONE,
932        }
933    }
934
935    /// Every addition in the module whose right operand is the constant zero.
936    fn adds_of_zero(module: &Module) -> usize {
937        let mut found = 0;
938        for id in module.funcs() {
939            let func = &module[id];
940            for block in func.blocks() {
941                for inst in func.insts(block) {
942                    if !matches!(func[inst].opcode, Opcode::Add | Opcode::PtrAdd) {
943                        continue;
944                    }
945                    let args = &func[func[inst].args];
946                    let Some(&rhs) = args.get(1) else { continue };
947                    let rucc_ir::Def::Result { inst: from, .. } = func[rhs].def else { continue };
948                    if func[from].opcode != Opcode::IConst {
949                        continue;
950                    }
951                    let Extra::Imm(at) = func[from].extra else { continue };
952                    found += usize::from(func[at].signed(func[rhs].ty) == 0);
953                }
954            }
955        }
956        found
957    }
958
959    /// An index the unroller worked out to zero does not leave the addition behind.
960    ///
961    /// The peephole is what removes it and the peephole used to run only near the top of the
962    /// list, before the unroller had made any of these. Folding writes the constant down and
963    /// leaves the addition, so an `add x, 0` reached the selector and was written out as an
964    /// `addq $0` the machine runs for nothing. tamnd/rucc#875.
965    #[test]
966    fn an_index_folded_to_zero_is_not_added_to_anything() {
967        let (names, mut module) = a_short_loop();
968        assert_eq!(adds_of_zero(&module), 0, "the fixture already has one before anything runs");
969        let report = super::run(&mut module, &names, &Options::for_level(OptLevel::O2));
970        assert!(report.broke.is_empty(), "{:?}", report.broke);
971        assert!(spent(&report, "unroll").is_some_and(|it| it > 0), "the loop was not unrolled");
972        assert_eq!(adds_of_zero(&module), 0, "{}", rucc_ir::print(&module, &names));
973    }
974
975    #[test]
976    fn every_pass_a_pipeline_names_is_a_pass_that_exists() {
977        for level in
978            [OptLevel::O0, OptLevel::O1, OptLevel::O2, OptLevel::O3, OptLevel::Os, OptLevel::Oz]
979        {
980            for name in for_level(level) {
981                assert!(
982                    pass::find(name).is_some(),
983                    "{level} names `{name}` and no pass answers to it"
984                );
985            }
986        }
987    }
988
989    #[test]
990    fn a_pass_a_pipeline_names_twice_is_never_named_twice_in_a_row() {
991        // Running a pass again after another pass has been through is the point of naming it
992        // twice, and `simplify` around `narrow` is why the rule that used to be here, which was
993        // that no level names a pass twice at all, is not the rule any more. Two runs with
994        // nothing between them is still a mistake: the second one sees exactly what the first
995        // one finished with, so it can only report that it found nothing.
996        for level in
997            [OptLevel::O0, OptLevel::O1, OptLevel::O2, OptLevel::O3, OptLevel::Os, OptLevel::Oz]
998        {
999            for pair in for_level(level).windows(2) {
1000                assert_ne!(pair[0], pair[1], "{level} runs `{}` twice in a row", pair[0]);
1001            }
1002        }
1003    }
1004
1005    #[test]
1006    fn a_pass_the_pipeline_runs_twice_gets_one_allowance_and_reports_one_number() {
1007        // `-fpass-fuel=<pass>=<n>` is halved to find one rewrite, so the number in the flag has
1008        // to be the number of rewrites that happened however many times the list names the pass.
1009        // The peephole is named more than once from `-O1` up and the function below holds two
1010        // identities it takes, so a cap of one has to stop after one rather than after one per
1011        // occurrence.
1012        assert!(for_level(OptLevel::O2).iter().filter(|it| **it == "simplify").count() > 1);
1013
1014        let (names, mut module) = identities();
1015        let free = super::run(&mut module, &names, &Options::for_level(OptLevel::O2));
1016        assert_eq!(spent(&free, "simplify"), Some(2), "{:?}", free.spent);
1017
1018        let (names, mut module) = identities();
1019        let mut opts = Options::for_level(OptLevel::O2);
1020        opts.fuel.insert("simplify".to_owned(), 1);
1021        let capped = super::run(&mut module, &names, &opts);
1022        assert_eq!(capped.spent.iter().filter(|(name, _)| *name == "simplify").count(), 1);
1023        assert_eq!(spent(&capped, "simplify"), Some(1), "{:?}", capped.spent);
1024    }
1025
1026    #[test]
1027    fn an_identity_only_the_narrow_pass_can_produce_is_still_taken() {
1028        // Issue 505, and the reason the peephole is named on both sides of `narrow`. C promotes
1029        // before it operates, so `unsigned char x; (unsigned char)(x & 255)` arrives here as a
1030        // thirty two bit `and` of a zero extension, and the rule that says `and` with every bit
1031        // set is the value has nothing at eight bits to match. `narrow` is the only producer that
1032        // width has. Before this ran twice the `and.i8` below reached the back end untouched.
1033        let mut names = Interner::new();
1034        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
1035        let mut module = Module::new(names.intern("test.c"), &target);
1036        let (i8_, i32_) = (Type::int(8), Type::int(32));
1037        let mut func =
1038            Func::new(names.intern("f"), Signature::new().with_params(&[i8_]).with_returns(&[i8_]));
1039        let entry = func.create_block();
1040        let x = func.append_param(entry, i8_);
1041        let mut build = Builder::new(&mut func, entry);
1042        let wide = build.unary(Opcode::ZExt, x, i32_);
1043        let mask = build.iconst(i32_, 255);
1044        let kept = build.binary(Opcode::And, wide, mask, Flags::NONE);
1045        let back = build.unary(Opcode::Trunc, kept, i8_);
1046        build.ret(&[back]);
1047        module.add_func(func);
1048
1049        let report = super::run(&mut module, &names, &Options::for_level(OptLevel::O2));
1050        assert!(report.broke.is_empty(), "{:?}", report.broke);
1051        let text = rucc_ir::print(&module, &names);
1052        assert!(!text.contains("and."), "the masking survived the pipeline\n{text}");
1053    }
1054
1055    /// What a pass spent, or `None` if it did not run.
1056    fn spent(report: &super::Report, pass: &str) -> Option<u32> {
1057        report.spent.iter().find(|(name, _)| *name == pass).map(|&(_, count)| count)
1058    }
1059
1060    /// The names of the passes a set of options would run, in order.
1061    fn names(opts: &Options) -> Vec<&'static str> {
1062        opts.passes().into_iter().map(Pass::name).collect()
1063    }
1064
1065    #[test]
1066    fn every_level_that_splits_a_loop_looks_at_what_the_split_wrote() {
1067        // A guard goes in the preheader of the loop being split, which for an inner loop is inside
1068        // the loops around it, and it asks the runtime how big an object is. Nothing after `split`
1069        // moves anything, so a level that splits and then stops leaves those queries where they
1070        // cost the most.
1071        for level in [super::O1, super::O2, super::O3, super::OS, super::OZ] {
1072            let Some(at) = level.iter().position(|pass| *pass == "split") else {
1073                continue;
1074            };
1075            assert!(
1076                level[at..].contains(&"licm"),
1077                "a level splits a loop and never looks at the guard again"
1078            );
1079        }
1080    }
1081
1082    #[test]
1083    fn every_level_that_chooses_induction_variables_takes_the_old_one_away_afterwards() {
1084        // The counter a loop stops asking anything is still incremented round it, and what removes
1085        // the parameter carrying it is `simplify-cfg` rather than `dce`. See the comment on `O2`.
1086        // A level that chooses and then stops keeps both variables and is worse off than if it had
1087        // never chosen at all.
1088        for level in [super::O1, super::O2, super::O3, super::OS, super::OZ] {
1089            let Some(at) = level.iter().position(|pass| *pass == "ivopts") else {
1090                continue;
1091            };
1092            assert!(
1093                level[at + 1..].contains(&"simplify-cfg"),
1094                "a level chooses induction variables and leaves the one it stopped using behind"
1095            );
1096        }
1097    }
1098
1099    #[test]
1100    fn every_run_of_the_pass_that_reads_a_summary_is_named_as_one_that_does() {
1101        // A run left off the list gets no table of globals and no caller guarantees, and answers
1102        // that there were none rather than that nobody built them.
1103        for pass in pass::PASSES {
1104            let name = pass.name();
1105            assert_eq!(
1106                name.starts_with("discharge"),
1107                super::READS_SUMMARIES.contains(&name),
1108                "`{name}` and READS_SUMMARIES disagree about whether it reads a summary"
1109            );
1110        }
1111    }
1112
1113    #[test]
1114    fn the_level_that_optimizes_nothing_still_removes_what_nothing_reaches() {
1115        // Two passes at `-O0`, and neither of them is an optimization. See the comment on the
1116        // level itself, and issue 359.
1117        assert_eq!(names(&Options::for_level(OptLevel::O0)), ["expect", "simplify-cfg"]);
1118        assert!(names(&Options::for_level(OptLevel::O2)).len() > 1);
1119    }
1120
1121    #[test]
1122    fn a_pass_is_removed_by_no_and_added_by_the_bare_name_and_the_last_word_wins() {
1123        let mut opts = Options::for_level(OptLevel::O2);
1124        opts.toggles.push(("fold".to_owned(), false));
1125        assert!(!names(&opts).contains(&"fold"), "{:?}", names(&opts));
1126        opts.toggles.push(("fold".to_owned(), true));
1127        assert!(names(&opts).contains(&"fold"), "{:?}", names(&opts));
1128
1129        let mut off = Options::for_level(OptLevel::O0);
1130        off.toggles.push(("fold".to_owned(), true));
1131        assert_eq!(
1132            names(&off),
1133            ["expect", "simplify-cfg", "fold"],
1134            "a pass the level did not choose is still reachable"
1135        );
1136    }
1137
1138    #[test]
1139    fn asking_for_a_pass_twice_does_not_run_it_twice() {
1140        let mut opts = Options::for_level(OptLevel::O2);
1141        let before = names(&opts);
1142        opts.toggles.push(("fold".to_owned(), true));
1143        assert_eq!(names(&opts), before);
1144    }
1145
1146    #[test]
1147    fn the_pipeline_listing_names_the_level_and_every_pass_in_order() {
1148        let text = super::print(&Options::for_level(OptLevel::O2));
1149        assert!(text.starts_with("level: -O2\n"), "{text}");
1150        assert!(text.contains("1: expect, "), "{text}");
1151        assert!(text.contains("2: fold, "), "{text}");
1152        // Turning off everything the level asked for leaves the one pass that cannot be turned
1153        // off, since the back end has no rule for what it removes. See `Pass::required`.
1154        let mut none = Options::for_level(OptLevel::O0);
1155        none.toggles.push(("expect".to_owned(), false));
1156        none.toggles.push(("simplify-cfg".to_owned(), false));
1157        let none = super::print(&none);
1158        assert!(none.contains("1: expect, "), "{none}");
1159        assert!(!none.contains("simplify-cfg"), "{none}");
1160    }
1161
1162    #[test]
1163    fn running_the_pipeline_changes_the_module_and_reports_what_it_spent() {
1164        let (names, mut module) = module();
1165        let report = super::run(&mut module, &names, &Options::for_level(OptLevel::O2));
1166        // Folding rewrites the sign extension into a constant, and then the constant it was
1167        // extending is read by nothing and dead code elimination takes it out. One
1168        // transformation each, which is what the two of them together are for. Asserted by
1169        // name rather than as the whole vector, so a pass added later does not fail this.
1170        assert_eq!(spent(&report, "fold"), Some(1));
1171        assert_eq!(spent(&report, "dce"), Some(1));
1172        assert!(report.broke.is_empty(), "{:?}", report.broke);
1173        assert!(report.dumps.is_empty(), "nothing asked for a dump");
1174        assert!(rucc_ir::print(&module, &names).contains("iconst.i64 7"));
1175    }
1176
1177    #[test]
1178    fn the_analyses_survive_a_pass_that_keeps_them_and_not_one_that_does_not() {
1179        // The pipeline half of the analysis manager. A branch on a constant, so `simplify-cfg`
1180        // has something to do and says it preserved nothing, and the whole run comes out with
1181        // the verifier and the manager both satisfied. What a pass that lied would produce is in
1182        // `crate::analysis`, where a lie can be told on purpose.
1183        let mut names = Interner::new();
1184        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
1185        let mut module = Module::new(names.intern("test.c"), &target);
1186        let mut func = Func::new(names.intern("f"), Signature::new());
1187        let entry = func.create_block();
1188        let dead = func.create_block();
1189        let exit = func.create_block();
1190        let mut build = Builder::new(&mut func, entry);
1191        let never = build.iconst(Type::int(1), 0);
1192        build.br_if(never, dead, &[], exit, &[]);
1193        for block in [dead, exit] {
1194            let mut build = Builder::new(&mut func, block);
1195            build.ret(&[]);
1196        }
1197        module.add_func(func);
1198        let report = super::run(&mut module, &names, &Options::for_level(OptLevel::O2));
1199        // The fold, and then the merge of the arm it left with one way into it.
1200        assert_eq!(spent(&report, "simplify-cfg"), Some(2));
1201        assert!(report.broke.is_empty(), "{:?}", report.broke);
1202        let text = rucc_ir::print(&module, &names);
1203        // The labels, which start a line, and not the mentions of one, which are indented. One
1204        // left: the arm nothing reaches went, and the arm that is always taken came up into the
1205        // entry, which is what is left of the branch.
1206        assert_eq!(text.matches("\nblock").count(), 1, "there is more than one block:\n{text}");
1207    }
1208
1209    #[test]
1210    fn no_pass_that_optimizes_runs_at_no_optimization_however_much_there_is_to_do() {
1211        let (names, mut module) = module();
1212        let before = rucc_ir::print(&module, &names);
1213        let report = super::run(&mut module, &names, &Options::for_level(OptLevel::O0));
1214        // The two passes the level runs looked, found no `__builtin_expect`, no branch they could
1215        // read and no block nothing reaches, and spent nothing. The constant arithmetic the fixture
1216        // is full of is still there, which is the part of `-O0` that has not changed.
1217        assert_eq!(report.spent, vec![("expect", 0), ("simplify-cfg", 0)]);
1218        assert_eq!(rucc_ir::print(&module, &names), before);
1219    }
1220
1221    #[test]
1222    fn a_gate_takes_a_pass_away_from_one_function_and_leaves_the_other_alone() {
1223        let (names, mut module) = two_functions();
1224        let mut opts = Options::for_level(OptLevel::O2);
1225        opts.gates.add(false, "fold=g").expect("g is a function and fold is a pass");
1226        let report = super::run(&mut module, &names, &opts);
1227        assert!(spoke_about(&report, "fold", "f", &names));
1228        assert!(!spoke_about(&report, "fold", "g", &names), "fold ran where it was gated off");
1229        assert!(spoke_about(&report, "dce", "g", &names), "one pass gated off is not all of them");
1230        // What the gate is for: the two functions came out different, and the difference is one
1231        // pass on one function rather than a level on a file.
1232        let text = rucc_ir::print(&module, &names);
1233        assert_eq!(text.matches("sext.i64").count(), 1, "{text}");
1234    }
1235
1236    #[test]
1237    fn a_function_can_be_gated_by_the_number_it_has_in_the_module() {
1238        let (names, mut module) = two_functions();
1239        let mut opts = Options::for_level(OptLevel::O2);
1240        opts.gates.add(false, "fold=0").expect("0 is a function and fold is a pass");
1241        let report = super::run(&mut module, &names, &opts);
1242        assert!(!spoke_about(&report, "fold", "f", &names), "function 0 is the first one");
1243        assert!(spoke_about(&report, "fold", "g", &names));
1244    }
1245
1246    #[test]
1247    fn enabling_a_pass_reaches_one_function_at_a_level_that_did_not_ask_for_it() {
1248        let (names, mut module) = two_functions();
1249        let mut opts = Options::for_level(OptLevel::O0);
1250        opts.gates.add(true, "fold=1").expect("1 is a function and fold is a pass");
1251        let running: Vec<&str> = opts.passes().into_iter().map(Pass::name).collect();
1252        assert_eq!(
1253            running,
1254            ["expect", "simplify-cfg", "fold"],
1255            "the flag has to put the pass in the pipeline"
1256        );
1257        let report = super::run(&mut module, &names, &opts);
1258        assert!(!spoke_about(&report, "fold", "f", &names), "nothing asked for f");
1259        assert!(spoke_about(&report, "fold", "g", &names));
1260        let text = rucc_ir::print(&module, &names);
1261        assert_eq!(text.matches("sext.i64").count(), 1, "{text}");
1262    }
1263
1264    #[test]
1265    fn a_pass_gated_off_everywhere_runs_on_nothing_and_still_says_so() {
1266        let (names, mut module) = two_functions();
1267        let before = rucc_ir::print(&module, &names);
1268        let mut opts = Options::for_level(OptLevel::O2);
1269        for pass in pass::PASSES {
1270            opts.gates.add(false, pass.name()).expect("a pass in the list is a pass that exists");
1271        }
1272        let report = super::run(&mut module, &names, &opts);
1273        assert!(report.remarks.is_empty(), "a pass that did not run has nothing to report");
1274        assert_eq!(spent(&report, "fold"), Some(0), "the pass is still in the pipeline");
1275        assert_eq!(rucc_ir::print(&module, &names), before);
1276    }
1277
1278    #[test]
1279    fn the_pipeline_listing_says_which_passes_a_gate_touched() {
1280        let mut opts = Options::for_level(OptLevel::O2);
1281        // `narrow` rather than `fold`, because a gate names a pass and the level runs some of its
1282        // passes more than once. A note on one of those is printed against every run of it, and
1283        // the count at the bottom would then be counting repeats rather than what it is asking.
1284        opts.gates.add(false, "narrow=2-4").expect("narrow is a pass");
1285        let text = super::print(&opts);
1286        assert!(text.contains("4: narrow, "), "{text}");
1287        assert!(text.contains("[off for 2-4]"), "{text}");
1288        assert_eq!(text.matches('[').count(), 1, "a pass no gate mentions says nothing extra");
1289    }
1290
1291    #[test]
1292    fn every_pass_at_no_fuel_leaves_the_module_exactly_as_it_found_it() {
1293        // The check section 9.10 asks for by name, and the reason it is here rather than in each
1294        // pass is that it has to hold for every pass that is ever added.
1295        for pass in pass::PASSES {
1296            let (names, mut module) = module();
1297            let before = rucc_ir::print(&module, &names);
1298            let mut opts = Options::for_level(OptLevel::O0);
1299            // The level's own passes out of the way first, so that what this measures is the one
1300            // pass under test. A pass turned off and then on again is on, so this is right for
1301            // those passes as well as for the others. `expect` cannot be turned off, so it is
1302            // starved of fuel instead and is expected in the report ahead of the pass under test.
1303            opts.toggles.push(("simplify-cfg".to_owned(), false));
1304            opts.toggles.push((pass.name().to_owned(), true));
1305            opts.fuel.insert("expect".to_owned(), 0);
1306            opts.fuel.insert(pass.name().to_owned(), 0);
1307            let report = super::run(&mut module, &names, &opts);
1308            let mut want = vec![("expect", 0)];
1309            if pass.name() != "expect" {
1310                want.push((pass.name(), 0));
1311            }
1312            assert_eq!(report.spent, want, "{} spent fuel it had none of", pass.name());
1313            assert_eq!(
1314                rucc_ir::print(&module, &names),
1315                before,
1316                "{} transformed the module at fuel zero",
1317                pass.name()
1318            );
1319        }
1320    }
1321
1322    #[test]
1323    fn fuel_is_shared_across_the_functions_of_a_module() {
1324        let mut names = Interner::new();
1325        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
1326        let mut module = Module::new(names.intern("test.c"), &target);
1327        for which in ["f", "g"] {
1328            let mut func =
1329                Func::new(names.intern(which), Signature::new().with_returns(&[Type::int(64)]));
1330            let block = func.create_block();
1331            let mut build = Builder::new(&mut func, block);
1332            let narrow = build.iconst(Type::int(32), 7);
1333            let wide = build.unary(Opcode::SExt, narrow, Type::int(64));
1334            build.ret(&[wide]);
1335            module.add_func(func);
1336        }
1337        let mut opts = Options::for_level(OptLevel::O2);
1338        opts.fuel.insert("fold".to_owned(), 1);
1339        let report = super::run(&mut module, &names, &opts);
1340        // One fold across both functions, because fuel is per pass and per compilation. Dead
1341        // code elimination has its own and spends it on the constant the one fold orphaned.
1342        assert_eq!(spent(&report, "fold"), Some(1));
1343        assert_eq!(spent(&report, "dce"), Some(1));
1344        let text = rucc_ir::print(&module, &names);
1345        assert_eq!(text.matches("sext.i64").count(), 1, "{text}");
1346    }
1347
1348    #[test]
1349    fn global_fuel_is_spent_by_the_passes_in_order_and_the_rest_get_none() {
1350        let (names, mut module) = module();
1351        let mut opts = Options::for_level(OptLevel::O2);
1352        opts.global_fuel = Some(1);
1353        let report = super::run(&mut module, &names, &opts);
1354        // Folding is first and there is one thing to fold, so it takes the one unit and dead
1355        // code elimination gets nothing. Without the budget it would have taken the constant
1356        // that fold orphaned, which is what the other test measures.
1357        assert_eq!(spent(&report, "fold"), Some(1));
1358        assert_eq!(spent(&report, "dce"), Some(0));
1359        let text = rucc_ir::print(&module, &names);
1360        assert!(text.contains("iconst.i64 7"), "{text}");
1361        assert!(text.contains("iconst.i32 7"), "the orphaned constant is still there, {text}");
1362    }
1363
1364    #[test]
1365    fn a_budget_of_nothing_leaves_the_module_alone_and_still_runs_every_pass() {
1366        let (names, mut module) = module();
1367        let before = rucc_ir::print(&module, &names);
1368        let mut opts = Options::for_level(OptLevel::O2);
1369        opts.global_fuel = Some(0);
1370        let report = super::run(&mut module, &names, &opts);
1371        assert_eq!(rucc_ir::print(&module, &names), before);
1372        assert!(report.spent.iter().all(|(_, spent)| *spent == 0), "{:?}", report.spent);
1373        // Every pass, because a pass out of fuel is a pass that ran and did nothing rather than
1374        // a pass that was skipped, and a bisection that skipped passes would be searching a
1375        // different pipeline at every step. One line per name rather than one per place the list
1376        // names it, because what a name was given is one allowance across all of them.
1377        let mut want: Vec<&str> = opts.passes().into_iter().map(Pass::name).collect();
1378        want.sort_unstable();
1379        want.dedup();
1380        let mut got: Vec<&str> = report.spent.iter().map(|&(name, _)| name).collect();
1381        got.sort_unstable();
1382        assert_eq!(got, want);
1383    }
1384
1385    #[test]
1386    fn the_tighter_of_the_two_limits_is_the_one_that_stops_the_pass() {
1387        // A pass allowed more than the budget gets the budget.
1388        let (names, mut under) = module();
1389        let mut opts = Options::for_level(OptLevel::O2);
1390        opts.global_fuel = Some(0);
1391        opts.fuel.insert("fold".to_owned(), 9);
1392        assert_eq!(spent(&super::run(&mut under, &names, &opts), "fold"), Some(0));
1393
1394        // And a pass allowed less than the budget keeps its own limit, with the budget left
1395        // over for whatever comes after it.
1396        let (names, mut over) = module();
1397        let mut opts = Options::for_level(OptLevel::O2);
1398        opts.global_fuel = Some(9);
1399        opts.fuel.insert("fold".to_owned(), 0);
1400        let report = super::run(&mut over, &names, &opts);
1401        assert_eq!(spent(&report, "fold"), Some(0));
1402        assert_eq!(spent(&report, "dce"), Some(0), "nothing was orphaned for it to remove");
1403    }
1404
1405    #[test]
1406    fn the_pipeline_listing_says_when_there_is_a_budget_and_says_nothing_when_there_is_not() {
1407        let opts = Options::for_level(OptLevel::O2);
1408        assert!(!super::print(&opts).contains("global fuel"));
1409        let with = Options { global_fuel: Some(12), ..Options::for_level(OptLevel::O2) };
1410        assert!(super::print(&with).contains("global fuel: 12"), "{}", super::print(&with));
1411    }
1412
1413    #[test]
1414    fn a_dump_is_taken_on_the_side_that_asked_for_it_and_not_the_other() {
1415        let (names, mut module) = module();
1416        let mut opts = Options::for_level(OptLevel::O2);
1417        opts.dumps.add("after-fold").expect("a pass that exists");
1418        let report = super::run(&mut module, &names, &opts);
1419        // The level folds twice, once at the top and once after the loop pipeline, and what a
1420        // dump request names is a pass rather than a position, so both runs are written out. The
1421        // side is what this is about: not one of the two is a `before`.
1422        assert_eq!(report.dumps.len(), 2, "both runs of the pass, one dump each");
1423        assert!(
1424            report.dumps.iter().all(|dump| dump.name.ends_with("-after-fold")),
1425            "{:?}",
1426            report.dumps.iter().map(|dump| &dump.name).collect::<Vec<&String>>()
1427        );
1428        assert_eq!(report.dumps[0].name, "01-after-fold");
1429        assert!(report.dumps[0].text.contains("iconst.i64 7"));
1430    }
1431
1432    #[test]
1433    fn asking_for_all_dumps_gives_both_sides_of_every_pass() {
1434        let (interner, mut module) = module();
1435        let opts = {
1436            let mut opts = Options::for_level(OptLevel::O2);
1437            opts.dumps.add("all").expect("all is always a dump");
1438            opts
1439        };
1440        let report = super::run(&mut module, &interner, &opts);
1441        // Both sides of every pass in the level, numbered by position, whatever the level
1442        // holds. Written out of the pipeline rather than as a literal, because the point of
1443        // the test is the pairing and the numbering and not which passes exist this month.
1444        let taken: Vec<&str> = report.dumps.iter().map(|d| d.name.as_str()).collect();
1445        let expected: Vec<String> = names(&opts)
1446            .into_iter()
1447            .enumerate()
1448            .flat_map(|(at, name)| {
1449                [format!("{at:02}-before-{name}"), format!("{at:02}-after-{name}")]
1450            })
1451            .collect();
1452        assert_eq!(taken, expected);
1453        // Either side of the fold, which is the pass that has something to do to this fixture,
1454        // found by name rather than by position so that a pass in front of it does not move it.
1455        let side = |which: &str| {
1456            let tail = format!("-{which}-fold");
1457            let dump = report.dumps.iter().find(|dump| dump.name.ends_with(&tail));
1458            dump.expect("the level folds").text.clone()
1459        };
1460        assert!(side("before").contains("sext.i64"));
1461        assert!(!side("after").contains("sext.i64"));
1462    }
1463
1464    #[test]
1465    fn every_pass_leaves_a_record_for_every_function_whether_or_not_it_had_anything_to_say() {
1466        let (names, mut module) = module();
1467        let opts = Options::for_level(OptLevel::O2);
1468        let report = super::run(&mut module, &names, &opts);
1469        let ran: Vec<&'static str> = opts.passes().into_iter().map(Pass::name).collect();
1470        // One function in the fixture, so one record per pass, and the passes in the order they
1471        // ran. A pass that found nothing is in here with an empty record, which is the point:
1472        // a pass that fires on nothing is either dead code or a bug, and output that leaves it
1473        // out cannot say which.
1474        let seen: Vec<&'static str> = report.remarks.iter().map(|it| it.pass).collect();
1475        assert_eq!(seen, ran);
1476        assert!(report.remarks.iter().all(|it| names.resolve(it.func) == "f"));
1477        assert!(
1478            report.remarks.iter().any(|it| it.pass == "simplify" && it.stats.is_empty()),
1479            "there is nothing in the fixture for the peephole to do"
1480        );
1481    }
1482
1483    #[test]
1484    fn a_pass_spends_one_unit_of_fuel_for_each_rewrite_it_reports() {
1485        // The invariant that keeps the record honest, checked over every pass rather than
1486        // written into each one. Fuel is taken immediately before a transformation and a
1487        // rewrite is recorded immediately after it, so the two counts are the same number
1488        // arrived at from two directions. A pass where they disagree either transformed without
1489        // asking, which breaks bisection, or rewrote without recording, which means the manager
1490        // did not run the verifier over what it produced.
1491        let (names, mut module) = module();
1492        let report = super::run(&mut module, &names, &Options::for_level(OptLevel::O2));
1493        for (pass, spent) in &report.spent {
1494            assert_eq!(
1495                report.totals(pass).total(Kind::Optimized),
1496                *spent,
1497                "{pass} spent {spent} units of fuel and did not say on what"
1498            );
1499        }
1500        assert!(report.spent.iter().any(|(_, spent)| *spent > 0), "nothing happened at all");
1501    }
1502
1503    #[test]
1504    fn what_the_passes_said_is_what_opt_info_prints() {
1505        let (names, mut module) = module();
1506        let report = super::run(&mut module, &names, &Options::for_level(OptLevel::O2));
1507        let text = crate::optinfo::render("t.c", &report, &names, crate::Wants::all());
1508        assert!(
1509            text.contains("t.c: f: optimized: integer instruction folded to a constant (1) [fold]"),
1510            "{text}"
1511        );
1512        assert!(
1513            text.contains(
1514                "t.c: f: optimized: instruction with no effects and no users removed (1) [dce]"
1515            ),
1516            "{text}"
1517        );
1518        // Nothing in the fixture is a miss, so asking only for the misses gets nothing back,
1519        // and that is different from the flag having been left off.
1520        let mut misses = crate::Wants::none();
1521        misses.add("missed").expect("that kind exists");
1522        assert_eq!(crate::optinfo::render("t.c", &report, &names, misses), "");
1523    }
1524
1525    #[test]
1526    fn the_verifier_says_which_function_it_refused_and_leaves_the_others_out_of_it() {
1527        // Two functions with the same foldable body, and a block in the second one that nothing
1528        // reaches, which the verifier refuses. The pass is not what put it there, and the
1529        // complaint says the pass anyway, because a pass that hands back a function the
1530        // verifier will not take is where the search has to start whoever wrote the block.
1531        let mut names = Interner::new();
1532        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
1533        let mut module = Module::new(names.intern("test.c"), &target);
1534        module.add_func(foldable(&mut names, "f"));
1535        let mut g = foldable(&mut names, "g");
1536        let stranded = g.create_block();
1537        let mut build = Builder::new(&mut g, stranded);
1538        let seven = build.iconst(Type::int(64), 7);
1539        build.ret(&[seven]);
1540        module.add_func(g);
1541
1542        // Folding on its own, because simplify-CFG would take the stranded block out and there
1543        // would be nothing left to complain about.
1544        let mut opts = Options::for_level(OptLevel::O0);
1545        opts.toggles.push(("simplify-cfg".to_owned(), false));
1546        opts.toggles.push(("fold".to_owned(), true));
1547        opts.verify = true;
1548        let report = super::run(&mut module, &names, &opts);
1549
1550        assert_eq!(report.broke.len(), 1, "{:?}", report.broke);
1551        let complaint = &report.broke[0];
1552        assert!(complaint.starts_with("the fold pass left invalid IR in g,"), "{complaint}");
1553        assert!(complaint.contains("this block is not reachable"), "{complaint}");
1554    }
1555
1556    #[test]
1557    fn a_function_a_pass_did_not_change_is_not_verified_after_it() {
1558        // The stranded block is in `f` this time and `f` has nothing to fold, so the pass runs
1559        // over an invalid function, changes nothing, and says nothing. That is the whole trade:
1560        // the verifier answers for the rewrite that just happened, and a function no rewrite
1561        // touched was already answered for when it was built.
1562        let mut names = Interner::new();
1563        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
1564        let mut module = Module::new(names.intern("test.c"), &target);
1565        let mut f = Func::new(names.intern("f"), Signature::new().with_returns(&[Type::int(64)]));
1566        for _ in 0..2 {
1567            let block = f.create_block();
1568            let mut build = Builder::new(&mut f, block);
1569            let seven = build.iconst(Type::int(64), 7);
1570            build.ret(&[seven]);
1571        }
1572        module.add_func(f);
1573        module.add_func(foldable(&mut names, "g"));
1574
1575        let mut opts = Options::for_level(OptLevel::O0);
1576        opts.toggles.push(("simplify-cfg".to_owned(), false));
1577        opts.toggles.push(("fold".to_owned(), true));
1578        opts.verify = true;
1579        let report = super::run(&mut module, &names, &opts);
1580
1581        assert!(report.broke.is_empty(), "{:?}", report.broke);
1582        // And it did run on it, so this is the verifier staying quiet rather than the pass
1583        // being skipped.
1584        assert!(spoke_about(&report, "fold", "f", &names));
1585    }
1586
1587    #[test]
1588    fn a_dump_of_a_pass_that_does_not_exist_is_refused_rather_than_ignored() {
1589        let mut dumps = Dumps::default();
1590        assert!(dumps.add("after-no-such-pass").is_err());
1591        assert!(dumps.add("sideways-fold").is_err());
1592        assert!(dumps.add("fold").is_err());
1593        assert!(dumps.is_empty());
1594    }
1595}