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, HashSet};
34use std::fmt::Write as _;
35use std::sync::Arc;
36
37use rucc_base::{Interner, Symbol};
38use rucc_cost::heuristics;
39use rucc_ir::{Datum, FuncId, Global, Imm, Linkage, Module, Pic, Reloc};
40use rucc_session::OptLevel;
41use rucc_tuple::{Arch, ObjectFormat};
42
43use crate::{
44    Analyses, CallGraph, Fuel, Gates, Machine, Pass, Preserved, Stats, constant_p, dce, extents,
45    heap, image, inline, ipasra, ipcp, libcall, load, modref, nofree, number, objsize, outside,
46    params, pass, purity, readonly, reload,
47};
48
49/// The passes that read a summary [`nofree::annotate`], [`extents::annotate`],
50/// [`params::annotate`] or [`heap::annotate`] writes onto the IR.
51///
52/// A list rather than one name because there will be more of them: section 7.5 asks for three more
53/// summary fields and section 7.3's lifetime elimination is the next thing to want this one. A pass
54/// that reads a summary and is not named here reads whatever the last build left, which is nothing,
55/// so the cost of forgetting to add a name is a missed optimization.
56///
57/// The five after the first are `crate::discharge`'s measurement runs, and leaving them out was a
58/// missed optimization of exactly that kind: a run measuring what an object says about itself, with
59/// no table saying how big any global is, answers that objects say nothing, and the number looks
60/// like a result rather than like a list with a name missing from it.
61const READS_SUMMARIES: &[&str] = &[
62    "discharge",
63    "discharge-objects",
64    "discharge-dominance",
65    "discharge-summaries",
66    "discharge-narrow",
67    "discharge-every",
68];
69
70/// Which passes build an alias oracle, and so want the module facts it asks about.
71///
72/// A list for the same reason as the one above, and it will grow the same way: the redundant load
73/// elimination of `spec/optimizer/16-gvn-and-pre.md` section 16.2 and the dead store elimination of
74/// document 17 both want one, and neither is written. A pass left off here builds its oracle on an
75/// empty table, which answers `May` to every question it would have used the module for, so what
76/// forgetting a name costs is a missed optimization rather than a wrong answer.
77const READS_OUTSIDE: &[&str] = &[load::NAME, reload::NAME];
78
79/// Which passes ask what a call is allowed to do.
80///
81/// Two, which are two of the four consumers section 34.6 of `spec/optimizer/34-ipa.md` names.
82/// Document 17's dead code elimination deletes a call whose result nothing reads. Document 16's
83/// value numbering makes two calls with the same arguments one value. The other two turned out to
84/// want the finer answer rather than this one and are in the list below: document 27.1's
85/// speculation predicate and document 08.4's call handling both read the mod and ref summaries,
86/// which say which memory rather than whether any. A list from the start for the reason the two
87/// above it are lists, which is that a pass left out of one reads the empty answer and loses an
88/// optimization rather than producing a wrong program.
89const READS_PURITY: &[&str] = &[dce::NAME, number::NAME];
90
91/// Which passes ask what a call does to the memory it was handed.
92///
93/// The two that move a load, which is the question section 34.6 says the per parameter answer is
94/// worth having for: whether the call in the middle of this loop can have written the array about
95/// to be reloaded. A list for the same reason as the three above it.
96const READS_MODREF: &[&str] = &[load::NAME, reload::NAME];
97
98/// `-O0`. Two passes, and neither of them is an optimization. Section 9.1 gives this level SSA
99/// construction, which the lowering walk in `spec/08-ir.md` already does, and mem2reg for the
100/// allocas that are left, which is the next pass to be written.
101///
102/// `expect` is here because what it takes out is a node the front end writes for every
103/// `__builtin_expect` in the program and gcc writes for none of them. Left standing it would be an
104/// instruction in the output of a level whose whole contract is that it emits what it was given.
105///
106/// `simplify-cfg` is here because a branch on a condition that is a constant is not a missed
107/// optimization, it is a call to a function the program never calls, and a program that calls a
108/// function it never calls is one that does not link. That is issue 359, gcc removes the code at
109/// every level including this one, and a `-O0` that emitted it would be a `-O0` some correct
110/// programs cannot be built at. Nothing else runs, and no analysis beyond the graph the pass
111/// reads reachability out of is computed.
112const O0: &[&str] = &["expect", "simplify-cfg"];
113
114/// `-O1`. Section 9.1 asks for one e-graph round, conservative inlining, simplify-CFG, SROA,
115/// GVN, DCE, LICM and the loop canonicalizations. Folding, control flow simplification and dead
116/// code elimination are the part of that which exists, with the peephole among them. They run in
117/// that order because folding and the peephole are what make most of the dead code there is to
118/// eliminate, because a constant a fold produced is a branch condition the control flow pass can
119/// then read, and because the comparison that branch was on is dead once it has.
120///
121/// `image` has a `fold` on each side of it, which is the other place in this list where a pass is
122/// named twice in a row, and both of them are the position rather than the pass. What it reads is
123/// a load from a `const` global at a constant byte offset, and until something has folded the
124/// address there is no constant byte offset: a subscript arrives from the front end as the index
125/// sign extended and multiplied by the element size, so without the `fold` ahead of it every array
126/// and every string in the program is a load it cannot answer. The `fold` behind it is the mirror
127/// of that. What `image` writes is a constant where a load stood, and what stands on top of it is
128/// whatever the program did with the value it read, so a `const double` converted to an `int` and
129/// compared against one is three folds in a row and only the first of them is this pass. Nothing
130/// later in the list would do it in time: the branch passes read the condition, and a condition
131/// still spelled as a conversion of a constant is a branch they leave standing. Running `image`
132/// before the pipeline rather than inside it is the alternative that does not work, and
133/// `crate::image` says at length why.
134///
135/// The peephole runs on both sides of `narrow`, which is the one place in this list where a pass
136/// is named twice, so the reason is worth stating. The rewrite table is written at a width, and
137/// the widths below `int` are unreachable from C source: the integer promotions mean an addition
138/// of two `char` values arrives here as an `add.i32`, so a rule about `add.i8` matches nothing
139/// that a front end can produce. `narrow` is what puts the width back, and it is therefore the
140/// only producer the narrow half of the table has. Running the peephole only before it left
141/// sixty nine of the first hundred and twenty five rules unable to fire on any program, which is
142/// issue 505 and is what the corpus measured. Running it only after it would give up the smaller
143/// trees the peephole hands `narrow`, since a subtree `narrow` redoes has to have one reader and
144/// an identity left standing is a second one. Both sides costs one more walk over each function
145/// and is what the pass is for.
146///
147/// `phiopt` comes after `thread` and the order between them is not arbitrary. Both look at a
148/// diamond whose arms carry a value to a join. Where the join then branches on that value,
149/// threading removes a branch and costs nothing, and if-conversion would have turned the same
150/// shape into a `select` the join branches on instead, which is strictly worse. Threading first
151/// leaves if-conversion the diamonds whose value is used rather than tested, which are the ones it
152/// is for.
153///
154/// That is only true of the threads that copy nothing, so at `-O1` and above threading is split
155/// around `phiopt`. `thread` goes first and takes the free ones. `thread-copy` goes after, and
156/// finds the same edges again plus the ones that need the block copied. The other way round, a
157/// copy takes apart a diamond `phiopt` would have made a `select`: `raw < 15 ? 15 : raw` followed
158/// by a test of the result is two conditional moves when `phiopt` sees it first, and two branches
159/// and three more saved registers when the copy does. The corpus `clamp` shapes found that.
160///
161/// `prune` is between `phiopt` and `simplify-cfg` and both sides of that are load bearing. It reads
162/// document 10's ranges off the graph to find a branch that can only go one way and a switch case
163/// nothing can reach, so it has to run after the two passes that change the graph most. What it
164/// leaves is a jump where a branch was and a block nothing reaches, and `simplify-cfg` is the pass
165/// that takes those out, so it has to run before it rather than after.
166///
167/// `canon` is where document 26's loop pipeline opens, so it goes after the value level passes and
168/// before the cleanup. It gives every loop a preheader, one latch, exits of its own and loop closed
169/// form, which is what lets the loop passes that follow it write `insert at the end of the
170/// preheader` rather than each making one. On its own it generates nothing: the blocks it adds are
171/// empty and the parameters it adds have one argument each, and `simplify-cfg` runs straight after
172/// it and takes both back out to a fixed point. That is section 26.7's arrangement, and it is why
173/// the position matters more than the pass does until the loop passes land on top of it.
174///
175/// `header-copy` is section 26.7's third step and `canon` runs again after it, which is the same
176/// section's instruction to re-canonicalize the loops it changed. It has to: what the copy leaves
177/// is a loop entered from a block that branches two ways, and a block that branches two ways is not
178/// a preheader. Nothing between the two needs the properties, so the second run is bookkeeping
179/// against the loop passes that come later rather than something this level's output depends on,
180/// and `simplify-cfg` after it takes out the blocks and parameters both runs added that nothing
181/// used.
182///
183/// `licm` comes after the copy and the canonicalization behind it, and section 27.1 says why it has
184/// to. What it may move in front of a loop depends on what runs on every entry to the loop, and
185/// after that pair that is the whole body rather than the header alone. Running it before the
186/// copy would leave it the header, which is most of the pass's value gone. It is also the reason
187/// the copy exists, so the two are one arrangement read from either end.
188///
189/// It is in the three speed levels and not in `-Os` or `-Oz`. Moving a computation out of a loop
190/// does not remove one, so there are no bytes in it for a level whose cost model is size, and the
191/// one thing it can cost is a spill inside the loop, which is bytes. That trade is worth making for
192/// time and there is nothing on the other side of it for space.
193///
194/// `unroll` runs after `licm` and only at the two speed levels. After, because what it does is copy
195/// the body, and a computation licm has already moved in front of the loop is one the copies do not
196/// each get their own of. It needs the same shape licm does and for the same reason, a loop that
197/// tests at the bottom with a preheader in front of it, so it sits at the end of the same run of
198/// loop passes rather than anywhere of its own. `simplify-cfg` straight after it is what turns the
199/// chain of copies into one block, since each copy now ends in a jump to the next and a block with
200/// one way in and one way out is a block that goes away.
201///
202/// `number` goes straight after that `simplify-cfg` and immediately before `load-forward`, and the
203/// two are one arrangement rather than two passes that happen to be adjacent. On its own it removes
204/// an instruction here and there, because the arithmetic a person writes is not usually written
205/// twice. What it is really for is the arithmetic the front end writes underneath: a subscript
206/// lowered twice is the same multiply and add twice, and giving the two one name is what turns a
207/// store and a load that `load-forward` was refusing into a store and a load of the same address.
208/// Running it the other way round would leave the pass after it nothing it did not already have.
209///
210/// `load-forward` goes next, and the position is the whole of what
211/// the pass is worth. It is the block local half of document 16, so what it can find is bounded by
212/// how much code is in one block, and `simplify-cfg` merging the straight line chains is what makes
213/// the blocks the largest they are ever going to be. At the two speed levels that position is also
214/// just after `unroll`, which is where the case the pass was written for lives: the body
215/// copies now sit in one block, and a copy that stored to an array slot and read it straight back
216/// is a store and a load of the same address with nothing in between.
217///
218/// `fold` runs a second time after it, and it is not there out of habit. What forwarding leaves
219/// behind is a value that arrived as a constant through memory, `grid[i] = 3` read back as a load
220/// that is now the literal three, and nothing else this late in the list would fold the arithmetic
221/// on top of it. The first `fold` ran before any of this existed.
222///
223/// `simplify` runs after that second `fold` for the same reason the second `fold` runs at all.
224/// Folding does not remove an instruction whose answer is a constant somebody still adds, it only
225/// writes the constant down, and an index folded to zero leaves a `ptr_add x, 0` behind. That is
226/// an identity the peephole takes and nothing else in the list is about. The unrolled body is
227/// where they come from: the copy that runs first subscripts the array at zero, so the multiply
228/// that worked its offset out is a multiply by zero, and until now the last thing any level did to
229/// that arithmetic was fold it. The add of zero reached the selector and was written out as an
230/// `addq $0`. Over the corpus at `-O2` the run is worth 3000 bytes across 1830 programs, 224 of
231/// them smaller and 4 larger, with every result unchanged.
232///
233/// `phiopt` runs a second time after that `simplify` at the two speed levels, for the diamonds the
234/// loop passes leave that the first run never saw. It has to come after `fold` rather than straight
235/// after `unroll`. Each copy of a loop body with a branch in it tests the copy's own counter, which
236/// is a constant only once `fold` has been. Before that the branch looks like any other diamond and
237/// becomes a conditional move on a constant, and the corpus loop with a branch in its body grew by
238/// sixty bytes where `-O2` had folded the whole loop to one number.
239///
240/// A second `simplify-cfg` runs after that `simplify` at `-O1`, `-Os` and `-Oz`, and the two speed
241/// levels already had one further down for what `ivopts` and the second `licm` leave behind. What
242/// it is for is the branch nobody has to take any more. Forwarding a load turns a comparison of
243/// what was read into a comparison of what was written, `fold` settles it, and what is then left is
244/// a conditional branch on a constant with a block on the other side of it that the program cannot
245/// reach. Nothing else at these three levels looks at an edge after `load-forward` has run, so
246/// until now the branch and the block it guards were both written out. The block is usually the
247/// interesting half, since it is where the work that was never going to happen is, and at the two
248/// size levels a block that goes is bytes that go.
249///
250/// `hoist` is the first of the two check passes and it runs where it does because of what is above
251/// it. It needs a loop that tests at the bottom, which is what `header-copy` makes, and it needs a
252/// preheader to put a check in, which is what the `canon` after it puts back. Running it before
253/// `discharge` rather than after is deliberate as well: what it leaves in the preheader is a check
254/// over the whole range the loop sweeps, and that is a fact `discharge` can then use on anything
255/// else in front of the loop that is about the same bytes.
256///
257/// `discharge` is second to last, between `hoist` and `dce`, and both neighbours are the reason. It
258/// reads the dominator tree to find a safety check whose bytes an earlier check already covered, so
259/// it wants the graph after the block merging rather than before, when a straight run of code is
260/// still several blocks and a fact does not reach the check it would cover. What it leaves behind
261/// is the `cap_of` the check it removed was reading, which nothing now reads, so `dce` after it is
262/// what makes the function smaller rather than shorter by one instruction. It is in every level
263/// except `-O0`, which keeps every check on purpose: document 14 measures against a build where
264/// nothing was discharged, and that build is `-O0`.
265///
266/// `short-circuit-free` is the collapse of section 22.5 restricted to the cases that cost nothing,
267/// which are the ones where both halves of the `&&` ask about the same two values, so that what it
268/// writes is one comparison or a constant and not an and on top of two comparisons. The full
269/// version of that pass is a speed for size trade and belongs to `-O2`. This version is not a trade
270/// at all, so there is nothing here to decline. It sits where `-O2` puts the full pass, which is
271/// immediately above `thread`, and that pass is the reason: threading turns the shape this matches
272/// into one it does not.
273const O1: &[&str] = &[
274    "expect",
275    "fold",
276    "image",
277    "fold",
278    "simplify",
279    "narrow",
280    "simplify",
281    "short-circuit-free",
282    "thread",
283    "phiopt",
284    "thread-copy",
285    "prune",
286    "canon",
287    "header-copy",
288    "canon",
289    "licm",
290    "simplify-cfg",
291    "number",
292    "load-forward",
293    "constant-p",
294    "fold",
295    "simplify",
296    "simplify-cfg",
297    "hoist",
298    "discharge",
299    "dead-plane",
300    "coalesce",
301    "plane-sink",
302    "dce",
303    "loop-delete",
304];
305
306/// `-O2`. The level the code quality claim is about. Section 9.1 asks for two e-graph rounds
307/// around the loop pipeline, the full inlining cost model, Memory SSA and the full alias
308/// analysis stack, and then the scalar and machine passes on top.
309///
310/// `short-circuit` is the one pass here that `-O1` does not have, and section 22.5 is where the
311/// level comes from. It folds the two branches of an `a && b` into one, which costs the right
312/// operand's work on the path that was skipping it and buys a branch the machine no longer has to
313/// guess. That is a trade worth making when the aim is speed and the branch is hard to call, and
314/// it is not one to make by default, which is what `-O1` is. What `-O1` has in its place is
315/// `short-circuit-free`, which is the same pass taking only the collapses that cost nothing, so
316/// this one is the trade and not the transformation.
317///
318/// It runs before `thread` and `phiopt` rather than after, and the order is not arbitrary. Both of
319/// those look at edges, and the collapse removes a block and turns two edges into one, so running
320/// it first hands them a smaller graph with nothing lost. The other way round, threading is free
321/// to give the second branch's block another predecessor, and a block two edges reach is one the
322/// collapse will not touch, so a chain that was foldable stops being foldable.
323///
324/// `canon` and `licm` run a second time after `split`, and that pair is the only thing here that
325/// looks at what `split` wrote. A guard goes in the preheader of the loop being split, which for an
326/// inner loop is a block inside the loops around it, and the guard asks the runtime how big each
327/// object is. On a matrix multiply that is four queries per entry to the innermost loop, two of
328/// them about a pointer that has not changed since it was allocated, and the pass that would take
329/// those out ran seven passes ago. `spec/safe-memory/13-performance.md` section 13.1 measured the
330/// cost and tamnd/rucc#893 is the rest of it.
331///
332/// `plane-sink` runs twice, once between `hoist` and `split` and once near the end. The first one
333/// is there for the loops `split` is about to cut in two. What `split` hands back is a run of
334/// iterations with no checks in it that can leave at its own test or at the guard into the rest,
335/// and a loop with two ways out is one `plane-sink` does not take, so on a copy whose checks came
336/// out the plane writes stayed in the half that runs every iteration and went only from the half
337/// that almost never does. `a-byte-at-a-time-copy` went from 2545314219 instructions to 282430669
338/// on that alone. The second run is for what the passes in between leave in a shape the first one
339/// could not take, and it finds nothing to do on a loop the first run already emptied.
340///
341/// `ivopts` goes last of the loop passes, because it is the one that decides what the loop's
342/// variables finally are and everything above it is still moving code around. It is followed by
343/// `simplify-cfg` and the pair cannot be separated. Section 28.4 has a loop stop asking its counter
344/// anything, and the counter goes on being incremented round the loop until the parameter carrying
345/// it is taken away. `crate::dce` says in its own documentation that it cannot do that, because the
346/// only reader left is the addition feeding the parameter back and a use count never reaches zero
347/// on a cycle. `crate::simplify_cfg` can, and says it was written for this. Without it the loop
348/// pays for the new pointer and keeps the old counter as well, which over the corpus is about half
349/// of what choosing badly costs.
350const O2: &[&str] = &[
351    "expect",
352    "fold",
353    "image",
354    "fold",
355    "simplify",
356    "narrow",
357    "simplify",
358    "switch-conv",
359    "short-circuit",
360    "thread",
361    "phiopt",
362    "thread-copy",
363    "prune",
364    "canon",
365    "header-copy",
366    "canon",
367    "licm",
368    "unroll",
369    "simplify-cfg",
370    "number",
371    "load-forward",
372    "redundant-load",
373    "constant-p",
374    "fold",
375    "simplify",
376    "phiopt",
377    "hoist",
378    "plane-sink",
379    "split",
380    "canon",
381    "licm",
382    "ivopts",
383    "simplify-cfg",
384    "discharge",
385    "dead-plane",
386    "coalesce",
387    "plane-sink",
388    "dce",
389    "loop-delete",
390];
391
392/// `-O3`. `-O2` plus loop vectorization, larger inlining and unrolling thresholds, interchange
393/// and distribution where the dependence analysis is confident, and function specialization.
394const O3: &[&str] = &[
395    "expect",
396    "fold",
397    "image",
398    "fold",
399    "simplify",
400    "narrow",
401    "simplify",
402    "switch-conv",
403    "short-circuit",
404    "thread",
405    "phiopt",
406    "thread-copy",
407    "prune",
408    "canon",
409    "header-copy",
410    "canon",
411    "licm",
412    "unroll",
413    "simplify-cfg",
414    "number",
415    "load-forward",
416    "redundant-load",
417    "constant-p",
418    "fold",
419    "simplify",
420    "phiopt",
421    "hoist",
422    "plane-sink",
423    "split",
424    "canon",
425    "licm",
426    "ivopts",
427    "simplify-cfg",
428    "discharge",
429    "dead-plane",
430    "coalesce",
431    "plane-sink",
432    "dce",
433    "loop-delete",
434];
435
436/// `-Os`. `-O2`'s passes under a size cost model: inlining only where it shrinks, no unrolling
437/// and no vectorization.
438///
439/// The second peephole is here rather than cut for size, because every rule it can fire replaces
440/// a term with a strictly smaller one. Tier one of `spec/optimizer/13-rewrite-rules.md` is
441/// defined that way, so a level that wants smaller code wants more of it and not less.
442///
443/// `short-circuit` is the pass this level drops from `-O2`, for the mirror of that reason. What it
444/// removes is a branch, which is time, and what it adds is the right operand's instructions on a
445/// path that did not run them and an and on top. The code comes out no smaller and usually a byte
446/// or two larger, so a level whose cost model is size has nothing to gain from it. What it keeps is
447/// `short-circuit-free` below.
448///
449/// `hoist` is dropped here as well, and the reason is the same trade read the other way.
450/// It takes a check out of a loop body and puts one in the preheader, plus the address arithmetic
451/// the new check needs, so the loop runs faster and the function is a few instructions larger. That
452/// is a speed transformation with a size cost, which is what `-Os` and `-Oz` are for declining.
453///
454/// `short-circuit-free` is here for the reason the `-O1` list gives, and the reason reads the same
455/// at a level whose cost model is size. The collapse it is restricted to takes a branch and a block
456/// away and adds nothing, so declining it would be declining something smaller.
457///
458/// `header-copy-small` is the same pass `-O1` and above run under section 26.6's smaller budget.
459/// The copy is code growth and this level pays for it once per loop, so five instructions is what
460/// it will pay. What it gets back is a body that is one region and an exit test at the bottom,
461/// which is slightly smaller in the steady state, so the trade is worth making at a limit that
462/// keeps the header small and not at one that copies twenty instructions to save two.
463const OS: &[&str] = &[
464    "expect",
465    "fold",
466    "image",
467    "fold",
468    "simplify",
469    "narrow",
470    "simplify",
471    "switch-conv",
472    "short-circuit-free",
473    "thread",
474    "phiopt",
475    "prune",
476    "canon",
477    "header-copy-small",
478    "canon",
479    "simplify-cfg",
480    "number",
481    "load-forward",
482    "constant-p",
483    "fold",
484    "simplify",
485    "simplify-cfg",
486    "discharge",
487    "dead-plane",
488    "coalesce",
489    "plane-sink",
490    "dce",
491    "loop-delete",
492];
493
494/// `-Oz`. `-Os` and additionally the outliner, with instruction selection preferring the smaller
495/// encoding wherever there is a choice.
496///
497/// Header copying is the pass this level drops from `-Os`, which section 26.6 asks for by name. It
498/// is the one loop canonicalization that makes the function bigger, `-Oz` is the level that would
499/// rather have the branch than the bytes, and every reason to want the do-while form here is a
500/// speed reason.
501const OZ: &[&str] = &[
502    "expect",
503    "fold",
504    "image",
505    "fold",
506    "simplify",
507    "narrow",
508    "simplify",
509    "switch-conv",
510    "short-circuit-free",
511    "thread",
512    "phiopt",
513    "prune",
514    "canon",
515    "simplify-cfg",
516    "number",
517    "load-forward",
518    "constant-p",
519    "fold",
520    "simplify",
521    "simplify-cfg",
522    "discharge",
523    "dead-plane",
524    "coalesce",
525    "plane-sink",
526    "dce",
527    "loop-delete",
528];
529
530/// The passes this level runs, before the command line adds to or removes from them.
531#[must_use]
532pub const fn for_level(level: OptLevel) -> &'static [&'static str] {
533    match level {
534        OptLevel::O0 => O0,
535        OptLevel::O1 => O1,
536        OptLevel::O2 => O2,
537        OptLevel::O3 => O3,
538        OptLevel::Os => OS,
539        OptLevel::Oz => OZ,
540    }
541}
542
543/// Which passes the IR is written out around.
544///
545/// Empty by default, which is the whole point: a dump is a debugging aid and writing files
546/// nobody asked for is not one.
547#[derive(Debug, Clone, Default, PartialEq, Eq)]
548pub struct Dumps {
549    /// Every pass, on both sides.
550    all: bool,
551    /// The passes to write out before.
552    before: Vec<String>,
553    /// The passes to write out after.
554    after: Vec<String>,
555}
556
557impl Dumps {
558    /// Adds one `-fdump-ir=` argument.
559    ///
560    /// # Errors
561    ///
562    /// When the argument is not `all`, `before-<pass>` or `after-<pass>`, or when it names a
563    /// pass this compiler does not have. A misspelled pass name that quietly dumped nothing
564    /// would look exactly like a pass that did not run.
565    pub fn add(&mut self, spec: &str) -> Result<(), String> {
566        if spec == "all" {
567            self.all = true;
568            return Ok(());
569        }
570        let (side, name) = match spec.split_once('-') {
571            Some(("before", name)) => (&mut self.before, name),
572            Some(("after", name)) => (&mut self.after, name),
573            _ => {
574                return Err(format!(
575                    "`{spec}` is not a dump this compiler makes, which are `all`, \
576                     `before-<pass>` and `after-<pass>`"
577                ));
578            }
579        };
580        if pass::find(name).is_none() {
581            return Err(format!("`{name}` is not a pass this compiler has, see --print-pipeline"));
582        }
583        side.push(name.to_owned());
584        Ok(())
585    }
586
587    /// Whether anything is dumped at all.
588    #[must_use]
589    pub fn is_empty(&self) -> bool {
590        !self.all && self.before.is_empty() && self.after.is_empty()
591    }
592
593    /// Whether the IR is written out before this pass runs.
594    #[must_use]
595    pub fn wants_before(&self, name: &str) -> bool {
596        self.all || self.before.iter().any(|it| it == name)
597    }
598
599    /// Whether the IR is written out after this pass runs.
600    #[must_use]
601    pub fn wants_after(&self, name: &str) -> bool {
602        self.all || self.after.iter().any(|it| it == name)
603    }
604}
605
606/// What the command line asked the optimizer for.
607#[derive(Debug, Clone, PartialEq, Eq)]
608pub struct Options {
609    /// Which pipeline to start from.
610    pub level: OptLevel,
611    /// The passes `-f<name>` added and `-fno-<name>` removed, in the order they were given, so
612    /// that the last mention of a pass is the one that decides.
613    pub toggles: Vec<(String, bool)>,
614    /// What `-fpass-fuel=<pass>=<n>` limited, by pass name.
615    pub fuel: HashMap<String, u32>,
616    /// What `-fpass-fuel-global=<n>` limited the whole pipeline to, across every pass.
617    ///
618    /// This is the outer search of the two in section 4.5 of
619    /// `spec/optimizer/04-pass-manager.md`. Halving this finds the pass, and halving
620    /// `-fpass-fuel` for that pass finds the rewrite inside it. Two searches of twenty
621    /// compilations each beat one search over a space nobody knows the shape of.
622    pub global_fuel: Option<u32>,
623    /// What `-fdisable-<pass>` and `-fenable-<pass>` said about which functions a pass runs on.
624    pub gates: Gates,
625    /// What `-fdump-ir=` asked to see.
626    pub dumps: Dumps,
627    /// Whether the verifier runs after every pass that changed anything.
628    pub verify: bool,
629    /// Which definitions in this module something else may replace at load time.
630    ///
631    /// The analyses that read a body and write down what they found have to stop at a name like
632    /// that, because the body they read is not the one that will run. [`Pic::Library`] is the
633    /// answer when the object may end up in a shared library and the exported names in it are
634    /// interposable, which is what `-fPIC` alone means and is gcc's default.
635    ///
636    /// [`Pic::Executable`] is the answer for everything else, and that includes
637    /// `-fno-semantic-interposition`, where the build has promised that the definition here is the
638    /// one that runs. It is a promise and not a deduction, and it is the one every distribution
639    /// makes, because a library that cannot inline its own functions into each other pays for the
640    /// possibility of an interposition that never happens.
641    ///
642    /// This is not the same value the code generator is given. How an address is reached does not
643    /// change under that promise, and gcc does not change it either: a variable a shared library
644    /// exports is still read out of the global offset table, because the promise is about which
645    /// definition runs rather than about how many copies of the variable there are.
646    pub interposition: Pic,
647    /// Whether a call to a library function may be taken to mean what the standard says it means.
648    ///
649    /// `-fno-builtin` and `-ffreestanding` turned around, which is the pair section 20.1 of
650    /// `spec/optimizer/20-idioms-and-libcalls.md` describes. False stops [`crate::libcall`] from
651    /// reading a `printf` as anything but a call to whatever the program links against.
652    pub builtins: bool,
653    /// The library names `-fno-builtin-<name>` took away one at a time.
654    pub no_builtin: Vec<String>,
655}
656
657impl Default for Options {
658    /// The default level with nothing added to it, and the verifier on in a debug build, which
659    /// is what section 9.10 asks for.
660    fn default() -> Self {
661        Self {
662            level: OptLevel::default(),
663            toggles: Vec::new(),
664            fuel: HashMap::new(),
665            global_fuel: None,
666            gates: Gates::default(),
667            dumps: Dumps::default(),
668            verify: cfg!(debug_assertions),
669            interposition: Pic::Executable,
670            builtins: true,
671            no_builtin: Vec::new(),
672        }
673    }
674}
675
676impl Options {
677    /// The options a level asks for on its own.
678    #[must_use]
679    pub fn for_level(level: OptLevel) -> Self {
680        Self { level, ..Self::default() }
681    }
682
683    /// The passes the level and the `-f` flags chose, in order, before the gates are consulted.
684    ///
685    /// A pass named by `-f<name>` that the level did not choose is appended, because the only
686    /// place it could go that does not need an ordering rule nobody wrote down is the end.
687    #[must_use]
688    pub fn chosen(&self) -> Vec<&'static str> {
689        let mut names: Vec<&str> = for_level(self.level).to_vec();
690        for (name, on) in &self.toggles {
691            let name = name.as_str();
692            match *on {
693                true if !names.contains(&name) => names.push(name),
694                true => {}
695                // A pass that says it is required stays, since turning it off is a compile that
696                // fails rather than one that optimizes less. See [`Pass::required`].
697                false => names.retain(|it| *it != name || required(it)),
698            }
699        }
700        names.into_iter().filter_map(pass::find).map(Pass::name).collect()
701    }
702
703    /// Whether a module at a time transformation the level asked for is still asked for.
704    ///
705    /// [`Options::chosen`] cannot answer this. Everything it returns is a [`Pass`], which is one
706    /// function at a time, and section 34.6's propagation is a module at a time because what a
707    /// parameter holds is something the callers say. The last word wins, as it does there, so a
708    /// command line with both spellings on it means the one written second.
709    #[must_use]
710    pub fn wants(&self, name: &str) -> bool {
711        self.toggles.iter().rfind(|(it, _)| it == name).is_none_or(|&(_, on)| on)
712    }
713}
714
715/// Whether the pass of that name is one `-fno-<name>` does not turn off.
716fn required(name: &str) -> bool {
717    pass::find(name).is_some_and(|pass| pass.required())
718}
719
720impl Options {
721    /// The passes that will run, in order, over at least one function.
722    ///
723    /// A pass `-fenable-<name>` reached that the level did not choose is appended after them,
724    /// for the same reason and in the same place. It runs only over the functions the gate names,
725    /// which is the whole point of the flag: a pass being in this list is not the same question as
726    /// a pass running on the function somebody is looking at.
727    #[must_use]
728    pub fn passes(&self) -> Vec<&'static dyn Pass> {
729        let mut names = self.chosen();
730        for name in self.gates.enabled() {
731            // Through the pass list rather than straight from the gate, because the name the
732            // pass holds outlives this call and the one the gate holds does not.
733            let Some(found) = pass::find(name) else { continue };
734            if !names.contains(&found.name()) {
735                names.push(found.name());
736            }
737        }
738        names.into_iter().filter_map(pass::find).collect()
739    }
740}
741
742/// One written out copy of the IR.
743#[derive(Debug, Clone, PartialEq, Eq)]
744pub struct Dump {
745    /// What to call it, which is a number, a side and a pass name, as in `01-after-fold`. The
746    /// number is there so that a directory listing is in the order the passes ran.
747    pub name: String,
748    /// The module, in the textual form from `spec/08-ir.md`.
749    pub text: String,
750}
751
752/// What one pass had to say about one function.
753///
754/// One of these per pass per function with a body, whether or not the pass said anything, because
755/// a pass that reports nothing being visible as a pass that reports nothing is the point of the
756/// record. Section 42.2 of `spec/optimizer/42-measurement.md` has the argument.
757#[derive(Debug, Clone, PartialEq, Eq)]
758pub struct Remark {
759    /// Which pass, by the name a `-f` flag spells.
760    pub pass: &'static str,
761    /// Which function, by the name in the source.
762    pub func: Symbol,
763    /// What it said.
764    pub stats: Stats,
765}
766
767/// What running the pipeline produced beyond the changed module.
768#[derive(Debug, Clone, Default, PartialEq, Eq)]
769pub struct Report {
770    /// The dumps asked for, in the order they were taken. The manager does not write files,
771    /// because nothing below the driver in `spec/18-package-layout.md` knows what a file is.
772    pub dumps: Vec<Dump>,
773    /// A pass that left the IR in a state the verifier refuses, named, with what it said.
774    pub broke: Vec<String>,
775    /// How much fuel each pass spent, which is the number a bisection halves.
776    pub spent: Vec<(&'static str, u32)>,
777    /// What every pass said about every function, in the order the passes ran and then in the
778    /// order the module holds its functions. This is what `-fopt-info` prints.
779    pub remarks: Vec<Remark>,
780}
781
782impl Report {
783    /// Everything one pass said across the whole module, added up.
784    ///
785    /// The counts of an event are addable across functions because an event names a site in a
786    /// pass rather than a fact about a program, which is the reason [`crate::stats::Event::what`]
787    /// is a fixed string.
788    #[must_use]
789    pub fn totals(&self, pass: &str) -> Stats {
790        let mut total = Stats::new();
791        for remark in self.remarks.iter().filter(|it| it.pass == pass) {
792            total.merge(&remark.stats);
793        }
794        total
795    }
796}
797
798/// Runs the pipeline over the module.
799///
800/// Every pass sees every function with a body, one at a time, and a pass runs over the whole
801/// module before the next one starts. That order is what makes the dumps readable: a dump is
802/// the state of the program between two passes rather than between two functions.
803pub fn run(module: &mut Module, names: &mut Interner, opts: &Options) -> Report {
804    let mut report = Report::default();
805    let chosen = opts.chosen();
806    // One cache per function, kept across passes because a pass runs over the whole module
807    // before the next one starts. A cache that lived only as long as one function would be
808    // thrown away between every pass and would never answer a second question. Section 4.2 of
809    // `spec/optimizer/04-pass-manager.md` is the plan for turning the loop inside out, and the
810    // day that happens this map becomes a local in the inner loop.
811    let mut cached: HashMap<FuncId, Analyses> = HashMap::new();
812    // The machine, once for the module, because every function in it is compiled for the same
813    // target at the same goal. It goes into each function's cache rather than into a parameter of
814    // its own, per `crate::machine`.
815    let machine = Machine::of(module, opts.level);
816    // What the whole pipeline has left, which every pass draws its own allowance out of and
817    // gives the unspent part of back. A pass past the end of it is given nothing rather than
818    // skipped, so it still runs, still reports, and still transforms nothing.
819    let mut budget = opts.global_fuel;
820    // What each pass has left of what `-fpass-fuel` gave it. One allowance across every place
821    // the list names that pass, rather than one allowance each, because the number in the flag
822    // is meant to be the number of rewrites that happened. A peephole that runs twice under
823    // `-fpass-fuel=simplify=5` and rewrites ten things would make the bisection in section 4.5
824    // of `spec/optimizer/04-pass-manager.md` step over the rewrite it was looking for.
825    let mut allowance = opts.fuel.clone();
826    let passes = opts.passes();
827    // Before everything, at every level, because `always_inline` is a promise gcc keeps at `-O0`
828    // and a fortified header relies on it: the wrapper's body has to be where the call was
829    // before `objsize` below asks what the destination is. See [`inline`]. From `-O1` up the same
830    // step takes a small function declared `inline` too, unless `-fno-inline` said not to, with
831    // gcc's limit for the level.
832    let limit = match opts.level {
833        OptLevel::O0 => None,
834        _ if !opts.wants(inline::NAME) => None,
835        OptLevel::O3 => Some(heuristics::INLINE_INSNS_SINGLE_O3),
836        _ => Some(heuristics::INLINE_INSNS_SINGLE),
837    };
838    for (id, stats) in inline::run(module, limit) {
839        if opts.verify {
840            if let Err(errors) = rucc_ir::verify_func(module, &module[id], names) {
841                let func = names.resolve(module[id].name);
842                for error in errors {
843                    report.broke.push(format!(
844                        "the {} pass left invalid IR in {func}, {error}",
845                        inline::NAME
846                    ));
847                }
848            }
849        }
850        report.remarks.push(Remark { pass: inline::NAME, func: module[id].name, stats });
851    }
852    // First of all and whatever the pass list says, because the instruction is a question the
853    // front end left for the IR and nothing after this is allowed to see one. The walk is skipped
854    // at `-O0`, which answers every question as not known, the way gcc does at that level.
855    objsize::answer(module, opts.interposition, opts.level != OptLevel::O0);
856    // The same for `__builtin_constant_p`, but only at `-O0`, where every question is answered
857    // zero the way gcc answers it there. Above that the question waits for `constant-p` in the
858    // list, which is after the folding that can turn the value into a constant.
859    if opts.level == OptLevel::O0 {
860        constant_p::answer(module, false);
861    }
862    // Before anything runs, because each of these is a fact about the module and every pass after
863    // this sees one function. Only when a pass in this run reads them: a flag nothing looks at
864    // would show up in every `-O0` dump and mean nothing to anybody reading one.
865    if passes.iter().any(|pass| READS_SUMMARIES.contains(&pass.name())) {
866        nofree::annotate(module, names, opts.interposition);
867        extents::annotate(module, opts.interposition);
868        params::annotate(module, opts.interposition);
869        heap::annotate(module, names);
870    }
871    // In the same place and for the same reason, except that this one is read by a pass rather
872    // than by a summary, so it is handed over on the analysis cache instead of written onto the
873    // module. Only when the run has that pass in it, since it is a copy of the module's read only
874    // data and nothing else would ever look at it.
875    let images = if passes.iter().any(|pass| pass.name() == image::NAME) {
876        Arc::new(image::Images::of(module, opts.interposition))
877    } else {
878        Arc::default()
879    };
880    // And the same again for the alias oracle's half of the module, which is what each name
881    // refers to, what a callee is declared to do, the tree of type nodes and the layout. Built
882    // only for a run with a pass that asks, since the empty one answers `May` and every pass here
883    // is correct against that.
884    let outside = if passes.iter().any(|pass| READS_OUTSIDE.contains(&pass.name())) {
885        Arc::new(outside::Outside::of(module))
886    } else {
887        Arc::default()
888    };
889    // And once more for what each function is allowed to do, which wants the call graph under it
890    // and is the one thing here that reads every body in the module rather than looking at the
891    // outside of each one. Section 34.6 puts it at `-O1` and above, which is where gcc turns
892    // `-fipa-pure-const` on, and the level is the gate rather than the pass list alone because
893    // `-O0` has `dce` in it and the promise of that level is compile time.
894    let wants_purity =
895        opts.level != OptLevel::O0 && passes.iter().any(|pass| READS_PURITY.contains(&pass.name()));
896    // And the per parameter answer a level above that, where section 34.6 puts it and where gcc
897    // turns `-fipa-modref` on for anything that is not `-O0` or a debug build. A level above
898    // because this one reads every instruction of every body rather than every call in each one,
899    // so it is the more expensive of the two and `-O1` is the level whose promise is compile time.
900    let wants_modref = !matches!(opts.level, OptLevel::O0 | OptLevel::O1)
901        && passes.iter().any(|pass| READS_MODREF.contains(&pass.name()));
902    // And section 34.6's propagation, at the level it puts it at, which is where gcc turns
903    // `-fipa-cp` on (`gcc/opts.cc:654`). A transformation rather than an analysis, so it is not in
904    // the pass list: everything in that list is a [`Pass`], which is one function at a time, and
905    // what a parameter holds is something the callers say. The level decides and `-fno-ipa-cp`
906    // overrides, which is what the list itself gets from [`Options::chosen`].
907    let wants_ipcp = !matches!(opts.level, OptLevel::O0 | OptLevel::O1) && opts.wants(ipcp::NAME);
908    // And section 34.6's other half, at the same level, which is where gcc turns `-fipa-sra` on as
909    // well. After the propagation rather than before it: a parameter the propagation turned into a
910    // constant in the body is a parameter nothing reads any more, and this is what then takes it
911    // out along with the argument at every call.
912    let wants_ipasra =
913        !matches!(opts.level, OptLevel::O0 | OptLevel::O1) && opts.wants(ipasra::NAME);
914    // Before the call graph, because it is the one transformation here that takes a call away
915    // altogether and a graph built over the module after it is the smaller of the two. `-O1` and
916    // above, which is where gcc folds these, and off under `-fno-builtin` or `-ffreestanding`,
917    // since a freestanding program left with a call to a `puts` it never wrote will not link.
918    if opts.level != OptLevel::O0 && opts.builtins && opts.wants(libcall::NAME) {
919        let mut fuel = match (allowance.get(libcall::NAME).copied(), budget) {
920            (Some(count), Some(left)) => Fuel::of(count.min(left)),
921            (Some(count), None) => Fuel::of(count),
922            (None, Some(left)) => Fuel::of(left),
923            (None, None) => Fuel::unlimited(),
924        };
925        let folded = libcall::fold(module, names, &opts.no_builtin, opts.interposition, &mut fuel);
926        for (id, stats) in folded {
927            if opts.verify {
928                if let Err(errors) = rucc_ir::verify_func(module, &module[id], names) {
929                    let func = names.resolve(module[id].name);
930                    for error in errors {
931                        report.broke.push(format!(
932                            "the {} pass left invalid IR in {func}, {error}",
933                            libcall::NAME
934                        ));
935                    }
936                }
937            }
938            report.remarks.push(Remark { pass: libcall::NAME, func: module[id].name, stats });
939        }
940        report.spent.push((libcall::NAME, fuel.spent()));
941        if let Some(left) = &mut budget {
942            *left -= fuel.spent();
943        }
944        if let Some(left) = allowance.get_mut(libcall::NAME) {
945            *left -= fuel.spent();
946        }
947    }
948    // One graph for all four, because building it is a walk over the module and none of them adds
949    // an edge to it. The two transformations take edges away, by leaving a call nothing reaches or
950    // an address nothing hands out, and a graph that still holds those is the conservative one.
951    let graph = (wants_purity || wants_modref || wants_ipcp || wants_ipasra)
952        .then(|| CallGraph::of(module, opts.interposition));
953    // Before the two below rather than after them, because it is the one of the three that changes
954    // a body, and an answer worked out from a body should be worked out from the body the passes
955    // will see. It leaves the edges alone, so the graph under it is the same graph either way.
956    if let (true, Some(graph)) = (wants_ipcp, graph.as_ref()) {
957        let mut fuel = match (allowance.get(ipcp::NAME).copied(), budget) {
958            (Some(count), Some(left)) => Fuel::of(count.min(left)),
959            (Some(count), None) => Fuel::of(count),
960            (None, Some(left)) => Fuel::of(left),
961            (None, None) => Fuel::unlimited(),
962        };
963        for (id, stats) in ipcp::propagate(module, graph, &mut fuel) {
964            if opts.verify {
965                if let Err(errors) = rucc_ir::verify_func(module, &module[id], names) {
966                    let func = names.resolve(module[id].name);
967                    for error in errors {
968                        report.broke.push(format!(
969                            "the {} pass left invalid IR in {func}, {error}",
970                            ipcp::NAME
971                        ));
972                    }
973                }
974            }
975            report.remarks.push(Remark { pass: ipcp::NAME, func: module[id].name, stats });
976        }
977        report.spent.push((ipcp::NAME, fuel.spent()));
978        if let Some(left) = &mut budget {
979            *left -= fuel.spent();
980        }
981        if let Some(left) = allowance.get_mut(ipcp::NAME) {
982            *left -= fuel.spent();
983        }
984    }
985    if let (true, Some(graph)) = (wants_ipasra, graph.as_ref()) {
986        let mut fuel = match (allowance.get(ipasra::NAME).copied(), budget) {
987            (Some(count), Some(left)) => Fuel::of(count.min(left)),
988            (Some(count), None) => Fuel::of(count),
989            (None, Some(left)) => Fuel::of(left),
990            (None, None) => Fuel::unlimited(),
991        };
992        for (id, stats) in ipasra::remove(module, graph, names, &mut fuel) {
993            if opts.verify {
994                if let Err(errors) = rucc_ir::verify_func(module, &module[id], names) {
995                    let func = names.resolve(module[id].name);
996                    for error in errors {
997                        report.broke.push(format!(
998                            "the {} pass left invalid IR in {func}, {error}",
999                            ipasra::NAME
1000                        ));
1001                    }
1002                }
1003            }
1004            report.remarks.push(Remark { pass: ipasra::NAME, func: module[id].name, stats });
1005        }
1006        report.spent.push((ipasra::NAME, fuel.spent()));
1007        if let Some(left) = &mut budget {
1008            *left -= fuel.spent();
1009        }
1010        if let Some(left) = allowance.get_mut(ipasra::NAME) {
1011            *left -= fuel.spent();
1012        }
1013    }
1014    let purity = match (wants_purity, graph.as_ref()) {
1015        (true, Some(graph)) => {
1016            let mut facts = purity::Facts::of_module(module, names);
1017            purity::infer(module, graph, &mut facts);
1018            Arc::new(facts)
1019        }
1020        _ => Arc::default(),
1021    };
1022    let modref = match (wants_modref, graph.as_ref()) {
1023        (true, Some(graph)) => {
1024            let mut summaries = modref::Summaries::of_module(module);
1025            modref::summarize(module, graph, &mut summaries);
1026            Arc::new(summaries)
1027        }
1028        _ => Arc::default(),
1029    };
1030    // Every name the module had before any pass ran, which is what a table a pass asks for has to
1031    // stay clear of, and the number the next table's name is made from. See `crate::readonly`.
1032    let taken: HashSet<Symbol> = module
1033        .funcs()
1034        .map(|id| module[id].name)
1035        .chain(module.globals().map(|id| module[id].name))
1036        .chain(module.aliases().map(|id| module[id].name))
1037        .collect();
1038    let mut tables = 0;
1039    for (index, pass) in passes.into_iter().enumerate() {
1040        let name = pass.name();
1041        if opts.dumps.wants_before(name) {
1042            report.dumps.push(dump(index, "before", name, module, names));
1043        }
1044        let mut fuel = match (allowance.get(name).copied(), budget) {
1045            // Whichever limit is tighter, because two limits that disagree mean the one that
1046            // stops first, and a bisection that started with the global one has to stay inside
1047            // it while the per pass one is halved.
1048            (Some(count), Some(left)) => Fuel::of(count.min(left)),
1049            (Some(count), None) => Fuel::of(count),
1050            (None, Some(left)) => Fuel::of(left),
1051            (None, None) => Fuel::unlimited(),
1052        };
1053        // What the level and the `-f` flags decided, which is what a gate overrides for the
1054        // functions it names and leaves alone for the ones it does not.
1055        let default = chosen.contains(&name);
1056        // Whether a table may hold how far a name is from it, which wants a four byte relocation
1057        // measured from where it is written. See `crate::switch_conv`.
1058        let measures = module.tuple.arch() == Arch::X86_64
1059            && module.tuple.object_format() == ObjectFormat::Elf;
1060        for id in module.funcs() {
1061            if module[id].is_declaration() {
1062                continue;
1063            }
1064            if !opts.gates.allows(name, default, id.raw(), names.resolve(module[id].name)) {
1065                // No remark either. A pass that did not run on a function has nothing to say
1066                // about it, and a record saying it found nothing would read as a pass that
1067                // looked.
1068                continue;
1069            }
1070            let an = cached.entry(id).or_insert_with(|| {
1071                Analyses::new(machine)
1072                    .reading(Arc::clone(&images))
1073                    .about(Arc::clone(&outside))
1074                    .calling(Arc::clone(&purity))
1075                    .touching(Arc::clone(&modref))
1076            });
1077            let pointer_bits = module.datalayout.pointer_bits;
1078            let mut data =
1079                readonly::ReadOnly::new(names, &taken, pointer_bits, tables).measuring(measures);
1080            let stats = pass.run_emitting(&mut module[id], an, &mut fuel, &mut data);
1081            tables = data.next();
1082            for table in data.into_tables() {
1083                add_table(module, table);
1084            }
1085            // A pass that changed nothing preserved everything, whatever it says about itself,
1086            // so the cheap case does not need every pass to have a second opinion about it.
1087            // A pass that did change something is taken at its word, and in a checked build the
1088            // word is checked.
1089            let keeps = if stats.changed() { pass.preserves() } else { Preserved::ALL };
1090            for broken in an.settle(&module[id], keeps, opts.verify) {
1091                let func = names.resolve(module[id].name);
1092                report.broke.push(format!(
1093                    "the {name} pass said it preserved {} of {func} and did not",
1094                    broken.name()
1095                ));
1096            }
1097            // Here rather than after the pass, and this function rather than the module. A pass
1098            // is a function pass, so the only thing it can have broken is the function it was
1099            // given, and walking the other ones again after every one of them is the quadratic
1100            // walk `rucc_ir::verify_func` exists to avoid. Doing it here is also what lets the
1101            // message name the function, which the module walk could not, and it puts the
1102            // failure next to the pass that caused it rather than at the end of the module.
1103            if stats.changed() && opts.verify {
1104                if let Err(errors) = rucc_ir::verify_func(module, &module[id], names) {
1105                    let func = names.resolve(module[id].name);
1106                    for error in errors {
1107                        report
1108                            .broke
1109                            .push(format!("the {name} pass left invalid IR in {func}, {error}"));
1110                    }
1111                }
1112            }
1113            // The record is the only place the manager learns that anything happened, which is
1114            // why the pass cannot leave recording until later. See `crate::stats`.
1115            report.remarks.push(Remark { pass: name, func: module[id].name, stats });
1116        }
1117        // Added to rather than pushed, so a pass the list names twice is one line here with what
1118        // both of its runs spent. That is the number a bisection halves, and two lines under one
1119        // name would be two numbers where the flag takes one.
1120        match report.spent.iter_mut().find(|(it, _)| *it == name) {
1121            Some((_, total)) => *total += fuel.spent(),
1122            None => report.spent.push((name, fuel.spent())),
1123        }
1124        if let Some(left) = &mut budget {
1125            // Never below zero, because the allowance the pass was given was at most this.
1126            *left -= fuel.spent();
1127        }
1128        if let Some(left) = allowance.get_mut(name) {
1129            // Same, and for the same reason.
1130            *left -= fuel.spent();
1131        }
1132        if opts.dumps.wants_after(name) {
1133            report.dumps.push(dump(index, "after", name, module, names));
1134        }
1135    }
1136    // Whatever a list without `constant-p` in it, or a gate that kept the pass off a function,
1137    // left standing. Nothing below the optimizer lowers the instruction, so the answer is written
1138    // here, and it is the one the pass would have given.
1139    constant_p::answer(module, true);
1140    report
1141}
1142
1143/// Adds a table a pass asked for to the module, as the read only array its load expects.
1144///
1145/// Internal, so that it is in no other object's way, and constant, which is what puts it in
1146/// `.rodata`. Aligned to its cell, which is all a load of one cell asks for.
1147fn add_table(module: &mut Module, table: readonly::Table) {
1148    let bytes = table.ty.bits() / 8;
1149    let cells: Vec<Datum> = table
1150        .cells
1151        .iter()
1152        .enumerate()
1153        .map(|(at, &cell)| match table.to.get(at).copied().flatten() {
1154            // Measured from the cell, which is `at` cells into the table, so that much is added
1155            // back to make it the distance from the table.
1156            Some(symbol) => {
1157                let addend = cell as i64 + at as i64 * i64::from(bytes);
1158                Datum::Away(module.add_reloc(Reloc { symbol, addend, size: bytes }))
1159            }
1160            None => Datum::Scalar { ty: table.ty, value: module.add_imm(Imm::int(cell, table.ty)) },
1161        })
1162        .collect();
1163    let init = module.push_data(&cells);
1164    let mut global = Global::new(table.name, u64::from(bytes) * cells.len() as u64, bytes);
1165    global.linkage = Linkage::Internal;
1166    global.constant = true;
1167    global.init = Some(init);
1168    module.add_global(global);
1169}
1170
1171/// The module written out, under a name that sorts in the order the passes ran.
1172fn dump(index: usize, side: &str, name: &str, module: &Module, names: &Interner) -> Dump {
1173    Dump { name: format!("{index:02}-{side}-{name}"), text: rucc_ir::print(module, names) }
1174}
1175
1176/// Renders what `--print-pipeline` prints.
1177///
1178/// One line per pass, numbered from one, with what the pass does after it. A level that runs
1179/// nothing says so rather than printing an empty list, because an empty answer and a broken
1180/// command look the same.
1181#[must_use]
1182pub fn print(opts: &Options) -> String {
1183    let mut out = String::new();
1184    let _ = writeln!(out, "level: {}", opts.level);
1185    // Only when it was asked for, so the listing of a compilation nobody is bisecting is the
1186    // same listing it has always been. A run under a budget is a run whose output is not the
1187    // one the level asked for, and the listing is where that has to be visible.
1188    if let Some(count) = opts.global_fuel {
1189        let _ = writeln!(out, "global fuel: {count}");
1190    }
1191    let passes = opts.passes();
1192    if passes.is_empty() {
1193        let _ = writeln!(out, "no passes");
1194        return out;
1195    }
1196    for (index, pass) in passes.iter().enumerate() {
1197        let _ = write!(out, "{}: {}, {}", index + 1, pass.name(), pass.describe());
1198        // Only when a gate mentions the pass, so the listing of a compilation nobody is
1199        // debugging is the same listing it has always been.
1200        if let Some(note) = opts.gates.note(pass.name()) {
1201            let _ = write!(out, " [{note}]");
1202        }
1203        out.push('\n');
1204    }
1205    out
1206}
1207
1208#[cfg(test)]
1209mod tests {
1210    use rucc_base::Interner;
1211    use rucc_ir::{
1212        Builder, Extra, Flags, Func, IntPred, MemInfo, MemOrder, Module, Opcode, Restrict,
1213        Signature, Type,
1214    };
1215    use rucc_session::OptLevel;
1216    use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
1217
1218    use super::{Dumps, Options, for_level};
1219    use crate::stats::Kind;
1220    use crate::{Pass, ipasra, ipcp, libcall, pass};
1221
1222    /// A module with one function whose body has something to fold in it.
1223    fn module() -> (Interner, Module) {
1224        let mut names = Interner::new();
1225        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
1226        let mut module = Module::new(names.intern("test.c"), &target);
1227        let func = foldable(&mut names, "f");
1228        module.add_func(func);
1229        (names, module)
1230    }
1231
1232    /// A module with two of them, called `f` and `g`, in that order, so `f` is function 0.
1233    fn two_functions() -> (Interner, Module) {
1234        let mut names = Interner::new();
1235        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
1236        let mut module = Module::new(names.intern("test.c"), &target);
1237        for name in ["f", "g"] {
1238            let func = foldable(&mut names, name);
1239            module.add_func(func);
1240        }
1241        (names, module)
1242    }
1243
1244    /// A module with one function holding two identities the peephole takes, on a value that
1245    /// arrives as a parameter so that folding cannot get to them first.
1246    fn identities() -> (Interner, Module) {
1247        let mut names = Interner::new();
1248        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
1249        let mut module = Module::new(names.intern("test.c"), &target);
1250        let i32_ = Type::int(32);
1251        let mut func = Func::new(
1252            names.intern("h"),
1253            Signature::new().with_params(&[i32_]).with_returns(&[i32_]),
1254        );
1255        let entry = func.create_block();
1256        let x = func.append_param(entry, i32_);
1257        let mut build = Builder::new(&mut func, entry);
1258        let zero = build.iconst(i32_, 0);
1259        let one = build.iconst(i32_, 1);
1260        let sum = build.binary(Opcode::Add, x, zero, Flags::NONE);
1261        let product = build.binary(Opcode::Mul, sum, one, Flags::NONE);
1262        build.ret(&[product]);
1263        module.add_func(func);
1264        (names, module)
1265    }
1266
1267    /// A function that returns a sign extension of a constant, which folding rewrites.
1268    fn foldable(names: &mut Interner, name: &str) -> Func {
1269        let mut func =
1270            Func::new(names.intern(name), Signature::new().with_returns(&[Type::int(64)]));
1271        let block = func.create_block();
1272        let mut build = Builder::new(&mut func, block);
1273        let narrow = build.iconst(Type::int(32), 7);
1274        let wide = build.unary(Opcode::SExt, narrow, Type::int(64));
1275        build.ret(&[wide]);
1276        func
1277    }
1278
1279    /// Whether the pass said anything about the function, which it only does when it ran on it.
1280    fn spoke_about(report: &super::Report, pass: &str, func: &str, names: &Interner) -> bool {
1281        report.remarks.iter().any(|it| it.pass == pass && names.resolve(it.func) == func)
1282    }
1283
1284    /// A module with a loop short enough for the unroller to flatten, over an array a parameter
1285    /// points at.
1286    ///
1287    /// Four iterations, which is a trip count the unroller takes whole. The copy that runs first
1288    /// subscripts the array at zero, so what works its offset out is a multiply by zero, and
1289    /// folding that is what leaves the addition this is here to look for.
1290    fn a_short_loop() -> (Interner, Module) {
1291        let mut names = Interner::new();
1292        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
1293        let mut module = Module::new(names.intern("test.c"), &target);
1294        let (i32_, i64_) = (Type::int(32), Type::int(64));
1295        let signature = Signature::new().with_params(&[Type::PTR]).with_returns(&[i32_]);
1296        let mut func = Func::new(names.intern("sum"), signature);
1297        let entry = func.create_block();
1298        let head = func.create_block();
1299        let body = func.create_block();
1300        let exit = func.create_block();
1301        let p = func.append_param(entry, Type::PTR);
1302        let i = func.append_param(head, i32_);
1303        let acc = func.append_param(head, i32_);
1304
1305        let mut build = Builder::new(&mut func, entry);
1306        let zero = build.iconst(i32_, 0);
1307        build.jump(head, &[zero, zero]);
1308
1309        let mut build = Builder::new(&mut func, head);
1310        let four = build.iconst(i32_, 4);
1311        let more = build.icmp(IntPred::Slt, i, four);
1312        build.br_if(more, body, &[], exit, &[]);
1313
1314        let mut build = Builder::new(&mut func, body);
1315        let wide = build.unary(Opcode::SExt, i, i64_);
1316        let scale = build.iconst(i64_, 4);
1317        let offset = build.binary(Opcode::Mul, wide, scale, Flags::NSW);
1318        let at = build.binary(Opcode::PtrAdd, p, offset, Flags::NONE);
1319        let read = build.load(i32_, at, plain(), Flags::NONE);
1320        let total = build.binary(Opcode::Add, acc, read, Flags::NONE);
1321        let one = build.iconst(i32_, 1);
1322        let next = build.binary(Opcode::Add, i, one, Flags::NSW);
1323        build.jump(head, &[next, total]);
1324
1325        let mut build = Builder::new(&mut func, exit);
1326        build.ret(&[acc]);
1327        module.add_func(func);
1328        (names, module)
1329    }
1330
1331    /// Memory with nothing said about it, which is what a plain subscript reads through.
1332    fn plain() -> MemInfo {
1333        MemInfo {
1334            size: 4,
1335            align: 4,
1336            order: MemOrder::NotAtomic,
1337            tbaa: None,
1338            owns: 0,
1339            restrict: Restrict::NONE,
1340        }
1341    }
1342
1343    /// Every addition in the module whose right operand is the constant zero.
1344    fn adds_of_zero(module: &Module) -> usize {
1345        let mut found = 0;
1346        for id in module.funcs() {
1347            let func = &module[id];
1348            for block in func.blocks() {
1349                for inst in func.insts(block) {
1350                    if !matches!(func[inst].opcode, Opcode::Add | Opcode::PtrAdd) {
1351                        continue;
1352                    }
1353                    let args = &func[func[inst].args];
1354                    let Some(&rhs) = args.get(1) else { continue };
1355                    let rucc_ir::Def::Result { inst: from, .. } = func[rhs].def else { continue };
1356                    if func[from].opcode != Opcode::IConst {
1357                        continue;
1358                    }
1359                    let Extra::Imm(at) = func[from].extra else { continue };
1360                    found += usize::from(func[at].signed(func[rhs].ty) == 0);
1361                }
1362            }
1363        }
1364        found
1365    }
1366
1367    /// An index the unroller worked out to zero does not leave the addition behind.
1368    ///
1369    /// The peephole is what removes it and the peephole used to run only near the top of the
1370    /// list, before the unroller had made any of these. Folding writes the constant down and
1371    /// leaves the addition, so an `add x, 0` reached the selector and was written out as an
1372    /// `addq $0` the machine runs for nothing. tamnd/rucc#875.
1373    #[test]
1374    fn an_index_folded_to_zero_is_not_added_to_anything() {
1375        let (mut names, mut module) = a_short_loop();
1376        assert_eq!(adds_of_zero(&module), 0, "the fixture already has one before anything runs");
1377        let report = super::run(&mut module, &mut names, &Options::for_level(OptLevel::O2));
1378        assert!(report.broke.is_empty(), "{:?}", report.broke);
1379        assert!(spent(&report, "unroll").is_some_and(|it| it > 0), "the loop was not unrolled");
1380        assert_eq!(adds_of_zero(&module), 0, "{}", rucc_ir::print(&module, &names));
1381    }
1382
1383    #[test]
1384    fn every_pass_a_pipeline_names_is_a_pass_that_exists() {
1385        for level in
1386            [OptLevel::O0, OptLevel::O1, OptLevel::O2, OptLevel::O3, OptLevel::Os, OptLevel::Oz]
1387        {
1388            for name in for_level(level) {
1389                assert!(
1390                    pass::find(name).is_some(),
1391                    "{level} names `{name}` and no pass answers to it"
1392                );
1393            }
1394        }
1395    }
1396
1397    #[test]
1398    fn a_pass_a_pipeline_names_twice_is_never_named_twice_in_a_row() {
1399        // Running a pass again after another pass has been through is the point of naming it
1400        // twice, and `simplify` around `narrow` is why the rule that used to be here, which was
1401        // that no level names a pass twice at all, is not the rule any more. Two runs with
1402        // nothing between them is still a mistake: the second one sees exactly what the first
1403        // one finished with, so it can only report that it found nothing.
1404        for level in
1405            [OptLevel::O0, OptLevel::O1, OptLevel::O2, OptLevel::O3, OptLevel::Os, OptLevel::Oz]
1406        {
1407            for pair in for_level(level).windows(2) {
1408                assert_ne!(pair[0], pair[1], "{level} runs `{}` twice in a row", pair[0]);
1409            }
1410        }
1411    }
1412
1413    #[test]
1414    fn a_pass_the_pipeline_runs_twice_gets_one_allowance_and_reports_one_number() {
1415        // `-fpass-fuel=<pass>=<n>` is halved to find one rewrite, so the number in the flag has
1416        // to be the number of rewrites that happened however many times the list names the pass.
1417        // The peephole is named more than once from `-O1` up and the function below holds two
1418        // identities it takes, so a cap of one has to stop after one rather than after one per
1419        // occurrence.
1420        assert!(for_level(OptLevel::O2).iter().filter(|it| **it == "simplify").count() > 1);
1421
1422        let (mut names, mut module) = identities();
1423        let free = super::run(&mut module, &mut names, &Options::for_level(OptLevel::O2));
1424        assert_eq!(spent(&free, "simplify"), Some(2), "{:?}", free.spent);
1425
1426        let (mut names, mut module) = identities();
1427        let mut opts = Options::for_level(OptLevel::O2);
1428        opts.fuel.insert("simplify".to_owned(), 1);
1429        let capped = super::run(&mut module, &mut names, &opts);
1430        assert_eq!(capped.spent.iter().filter(|(name, _)| *name == "simplify").count(), 1);
1431        assert_eq!(spent(&capped, "simplify"), Some(1), "{:?}", capped.spent);
1432    }
1433
1434    #[test]
1435    fn an_identity_only_the_narrow_pass_can_produce_is_still_taken() {
1436        // Issue 505, and the reason the peephole is named on both sides of `narrow`. C promotes
1437        // before it operates, so `unsigned char x; (unsigned char)(x & 255)` arrives here as a
1438        // thirty two bit `and` of a zero extension, and the rule that says `and` with every bit
1439        // set is the value has nothing at eight bits to match. `narrow` is the only producer that
1440        // width has. Before this ran twice the `and.i8` below reached the back end untouched.
1441        let mut names = Interner::new();
1442        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
1443        let mut module = Module::new(names.intern("test.c"), &target);
1444        let (i8_, i32_) = (Type::int(8), Type::int(32));
1445        let mut func =
1446            Func::new(names.intern("f"), Signature::new().with_params(&[i8_]).with_returns(&[i8_]));
1447        let entry = func.create_block();
1448        let x = func.append_param(entry, i8_);
1449        let mut build = Builder::new(&mut func, entry);
1450        let wide = build.unary(Opcode::ZExt, x, i32_);
1451        let mask = build.iconst(i32_, 255);
1452        let kept = build.binary(Opcode::And, wide, mask, Flags::NONE);
1453        let back = build.unary(Opcode::Trunc, kept, i8_);
1454        build.ret(&[back]);
1455        module.add_func(func);
1456
1457        let report = super::run(&mut module, &mut names, &Options::for_level(OptLevel::O2));
1458        assert!(report.broke.is_empty(), "{:?}", report.broke);
1459        let text = rucc_ir::print(&module, &names);
1460        assert!(!text.contains("and."), "the masking survived the pipeline\n{text}");
1461    }
1462
1463    /// What a pass spent, or `None` if it did not run.
1464    fn spent(report: &super::Report, pass: &str) -> Option<u32> {
1465        report.spent.iter().find(|(name, _)| *name == pass).map(|&(_, count)| count)
1466    }
1467
1468    /// The names of the passes a set of options would run, in order.
1469    fn names(opts: &Options) -> Vec<&'static str> {
1470        opts.passes().into_iter().map(Pass::name).collect()
1471    }
1472
1473    #[test]
1474    fn every_level_that_splits_a_loop_looks_at_what_the_split_wrote() {
1475        // A guard goes in the preheader of the loop being split, which for an inner loop is inside
1476        // the loops around it, and it asks the runtime how big an object is. Nothing after `split`
1477        // moves anything, so a level that splits and then stops leaves those queries where they
1478        // cost the most.
1479        for level in [super::O1, super::O2, super::O3, super::OS, super::OZ] {
1480            let Some(at) = level.iter().position(|pass| *pass == "split") else {
1481                continue;
1482            };
1483            assert!(
1484                level[at..].contains(&"licm"),
1485                "a level splits a loop and never looks at the guard again"
1486            );
1487        }
1488    }
1489
1490    #[test]
1491    fn every_level_that_chooses_induction_variables_takes_the_old_one_away_afterwards() {
1492        // The counter a loop stops asking anything is still incremented round it, and what removes
1493        // the parameter carrying it is `simplify-cfg` rather than `dce`. See the comment on `O2`.
1494        // A level that chooses and then stops keeps both variables and is worse off than if it had
1495        // never chosen at all.
1496        for level in [super::O1, super::O2, super::O3, super::OS, super::OZ] {
1497            let Some(at) = level.iter().position(|pass| *pass == "ivopts") else {
1498                continue;
1499            };
1500            assert!(
1501                level[at + 1..].contains(&"simplify-cfg"),
1502                "a level chooses induction variables and leaves the one it stopped using behind"
1503            );
1504        }
1505    }
1506
1507    #[test]
1508    fn every_run_of_the_pass_that_reads_a_summary_is_named_as_one_that_does() {
1509        // A run left off the list gets no table of globals and no caller guarantees, and answers
1510        // that there were none rather than that nobody built them.
1511        for pass in pass::PASSES {
1512            let name = pass.name();
1513            assert_eq!(
1514                name.starts_with("discharge"),
1515                super::READS_SUMMARIES.contains(&name),
1516                "`{name}` and READS_SUMMARIES disagree about whether it reads a summary"
1517            );
1518        }
1519    }
1520
1521    #[test]
1522    fn the_level_that_optimizes_nothing_still_removes_what_nothing_reaches() {
1523        // Two passes at `-O0`, and neither of them is an optimization. See the comment on the
1524        // level itself, and issue 359.
1525        assert_eq!(names(&Options::for_level(OptLevel::O0)), ["expect", "simplify-cfg"]);
1526        assert!(names(&Options::for_level(OptLevel::O2)).len() > 1);
1527    }
1528
1529    #[test]
1530    fn a_pass_is_removed_by_no_and_added_by_the_bare_name_and_the_last_word_wins() {
1531        let mut opts = Options::for_level(OptLevel::O2);
1532        opts.toggles.push(("fold".to_owned(), false));
1533        assert!(!names(&opts).contains(&"fold"), "{:?}", names(&opts));
1534        opts.toggles.push(("fold".to_owned(), true));
1535        assert!(names(&opts).contains(&"fold"), "{:?}", names(&opts));
1536
1537        let mut off = Options::for_level(OptLevel::O0);
1538        off.toggles.push(("fold".to_owned(), true));
1539        assert_eq!(
1540            names(&off),
1541            ["expect", "simplify-cfg", "fold"],
1542            "a pass the level did not choose is still reachable"
1543        );
1544    }
1545
1546    #[test]
1547    fn asking_for_a_pass_twice_does_not_run_it_twice() {
1548        let mut opts = Options::for_level(OptLevel::O2);
1549        let before = names(&opts);
1550        opts.toggles.push(("fold".to_owned(), true));
1551        assert_eq!(names(&opts), before);
1552    }
1553
1554    #[test]
1555    fn the_pipeline_listing_names_the_level_and_every_pass_in_order() {
1556        let text = super::print(&Options::for_level(OptLevel::O2));
1557        assert!(text.starts_with("level: -O2\n"), "{text}");
1558        assert!(text.contains("1: expect, "), "{text}");
1559        assert!(text.contains("2: fold, "), "{text}");
1560        // Turning off everything the level asked for leaves the one pass that cannot be turned
1561        // off, since the back end has no rule for what it removes. See `Pass::required`.
1562        let mut none = Options::for_level(OptLevel::O0);
1563        none.toggles.push(("expect".to_owned(), false));
1564        none.toggles.push(("simplify-cfg".to_owned(), false));
1565        let none = super::print(&none);
1566        assert!(none.contains("1: expect, "), "{none}");
1567        assert!(!none.contains("simplify-cfg"), "{none}");
1568    }
1569
1570    #[test]
1571    fn running_the_pipeline_changes_the_module_and_reports_what_it_spent() {
1572        let (mut names, mut module) = module();
1573        let report = super::run(&mut module, &mut names, &Options::for_level(OptLevel::O2));
1574        // Folding rewrites the sign extension into a constant, and then the constant it was
1575        // extending is read by nothing and dead code elimination takes it out. One
1576        // transformation each, which is what the two of them together are for. Asserted by
1577        // name rather than as the whole vector, so a pass added later does not fail this.
1578        assert_eq!(spent(&report, "fold"), Some(1));
1579        assert_eq!(spent(&report, "dce"), Some(1));
1580        assert!(report.broke.is_empty(), "{:?}", report.broke);
1581        assert!(report.dumps.is_empty(), "nothing asked for a dump");
1582        assert!(rucc_ir::print(&module, &names).contains("iconst.i64 7"));
1583    }
1584
1585    #[test]
1586    fn the_analyses_survive_a_pass_that_keeps_them_and_not_one_that_does_not() {
1587        // The pipeline half of the analysis manager. A branch on a constant, so `simplify-cfg`
1588        // has something to do and says it preserved nothing, and the whole run comes out with
1589        // the verifier and the manager both satisfied. What a pass that lied would produce is in
1590        // `crate::analysis`, where a lie can be told on purpose.
1591        let mut names = Interner::new();
1592        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
1593        let mut module = Module::new(names.intern("test.c"), &target);
1594        let mut func = Func::new(names.intern("f"), Signature::new());
1595        let entry = func.create_block();
1596        let dead = func.create_block();
1597        let exit = func.create_block();
1598        let mut build = Builder::new(&mut func, entry);
1599        let never = build.iconst(Type::int(1), 0);
1600        build.br_if(never, dead, &[], exit, &[]);
1601        for block in [dead, exit] {
1602            let mut build = Builder::new(&mut func, block);
1603            build.ret(&[]);
1604        }
1605        module.add_func(func);
1606        let report = super::run(&mut module, &mut names, &Options::for_level(OptLevel::O2));
1607        // The fold, and then the merge of the arm it left with one way into it.
1608        assert_eq!(spent(&report, "simplify-cfg"), Some(2));
1609        assert!(report.broke.is_empty(), "{:?}", report.broke);
1610        let text = rucc_ir::print(&module, &names);
1611        // The labels, which start a line, and not the mentions of one, which are indented. One
1612        // left: the arm nothing reaches went, and the arm that is always taken came up into the
1613        // entry, which is what is left of the branch.
1614        assert_eq!(text.matches("\nblock").count(), 1, "there is more than one block:\n{text}");
1615    }
1616
1617    #[test]
1618    fn no_pass_that_optimizes_runs_at_no_optimization_however_much_there_is_to_do() {
1619        let (mut names, mut module) = module();
1620        let before = rucc_ir::print(&module, &names);
1621        let report = super::run(&mut module, &mut names, &Options::for_level(OptLevel::O0));
1622        // The two passes the level runs looked, found no `__builtin_expect`, no branch they could
1623        // read and no block nothing reaches, and spent nothing. The constant arithmetic the fixture
1624        // is full of is still there, which is the part of `-O0` that has not changed.
1625        assert_eq!(report.spent, vec![("expect", 0), ("simplify-cfg", 0)]);
1626        assert_eq!(rucc_ir::print(&module, &names), before);
1627    }
1628
1629    #[test]
1630    fn a_gate_takes_a_pass_away_from_one_function_and_leaves_the_other_alone() {
1631        let (mut names, mut module) = two_functions();
1632        let mut opts = Options::for_level(OptLevel::O2);
1633        opts.gates.add(false, "fold=g").expect("g is a function and fold is a pass");
1634        let report = super::run(&mut module, &mut names, &opts);
1635        assert!(spoke_about(&report, "fold", "f", &names));
1636        assert!(!spoke_about(&report, "fold", "g", &names), "fold ran where it was gated off");
1637        assert!(spoke_about(&report, "dce", "g", &names), "one pass gated off is not all of them");
1638        // What the gate is for: the two functions came out different, and the difference is one
1639        // pass on one function rather than a level on a file.
1640        let text = rucc_ir::print(&module, &names);
1641        assert_eq!(text.matches("sext.i64").count(), 1, "{text}");
1642    }
1643
1644    #[test]
1645    fn a_function_can_be_gated_by_the_number_it_has_in_the_module() {
1646        let (mut names, mut module) = two_functions();
1647        let mut opts = Options::for_level(OptLevel::O2);
1648        opts.gates.add(false, "fold=0").expect("0 is a function and fold is a pass");
1649        let report = super::run(&mut module, &mut names, &opts);
1650        assert!(!spoke_about(&report, "fold", "f", &names), "function 0 is the first one");
1651        assert!(spoke_about(&report, "fold", "g", &names));
1652    }
1653
1654    #[test]
1655    fn enabling_a_pass_reaches_one_function_at_a_level_that_did_not_ask_for_it() {
1656        let (mut names, mut module) = two_functions();
1657        let mut opts = Options::for_level(OptLevel::O0);
1658        opts.gates.add(true, "fold=1").expect("1 is a function and fold is a pass");
1659        let running: Vec<&str> = opts.passes().into_iter().map(Pass::name).collect();
1660        assert_eq!(
1661            running,
1662            ["expect", "simplify-cfg", "fold"],
1663            "the flag has to put the pass in the pipeline"
1664        );
1665        let report = super::run(&mut module, &mut names, &opts);
1666        assert!(!spoke_about(&report, "fold", "f", &names), "nothing asked for f");
1667        assert!(spoke_about(&report, "fold", "g", &names));
1668        let text = rucc_ir::print(&module, &names);
1669        assert_eq!(text.matches("sext.i64").count(), 1, "{text}");
1670    }
1671
1672    #[test]
1673    fn a_pass_gated_off_everywhere_runs_on_nothing_and_still_says_so() {
1674        let (mut names, mut module) = two_functions();
1675        let before = rucc_ir::print(&module, &names);
1676        let mut opts = Options::for_level(OptLevel::O2);
1677        for pass in pass::PASSES {
1678            opts.gates.add(false, pass.name()).expect("a pass in the list is a pass that exists");
1679        }
1680        let report = super::run(&mut module, &mut names, &opts);
1681        assert!(report.remarks.is_empty(), "a pass that did not run has nothing to report");
1682        assert_eq!(spent(&report, "fold"), Some(0), "the pass is still in the pipeline");
1683        assert_eq!(rucc_ir::print(&module, &names), before);
1684    }
1685
1686    #[test]
1687    fn the_pipeline_listing_says_which_passes_a_gate_touched() {
1688        let mut opts = Options::for_level(OptLevel::O2);
1689        // `narrow` rather than `fold`, because a gate names a pass and the level runs some of its
1690        // passes more than once. A note on one of those is printed against every run of it, and
1691        // the count at the bottom would then be counting repeats rather than what it is asking.
1692        opts.gates.add(false, "narrow=2-4").expect("narrow is a pass");
1693        let text = super::print(&opts);
1694        assert!(text.contains("6: narrow, "), "{text}");
1695        assert!(text.contains("[off for 2-4]"), "{text}");
1696        assert_eq!(text.matches('[').count(), 1, "a pass no gate mentions says nothing extra");
1697    }
1698
1699    #[test]
1700    fn every_pass_at_no_fuel_leaves_the_module_exactly_as_it_found_it() {
1701        // The check section 9.10 asks for by name, and the reason it is here rather than in each
1702        // pass is that it has to hold for every pass that is ever added.
1703        for pass in pass::PASSES {
1704            let (mut names, mut module) = module();
1705            let before = rucc_ir::print(&module, &names);
1706            let mut opts = Options::for_level(OptLevel::O0);
1707            // The level's own passes out of the way first, so that what this measures is the one
1708            // pass under test. A pass turned off and then on again is on, so this is right for
1709            // those passes as well as for the others. `expect` cannot be turned off, so it is
1710            // starved of fuel instead and is expected in the report ahead of the pass under test.
1711            opts.toggles.push(("simplify-cfg".to_owned(), false));
1712            opts.toggles.push((pass.name().to_owned(), true));
1713            opts.fuel.insert("expect".to_owned(), 0);
1714            opts.fuel.insert(pass.name().to_owned(), 0);
1715            let report = super::run(&mut module, &mut names, &opts);
1716            let mut want = vec![("expect", 0)];
1717            if pass.name() != "expect" {
1718                want.push((pass.name(), 0));
1719            }
1720            assert_eq!(report.spent, want, "{} spent fuel it had none of", pass.name());
1721            assert_eq!(
1722                rucc_ir::print(&module, &names),
1723                before,
1724                "{} transformed the module at fuel zero",
1725                pass.name()
1726            );
1727        }
1728    }
1729
1730    #[test]
1731    fn fuel_is_shared_across_the_functions_of_a_module() {
1732        let mut names = Interner::new();
1733        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
1734        let mut module = Module::new(names.intern("test.c"), &target);
1735        for which in ["f", "g"] {
1736            let mut func =
1737                Func::new(names.intern(which), Signature::new().with_returns(&[Type::int(64)]));
1738            let block = func.create_block();
1739            let mut build = Builder::new(&mut func, block);
1740            let narrow = build.iconst(Type::int(32), 7);
1741            let wide = build.unary(Opcode::SExt, narrow, Type::int(64));
1742            build.ret(&[wide]);
1743            module.add_func(func);
1744        }
1745        let mut opts = Options::for_level(OptLevel::O2);
1746        opts.fuel.insert("fold".to_owned(), 1);
1747        let report = super::run(&mut module, &mut names, &opts);
1748        // One fold across both functions, because fuel is per pass and per compilation. Dead
1749        // code elimination has its own and spends it on the constant the one fold orphaned.
1750        assert_eq!(spent(&report, "fold"), Some(1));
1751        assert_eq!(spent(&report, "dce"), Some(1));
1752        let text = rucc_ir::print(&module, &names);
1753        assert_eq!(text.matches("sext.i64").count(), 1, "{text}");
1754    }
1755
1756    #[test]
1757    fn global_fuel_is_spent_by_the_passes_in_order_and_the_rest_get_none() {
1758        let (mut names, mut module) = module();
1759        let mut opts = Options::for_level(OptLevel::O2);
1760        opts.global_fuel = Some(1);
1761        let report = super::run(&mut module, &mut names, &opts);
1762        // Folding is first and there is one thing to fold, so it takes the one unit and dead
1763        // code elimination gets nothing. Without the budget it would have taken the constant
1764        // that fold orphaned, which is what the other test measures.
1765        assert_eq!(spent(&report, "fold"), Some(1));
1766        assert_eq!(spent(&report, "dce"), Some(0));
1767        let text = rucc_ir::print(&module, &names);
1768        assert!(text.contains("iconst.i64 7"), "{text}");
1769        assert!(text.contains("iconst.i32 7"), "the orphaned constant is still there, {text}");
1770    }
1771
1772    #[test]
1773    fn a_budget_of_nothing_leaves_the_module_alone_and_still_runs_every_pass() {
1774        let (mut names, mut module) = module();
1775        let before = rucc_ir::print(&module, &names);
1776        let mut opts = Options::for_level(OptLevel::O2);
1777        opts.global_fuel = Some(0);
1778        let report = super::run(&mut module, &mut names, &opts);
1779        assert_eq!(rucc_ir::print(&module, &names), before);
1780        assert!(report.spent.iter().all(|(_, spent)| *spent == 0), "{:?}", report.spent);
1781        // Every pass, because a pass out of fuel is a pass that ran and did nothing rather than
1782        // a pass that was skipped, and a bisection that skipped passes would be searching a
1783        // different pipeline at every step. One line per name rather than one per place the list
1784        // names it, because what a name was given is one allowance across all of them.
1785        let mut want: Vec<&str> = opts.passes().into_iter().map(Pass::name).collect();
1786        // And the three transformations that are not in that list, because they are a module at a
1787        // time rather than one function at a time. They spend out of the same budget and are
1788        // bisected the same way, so they belong in the same accounting.
1789        want.push(ipcp::NAME);
1790        want.push(ipasra::NAME);
1791        want.push(libcall::NAME);
1792        want.sort_unstable();
1793        want.dedup();
1794        let mut got: Vec<&str> = report.spent.iter().map(|&(name, _)| name).collect();
1795        got.sort_unstable();
1796        assert_eq!(got, want);
1797    }
1798
1799    #[test]
1800    fn the_module_at_a_time_removal_is_on_at_the_level_and_off_when_the_flag_says_so() {
1801        let mut opts = Options::for_level(OptLevel::O2);
1802        assert!(opts.wants(ipasra::NAME));
1803        opts.toggles.push((ipasra::NAME.to_owned(), false));
1804        assert!(!opts.wants(ipasra::NAME));
1805    }
1806
1807    #[test]
1808    fn the_module_at_a_time_propagation_is_on_at_the_level_and_off_when_the_flag_says_so() {
1809        // The same reading of a toggle that [`Options::chosen`] gives the pass list, done by hand
1810        // because what this names is not a pass.
1811        let mut opts = Options::for_level(OptLevel::O2);
1812        assert!(opts.wants(ipcp::NAME));
1813        opts.toggles.push((ipcp::NAME.to_owned(), false));
1814        assert!(!opts.wants(ipcp::NAME));
1815        opts.toggles.push((ipcp::NAME.to_owned(), true));
1816        assert!(opts.wants(ipcp::NAME), "the last word on the command line is the one that wins");
1817    }
1818
1819    #[test]
1820    fn the_tighter_of_the_two_limits_is_the_one_that_stops_the_pass() {
1821        // A pass allowed more than the budget gets the budget.
1822        let (mut names, mut under) = module();
1823        let mut opts = Options::for_level(OptLevel::O2);
1824        opts.global_fuel = Some(0);
1825        opts.fuel.insert("fold".to_owned(), 9);
1826        assert_eq!(spent(&super::run(&mut under, &mut names, &opts), "fold"), Some(0));
1827
1828        // And a pass allowed less than the budget keeps its own limit, with the budget left
1829        // over for whatever comes after it.
1830        let (mut names, mut over) = module();
1831        let mut opts = Options::for_level(OptLevel::O2);
1832        opts.global_fuel = Some(9);
1833        opts.fuel.insert("fold".to_owned(), 0);
1834        let report = super::run(&mut over, &mut names, &opts);
1835        assert_eq!(spent(&report, "fold"), Some(0));
1836        assert_eq!(spent(&report, "dce"), Some(0), "nothing was orphaned for it to remove");
1837    }
1838
1839    #[test]
1840    fn the_pipeline_listing_says_when_there_is_a_budget_and_says_nothing_when_there_is_not() {
1841        let opts = Options::for_level(OptLevel::O2);
1842        assert!(!super::print(&opts).contains("global fuel"));
1843        let with = Options { global_fuel: Some(12), ..Options::for_level(OptLevel::O2) };
1844        assert!(super::print(&with).contains("global fuel: 12"), "{}", super::print(&with));
1845    }
1846
1847    #[test]
1848    fn a_dump_is_taken_on_the_side_that_asked_for_it_and_not_the_other() {
1849        let (mut names, mut module) = module();
1850        let mut opts = Options::for_level(OptLevel::O2);
1851        opts.dumps.add("after-fold").expect("a pass that exists");
1852        let report = super::run(&mut module, &mut names, &opts);
1853        // The level folds three times, twice at the top on either side of `image` and once after
1854        // the loop pipeline, and what a dump request names is a pass rather than a position, so
1855        // every run is written out. The side is what this is about: not one of the three is a
1856        // `before`.
1857        assert_eq!(report.dumps.len(), 3, "every run of the pass, one dump each");
1858        assert!(
1859            report.dumps.iter().all(|dump| dump.name.ends_with("-after-fold")),
1860            "{:?}",
1861            report.dumps.iter().map(|dump| &dump.name).collect::<Vec<&String>>()
1862        );
1863        assert_eq!(report.dumps[0].name, "01-after-fold");
1864        assert!(report.dumps[0].text.contains("iconst.i64 7"));
1865    }
1866
1867    #[test]
1868    fn asking_for_all_dumps_gives_both_sides_of_every_pass() {
1869        let (mut interner, mut module) = module();
1870        let opts = {
1871            let mut opts = Options::for_level(OptLevel::O2);
1872            opts.dumps.add("all").expect("all is always a dump");
1873            opts
1874        };
1875        let report = super::run(&mut module, &mut interner, &opts);
1876        // Both sides of every pass in the level, numbered by position, whatever the level
1877        // holds. Written out of the pipeline rather than as a literal, because the point of
1878        // the test is the pairing and the numbering and not which passes exist this month.
1879        let taken: Vec<&str> = report.dumps.iter().map(|d| d.name.as_str()).collect();
1880        let expected: Vec<String> = names(&opts)
1881            .into_iter()
1882            .enumerate()
1883            .flat_map(|(at, name)| {
1884                [format!("{at:02}-before-{name}"), format!("{at:02}-after-{name}")]
1885            })
1886            .collect();
1887        assert_eq!(taken, expected);
1888        // Either side of the fold, which is the pass that has something to do to this fixture,
1889        // found by name rather than by position so that a pass in front of it does not move it.
1890        let side = |which: &str| {
1891            let tail = format!("-{which}-fold");
1892            let dump = report.dumps.iter().find(|dump| dump.name.ends_with(&tail));
1893            dump.expect("the level folds").text.clone()
1894        };
1895        assert!(side("before").contains("sext.i64"));
1896        assert!(!side("after").contains("sext.i64"));
1897    }
1898
1899    #[test]
1900    fn every_pass_leaves_a_record_for_every_function_whether_or_not_it_had_anything_to_say() {
1901        let (mut names, mut module) = module();
1902        let opts = Options::for_level(OptLevel::O2);
1903        let report = super::run(&mut module, &mut names, &opts);
1904        let ran: Vec<&'static str> = opts.passes().into_iter().map(Pass::name).collect();
1905        // One function in the fixture, so one record per pass, and the passes in the order they
1906        // ran. A pass that found nothing is in here with an empty record, which is the point:
1907        // a pass that fires on nothing is either dead code or a bug, and output that leaves it
1908        // out cannot say which.
1909        let seen: Vec<&'static str> = report.remarks.iter().map(|it| it.pass).collect();
1910        assert_eq!(seen, ran);
1911        assert!(report.remarks.iter().all(|it| names.resolve(it.func) == "f"));
1912        assert!(
1913            report.remarks.iter().any(|it| it.pass == "simplify" && it.stats.is_empty()),
1914            "there is nothing in the fixture for the peephole to do"
1915        );
1916    }
1917
1918    #[test]
1919    fn a_pass_spends_one_unit_of_fuel_for_each_rewrite_it_reports() {
1920        // The invariant that keeps the record honest, checked over every pass rather than
1921        // written into each one. Fuel is taken immediately before a transformation and a
1922        // rewrite is recorded immediately after it, so the two counts are the same number
1923        // arrived at from two directions. A pass where they disagree either transformed without
1924        // asking, which breaks bisection, or rewrote without recording, which means the manager
1925        // did not run the verifier over what it produced.
1926        let (mut names, mut module) = module();
1927        let report = super::run(&mut module, &mut names, &Options::for_level(OptLevel::O2));
1928        for (pass, spent) in &report.spent {
1929            assert_eq!(
1930                report.totals(pass).total(Kind::Optimized),
1931                *spent,
1932                "{pass} spent {spent} units of fuel and did not say on what"
1933            );
1934        }
1935        assert!(report.spent.iter().any(|(_, spent)| *spent > 0), "nothing happened at all");
1936    }
1937
1938    #[test]
1939    fn what_the_passes_said_is_what_opt_info_prints() {
1940        let (mut names, mut module) = module();
1941        let report = super::run(&mut module, &mut names, &Options::for_level(OptLevel::O2));
1942        let text = crate::optinfo::render("t.c", &report, &names, crate::Wants::all());
1943        assert!(
1944            text.contains(
1945                "t.c: f: optimized: instruction with constant operands folded to a constant (1) [fold]"
1946            ),
1947            "{text}"
1948        );
1949        assert!(
1950            text.contains(
1951                "t.c: f: optimized: instruction with no effects and no users removed (1) [dce]"
1952            ),
1953            "{text}"
1954        );
1955        // Nothing in the fixture is a miss, so asking only for the misses gets nothing back,
1956        // and that is different from the flag having been left off.
1957        let mut misses = crate::Wants::none();
1958        misses.add("missed").expect("that kind exists");
1959        assert_eq!(crate::optinfo::render("t.c", &report, &names, misses), "");
1960    }
1961
1962    #[test]
1963    fn the_verifier_says_which_function_it_refused_and_leaves_the_others_out_of_it() {
1964        // Two functions with the same foldable body, and a block in the second one that nothing
1965        // reaches, which the verifier refuses. The pass is not what put it there, and the
1966        // complaint says the pass anyway, because a pass that hands back a function the
1967        // verifier will not take is where the search has to start whoever wrote the block.
1968        let mut names = Interner::new();
1969        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
1970        let mut module = Module::new(names.intern("test.c"), &target);
1971        module.add_func(foldable(&mut names, "f"));
1972        let mut g = foldable(&mut names, "g");
1973        let stranded = g.create_block();
1974        let mut build = Builder::new(&mut g, stranded);
1975        let seven = build.iconst(Type::int(64), 7);
1976        build.ret(&[seven]);
1977        module.add_func(g);
1978
1979        // Folding on its own, because simplify-CFG would take the stranded block out and there
1980        // would be nothing left to complain about.
1981        let mut opts = Options::for_level(OptLevel::O0);
1982        opts.toggles.push(("simplify-cfg".to_owned(), false));
1983        opts.toggles.push(("fold".to_owned(), true));
1984        opts.verify = true;
1985        let report = super::run(&mut module, &mut names, &opts);
1986
1987        assert_eq!(report.broke.len(), 1, "{:?}", report.broke);
1988        let complaint = &report.broke[0];
1989        assert!(complaint.starts_with("the fold pass left invalid IR in g,"), "{complaint}");
1990        assert!(complaint.contains("this block is not reachable"), "{complaint}");
1991    }
1992
1993    #[test]
1994    fn a_function_a_pass_did_not_change_is_not_verified_after_it() {
1995        // The stranded block is in `f` this time and `f` has nothing to fold, so the pass runs
1996        // over an invalid function, changes nothing, and says nothing. That is the whole trade:
1997        // the verifier answers for the rewrite that just happened, and a function no rewrite
1998        // touched was already answered for when it was built.
1999        let mut names = Interner::new();
2000        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
2001        let mut module = Module::new(names.intern("test.c"), &target);
2002        let mut f = Func::new(names.intern("f"), Signature::new().with_returns(&[Type::int(64)]));
2003        for _ in 0..2 {
2004            let block = f.create_block();
2005            let mut build = Builder::new(&mut f, block);
2006            let seven = build.iconst(Type::int(64), 7);
2007            build.ret(&[seven]);
2008        }
2009        module.add_func(f);
2010        module.add_func(foldable(&mut names, "g"));
2011
2012        let mut opts = Options::for_level(OptLevel::O0);
2013        opts.toggles.push(("simplify-cfg".to_owned(), false));
2014        opts.toggles.push(("fold".to_owned(), true));
2015        opts.verify = true;
2016        let report = super::run(&mut module, &mut names, &opts);
2017
2018        assert!(report.broke.is_empty(), "{:?}", report.broke);
2019        // And it did run on it, so this is the verifier staying quiet rather than the pass
2020        // being skipped.
2021        assert!(spoke_about(&report, "fold", "f", &names));
2022    }
2023
2024    #[test]
2025    fn a_dump_of_a_pass_that_does_not_exist_is_refused_rather_than_ignored() {
2026        let mut dumps = Dumps::default();
2027        assert!(dumps.add("after-no-such-pass").is_err());
2028        assert!(dumps.add("sideways-fold").is_err());
2029        assert!(dumps.add("fold").is_err());
2030        assert!(dumps.is_empty());
2031    }
2032}