Skip to main content

rucc_opt/
simplify_cfg.rs

1//! Control flow simplification: unreachable blocks go, a branch that only ever goes one way
2//! becomes a jump, a block that does nothing but jump somewhere else stops being in the way, a
3//! block parameter that is the same value on every way in stops being a parameter, and a block
4//! with one way in is folded into the block above it.
5//!
6//! Design: `spec/optimizer/21-cfg-simplification.md`, and section 6.5 of
7//! `spec/optimizer/06-cfg-and-dominators.md`, which states the rule for the whole optimizer, that
8//! a block the entry does not reach is invisible to every analysis and is deleted here rather than
9//! by whichever pass happened to notice it.
10//!
11//! # The order
12//!
13//! Section 21.4, and it is an order rather than a loop. Unreachable removal, then the branches,
14//! then the straightening, then merging, each once. Running the four to a fixed point would cost a
15//! walk of the function for every pass over it and buy back a case nobody has: what merging leaves
16//! behind is a bigger block, and a bigger block does not make a branch foldable that was not
17//! foldable before. The pipeline runs this pass more than once anyway, so the second chance is a
18//! pass boundary away rather than a loop away, and that is a chance the pass manager can count and
19//! print.
20//!
21//! Step three is the exception, and it is the spec's exception rather than one taken here. Taking a
22//! forwarder out gives the block below it a way in it did not have, which can be the way in that
23//! makes one of its parameters the same value from everywhere; and taking a parameter away can be
24//! what leaves a block empty enough to be a forwarder. So the two run together on one worklist,
25//! which is a fixed point over a step and not over the pass.
26//!
27//! # The parameter nothing reads
28//!
29//! Section 21.2 is about a parameter that is the same value every way in, and there is a second
30//! kind that goes, which is one nothing reads at all. `crate::dce` is the pass that removes what
31//! nothing reads and it says in its own documentation why this one is not its job: a count driven
32//! to zero does not see a loop counter, because the counter's only reader is the addition that
33//! produces the value handed back to the counter. The count never reaches zero and the whole cycle
34//! is dead anyway.
35//!
36//! So it is answered the other way round, by asking what is live rather than what is dead.
37//! Something is live if an instruction that has to happen reads it, and then live spreads: the
38//! operands of a live instruction are live, and the argument every edge passes in a live
39//! parameter's place is live. Anything the spread does not reach is not read by anything that
40//! happens, and a parameter it does not reach goes along with the argument in its place on every
41//! edge into the block. What that strands is an addition whose result nobody wants any more, which
42//! is exactly the shape `crate::dce` was already good at.
43//!
44//! Starting from nothing live rather than from everything live is what breaks the cycle, and it is
45//! the same optimistic reading section 14.1 takes and section 21.2 takes one paragraph up. The
46//! risk in reading optimistically is claiming something is dead when it is not, so the seeds are
47//! generous: a terminator or an instruction with effects makes its operands live whatever else is
48//! true, and the entry block's parameters are the function's own and stay whether anybody reads
49//! them or not.
50//!
51//! `crate::ivopts` is what makes this worth having. Section 28.4 of
52//! `spec/optimizer/28-induction-variables.md` has a loop stop asking its counter anything, and
53//! before this the counter went on being incremented round a loop that had no other use for it.
54//!
55//! What the addition is left reading matters, and this used to leave it reading the parameter that
56//! had just gone. The argument for that was that the addition is dead and `crate::dce` is the pass
57//! for what is dead, and the argument is true at every level except the one where it counts:
58//! `-O0` runs this pass and nothing else, so nothing came along behind it and the operand reached
59//! the printer as a use with no definition. So the addition goes here, with the parameter that
60//! stranded it, and the `strand` function below carries the argument for why that is always safe.
61//!
62//! Cross jumping is the one transformation of section 21.1 that is not here at all, and that is
63//! section 21.1's last paragraph telling us not to: it costs a branch to save a copy, so it belongs
64//! at the machine level under `-Os`, which is document 37.
65//!
66//! # What a forwarder is allowed to be
67//!
68//! Section 21.1 wants four things of a block before its predecessors are pointed past it: one
69//! successor, the successor is not the exit, the successor is not the block itself, and the edge
70//! out is not abnormal. Then it adds the one the block parameter form needs, which is that the
71//! arguments the block passes on all dominate every predecessor of it, because those predecessors
72//! are the ones that will be passing them.
73//!
74//! Requiring the block to have no parameters of its own discharges that last one without a
75//! dominator tree, and the argument is short. A value defined in a block that dominates the
76//! forwarder dominates every predecessor of it too: take any path to a predecessor, follow the edge
77//! to the forwarder, and the definition is somewhere on the result, which is either before the
78//! predecessor or is the forwarder itself. A block with no parameters and no instructions defines
79//! nothing, so the second case cannot arise and the first is the condition.
80//!
81//! The requirement earns something else as well. A parameter of the forwarder could be read by a
82//! block below it, which is legal exactly when the forwarder dominates that block, and pointing the
83//! predecessors past would leave that read with nothing to read. Insisting on no parameters is one
84//! rule that answers both, and a forwarder that has one gets taken apart by the other half of the
85//! worklist first.
86//!
87//! There is no exit block in this IR, so the second condition is not a condition. Abnormal edges
88//! are the ones into a block whose address is taken, which arrive from an `indirect_br` the graph
89//! reads from the other end, and those blocks are refused here the same way they are refused
90//! everywhere else in this pass.
91//!
92//! One condition is here that the section does not ask for. A forwarder that passes arguments on,
93//! and that is arrived at from a block which branches, is the block the moves for those arguments
94//! go in. Take it out and the edge it was on becomes one that goes out of a block with two ways out
95//! and into a block with two ways in, which has no end of a block to put a move at, so the back end
96//! splits it and puts an empty block back. The block comes back at the end of the layout instead of
97//! where it was, the jump that was free because it fell through is a jump that is taken, and the
98//! value the move was carrying is live across more of the function. On the corpus at -O2 that costs
99//! more than the block is worth, so a forwarder in that position stays. A forwarder that carries
100//! nothing is taken out whatever the edges look like, since there is no move to find a place for.
101//!
102//! # Why this is not only an optimization
103//!
104//! Issue 359 is a program that does not link:
105//!
106//! ```c
107//! extern void link_error(void);
108//! void foo(int x) {
109//!     switch (x) {
110//!     case 0:
111//!         if (0) { link_error(); case 1: bar(); }
112//!     }
113//! }
114//! ```
115//!
116//! Nothing calls `link_error`, so a compiler that emits the call produces an object file that
117//! does not link, and the difference between the two compilers is not how fast the program runs.
118//! The file is in a suite of forty years of compiler bugs for the reason the `case 1:` is where
119//! it is: control does reach `bar` through the switch, and it reaches it from inside the body of
120//! the dead `if`. A compiler that deletes the compound statement gets this as wrong as one that
121//! keeps all of it.
122//!
123//! Doing it in two steps is what makes that come out right without a special case for it. The
124//! branch on the constant becomes a jump, which takes the edge into the dead arm away, and then
125//! reachability from the entry decides what is left. The block holding `bar` has an edge from the
126//! `switch` and stays. The block holding `link_error` has no edges at all and goes.
127//!
128//! # The condition it can read
129//!
130//! A constant, and a comparison of two constants. The second is here rather than in
131//! [`crate::fold`] because folding a comparison would produce an `i1` standing on its own, which
132//! is issue 352 and does not lower, so the pass that folds arithmetic deliberately leaves
133//! comparisons alone. Reading one to decide which way a branch goes produces no `i1` at all: the
134//! comparison is left exactly where it was, used by nothing, and [`crate::dce`] takes it out.
135//!
136//! # Fuel
137//!
138//! Fuel is charged for each branch that folds and for each block that is merged away, and not for
139//! the blocks that go because nothing reaches them. Removal is the second half of the
140//! transformation that was already paid for rather than a transformation of its own, and a fuel
141//! limit that could stop between the two halves would hand the verifier a block nothing reaches.
142//! Section 41.5 of `spec/optimizer/41-correctness.md` asks for fuel that is monotonic, which means
143//! each step being all of one change and not part of one.
144//!
145//! That reasoning covers the blocks a fold stranded. It does not cover the ones that arrived
146//! unreachable, and those are not charged for either, for a different reason: section 6.5 makes
147//! removing them this pass's standing obligation rather than an optimization, everything below
148//! reads the graph as though they are not there, and a bisection that turned the obligation off
149//! would be bisecting over a function the rest of the optimizer does not believe in.
150
151use std::collections::{HashMap, HashSet, VecDeque};
152
153use rucc_base::Idx;
154use rucc_ir::{Block, BlockCall, Def, Extra, Func, Inst, Opcode, Value};
155
156use crate::fold::constant;
157use crate::{Analyses, Fuel, Pass, Preserved, Stats, uses};
158
159/// Recorded once for each branch that turned into a jump.
160const FOLDED: &str = "branch on a condition that is always the same way replaced by a jump";
161
162/// Recorded once for each block that went with it.
163pub(crate) const REMOVED: &str = "block nothing reaches removed";
164
165/// Recorded once for each block folded into the one above it.
166const MERGED: &str = "block with one way into it merged into the block above it";
167
168/// Recorded once for each block that did nothing but jump and is no longer in the way.
169const FORWARDED: &str = "block that only jumped somewhere else removed and its edges pointed past";
170
171/// Recorded once for each block parameter that turned out to be one value.
172const SAME_EVERY_WAY: &str = "block parameter that arrives as the same value every way in removed";
173
174/// Recorded for a branch that would have folded if there had been fuel for it.
175const NO_FUEL: &str = "branch on a known condition left alone, the pass ran out of fuel";
176
177/// Recorded for a block that would have been merged if there had been fuel for it.
178const NO_FUEL_MERGE: &str = "block with one way into it left alone, the pass ran out of fuel";
179
180/// Recorded for a forwarder that would have gone if there had been fuel for it.
181const NO_FUEL_FORWARD: &str =
182    "block that only jumped somewhere else kept, the pass ran out of fuel";
183
184/// Recorded for a block parameter that would have gone if there had been fuel for it.
185const NO_FUEL_PARAM: &str = "block parameter that is one value kept, the pass ran out of fuel";
186
187/// Recorded once for each block parameter that nothing turned out to read.
188const NOTHING_READS_IT: &str = "block parameter nothing reads removed, and the argument on every \
189                                edge that was feeding it";
190
191/// Recorded for a parameter nothing reads that would have gone if there had been fuel for it.
192const NO_FUEL_UNREAD: &str = "block parameter nothing reads kept, the pass ran out of fuel";
193
194/// The pass.
195#[derive(Debug, Clone, Copy, PartialEq, Eq)]
196pub struct SimplifyCfg;
197
198impl Pass for SimplifyCfg {
199    fn name(&self) -> &'static str {
200        "simplify-cfg"
201    }
202
203    fn describe(&self) -> &'static str {
204        "unreachable blocks go, a branch that only goes one way becomes a jump, a block that only \
205         jumps stops being in the way, and a block with one way in is merged into the one above it"
206    }
207
208    fn preserves(&self) -> Preserved {
209        // Nothing at all, and this is the pass the declaration exists for. An edge moves, so the
210        // graph is a different graph, and everything built on the graph was about the old one.
211        Preserved::NONE
212    }
213
214    fn run(&self, func: &mut Func, an: &mut Analyses, fuel: &mut Fuel) -> Stats {
215        let mut stats = Stats::new();
216        // Step one, and it is first for a reason beyond tidiness: a branch in a block nothing
217        // reaches is a branch nothing executes, and folding one would spend fuel on a change
218        // nobody can see and charge the two steps below for walking blocks that are not there.
219        sweep(func, an, &mut stats);
220        let mut folded = false;
221        // Nothing bound, because this step asks where a branch goes whichever way control arrived
222        // at it. Binding a block's parameters to one edge's arguments is the question
223        // [`crate::thread`] asks, and it is a different question with a different answer.
224        let unbound = Bindings::new();
225        for block in func.blocks().collect::<Vec<Block>>() {
226            let Some(term) = func.terminator(block) else { continue };
227            let Some(taken) = taken(func, term, &unbound) else { continue };
228            if !fuel.take() {
229                // Out of fuel stops the transforming and not the looking, the same way the other
230                // passes treat it, so that the walk is the same walk at every fuel setting.
231                stats.missed(NO_FUEL);
232                continue;
233            }
234            jump_to(func, term, taken);
235            stats.optimized(FOLDED);
236            folded = true;
237        }
238        if folded {
239            // The second sweep section 21.4 folds into step two. The cache is holding answers
240            // about the function as it was a moment ago, and the manager clears it after the pass
241            // returns, which is too late for the pass itself.
242            an.clear();
243            sweep(func, an, &mut stats);
244        }
245        let mut forward = HashMap::new();
246        // Step three, and it keeps its own record of the edges rather than asking for the graph,
247        // because it changes the edges as it goes and a cached answer would be about the shape the
248        // function had one forwarder ago. The parameters nothing reads go first, for the reason
249        // this module's documentation gives, which is that a block they leave empty is a forwarder
250        // and the worklist below is what takes forwarders out.
251        let dropped = drop_unread(func, fuel, &mut stats);
252        if straighten(func, fuel, &mut stats, &mut forward) || dropped {
253            an.clear();
254        }
255        // Merging reads which blocks have one predecessor, so it has to run on the graph as it is
256        // after the stranded ones have gone. A block kept alive only by an edge from a block
257        // nothing reaches looks like it has two ways in until that block is out of the function.
258        for chain in chains(func, an) {
259            for (at, &block) in chain.iter().enumerate().skip(1) {
260                if !fuel.take() {
261                    // The rest of the chain goes with it. A block is merged into the one at the
262                    // head of its chain, and it can only get there once everything between them
263                    // has already arrived.
264                    for _ in at..chain.len() {
265                        stats.missed(NO_FUEL_MERGE);
266                    }
267                    break;
268                }
269                merge(func, chain[0], block, &mut forward);
270                stats.optimized(MERGED);
271            }
272        }
273        if !forward.is_empty() {
274            // Once, for every parameter every merge bound, rather than a walk of the function per
275            // block merged.
276            uses::substitute(func, &forward);
277        }
278        stats
279    }
280}
281
282/// What a block's parameters hold along one particular edge into it.
283///
284/// Empty is the honest answer for a question asked about a block rather than about an edge, and it
285/// is what this pass passes, since a branch it folds has to fold whichever way control arrived.
286/// [`crate::thread`] asks the same question one edge at a time and fills this in, which is the
287/// whole difference between folding a branch and threading one.
288pub(crate) type Bindings = HashMap<Value, Value>;
289
290/// The value this one stands for along the edge, which is itself when the edge says nothing.
291fn resolve(subst: &Bindings, value: Value) -> Value {
292    subst.get(&value).copied().unwrap_or(value)
293}
294
295/// Where this terminator always goes, if it always goes to one place.
296///
297/// `None` is every reason not to fold and does not say which, because the answer to all of them
298/// is to leave the branch alone.
299///
300/// Shared with [`crate::thread`], which asks it under a `subst` that binds the block's parameters
301/// to what one edge into the block carries. Two answers about when a branch is decided would be
302/// two compilers, and the threading pass would be the one nobody checked.
303pub(crate) fn taken(func: &Func, term: Inst, subst: &Bindings) -> Option<BlockCall> {
304    let data = &func[term];
305    let arg = *func[data.args].first()?;
306    match data.opcode {
307        Opcode::BrIf => {
308            let Extra::Targets(targets) = data.extra else { return None };
309            if let Some(call) = one_place(func, &func[targets]) {
310                return Some(call);
311            }
312            // The first target is the one taken when the condition is one, which is what
313            // `Builder::br_if` writes and what the printer reads back.
314            let arm = usize::from(!known(func, arg, subst)?);
315            func[targets].get(arm).copied()
316        }
317        Opcode::Switch => {
318            let Extra::Switch(at) = data.extra else { return None };
319            let info = func[at];
320            if let Some(call) = one_place(func, &func[info.targets]) {
321                return Some(call);
322            }
323            let (value, _) = constant(func, resolve(subst, arg))?;
324            // The default is the first target and the cases follow it in the order their values
325            // are in, so the target for a case that matches is one past the value's own place.
326            let case = func[info.cases].iter().position(|it| *it == value);
327            func[info.targets].get(case.map_or(0, |case| case + 1)).copied()
328        }
329        _ => None,
330    }
331}
332
333/// The one edge every arm of a branch is, when they are all the same edge.
334///
335/// Section 21.1's branch simplification, the half of it that is not about a constant. A branch
336/// whose arms all go to the same block with the same arguments goes there whatever the condition
337/// says, so it is a jump, and the condition becomes something nothing reads for
338/// [`crate::dce`] to take out.
339///
340/// The arguments have to match and not only the block. Two edges to one block carrying different
341/// arguments are two different edges, and that is the whole reason this IR passes arguments along
342/// an edge rather than writing a phi in the block: `if (c) goto L(1); else goto L(2);` is a real
343/// program and turning it into a jump would have to pick one of the two numbers.
344fn one_place(func: &Func, calls: &[BlockCall]) -> Option<BlockCall> {
345    let &first = calls.first()?;
346    let same = |call: &BlockCall| call.block == first.block && func[call.args] == func[first.args];
347    calls[1..].iter().all(same).then_some(first)
348}
349
350/// Rewrites the terminator as a jump to that one of its targets.
351///
352/// In place, and the target keeps the arguments it already had, because the arguments belong to
353/// the edge and the edge is the one that survives.
354pub(crate) fn jump_to(func: &mut Func, term: Inst, call: BlockCall) {
355    let targets = func.push_block_calls(&[call]);
356    let args = func.push_values(&[]);
357    let data = &mut func[term];
358    data.opcode = Opcode::Jump;
359    data.args = args;
360    data.extra = Extra::Targets(targets);
361}
362
363/// Takes every block the entry does not reach out of the function.
364///
365/// The cache goes with them, because what it is holding is answers about a function that had them
366/// in it, and the pass is not finished asking.
367///
368/// Shared with [`crate::thread`]. Section 6.5 makes this a standing obligation of whichever pass
369/// stranded the block rather than an optimization of this one, the verifier holds every pass to it,
370/// and a second walk written next door would be a second answer about what reachable means.
371pub(crate) fn sweep(func: &mut Func, an: &mut Analyses, stats: &mut Stats) {
372    let gone = stranded(func, an);
373    if gone.is_empty() {
374        return;
375    }
376    for block in gone {
377        func.remove_block(block);
378        stats.optimized(REMOVED);
379    }
380    an.clear();
381}
382
383/// The blocks the entry cannot reach, in block order.
384///
385/// This is reachability as the verifier counts it, which is over the edges the terminators name
386/// and additionally over the blocks a `block_addr` mentions. A block whose address is taken is
387/// arrived at by an `indirect_br` somewhere, and that instruction lists every block the address
388/// can hold, so the edge is already in the graph from the place control really leaves. What the
389/// graph does not carry is the `block_addr` itself, and deleting the block under one would leave
390/// an instruction naming a block that is not there.
391fn stranded(func: &Func, an: &mut Analyses) -> Vec<Block> {
392    let cfg = an.cfg(func);
393    let Some(entry) = cfg.entry() else { return Vec::new() };
394    let mut seen = vec![false; cfg.capacity()];
395    seen[entry.index()] = true;
396    let mut stack = vec![entry];
397    // And every block an image names, whatever the entry reaches. The `indirect_br` that arrives at
398    // one of those can be in another function, so unlike a `block_addr` below there is nothing in
399    // this function that says the block is still wanted.
400    for (block, _) in func.named_blocks() {
401        if !seen[block.index()] {
402            seen[block.index()] = true;
403            stack.push(block);
404        }
405    }
406    let mut reached = Vec::new();
407    while let Some(block) = stack.pop() {
408        for &succ in cfg.successors(block) {
409            if !seen[succ.index()] {
410                seen[succ.index()] = true;
411                stack.push(succ);
412            }
413        }
414        reached.push(block);
415    }
416    // The addresses in a second walk over the blocks the first one reached, because an address
417    // taken in a block nothing reaches is an address nothing takes.
418    let mut next = reached;
419    while !next.is_empty() {
420        let mut found = Vec::new();
421        for block in next {
422            for inst in func.insts(block) {
423                if func[inst].opcode != Opcode::BlockAddr {
424                    continue;
425                }
426                for call in func.successors(inst) {
427                    if !seen[call.block.index()] {
428                        seen[call.block.index()] = true;
429                        found.push(call.block);
430                    }
431                }
432            }
433        }
434        // Everything the newly kept blocks reach is kept too, which is what makes this a fixed
435        // point rather than one extra step.
436        let mut stack = found.clone();
437        while let Some(block) = stack.pop() {
438            for &succ in cfg.successors(block) {
439                if !seen[succ.index()] {
440                    seen[succ.index()] = true;
441                    stack.push(succ);
442                    found.push(succ);
443                }
444            }
445        }
446        next = found;
447    }
448    func.blocks().filter(|block| !seen[block.index()]).collect()
449}
450
451/// Where every edge that arrives at a block was written down, and which block it left.
452///
453/// A block call rather than a predecessor, because both halves of step three edit the edge and
454/// neither of them can find it again from the block it goes to. Redirecting one wants the slot in
455/// the pool, and taking a block parameter away wants the slot too, so this is what the step keeps
456/// instead of a [`crate::Cfg`].
457pub(crate) type Edges = HashMap<Block, Vec<(Block, Idx<BlockCall>)>>;
458
459/// Every edge in the function, filed under the block it arrives at.
460///
461/// Terminators only. A `block_addr` names a block and is not an edge, which is the same
462/// distinction [`stranded`] draws from the other side.
463///
464/// Shared with [`crate::thread`], which edits edges as well and so wants the slot in the pool for
465/// the same reason this step does.
466pub(crate) fn incoming(func: &Func) -> Edges {
467    let mut edges: Edges = HashMap::new();
468    for block in func.blocks() {
469        let Some(term) = func.terminator(block) else { continue };
470        for at in func.target_list(term).iter() {
471            edges.entry(func[at].block).or_default().push((block, at));
472        }
473    }
474    edges
475}
476
477/// Takes out every block parameter nothing reads, and the argument in its place on every edge.
478///
479/// This module's documentation is the argument. [`live`] is where the reading is done and this is
480/// what acts on it: one walk, in block order, because the answer is about the whole function and a
481/// worklist would only be asking the same question again.
482///
483/// # Fuel
484///
485/// One unit per parameter, and running out stops the removals rather than the looking, which this
486/// step can do because it has already worked out the whole answer. The parameters of a block are
487/// taken together once they have all been paid for, since an argument list that has lost some of
488/// its entries and not others is a function the verifier refuses.
489fn drop_unread(func: &mut Func, fuel: &mut Fuel, stats: &mut Stats) -> bool {
490    let Some(entry) = func.entry() else { return false };
491    let live = live(func, entry, &addressed(func));
492    let edges = incoming(func);
493    let mut changed = false;
494    let mut gone: HashSet<Value> = HashSet::new();
495    for block in func.blocks().collect::<Vec<Block>>() {
496        let mut taking = Vec::new();
497        for (index, &param) in func[block].params.iter().enumerate() {
498            if live.contains(&param) {
499                continue;
500            }
501            if !fuel.take() {
502                stats.missed(NO_FUEL_UNREAD);
503                continue;
504            }
505            taking.push(index);
506        }
507        if taking.is_empty() {
508            continue;
509        }
510        for _ in &taking {
511            stats.optimized(NOTHING_READS_IT);
512        }
513        gone.extend(taking.iter().map(|&index| func[block].params[index]));
514        take_params(func, block, &taking, edges.get(&block));
515        changed = true;
516    }
517    if !gone.is_empty() {
518        strand(func, gone);
519    }
520    changed
521}
522
523/// Takes out the instructions that were reading a parameter the step above removed.
524///
525/// Without this the step above leaves a use of a value nothing defines, which is a function the
526/// verifier is entitled to refuse and a printed IR that does not read back. It was written the
527/// other way round, on the argument that what is stranded is dead and [`crate::dce`] is the pass
528/// that takes what is dead. That holds at every level except the one that matters most, because
529/// `-O0` runs this pass and no other, so at `-O0` nothing came along afterwards and the dangling
530/// operand reached the printer. That was issue 1016.
531///
532/// Every instruction this removes is dead, and the proof is [`live`] read backwards. Live spreads
533/// from a result to the operands that produced it, so an instruction whose result were live would
534/// have made its operands live, and the parameter it reads was removed exactly because nothing
535/// made it live. A terminator's operands and an effectful instruction's operands are live by
536/// seeding, so neither of those can be reading a removed parameter either. The same argument
537/// applies again to the results of what goes, which is why this follows the chain.
538///
539/// # Fuel
540///
541/// None, for the reason this module's documentation gives about the blocks a fold strands: this is
542/// the second half of a transformation that has already been paid for, and a budget that could
543/// stop between the two halves would hand the verifier a use with no definition.
544fn strand(func: &mut Func, mut gone: HashSet<Value>) {
545    loop {
546        let mut spread = false;
547        for block in func.blocks().collect::<Vec<Block>>() {
548            for inst in func.insts(block).collect::<Vec<Inst>>() {
549                if !func[func[inst].args].iter().any(|value| gone.contains(value)) {
550                    continue;
551                }
552                let results: Vec<Value> = func[inst].results().collect();
553                for result in results {
554                    spread |= gone.insert(result);
555                }
556                func.remove_inst(inst);
557            }
558        }
559        // A second walk only when something this removed produced a value of its own, since that
560        // value may be read further down and layout order is not a promise about where.
561        if !spread {
562            return;
563        }
564    }
565}
566
567/// Every value something that happens reads, worked out from nothing live outwards.
568///
569/// The seeds are the operands of the instructions that have to happen, which are the terminators
570/// and the ones with effects. A terminator's own operands are seeds and the arguments it passes to
571/// the blocks it branches to are not, and that split is the whole point: an argument is read only
572/// if the parameter it lands in is read, so it waits for that parameter to be reached.
573///
574/// Then live spreads two ways. From a value an instruction produced, to that instruction's
575/// operands, because producing it meant reading them. From a parameter, to the argument in its
576/// place on every edge into the block, because arriving there meant passing them.
577///
578/// The entry block's parameters are the function's own and are live by declaration rather than by
579/// use, and so are the parameters of a block whose address is taken, because an `indirect_br` is a
580/// way in that this reads from the wrong end.
581fn live(func: &Func, entry: Block, addressed: &HashSet<Block>) -> HashSet<Value> {
582    let mut where_from: HashMap<Value, (Block, usize)> = HashMap::new();
583    let mut live: HashSet<Value> = HashSet::new();
584    let mut work: Vec<Value> = Vec::new();
585    let seed = |value: Value, live: &mut HashSet<Value>, work: &mut Vec<Value>| {
586        if live.insert(value) {
587            work.push(value);
588        }
589    };
590    for block in func.blocks() {
591        let held = block == entry || addressed.contains(&block);
592        for (index, &param) in func[block].params.iter().enumerate() {
593            where_from.insert(param, (block, index));
594            if held {
595                seed(param, &mut live, &mut work);
596            }
597        }
598        for inst in func.insts(block) {
599            if !func.is_terminator(inst) && !func[inst].opcode.has_effects() {
600                continue;
601            }
602            for &value in &func[func[inst].args] {
603                seed(value, &mut live, &mut work);
604            }
605        }
606    }
607
608    let edges = incoming(func);
609    while let Some(value) = work.pop() {
610        match func[value].def {
611            Def::Result { inst, .. } => {
612                for &operand in &func[func[inst].args] {
613                    seed(operand, &mut live, &mut work);
614                }
615            }
616            Def::Param { .. } => {
617                let Some(&(block, index)) = where_from.get(&value) else { continue };
618                for &(_, at) in edges.get(&block).into_iter().flatten() {
619                    let Some(&arg) = func[func[at].args].get(index) else { continue };
620                    seed(arg, &mut live, &mut work);
621                }
622            }
623        }
624    }
625    live
626}
627
628/// Section 21.4's step three, both halves of it, on one worklist. Says whether anything changed.
629///
630/// Forwarder removal and redundant block parameter removal are one step because each is the other's
631/// reason to look again. Pointing a block's predecessors past it hands the block below several ways
632/// in where there was one, and a parameter that was obviously one value may stop being one, or
633/// several arguments that were the same may now arrive together and make one; taking a parameter
634/// away can leave a block with nothing but its jump, which is the whole of what a forwarder is.
635///
636/// A block goes back on the worklist when an edge into it or out of it moved, and the loop stops
637/// when nothing has moved. That is a fixed point, and it is the one section 21.4 asks for, over the
638/// step rather than over the pass.
639///
640/// What this does not do is put the two halves in a particular order within a block. Parameters
641/// first is not a policy, it is the only order that gets a forwarder with a redundant parameter in
642/// one visit rather than two.
643///
644/// # Fuel
645///
646/// One unit for each forwarder and one for each parameter, and the first refusal is where the step
647/// stops rather than where it starts skipping. The other steps go on looking after they run out and
648/// say so once for each thing they did not do, which they can because each of them walks the
649/// function once. This one comes back to a block whenever an edge near it moved, so a refusal
650/// counted per visit would count one opportunity several times and the number would say more about
651/// the shape of the worklist than about the function. A budget that has reached zero is not going
652/// to have anything in it later, so there is one refusal recorded and it is the true one.
653fn straighten(
654    func: &mut Func,
655    fuel: &mut Fuel,
656    stats: &mut Stats,
657    forward: &mut HashMap<Value, Value>,
658) -> bool {
659    let Some(entry) = func.entry() else { return false };
660    let addressed = addressed(func);
661    let mut edges = incoming(func);
662    let mut work: VecDeque<Block> = func.blocks().collect();
663    let mut queued: HashSet<Block> = work.iter().copied().collect();
664    let mut gone: HashSet<Block> = HashSet::new();
665    let mut changed = false;
666    while let Some(block) = work.pop_front() {
667        queued.remove(&block);
668        if gone.contains(&block) {
669            continue;
670        }
671        let mut starved = false;
672        if block != entry {
673            let drop = redundant(func, block, edges.get(&block), forward);
674            let mut taking = Vec::new();
675            for (index, value) in drop {
676                if !fuel.take() {
677                    stats.missed(NO_FUEL_PARAM);
678                    starved = true;
679                    break;
680                }
681                // Through what an earlier one already decided, the same way merging does, because
682                // a parameter can be redundant on an argument that is on its way somewhere else.
683                let value = uses::chase(forward, value);
684                forward.insert(func[block].params[index], value);
685                taking.push(index);
686                stats.optimized(SAME_EVERY_WAY);
687            }
688            if !taking.is_empty() {
689                take_params(func, block, &taking, edges.get(&block));
690                // Itself, because a block that has run out of parameters may be a forwarder now,
691                // and because a parameter can be redundant on one that just went.
692                requeue(block, &mut work, &mut queued);
693                // And the blocks below, because a parameter passed straight on down is the shape
694                // section 21.2 means by one removal making the next one possible.
695                if let Some(term) = func.terminator(block) {
696                    for call in func.successors(term).collect::<Vec<BlockCall>>() {
697                        requeue(call.block, &mut work, &mut queued);
698                    }
699                }
700                changed = true;
701            }
702        }
703        // What was already paid for is applied first, and then the step stops, because a block
704        // whose parameters half went is a block whose edges have to agree with it.
705        if starved {
706            break;
707        }
708        let Some((term, into, args)) = forwards(func, block, entry, &addressed, &edges) else {
709            continue;
710        };
711        if !fuel.take() {
712            stats.missed(NO_FUEL_FORWARD);
713            break;
714        }
715        // The block's own edge stops existing along with the block, and it has to come out of the
716        // record before its predecessors' edges go in, or the block below would be told it has a
717        // way in from a block that is not there.
718        let out = func.target_list(term).iter().next().expect("a jump has a target");
719        if let Some(list) = edges.get_mut(&into) {
720            list.retain(|&(_, at)| at != out);
721        }
722        let ins = edges.remove(&block).unwrap_or_default();
723        for &(_, at) in &ins {
724            // A list of its own for each edge rather than one shared between them, because a
725            // later substitution rewrites a list in place and a shared one would be rewritten
726            // once for every edge that named it.
727            let call = func[at];
728            let args = func.push_values(&args);
729            func.set_block_call(at, BlockCall { block: into, args, ..call });
730        }
731        edges.entry(into).or_default().extend(ins.iter().copied());
732        func.remove_block(block);
733        gone.insert(block);
734        stats.optimized(FORWARDED);
735        changed = true;
736        requeue(into, &mut work, &mut queued);
737        for &(from, _) in &ins {
738            requeue(from, &mut work, &mut queued);
739        }
740    }
741    changed
742}
743
744/// Puts a block back on the worklist, if it is not on it already.
745fn requeue(block: Block, work: &mut VecDeque<Block>, queued: &mut HashSet<Block>) {
746    if queued.insert(block) {
747        work.push_back(block);
748    }
749}
750
751/// Which of a block's parameters arrive as the same value every way in, and what that value is.
752///
753/// Section 21.2. A parameter that is `x` from one edge and `x` from every other is not carrying
754/// anything, it is spelling `x` a second way, and document 12's hash consing cannot see through the
755/// spelling, so two equal values look different for as long as it is there.
756///
757/// The one subtlety is an argument that is the parameter itself, which is what a loop header looks
758/// like: the preheader passes `init` and the latch passes the parameter back. Reading that
759/// literally says two different values and the answer is `init`, because a value that can only ever
760/// be itself or `init` was `init` to begin with. So a self reference is not an argument for this
761/// purpose, which is the same optimistic reading section 14.1 takes.
762///
763/// A block with no way in gets nothing said about it. That is an unreachable block, [`sweep`] has
764/// already run, and answering `init` for a parameter with no arguments at all would be inventing
765/// one.
766fn redundant(
767    func: &Func,
768    block: Block,
769    ins: Option<&Vec<(Block, Idx<BlockCall>)>>,
770    forward: &HashMap<Value, Value>,
771) -> Vec<(usize, Value)> {
772    let Some(ins) = ins.filter(|ins| !ins.is_empty()) else { return Vec::new() };
773    let mut found = Vec::new();
774    for (index, &param) in func[block].params.iter().enumerate() {
775        let mut only = None;
776        let mut agree = true;
777        for &(_, at) in ins {
778            let list = func[at].args;
779            let Some(&arg) = func[list].get(index) else {
780                // Fewer arguments than parameters is a function the verifier will refuse, and
781                // guessing what the missing one was is not this pass's job.
782                agree = false;
783                break;
784            };
785            let arg = uses::chase(forward, arg);
786            if arg == param {
787                continue;
788            }
789            match only {
790                None => only = Some(arg),
791                Some(seen) if seen == arg => {}
792                Some(_) => {
793                    agree = false;
794                    break;
795                }
796            }
797        }
798        if !agree {
799            continue;
800        }
801        if let Some(value) = only {
802            found.push((index, value));
803        }
804    }
805    found
806}
807
808/// Drops those parameters of a block and the arguments in their places on every edge into it.
809///
810/// Both halves together, because a block whose parameters and arguments disagree in number is one
811/// the verifier refuses, and section 21.6 says that is the most common bug in this document.
812fn take_params(
813    func: &mut Func,
814    block: Block,
815    taking: &[usize],
816    ins: Option<&Vec<(Block, Idx<BlockCall>)>>,
817) {
818    for &(_, at) in ins.into_iter().flatten() {
819        let call = func[at];
820        let kept: Vec<Value> = func[call.args]
821            .iter()
822            .enumerate()
823            .filter(|(index, _)| !taking.contains(index))
824            .map(|(_, &value)| value)
825            .collect();
826        let args = func.push_values(&kept);
827        func.set_block_call(at, BlockCall { args, ..call });
828    }
829    let mut index = 0;
830    func.retain_params(block, |_| {
831        let keep = !taking.contains(&index);
832        index += 1;
833        keep
834    });
835}
836
837/// Where a block forwards to and what it passes on, when it is a forwarder.
838///
839/// The conditions are in this module's documentation, and every one of them is a `None` here. What
840/// comes back is the terminator, the block below, and the arguments the jump was carrying, which
841/// are what each of the block's predecessors will be carrying instead.
842fn forwards(
843    func: &Func,
844    block: Block,
845    entry: Block,
846    addressed: &HashSet<Block>,
847    edges: &Edges,
848) -> Option<(Inst, Block, Vec<Value>)> {
849    if block == entry || addressed.contains(&block) || !func[block].params.is_empty() {
850        return None;
851    }
852    let term = func.terminator(block)?;
853    if func[term].opcode != Opcode::Jump {
854        return None;
855    }
856    // Nothing above the jump, which is what "no instructions" means once the jump is counted as
857    // one of them.
858    if func.insts(block).count() != 1 {
859        return None;
860    }
861    let call = func.successors(term).next()?;
862    if call.block == block {
863        return None;
864    }
865    if carrying(func, block, call.block, func[call.args].len(), edges) {
866        return None;
867    }
868    Some((term, call.block, func[call.args].to_vec()))
869}
870
871/// Whether taking this forwarder out would put arguments on an edge that has nowhere to move them.
872///
873/// An edge carries values when the block it arrives at takes parameters, and giving a parameter its
874/// value is a move that has to happen on the edge itself. An edge out of a block that goes two ways
875/// and into a block arrived at two ways has no block to put that move in, so the back end splits it
876/// and puts an empty block back on it, which is `rucc_codegen::split::critical`. A forwarder that
877/// carries arguments and whose predecessor branches is already that block, sitting in the place the
878/// layout wants it rather than at the end where the splitter has to append it. Taking it out and
879/// having it put back costs a jump and a longer live range, and the measurement on the corpus says
880/// it costs enough to see, so it is not taken out.
881///
882/// A forwarder that carries nothing is removed whatever the edges look like, because there is no
883/// move to find a place for and the splitter would leave the edge alone as well.
884fn carrying(func: &Func, block: Block, into: Block, args: usize, edges: &Edges) -> bool {
885    if args == 0 {
886        return false;
887    }
888    let ins = edges.get(&block).map_or(0, Vec::len);
889    let after = edges.get(&into).map_or(0, Vec::len) - 1 + ins;
890    if after < 2 {
891        return false;
892    }
893    edges.get(&block).into_iter().flatten().any(|&(from, _)| {
894        let Some(term) = func.terminator(from) else { return false };
895        func.target_list(term).iter().count() >= 2
896    })
897}
898
899/// The runs of blocks that are one block written as several, head first.
900///
901/// Section 21.1's block merging, and the doc calls it a pure win for a reason worth stating: it
902/// does not delete an instruction or move one earlier, it takes a boundary out. Every analysis
903/// that is cheap inside a block and expensive across one gets more of the cheap kind, which is
904/// most of them, and the branch that stops being a branch is the smallest part of it.
905///
906/// A block goes into the one above it when the one above it ends in a jump and this is the only
907/// way in. Both halves are needed. One way in and a `br_if` above means the other arm would lose
908/// its terminator, and a jump above with two ways in means the second predecessor would arrive in
909/// the middle of a block.
910///
911/// The refusals are the entry block, which has to stay where control arrives even when one block
912/// jumps to it; a block that jumps to itself, whose one predecessor is itself; and a block whose
913/// address is taken, which is arrived at by an `indirect_br` the graph reads from the other end.
914///
915/// The answer is chains rather than pairs because a run of three is ordinary and the middle one
916/// stops existing partway through. Each block is the head of at most one of these and the tail of
917/// at most one, so what comes out is disjoint paths, and starting only from a head is what leaves
918/// a ring of blocks that all point at each other alone rather than walking it forever.
919fn chains(func: &Func, an: &mut Analyses) -> Vec<Vec<Block>> {
920    let cfg = an.cfg(func);
921    let Some(entry) = cfg.entry() else { return Vec::new() };
922    let addressed = addressed(func);
923    let mut below = HashMap::new();
924    let mut is_below = HashSet::new();
925    for block in func.blocks() {
926        let Some(term) = func.terminator(block) else { continue };
927        if func[term].opcode != Opcode::Jump {
928            continue;
929        }
930        let Some(call) = func.successors(term).next() else { continue };
931        let into = call.block;
932        let preds = cfg.predecessors(into);
933        if into == entry || into == block || addressed.contains(&into) {
934            continue;
935        }
936        if preds.len() != 1 || preds[0] != block {
937            continue;
938        }
939        below.insert(block, into);
940        is_below.insert(into);
941    }
942    let heads = func.blocks().filter(|it| below.contains_key(it) && !is_below.contains(it));
943    heads
944        .map(|head| {
945            let mut chain = vec![head];
946            let mut at = head;
947            while let Some(&next) = below.get(&at) {
948                chain.push(next);
949                at = next;
950            }
951            chain
952        })
953        .collect()
954}
955
956/// Every block some `block_addr` names, and every block an image names.
957///
958/// The second kind is the one with nothing in the function to find. An image is in another section
959/// and what it holds is a relocation, so the `goto *p` that arrives at the block can be in a
960/// function this one cannot see, and a block merged into the one above it would be a name pointing
961/// at the wrong instruction.
962fn addressed(func: &Func) -> HashSet<Block> {
963    let mut taken: HashSet<Block> = func.named_blocks().map(|(block, _)| block).collect();
964    for block in func.blocks() {
965        for inst in func.insts(block) {
966            if func[inst].opcode != Opcode::BlockAddr {
967                continue;
968            }
969            for call in func.successors(inst) {
970                taken.insert(call.block);
971            }
972        }
973    }
974    taken
975}
976
977/// Merges one block into the block above it, when that is the only way in, and says whether it did.
978///
979/// Step four of this pass asked for one chain at a time over the whole function, which is what it
980/// wants and is the wrong shape for a caller holding two particular blocks. [`crate::short_circuit`]
981/// is that caller. It has just left a head that jumps to a join nothing else reaches any more, and
982/// the jump and the join's parameter between them are what stands in the way of the collapse above
983/// this one: the next `&&` up asks whether its condition is a comparison, and a block parameter is
984/// not one until the block it belongs to has been merged away.
985///
986/// It is this function rather than one written next door for the reason the rest of the sharing in
987/// this module has. Two answers about when a block may be folded into the one above it would be two
988/// compilers, and the conditions are not obvious ones: the entry keeps its own block, a block whose
989/// address is taken is arrived at from somewhere this cannot see, and a block that jumps to itself
990/// is not above anything.
991pub(crate) fn merge_below(func: &mut Func, an: &mut Analyses, head: Block, into: Block) -> bool {
992    let cfg = an.cfg(func);
993    let Some(entry) = cfg.entry() else { return false };
994    if into == entry || into == head || cfg.predecessors(into) != [head] {
995        return false;
996    }
997    let Some(term) = func.terminator(head) else { return false };
998    if func[term].opcode != Opcode::Jump || addressed(func).contains(&into) {
999        return false;
1000    }
1001    let mut forward = HashMap::new();
1002    merge(func, head, into, &mut forward);
1003    uses::substitute(func, &forward);
1004    an.clear();
1005    true
1006}
1007
1008/// Moves everything in a block into the head of its chain and takes the block out of the function.
1009///
1010/// The jump is what is really being deleted, and the arguments it carried are what the merged
1011/// block's parameters were going to be told. Binding each parameter to the argument in its place
1012/// and pointing every reader at it is exactly what the jump was doing at run time, so the record
1013/// goes in the map and the whole map is spent in one walk when the pass is done.
1014fn merge(func: &mut Func, head: Block, block: Block, forward: &mut HashMap<Value, Value>) {
1015    let term = func.terminator(head).expect("the head of a chain ends in a jump");
1016    let call = func.successors(term).next().expect("a jump goes somewhere");
1017    let args = func[call.args].to_vec();
1018    let params = func[block].params.clone();
1019    for (param, arg) in params.into_iter().zip(args) {
1020        // Through whatever the merge above this one already decided, because a chain of three
1021        // binds the middle block's parameter to something the head passed and then binds the last
1022        // block's parameter to that same parameter.
1023        let arg = uses::chase(forward, arg);
1024        forward.insert(param, arg);
1025    }
1026    func.remove_inst(term);
1027    for inst in func.insts(block).collect::<Vec<Inst>>() {
1028        func.remove_inst(inst);
1029        func.append_inst(head, inst);
1030    }
1031    func.remove_block(block);
1032}
1033
1034/// Whether this condition is always true or always false, given what the edge binds.
1035fn known(func: &Func, value: Value, subst: &Bindings) -> Option<bool> {
1036    let value = resolve(subst, value);
1037    if let Some((imm, _)) = constant(func, value) {
1038        return Some(imm.unsigned() != 0);
1039    }
1040    compared(func, value, subst)
1041}
1042
1043/// What a comparison of two constants comes out as.
1044///
1045/// The comparison itself is never rewritten here, and it does not have to be. [`crate::fold`]
1046/// evaluates one whose operands are both already constants, so what is left for this is the case
1047/// folding cannot see: an operand that is a constant only along the edge being followed, which is
1048/// what `subst` carries. That is the whole reason this is still a question worth asking after
1049/// folding has run.
1050///
1051/// The arithmetic is [`crate::fold::compare`] rather than a copy of it, so the two places cannot
1052/// come to differ about what `slt` means.
1053fn compared(func: &Func, value: Value, subst: &Bindings) -> Option<bool> {
1054    let Def::Result { inst, .. } = func[value].def else { return None };
1055    let data = &func[inst];
1056    if data.opcode != Opcode::ICmp {
1057        return None;
1058    }
1059    let Extra::IntPred(pred) = data.extra else { return None };
1060    let args = &func[data.args];
1061    let (lhs, ty) = constant(func, resolve(subst, *args.first()?))?;
1062    let (rhs, _) = constant(func, resolve(subst, *args.get(1)?))?;
1063    Some(crate::fold::compare(pred, lhs, rhs, ty))
1064}
1065
1066#[cfg(test)]
1067mod tests {
1068    use rucc_base::Interner;
1069    use rucc_ir::{
1070        Block, Builder, Def, Flags, Func, Inst, IntPred, MemInfo, MemOrder, Module, Opcode,
1071        Restrict, Signature, Type, Value,
1072    };
1073    use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
1074
1075    use super::SimplifyCfg;
1076    use crate::stats::Kind;
1077    use crate::testing::graph;
1078    use crate::{Fuel, Pass, Preserved, Stats};
1079
1080    /// Runs the pass with as much fuel as it wants.
1081    fn simplify(func: &mut Func) -> Stats {
1082        SimplifyCfg.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1083    }
1084
1085    /// The blocks the function still has, by number.
1086    fn blocks(func: &Func) -> Vec<usize> {
1087        func.blocks().map(Block::index).collect()
1088    }
1089
1090    /// The opcode of a block's terminator.
1091    fn terminator(func: &Func, block: usize) -> Opcode {
1092        let block = Block::from_usize(block);
1093        func[func.terminator(block).expect("every block here has one")].opcode
1094    }
1095
1096    /// Where a block's terminator goes, as block numbers.
1097    fn goes_to(func: &Func, block: usize) -> Vec<usize> {
1098        let block = Block::from_usize(block);
1099        let term = func.terminator(block).expect("every block here has one");
1100        func.successors(term).map(|call| call.block.index()).collect()
1101    }
1102
1103    /// The block the instruction that produced a value is in now, if it is in one.
1104    ///
1105    /// Which arm of a branch survived is a question about where its code ended up rather than
1106    /// about the shape of the graph, because the arm that survives is merged into the block above
1107    /// it in the same run and the two blocks stop being two.
1108    fn lives_in(func: &Func, value: Value) -> Option<usize> {
1109        let Def::Result { inst, .. } = func[value].def else { return None };
1110        func.block_of(inst).map(Block::index)
1111    }
1112
1113    /// A function with an entry, a `br_if` on `cond`, two arms and a join.
1114    ///
1115    /// The condition is built by the caller out of the builder it is handed, which is what lets
1116    /// one shape stand for a constant, a comparison and a value nothing knows anything about. Each
1117    /// arm holds one instruction that does nothing, which is there to be told apart from the one
1118    /// in the other arm, and the two of them come back with the function.
1119    fn diamond(cond: impl FnOnce(&mut Builder<'_>) -> Value) -> (Func, [Value; 2]) {
1120        let mut names = Interner::new();
1121        let mut func = Func::new(names.intern("f"), Signature::new());
1122        let entry = func.create_block();
1123        let then_block = func.create_block();
1124        let else_block = func.create_block();
1125        let join = func.create_block();
1126        let mut build = Builder::new(&mut func, entry);
1127        let cond = cond(&mut build);
1128        build.br_if(cond, then_block, &[], else_block, &[]);
1129        let mut marks = Vec::new();
1130        for (arm, mark) in [(then_block, 111), (else_block, 222)] {
1131            let mut build = Builder::new(&mut func, arm);
1132            marks.push(build.iconst(Type::int(32), mark));
1133            build.jump(join, &[]);
1134        }
1135        let mut build = Builder::new(&mut func, join);
1136        build.ret(&[]);
1137        (func, [marks[0], marks[1]])
1138    }
1139
1140    #[test]
1141    fn a_branch_on_a_true_constant_becomes_a_jump_to_the_first_arm() {
1142        let (mut func, [taken, other]) = diamond(|build| build.iconst(Type::int(1), 1));
1143        let stats = simplify(&mut func);
1144        assert!(stats.changed());
1145        assert_eq!(stats.count(Kind::Optimized, super::FOLDED), 1);
1146        // The arm it did not take is gone, because nothing else went there, and the arm it did
1147        // take had one way in and went into the entry along with the join below it.
1148        assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
1149        assert_eq!(stats.count(Kind::Optimized, super::MERGED), 2);
1150        assert_eq!(lives_in(&func, taken), Some(0));
1151        assert_eq!(lives_in(&func, other), None);
1152        assert_eq!(blocks(&func), [0]);
1153    }
1154
1155    #[test]
1156    fn a_branch_on_a_false_constant_becomes_a_jump_to_the_second_arm() {
1157        let (mut func, [other, taken]) = diamond(|build| build.iconst(Type::int(1), 0));
1158        assert!(simplify(&mut func).changed());
1159        assert_eq!(lives_in(&func, taken), Some(0));
1160        assert_eq!(lives_in(&func, other), None);
1161        assert_eq!(blocks(&func), [0]);
1162    }
1163
1164    #[test]
1165    fn folding_a_branch_and_merging_what_it_leaves_are_two_things_fuel_buys_apart() {
1166        // The same function as the test above, with fuel for the fold and nothing after it. The
1167        // jump is there to be seen, which is the shape the merge would otherwise take away.
1168        let (mut func, _) = diamond(|build| build.iconst(Type::int(1), 1));
1169        let stats =
1170            SimplifyCfg.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::of(1));
1171        assert_eq!(terminator(&func, 0), Opcode::Jump);
1172        assert_eq!(goes_to(&func, 0), [1]);
1173        assert_eq!(blocks(&func), [0, 1, 3]);
1174        assert_eq!(stats.count(Kind::Optimized, super::MERGED), 0);
1175        // Both blocks of the chain, because a block only reaches the head once the block between
1176        // them has, so running out before the first one means neither.
1177        assert_eq!(stats.count(Kind::Missed, super::NO_FUEL_MERGE), 2);
1178    }
1179
1180    #[test]
1181    fn a_branch_on_a_comparison_of_two_constants_is_read_without_folding_it() {
1182        // Both ways round on every predicate, which is where a sign error or an inverted
1183        // comparison would hide. A comparison the pass reads is left standing, because folding
1184        // it would produce an `i1` on its own and issue 352 says that does not lower.
1185        let cases: &[(IntPred, i128, i128, bool)] = &[
1186            (IntPred::Eq, 7, 7, true),
1187            (IntPred::Eq, 7, 8, false),
1188            (IntPred::Ne, 7, 8, true),
1189            (IntPred::Ne, 7, 7, false),
1190            (IntPred::Slt, -1, 1, true),
1191            (IntPred::Slt, 1, -1, false),
1192            (IntPred::Sle, -1, -1, true),
1193            (IntPred::Sle, 1, -1, false),
1194            (IntPred::Sgt, 1, -1, true),
1195            (IntPred::Sgt, -1, 1, false),
1196            (IntPred::Sge, -1, -1, true),
1197            (IntPred::Sge, -1, 1, false),
1198            (IntPred::Ult, 1, -1, true),
1199            (IntPred::Ult, -1, 1, false),
1200            (IntPred::Ule, -1, -1, true),
1201            (IntPred::Ule, -1, 1, false),
1202            (IntPred::Ugt, -1, 1, true),
1203            (IntPred::Ugt, 1, -1, false),
1204            (IntPred::Uge, -1, -1, true),
1205            (IntPred::Uge, 1, -1, false),
1206        ];
1207        for &(pred, lhs, rhs, taken) in cases {
1208            let (mut func, marks) = diamond(|build| {
1209                let lhs = build.iconst(Type::int(32), lhs);
1210                let rhs = build.iconst(Type::int(32), rhs);
1211                build.icmp(pred, lhs, rhs)
1212            });
1213            assert!(simplify(&mut func).changed(), "{pred:?} {lhs} {rhs}");
1214            let [went, gone] = if taken { [marks[0], marks[1]] } else { [marks[1], marks[0]] };
1215            assert_eq!(lives_in(&func, went), Some(0), "{pred:?} {lhs} {rhs}");
1216            assert_eq!(lives_in(&func, gone), None, "{pred:?} {lhs} {rhs}");
1217            let kept = func.insts(Block::from_usize(0)).any(|it| func[it].opcode == Opcode::ICmp);
1218            assert!(kept, "the comparison was folded away and issue 352 says it must not be");
1219        }
1220    }
1221
1222    #[test]
1223    fn a_branch_on_something_nobody_knows_is_left_alone() {
1224        let mut names = Interner::new();
1225        let mut func = Func::new(names.intern("f"), Signature::new().with_params(&[Type::int(1)]));
1226        let entry = func.create_block();
1227        let then_block = func.create_block();
1228        let else_block = func.create_block();
1229        let cond = func.append_param(entry, Type::int(1));
1230        let mut build = Builder::new(&mut func, entry);
1231        build.br_if(cond, then_block, &[], else_block, &[]);
1232        for arm in [then_block, else_block] {
1233            let mut build = Builder::new(&mut func, arm);
1234            build.ret(&[]);
1235        }
1236        let stats = simplify(&mut func);
1237        assert!(!stats.changed());
1238        assert!(stats.is_empty(), "a pass with nothing to say should say nothing");
1239        assert_eq!(terminator(&func, 0), Opcode::BrIf);
1240        assert_eq!(blocks(&func), [0, 1, 2]);
1241    }
1242
1243    /// A function whose entry switches on a constant, with a marker in the default and in each
1244    /// case, in that order.
1245    fn switched(on: i128, cases: &[i128]) -> (Func, Vec<Value>) {
1246        let mut names = Interner::new();
1247        let mut func = Func::new(names.intern("f"), Signature::new());
1248        let entry = func.create_block();
1249        let arms: Vec<Block> = (0..=cases.len()).map(|_| func.create_block()).collect();
1250        let mut build = Builder::new(&mut func, entry);
1251        let value = build.iconst(Type::int(32), on);
1252        let pairs: Vec<(i128, Block)> =
1253            cases.iter().enumerate().map(|(at, &case)| (case, arms[at + 1])).collect();
1254        build.switch(value, arms[0], &pairs);
1255        let mut marks = Vec::new();
1256        for (at, &arm) in arms.iter().enumerate() {
1257            let mut build = Builder::new(&mut func, arm);
1258            marks.push(build.iconst(Type::int(32), 100 + at as i128));
1259            build.ret(&[]);
1260        }
1261        (func, marks)
1262    }
1263
1264    #[test]
1265    fn a_switch_on_a_constant_takes_the_case_that_matches() {
1266        let (mut func, marks) = switched(5, &[4, 5]);
1267        assert!(simplify(&mut func).changed());
1268        assert_eq!(lives_in(&func, marks[2]), Some(0));
1269        assert_eq!(lives_in(&func, marks[0]), None);
1270        assert_eq!(lives_in(&func, marks[1]), None);
1271        assert_eq!(blocks(&func), [0]);
1272    }
1273
1274    #[test]
1275    fn a_switch_on_a_constant_no_case_names_takes_the_default() {
1276        let (mut func, marks) = switched(9, &[4]);
1277        assert!(simplify(&mut func).changed());
1278        assert_eq!(lives_in(&func, marks[0]), Some(0));
1279        assert_eq!(lives_in(&func, marks[1]), None);
1280        assert_eq!(blocks(&func), [0]);
1281    }
1282
1283    #[test]
1284    fn the_arguments_travel_with_the_edge_that_survives() {
1285        // The whole reason there are no phi nodes: the argument is in the branch beside the
1286        // block it goes to, so the surviving arm brings its own and the other one leaves with
1287        // the edge it was on. Both arms name the same block, so this is also the case branch
1288        // simplification has to leave alone: one block and two edges, because the two edges say
1289        // different things.
1290        let mut names = Interner::new();
1291        let mut func = Func::new(names.intern("f"), Signature::new());
1292        let entry = func.create_block();
1293        let join = func.create_block();
1294        let param = func.append_param(join, Type::int(32));
1295        let mut build = Builder::new(&mut func, entry);
1296        let cond = build.iconst(Type::int(1), 0);
1297        let taken = build.iconst(Type::int(32), 11);
1298        let other = build.iconst(Type::int(32), 22);
1299        build.br_if(cond, join, &[other], join, &[taken]);
1300        let mut build = Builder::new(&mut func, join);
1301        build.ret(&[param]);
1302        assert!(simplify(&mut func).changed());
1303        // The jump took the edge that survived, and then the block below it had one way in and
1304        // came up, which is where the parameter stopped being a parameter: whatever read it reads
1305        // the argument that edge was carrying.
1306        assert_eq!(blocks(&func), [0]);
1307        let term = func.terminator(entry).expect("the entry has one");
1308        assert_eq!(func[func[term].args], [taken]);
1309        assert_ne!(func[func[term].args], [param]);
1310    }
1311
1312    #[test]
1313    fn a_branch_whose_arms_are_the_same_edge_becomes_a_jump() {
1314        // Section 21.1's branch simplification, which is about the targets rather than about the
1315        // condition: nothing here knows what `cond` is and it does not matter, because both ways
1316        // out arrive at the same place carrying the same thing.
1317        let mut names = Interner::new();
1318        let signature = Signature::new().with_params(&[Type::int(1)]);
1319        let mut func = Func::new(names.intern("f"), signature);
1320        let entry = func.create_block();
1321        let join = func.create_block();
1322        let cond = func.append_param(entry, Type::int(1));
1323        let mut build = Builder::new(&mut func, entry);
1324        build.br_if(cond, join, &[], join, &[]);
1325        let mut build = Builder::new(&mut func, join);
1326        build.ret(&[]);
1327        let stats = simplify(&mut func);
1328        assert_eq!(stats.count(Kind::Optimized, super::FOLDED), 1);
1329        assert_eq!(stats.count(Kind::Optimized, super::MERGED), 1);
1330        assert_eq!(blocks(&func), [0]);
1331        assert_eq!(terminator(&func, 0), Opcode::Return);
1332    }
1333
1334    #[test]
1335    fn a_switch_whose_cases_all_go_to_one_place_becomes_a_jump() {
1336        let mut names = Interner::new();
1337        let signature = Signature::new().with_params(&[Type::int(32)]);
1338        let mut func = Func::new(names.intern("f"), signature);
1339        let entry = func.create_block();
1340        let join = func.create_block();
1341        let value = func.append_param(entry, Type::int(32));
1342        let mut build = Builder::new(&mut func, entry);
1343        build.switch(value, join, &[(4, join), (5, join)]);
1344        let mut build = Builder::new(&mut func, join);
1345        build.ret(&[]);
1346        let stats = simplify(&mut func);
1347        assert_eq!(stats.count(Kind::Optimized, super::FOLDED), 1);
1348        assert_eq!(blocks(&func), [0]);
1349    }
1350
1351    #[test]
1352    fn a_branch_to_one_block_by_two_edges_that_differ_is_left_alone() {
1353        // One block and two edges. Folding would have to pick one of the two arguments, and
1354        // whichever it picked would be the wrong one half the time.
1355        let mut names = Interner::new();
1356        let signature = Signature::new().with_params(&[Type::int(1)]);
1357        let mut func = Func::new(names.intern("f"), signature);
1358        let entry = func.create_block();
1359        let join = func.create_block();
1360        let cond = func.append_param(entry, Type::int(1));
1361        let param = func.append_param(join, Type::int(32));
1362        let mut build = Builder::new(&mut func, entry);
1363        let first = build.iconst(Type::int(32), 11);
1364        let second = build.iconst(Type::int(32), 22);
1365        build.br_if(cond, join, &[first], join, &[second]);
1366        let mut build = Builder::new(&mut func, join);
1367        // Returned rather than dropped, because a parameter nobody reads is one the step that
1368        // takes those out would take, and this test is about the branch above it.
1369        build.ret(&[param]);
1370        let stats = simplify(&mut func);
1371        assert!(!stats.changed());
1372        assert_eq!(terminator(&func, 0), Opcode::BrIf);
1373        assert_eq!(blocks(&func), [0, 1]);
1374    }
1375
1376    #[test]
1377    fn a_block_the_dead_arm_shared_with_a_live_one_stays() {
1378        // Issue 359 in the small. The block holding `bar` is inside the body of the dead `if`
1379        // and is a `case` of the switch as well, so the arm goes and the block does not.
1380        let mut names = Interner::new();
1381        let mut func = Func::new(names.intern("f"), Signature::new().with_params(&[Type::int(32)]));
1382        let entry = func.create_block();
1383        let dead = func.create_block();
1384        let shared = func.create_block();
1385        let exit = func.create_block();
1386        let x = func.append_param(entry, Type::int(32));
1387        let mut build = Builder::new(&mut func, entry);
1388        let never = build.iconst(Type::int(1), 0);
1389        build.switch(x, exit, &[(0, dead), (1, shared)]);
1390        // The `if (0)` inside the first case, whose body is where the second case's label sits.
1391        // The constant is the body of the arm that survives, and it is there so that the block is
1392        // a block with something in it rather than a forwarder that step three points past.
1393        let mut build = Builder::new(&mut func, dead);
1394        build.iconst(Type::int(32), 1);
1395        build.br_if(never, shared, &[], exit, &[]);
1396        for arm in [shared, exit] {
1397            let mut build = Builder::new(&mut func, arm);
1398            build.ret(&[]);
1399        }
1400        let stats = simplify(&mut func);
1401        assert!(stats.changed());
1402        // The switch is on a parameter, so it stays. The branch inside the dead arm folds to the
1403        // exit, and nothing is removed at all, because the shared block is still a case.
1404        assert_eq!(terminator(&func, 0), Opcode::Switch);
1405        assert_eq!(goes_to(&func, 1), [3]);
1406        assert_eq!(blocks(&func), [0, 1, 2, 3]);
1407        assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 0);
1408    }
1409
1410    #[test]
1411    fn a_block_whose_address_is_taken_is_not_removed() {
1412        // Reachability here has to be the verifier's reachability. The graph does not carry the
1413        // edge from a `block_addr` to the block it names, and a pass that removed the block
1414        // under one would leave an instruction pointing at nothing.
1415        let mut names = Interner::new();
1416        let mut func = Func::new(names.intern("f"), Signature::new());
1417        let entry = func.create_block();
1418        let labelled = func.create_block();
1419        let arm = func.create_block();
1420        let mut build = Builder::new(&mut func, entry);
1421        let cond = build.iconst(Type::int(1), 1);
1422        let addr = build.block_addr(labelled);
1423        build.br_if(cond, arm, &[], labelled, &[]);
1424        let mut build = Builder::new(&mut func, arm);
1425        build.indirect_br(addr, &[labelled]);
1426        let mut build = Builder::new(&mut func, labelled);
1427        build.ret(&[]);
1428        assert!(simplify(&mut func).changed());
1429        assert!(blocks(&func).contains(&1), "the labelled block went with the arm");
1430        // The arm had one way in and came up into the entry, which is where the `indirect_br`
1431        // that reaches the labelled block is now.
1432        assert_eq!(blocks(&func), [0, 1]);
1433        assert_eq!(goes_to(&func, 0), [1]);
1434    }
1435
1436    #[test]
1437    fn a_block_only_an_unreachable_block_takes_the_address_of_goes_too() {
1438        // The other half of the same rule. Once the block holding the `block_addr` is gone, the
1439        // address is gone with it, and the block it named is reached by nothing.
1440        let mut names = Interner::new();
1441        let mut func = Func::new(names.intern("f"), Signature::new());
1442        let entry = func.create_block();
1443        let dead = func.create_block();
1444        let labelled = func.create_block();
1445        let mut build = Builder::new(&mut func, entry);
1446        let cond = build.iconst(Type::int(1), 1);
1447        build.br_if(cond, entry, &[], dead, &[]);
1448        let mut build = Builder::new(&mut func, dead);
1449        let addr = build.block_addr(labelled);
1450        build.indirect_br(addr, &[labelled]);
1451        let mut build = Builder::new(&mut func, labelled);
1452        build.ret(&[]);
1453        assert!(simplify(&mut func).changed());
1454        assert_eq!(blocks(&func), [0]);
1455    }
1456
1457    #[test]
1458    fn a_block_nothing_reaches_goes_even_when_no_branch_folded() {
1459        // Section 6.5 says this pass is the one that deletes them, and it says so about the
1460        // blocks the front end handed over as well as the ones a fold here stranded. Nothing in
1461        // this function folds, and the block still has to go, because every analysis below reads
1462        // the graph as though it is not there.
1463        let mut func = graph(&[&[], &[]]);
1464        let stats = simplify(&mut func);
1465        assert_eq!(stats.count(Kind::Optimized, super::FOLDED), 0);
1466        assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
1467        assert_eq!(blocks(&func), [0]);
1468    }
1469
1470    #[test]
1471    fn a_block_with_one_way_into_it_goes_into_the_block_above_it() {
1472        // Something in each of the first two blocks, so that this is three blocks for the merge
1473        // rather than two forwarders step three would point past before it got here.
1474        let mut names = Interner::new();
1475        let mut func = Func::new(names.intern("f"), Signature::new());
1476        let entry = func.create_block();
1477        let middle = func.create_block();
1478        let last = func.create_block();
1479        let mut build = Builder::new(&mut func, entry);
1480        build.iconst(Type::int(32), 1);
1481        build.jump(middle, &[]);
1482        let mut build = Builder::new(&mut func, middle);
1483        build.iconst(Type::int(32), 2);
1484        build.jump(last, &[]);
1485        let mut build = Builder::new(&mut func, last);
1486        build.ret(&[]);
1487        let stats = simplify(&mut func);
1488        // A run of three is one chain and not two rounds of one pair, because the block in the
1489        // middle stops being a block partway through.
1490        assert_eq!(stats.count(Kind::Optimized, super::MERGED), 2);
1491        assert_eq!(stats.count(Kind::Optimized, super::FORWARDED), 0);
1492        assert_eq!(blocks(&func), [0]);
1493        assert_eq!(terminator(&func, 0), Opcode::Return);
1494    }
1495
1496    #[test]
1497    fn a_block_with_two_ways_into_it_stays_where_it_is() {
1498        // The join of a diamond nobody can fold. Merging it into either arm would leave the other
1499        // arm branching into the middle of a block.
1500        let mut names = Interner::new();
1501        let signature = Signature::new().with_params(&[Type::int(1)]);
1502        let mut func = Func::new(names.intern("f"), signature);
1503        let entry = func.create_block();
1504        let then_block = func.create_block();
1505        let else_block = func.create_block();
1506        let join = func.create_block();
1507        let cond = func.append_param(entry, Type::int(1));
1508        let mut build = Builder::new(&mut func, entry);
1509        build.br_if(cond, then_block, &[], else_block, &[]);
1510        for (arm, mark) in [(then_block, 111), (else_block, 222)] {
1511            // An arm with something in it, because an empty one is a forwarder and step three
1512            // would take it away before merging ever looked at the join.
1513            let mut build = Builder::new(&mut func, arm);
1514            build.iconst(Type::int(32), mark);
1515            build.jump(join, &[]);
1516        }
1517        let mut build = Builder::new(&mut func, join);
1518        build.ret(&[]);
1519        let stats = simplify(&mut func);
1520        assert!(!stats.changed());
1521        assert_eq!(blocks(&func), [0, 1, 2, 3]);
1522    }
1523
1524    #[test]
1525    fn a_block_above_one_that_does_not_end_in_a_jump_keeps_it() {
1526        // One way into the join, and the block above it is a branch. Merging would take the
1527        // terminator off the other arm.
1528        let mut names = Interner::new();
1529        let signature = Signature::new().with_params(&[Type::int(1)]);
1530        let mut func = Func::new(names.intern("f"), signature);
1531        let entry = func.create_block();
1532        let arm = func.create_block();
1533        let exit = func.create_block();
1534        let cond = func.append_param(entry, Type::int(1));
1535        let mut build = Builder::new(&mut func, entry);
1536        build.br_if(cond, arm, &[], exit, &[]);
1537        for block in [arm, exit] {
1538            let mut build = Builder::new(&mut func, block);
1539            build.ret(&[]);
1540        }
1541        let stats = simplify(&mut func);
1542        assert!(!stats.changed());
1543        assert_eq!(blocks(&func), [0, 1, 2]);
1544    }
1545
1546    #[test]
1547    fn the_entry_block_is_never_the_one_that_moves() {
1548        // A loop back to the entry, so the entry has one way in and the block above it ends in a
1549        // jump, which is every condition but the one that matters. Control arrives at the entry
1550        // and it has to still be there when it does.
1551        let mut names = Interner::new();
1552        let signature = Signature::new().with_params(&[Type::int(1)]);
1553        let mut func = Func::new(names.intern("f"), signature);
1554        let entry = func.create_block();
1555        let latch = func.create_block();
1556        let exit = func.create_block();
1557        let cond = func.append_param(entry, Type::int(1));
1558        let mut build = Builder::new(&mut func, entry);
1559        build.br_if(cond, latch, &[], exit, &[]);
1560        // The body of the loop, which is there so that the latch is a block and not a forwarder.
1561        let mut build = Builder::new(&mut func, latch);
1562        build.iconst(Type::int(32), 1);
1563        build.jump(entry, &[]);
1564        let mut build = Builder::new(&mut func, exit);
1565        build.ret(&[]);
1566        let stats = simplify(&mut func);
1567        assert!(!stats.changed());
1568        assert_eq!(blocks(&func), [0, 1, 2]);
1569    }
1570
1571    #[test]
1572    fn a_block_whose_address_is_taken_is_not_merged_away_either() {
1573        // The same rule as the one about deleting it. Merging it into the block above would take
1574        // the block out of the function, and the `block_addr` would name one that is not there.
1575        let mut names = Interner::new();
1576        let mut func = Func::new(names.intern("f"), Signature::new());
1577        let entry = func.create_block();
1578        let middle = func.create_block();
1579        let labelled = func.create_block();
1580        let mut build = Builder::new(&mut func, entry);
1581        build.block_addr(labelled);
1582        build.jump(middle, &[]);
1583        // Something in the middle block, so that this is a question about merging rather than one
1584        // about the forwarder removal that would otherwise get there first.
1585        let mut build = Builder::new(&mut func, middle);
1586        build.iconst(Type::int(32), 1);
1587        build.jump(labelled, &[]);
1588        let mut build = Builder::new(&mut func, labelled);
1589        build.ret(&[]);
1590        let stats = simplify(&mut func);
1591        // The middle block had one way in and no address, so it came up. The labelled block has
1592        // one way in too, and stayed.
1593        assert_eq!(stats.count(Kind::Optimized, super::MERGED), 1);
1594        assert_eq!(blocks(&func), [0, 2]);
1595    }
1596
1597    #[test]
1598    fn a_block_an_image_names_is_not_merged_away_either() {
1599        // The case with nothing in the function to find. No `block_addr` names this block, only a
1600        // relocation in another section does, so the whole of what keeps it is the name, and a
1601        // merge would leave that name on an instruction in the middle of the block above.
1602        let mut names = Interner::new();
1603        let mut func = Func::new(names.intern("f"), Signature::new());
1604        let entry = func.create_block();
1605        let middle = func.create_block();
1606        let labelled = func.create_block();
1607        Builder::new(&mut func, entry).jump(middle, &[]);
1608        let mut build = Builder::new(&mut func, middle);
1609        build.iconst(Type::int(32), 1);
1610        build.jump(labelled, &[]);
1611        Builder::new(&mut func, labelled).ret(&[]);
1612        func.name_block(labelled, names.intern(".Llbl.0"));
1613        let stats = simplify(&mut func);
1614        assert_eq!(stats.count(Kind::Optimized, super::MERGED), 1);
1615        assert_eq!(blocks(&func), [0, 2]);
1616        assert_eq!(func.block_name(labelled), Some(names.intern(".Llbl.0")));
1617    }
1618
1619    #[test]
1620    fn merging_binds_a_block_parameter_to_the_argument_the_jump_carried() {
1621        let mut names = Interner::new();
1622        let mut func = Func::new(names.intern("f"), Signature::new());
1623        let entry = func.create_block();
1624        let below = func.create_block();
1625        let param = func.append_param(below, Type::int(32));
1626        let mut build = Builder::new(&mut func, entry);
1627        let arg = build.iconst(Type::int(32), 7);
1628        build.jump(below, &[arg]);
1629        let mut build = Builder::new(&mut func, below);
1630        build.ret(&[param]);
1631        assert!(simplify(&mut func).changed());
1632        assert_eq!(blocks(&func), [0]);
1633        let term = func.terminator(entry).expect("the entry has one");
1634        assert_eq!(func[func[term].args], [arg]);
1635    }
1636
1637    #[test]
1638    fn a_chain_of_merges_follows_a_parameter_bound_to_a_parameter() {
1639        // The middle block passes its own parameter down, so the last block's parameter is bound
1640        // to something that is on its way to being the entry's constant. Following the map is
1641        // what makes the second merge worth as much as the first.
1642        let mut names = Interner::new();
1643        let mut func = Func::new(names.intern("f"), Signature::new());
1644        let entry = func.create_block();
1645        let middle = func.create_block();
1646        let last = func.create_block();
1647        let carried = func.append_param(middle, Type::int(32));
1648        let arrived = func.append_param(last, Type::int(32));
1649        let mut build = Builder::new(&mut func, entry);
1650        let arg = build.iconst(Type::int(32), 7);
1651        build.jump(middle, &[arg]);
1652        let mut build = Builder::new(&mut func, middle);
1653        build.jump(last, &[carried]);
1654        let mut build = Builder::new(&mut func, last);
1655        build.ret(&[arrived]);
1656        assert!(simplify(&mut func).changed());
1657        assert_eq!(blocks(&func), [0]);
1658        let term = func.terminator(entry).expect("the entry has one");
1659        assert_eq!(func[func[term].args], [arg]);
1660    }
1661
1662    /// A function whose entry branches on its own parameter into two arms that each hold one
1663    /// instruction and then jump where the caller says, head to tail.
1664    ///
1665    /// Two arms rather than one because almost every question about step three is a question about
1666    /// a block with more than one way in, and something in each arm because an empty arm is itself
1667    /// a forwarder and would answer a different question. The blocks are entry 0, the arms 1 and 2,
1668    /// and whatever the caller builds after that.
1669    fn arms(func: &mut Func) -> (Value, [Block; 2]) {
1670        let entry = func.create_block();
1671        let first = func.create_block();
1672        let second = func.create_block();
1673        let cond = func.append_param(entry, Type::int(1));
1674        let mut build = Builder::new(func, entry);
1675        let carried = build.iconst(Type::int(32), 7);
1676        build.br_if(cond, first, &[], second, &[]);
1677        for (arm, mark) in [(first, 111), (second, 222)] {
1678            let mut build = Builder::new(func, arm);
1679            build.iconst(Type::int(32), mark);
1680        }
1681        (carried, [first, second])
1682    }
1683
1684    /// A function with one `i1` parameter, which is what [`arms`] wants.
1685    fn taking_a_condition() -> Func {
1686        let mut names = Interner::new();
1687        let signature = Signature::new().with_params(&[Type::int(1)]);
1688        Func::new(names.intern("f"), signature)
1689    }
1690
1691    /// The arguments a block's terminator passes on the edge in that place.
1692    fn carries(func: &Func, block: usize, edge: usize) -> Vec<Value> {
1693        let block = Block::from_usize(block);
1694        let term = func.terminator(block).expect("every block here has one");
1695        let call = func.successors(term).nth(edge).expect("the edge is there");
1696        func[call.args].to_vec()
1697    }
1698
1699    #[test]
1700    fn a_block_that_does_nothing_but_jump_stops_being_in_the_way() {
1701        // Section 21.1's edge forwarding. Two arms arrive at a block that only jumps, so the two
1702        // of them go where it was going and it is not there any more.
1703        let mut func = taking_a_condition();
1704        let (_, arms) = arms(&mut func);
1705        let forwarder = func.create_block();
1706        let exit = func.create_block();
1707        for arm in arms {
1708            Builder::new(&mut func, arm).jump(forwarder, &[]);
1709        }
1710        Builder::new(&mut func, forwarder).jump(exit, &[]);
1711        Builder::new(&mut func, exit).ret(&[]);
1712        let stats = simplify(&mut func);
1713        assert_eq!(stats.count(Kind::Optimized, super::FORWARDED), 1);
1714        assert_eq!(blocks(&func), [0, 1, 2, 4]);
1715        assert_eq!(goes_to(&func, 1), [4]);
1716        assert_eq!(goes_to(&func, 2), [4]);
1717    }
1718
1719    #[test]
1720    fn a_forwarder_hands_its_predecessors_the_arguments_it_was_passing() {
1721        // The forwarder was passing something, and taking it out means whoever ends up branching
1722        // to the block below has to pass it instead. Section 21.1's extra condition is about
1723        // exactly this, and a block with no parameters and no instructions cannot be where the
1724        // value came from, so there is nothing further to check. The one way in is through a block
1725        // that goes nowhere else, which keeps the edge off the list of ones that carry a move with
1726        // no block to put it in.
1727        let mut func = taking_a_condition();
1728        let (carried, [arm, above]) = arms(&mut func);
1729        let forwarder = func.create_block();
1730        let exit = func.create_block();
1731        let other = func.append_param(exit, Type::int(32));
1732        let mut build = Builder::new(&mut func, arm);
1733        let mine = build.iconst(Type::int(32), 9);
1734        build.jump(exit, &[mine]);
1735        Builder::new(&mut func, above).jump(forwarder, &[]);
1736        Builder::new(&mut func, forwarder).jump(exit, &[carried]);
1737        Builder::new(&mut func, exit).ret(&[other]);
1738        let stats = simplify(&mut func);
1739        assert_eq!(stats.count(Kind::Optimized, super::FORWARDED), 1);
1740        assert_eq!(blocks(&func), [0, 1, 2, 4]);
1741        // The block above the forwarder is the edge that used to go through it, and it is carrying
1742        // what the forwarder was carrying.
1743        assert_eq!(carries(&func, 2, 0), [carried]);
1744        assert_eq!(carries(&func, 1, 0), [mine]);
1745        // Two edges saying different things, so the parameter is not redundant and stays.
1746        assert_eq!(stats.count(Kind::Optimized, super::SAME_EVERY_WAY), 0);
1747    }
1748
1749    #[test]
1750    fn a_forwarder_carrying_something_on_an_edge_out_of_a_branch_stays() {
1751        // Both ways out of the entry end up at the same block, and that block takes a parameter, so
1752        // the edge through the forwarder is one the back end would have to split again the moment
1753        // the forwarder stopped being there. The block is already the split, in the place the
1754        // layout wants it, so it is left where it is.
1755        let mut func = taking_a_condition();
1756        let (carried, [arm, forwarder]) = arms(&mut func);
1757        let exit = func.create_block();
1758        let other = func.append_param(exit, Type::int(32));
1759        // The second arm is emptied back out, which is what makes it a forwarder at all.
1760        for inst in func.insts(forwarder).collect::<Vec<Inst>>() {
1761            func.remove_inst(inst);
1762        }
1763        let mut build = Builder::new(&mut func, arm);
1764        let mine = build.iconst(Type::int(32), 9);
1765        build.jump(exit, &[mine]);
1766        Builder::new(&mut func, forwarder).jump(exit, &[carried]);
1767        Builder::new(&mut func, exit).ret(&[other]);
1768        let stats = simplify(&mut func);
1769        assert_eq!(stats.count(Kind::Optimized, super::FORWARDED), 0);
1770        assert_eq!(blocks(&func), [0, 1, 2, 3]);
1771    }
1772
1773    #[test]
1774    fn a_forwarder_carrying_nothing_out_of_a_branch_goes_anyway() {
1775        // The same shape with nothing on the edge. There is no move to find a place for, so the
1776        // back end would leave the edge alone and the block is only in the way.
1777        let mut func = taking_a_condition();
1778        let (_, [arm, forwarder]) = arms(&mut func);
1779        let exit = func.create_block();
1780        for inst in func.insts(forwarder).collect::<Vec<Inst>>() {
1781            func.remove_inst(inst);
1782        }
1783        Builder::new(&mut func, arm).jump(exit, &[]);
1784        Builder::new(&mut func, forwarder).jump(exit, &[]);
1785        Builder::new(&mut func, exit).ret(&[]);
1786        let stats = simplify(&mut func);
1787        assert_eq!(stats.count(Kind::Optimized, super::FORWARDED), 1);
1788        assert_eq!(blocks(&func), [0, 1, 3]);
1789    }
1790
1791    #[test]
1792    fn a_block_that_jumps_to_itself_is_not_a_forwarder() {
1793        // Section 21.1 says so in as many words, and the reason is that it does not forward
1794        // anywhere: pointing its predecessors past it would have to point them at it.
1795        let mut names = Interner::new();
1796        let mut func = Func::new(names.intern("f"), Signature::new());
1797        let entry = func.create_block();
1798        let spin = func.create_block();
1799        Builder::new(&mut func, entry).jump(spin, &[]);
1800        Builder::new(&mut func, spin).jump(spin, &[]);
1801        let stats = simplify(&mut func);
1802        assert!(!stats.changed());
1803        assert_eq!(blocks(&func), [0, 1]);
1804    }
1805
1806    #[test]
1807    fn the_entry_block_is_never_the_forwarder_that_goes() {
1808        // The entry doing nothing but jumping is every condition of a forwarder except the one
1809        // that matters. What happens instead is the block below coming up into it, which leaves
1810        // control arriving where it has to arrive.
1811        let mut names = Interner::new();
1812        let mut func = Func::new(names.intern("f"), Signature::new());
1813        let entry = func.create_block();
1814        let below = func.create_block();
1815        Builder::new(&mut func, entry).jump(below, &[]);
1816        let mut build = Builder::new(&mut func, below);
1817        build.iconst(Type::int(32), 1);
1818        build.ret(&[]);
1819        let stats = simplify(&mut func);
1820        assert_eq!(stats.count(Kind::Optimized, super::FORWARDED), 0);
1821        assert_eq!(stats.count(Kind::Optimized, super::MERGED), 1);
1822        assert_eq!(blocks(&func), [0]);
1823    }
1824
1825    #[test]
1826    fn a_block_whose_address_is_taken_is_not_forwarded_past_either() {
1827        // The abnormal edge condition, which in this IR is the edge an `indirect_br` takes. The
1828        // block is arrived at from somewhere the graph reads from the other end, and pointing the
1829        // edges the graph does carry past it would not move that one.
1830        let mut names = Interner::new();
1831        let mut func = Func::new(names.intern("f"), Signature::new());
1832        let entry = func.create_block();
1833        let labelled = func.create_block();
1834        let exit = func.create_block();
1835        let mut build = Builder::new(&mut func, entry);
1836        let addr = build.block_addr(labelled);
1837        build.indirect_br(addr, &[labelled]);
1838        Builder::new(&mut func, labelled).jump(exit, &[]);
1839        let mut build = Builder::new(&mut func, exit);
1840        build.iconst(Type::int(32), 1);
1841        build.ret(&[]);
1842        let stats = simplify(&mut func);
1843        assert_eq!(stats.count(Kind::Optimized, super::FORWARDED), 0);
1844        assert!(blocks(&func).contains(&1), "the labelled block was forwarded past");
1845    }
1846
1847    #[test]
1848    fn a_run_of_forwarders_comes_out_as_one_edge() {
1849        let mut func = taking_a_condition();
1850        let (_, arms) = arms(&mut func);
1851        let first = func.create_block();
1852        let second = func.create_block();
1853        let exit = func.create_block();
1854        for arm in arms {
1855            Builder::new(&mut func, arm).jump(first, &[]);
1856        }
1857        Builder::new(&mut func, first).jump(second, &[]);
1858        Builder::new(&mut func, second).jump(exit, &[]);
1859        Builder::new(&mut func, exit).ret(&[]);
1860        let stats = simplify(&mut func);
1861        assert_eq!(stats.count(Kind::Optimized, super::FORWARDED), 2);
1862        assert_eq!(blocks(&func), [0, 1, 2, 5]);
1863        assert_eq!(goes_to(&func, 1), [5]);
1864        assert_eq!(goes_to(&func, 2), [5]);
1865    }
1866
1867    #[test]
1868    fn a_block_parameter_that_arrives_as_one_value_every_way_in_goes() {
1869        // Section 21.2. The parameter is not carrying anything, it is spelling the constant a
1870        // second way, and document 12 cannot see through the spelling.
1871        let mut func = taking_a_condition();
1872        let (carried, arms) = arms(&mut func);
1873        let join = func.create_block();
1874        let param = func.append_param(join, Type::int(32));
1875        for arm in arms {
1876            Builder::new(&mut func, arm).jump(join, &[carried]);
1877        }
1878        Builder::new(&mut func, join).ret(&[param]);
1879        let stats = simplify(&mut func);
1880        assert_eq!(stats.count(Kind::Optimized, super::SAME_EVERY_WAY), 1);
1881        assert!(func[Block::from_usize(3)].params.is_empty());
1882        // What read the parameter reads the value it was always going to be.
1883        let term = func.terminator(Block::from_usize(3)).expect("the join has one");
1884        assert_eq!(func[func[term].args], [carried]);
1885        // And the argument in its place is off both edges, because a branch that passes more
1886        // arguments than the block takes is one the verifier refuses.
1887        assert!(carries(&func, 1, 0).is_empty());
1888        assert!(carries(&func, 2, 0).is_empty());
1889    }
1890
1891    #[test]
1892    fn a_block_parameter_that_differs_on_one_way_in_stays() {
1893        let mut func = taking_a_condition();
1894        let (carried, arms) = arms(&mut func);
1895        let join = func.create_block();
1896        let param = func.append_param(join, Type::int(32));
1897        let mut build = Builder::new(&mut func, arms[0]);
1898        let mine = build.iconst(Type::int(32), 9);
1899        build.jump(join, &[mine]);
1900        Builder::new(&mut func, arms[1]).jump(join, &[carried]);
1901        Builder::new(&mut func, join).ret(&[param]);
1902        let stats = simplify(&mut func);
1903        assert!(!stats.changed());
1904        assert_eq!(func[Block::from_usize(3)].params, [param]);
1905    }
1906
1907    #[test]
1908    fn a_loop_header_parameter_whose_other_argument_is_itself_is_what_it_started_as() {
1909        // The subtlety section 21.2 spends its second paragraph on. The latch passes the
1910        // parameter back, so reading the arguments literally says two values and says leave it
1911        // alone. A value that can only ever be itself or the initial one was the initial one.
1912        let mut names = Interner::new();
1913        let signature = Signature::new().with_params(&[Type::int(1)]);
1914        let mut func = Func::new(names.intern("f"), signature);
1915        let entry = func.create_block();
1916        let header = func.create_block();
1917        let latch = func.create_block();
1918        let exit = func.create_block();
1919        let cond = func.append_param(entry, Type::int(1));
1920        let param = func.append_param(header, Type::int(32));
1921        let mut build = Builder::new(&mut func, entry);
1922        let init = build.iconst(Type::int(32), 7);
1923        build.jump(header, &[init]);
1924        Builder::new(&mut func, header).br_if(cond, latch, &[], exit, &[]);
1925        let mut build = Builder::new(&mut func, latch);
1926        build.iconst(Type::int(32), 1);
1927        build.jump(header, &[param]);
1928        Builder::new(&mut func, exit).ret(&[param]);
1929        let stats = simplify(&mut func);
1930        assert_eq!(stats.count(Kind::Optimized, super::SAME_EVERY_WAY), 1);
1931        assert!(func[Block::from_usize(1)].params.is_empty());
1932        let term = func.terminator(Block::from_usize(3)).expect("the exit has one");
1933        assert_eq!(func[func[term].args], [init]);
1934    }
1935
1936    #[test]
1937    fn the_entry_blocks_parameters_are_the_functions_and_stay() {
1938        // The entry's parameters arrive from the caller, which is a way in the graph has no edge
1939        // for. A branch back to the entry is one edge out of two, and reading it as though it
1940        // were the only one would replace an argument with whatever the loop happened to pass.
1941        let mut names = Interner::new();
1942        let signature = Signature::new().with_params(&[Type::int(1), Type::int(32)]);
1943        let mut func = Func::new(names.intern("f"), signature);
1944        let entry = func.create_block();
1945        let latch = func.create_block();
1946        let exit = func.create_block();
1947        let cond = func.append_param(entry, Type::int(1));
1948        let x = func.append_param(entry, Type::int(32));
1949        Builder::new(&mut func, entry).br_if(cond, latch, &[], exit, &[]);
1950        let mut build = Builder::new(&mut func, latch);
1951        let one = build.iconst(Type::int(1), 1);
1952        let seven = build.iconst(Type::int(32), 7);
1953        build.jump(entry, &[one, seven]);
1954        Builder::new(&mut func, exit).ret(&[x]);
1955        let stats = simplify(&mut func);
1956        assert!(!stats.changed());
1957        assert_eq!(func[Block::from_usize(0)].params, [cond, x]);
1958    }
1959
1960    #[test]
1961    fn taking_one_parameter_away_is_what_makes_the_next_one_redundant() {
1962        // Section 21.2's reason for a worklist. The last block's parameter arrives as the middle
1963        // block's parameter one way and as the constant the other way, which is two values until
1964        // the middle block's parameter turns out to be that same constant.
1965        let mut func = taking_a_condition();
1966        let (carried, arms) = arms(&mut func);
1967        let join = func.create_block();
1968        let inner = func.append_param(join, Type::int(32));
1969        let left = func.create_block();
1970        let right = func.create_block();
1971        let last = func.create_block();
1972        let outer = func.append_param(last, Type::int(32));
1973        for arm in arms {
1974            Builder::new(&mut func, arm).jump(join, &[carried]);
1975        }
1976        let cond = func[Block::from_usize(0)].params[0];
1977        Builder::new(&mut func, join).br_if(cond, left, &[], right, &[]);
1978        let mut build = Builder::new(&mut func, left);
1979        build.iconst(Type::int(32), 1);
1980        build.jump(last, &[inner]);
1981        let mut build = Builder::new(&mut func, right);
1982        build.iconst(Type::int(32), 2);
1983        build.jump(last, &[carried]);
1984        Builder::new(&mut func, last).ret(&[outer]);
1985        let stats = simplify(&mut func);
1986        assert_eq!(stats.count(Kind::Optimized, super::SAME_EVERY_WAY), 2);
1987        let term = func.terminator(Block::from_usize(6)).expect("the last block has one");
1988        assert_eq!(func[func[term].args], [carried]);
1989    }
1990
1991    #[test]
1992    fn a_forwarder_with_a_parameter_goes_once_the_parameter_does() {
1993        // The two halves of step three being one step. The block passes its own parameter on, so
1994        // it is not a forwarder while it has one, and the parameter is the same value both ways
1995        // in, so it does not have one for long.
1996        let mut func = taking_a_condition();
1997        let (carried, arms) = arms(&mut func);
1998        let forwarder = func.create_block();
1999        let param = func.append_param(forwarder, Type::int(32));
2000        let exit = func.create_block();
2001        let arrived = func.append_param(exit, Type::int(32));
2002        for arm in arms {
2003            Builder::new(&mut func, arm).jump(forwarder, &[carried]);
2004        }
2005        Builder::new(&mut func, forwarder).jump(exit, &[param]);
2006        Builder::new(&mut func, exit).ret(&[arrived]);
2007        let stats = simplify(&mut func);
2008        assert_eq!(stats.count(Kind::Optimized, super::FORWARDED), 1);
2009        // Both of them: the forwarder's, which is what let it go, and the exit's, which arrives
2010        // as the same thing from both arms once the block between them is not there.
2011        assert_eq!(stats.count(Kind::Optimized, super::SAME_EVERY_WAY), 2);
2012        assert_eq!(blocks(&func), [0, 1, 2, 4]);
2013        let term = func.terminator(Block::from_usize(4)).expect("the exit has one");
2014        assert_eq!(func[func[term].args], [carried]);
2015    }
2016
2017    #[test]
2018    fn fuel_stops_step_three_the_same_way_it_stops_the_rest() {
2019        // One unit, and the first thing that asks for it is the parameter, because parameters go
2020        // first within a block. The forwarder then has nothing to spend and stays.
2021        let mut func = taking_a_condition();
2022        let (carried, arms) = arms(&mut func);
2023        let forwarder = func.create_block();
2024        let param = func.append_param(forwarder, Type::int(32));
2025        let exit = func.create_block();
2026        // Arriving at the exit and returned there, so that the forwarder's parameter is one
2027        // something reads and the step that takes the unread ones out leaves it alone.
2028        let arrived = func.append_param(exit, Type::int(32));
2029        for arm in arms {
2030            Builder::new(&mut func, arm).jump(forwarder, &[carried]);
2031        }
2032        Builder::new(&mut func, forwarder).jump(exit, &[param]);
2033        Builder::new(&mut func, exit).ret(&[arrived]);
2034        let stats =
2035            SimplifyCfg.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::of(1));
2036        assert_eq!(stats.count(Kind::Optimized, super::SAME_EVERY_WAY), 1);
2037        assert_eq!(stats.count(Kind::Optimized, super::FORWARDED), 0);
2038        assert_eq!(stats.count(Kind::Missed, super::NO_FUEL_FORWARD), 1);
2039        assert_eq!(blocks(&func), [0, 1, 2, 3, 4]);
2040    }
2041
2042    /// A loop that carries a counter and a pointer, and leaves when the pointer reaches `end`.
2043    ///
2044    /// The shape `crate::ivopts` produces once section 28.4 has moved the exit test off the
2045    /// counter: nothing asks the counter anything any more, and the only thing left reading it is
2046    /// the addition that produces what the latch hands back to it.
2047    ///
2048    /// `on_counter` puts the exit test back on the counter, which is the same loop with the
2049    /// counter live, and it is the negative half of every test below.
2050    fn walking_a_pointer(on_counter: bool) -> Func {
2051        let mut names = Interner::new();
2052        let signature = Signature::new().with_params(&[Type::int(64)]);
2053        let mut func = Func::new(names.intern("f"), signature);
2054        let entry = func.create_block();
2055        let head = func.create_block();
2056        let out = func.create_block();
2057        let end = func.append_param(entry, Type::int(64));
2058        let counter = func.append_param(head, Type::int(32));
2059        let pointer = func.append_param(head, Type::int(64));
2060        let mut build = Builder::new(&mut func, entry);
2061        let from_zero = build.iconst(Type::int(32), 0);
2062        let from_start = build.iconst(Type::int(64), 0);
2063        build.jump(head, &[from_zero, from_start]);
2064        let mut build = Builder::new(&mut func, head);
2065        let one = build.iconst(Type::int(32), 1);
2066        let eight = build.iconst(Type::int(64), 8);
2067        let next = build.binary(Opcode::Add, counter, one, Flags::NONE);
2068        let along = build.binary(Opcode::Add, pointer, eight, Flags::NONE);
2069        // The write the loop is there for, so that the pointer is read by something that happens
2070        // whichever way the exit test is written.
2071        let address = build.unary(Opcode::IntToPtr, pointer, Type::PTR);
2072        let info = MemInfo {
2073            size: 8,
2074            align: 8,
2075            order: MemOrder::NotAtomic,
2076            tbaa: None,
2077            owns: 0,
2078            restrict: Restrict::NONE,
2079        };
2080        build.store(eight, address, info, Flags::NONE);
2081        let going = if on_counter {
2082            let limit = build.iconst(Type::int(32), 10);
2083            build.icmp(IntPred::Ne, next, limit)
2084        } else {
2085            build.icmp(IntPred::Ne, along, end)
2086        };
2087        build.br_if(going, head, &[next, along], out, &[]);
2088        Builder::new(&mut func, out).ret(&[]);
2089        func
2090    }
2091
2092    #[test]
2093    fn a_counter_the_loop_stopped_asking_about_stops_going_round() {
2094        let mut func = walking_a_pointer(false);
2095        let stats = simplify(&mut func);
2096        assert_eq!(stats.count(Kind::Optimized, super::NOTHING_READS_IT), 1);
2097        // The pointer stays, because the test that decides whether to go round again reads it.
2098        assert_eq!(func[Block::from_usize(1)].params.len(), 1);
2099        // And the edge that was feeding the counter is carrying one value now instead of two.
2100        assert_eq!(carries(&func, 1, 0).len(), 1);
2101        assert_eq!(carries(&func, 0, 0).len(), 1);
2102    }
2103
2104    #[test]
2105    fn a_counter_the_loop_still_asks_about_goes_round_exactly_as_before() {
2106        let mut func = walking_a_pointer(true);
2107        let stats = simplify(&mut func);
2108        assert_eq!(stats.count(Kind::Optimized, super::NOTHING_READS_IT), 0);
2109        assert_eq!(func[Block::from_usize(1)].params.len(), 2);
2110    }
2111
2112    /// A counter nothing reads, with two instructions behind it rather than one.
2113    ///
2114    /// The addition reads the parameter and the doubling reads the addition, so taking the
2115    /// parameter out strands the first and taking the first out strands the second. The condition
2116    /// is asked about the function's own parameter so that nothing here folds and the loop stays a
2117    /// loop.
2118    fn counting_into_nothing() -> (Func, Value, Value) {
2119        let mut names = Interner::new();
2120        let signature = Signature::new().with_params(&[Type::int(32)]);
2121        let mut func = Func::new(names.intern("f"), signature);
2122        let entry = func.create_block();
2123        let head = func.create_block();
2124        let out = func.create_block();
2125        let limit = func.append_param(entry, Type::int(32));
2126        let counter = func.append_param(head, Type::int(32));
2127        let mut build = Builder::new(&mut func, entry);
2128        let zero = build.iconst(Type::int(32), 0);
2129        build.jump(head, &[zero]);
2130        let mut build = Builder::new(&mut func, head);
2131        let one = build.iconst(Type::int(32), 1);
2132        let next = build.binary(Opcode::Add, counter, one, Flags::NONE);
2133        let twice = build.binary(Opcode::Add, next, next, Flags::NONE);
2134        let going = build.icmp(IntPred::Ne, limit, one);
2135        build.br_if(going, head, &[next], out, &[]);
2136        Builder::new(&mut func, out).ret(&[]);
2137        (func, next, twice)
2138    }
2139
2140    #[test]
2141    fn what_was_reading_a_parameter_nothing_reads_goes_with_it() {
2142        let (mut func, next, twice) = counting_into_nothing();
2143        let stats = simplify(&mut func);
2144        assert_eq!(stats.count(Kind::Optimized, super::NOTHING_READS_IT), 1);
2145        // Both of them, and the second one is the point: it was never reading the parameter, it
2146        // was reading what did, so one walk that only took the direct readers would have left a
2147        // use of a value nothing defines. This is issue 1016.
2148        assert_eq!(lives_in(&func, next), None);
2149        assert_eq!(lives_in(&func, twice), None);
2150    }
2151
2152    #[test]
2153    fn the_functions_own_parameters_stay_whether_or_not_anything_reads_them() {
2154        // The entry's parameters are the signature. Nothing in this function reads the one it
2155        // has, and taking it out would be changing what the function is rather than what it does.
2156        let mut names = Interner::new();
2157        let signature = Signature::new().with_params(&[Type::int(32)]);
2158        let mut func = Func::new(names.intern("f"), signature);
2159        let entry = func.create_block();
2160        func.append_param(entry, Type::int(32));
2161        Builder::new(&mut func, entry).ret(&[]);
2162        let stats = simplify(&mut func);
2163        assert_eq!(stats.count(Kind::Optimized, super::NOTHING_READS_IT), 0);
2164        assert_eq!(func[entry].params.len(), 1);
2165    }
2166
2167    #[test]
2168    fn a_parameter_nothing_reads_costs_one_unit_of_fuel_and_stays_without_it() {
2169        let mut func = walking_a_pointer(false);
2170        let stats =
2171            SimplifyCfg.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::of(0));
2172        assert_eq!(stats.count(Kind::Optimized, super::NOTHING_READS_IT), 0);
2173        assert_eq!(stats.count(Kind::Missed, super::NO_FUEL_UNREAD), 1);
2174        assert_eq!(func[Block::from_usize(1)].params.len(), 2);
2175    }
2176
2177    #[test]
2178    fn the_counter_that_went_leaves_the_verifier_nothing_to_complain_about() {
2179        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
2180        let mut names = Interner::new();
2181        let mut module = Module::new(names.intern("test.c"), &target);
2182        let mut func = walking_a_pointer(false);
2183        simplify(&mut func);
2184        module.add_func(func);
2185        rucc_ir::verify(&module, &names).expect("taking a parameter out left the function whole");
2186    }
2187
2188    #[test]
2189    fn step_three_leaves_the_verifier_nothing_to_complain_about() {
2190        // Section 21.6 names an argument list that stops matching its block's parameters as the
2191        // most common bug in this document, and both halves of step three change one.
2192        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
2193        let mut names = Interner::new();
2194        let mut module = Module::new(names.intern("test.c"), &target);
2195        let mut func = taking_a_condition();
2196        let (carried, arms) = arms(&mut func);
2197        let forwarder = func.create_block();
2198        let param = func.append_param(forwarder, Type::int(32));
2199        let exit = func.create_block();
2200        let arrived = func.append_param(exit, Type::int(32));
2201        let mut build = Builder::new(&mut func, arms[0]);
2202        let mine = build.iconst(Type::int(32), 9);
2203        build.jump(exit, &[mine]);
2204        Builder::new(&mut func, arms[1]).jump(forwarder, &[carried]);
2205        Builder::new(&mut func, forwarder).jump(exit, &[param]);
2206        let mut build = Builder::new(&mut func, exit);
2207        // A reader of the parameter that is not the return, because this function returns nothing
2208        // and the point is that something downstream still has the value it was passed.
2209        build.icmp(IntPred::Eq, arrived, arrived);
2210        build.ret(&[]);
2211        simplify(&mut func);
2212        module.add_func(func);
2213        rucc_ir::verify(&module, &names).expect("step three left the function verifiable");
2214    }
2215
2216    #[test]
2217    fn out_of_fuel_leaves_the_function_exactly_as_it_was() {
2218        let (mut func, _) = diamond(|build| build.iconst(Type::int(1), 1));
2219        let before = blocks(&func);
2220        let stats =
2221            SimplifyCfg.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::of(0));
2222        assert!(!stats.changed());
2223        assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
2224        assert_eq!(terminator(&func, 0), Opcode::BrIf);
2225        assert_eq!(blocks(&func), before);
2226    }
2227
2228    #[test]
2229    fn what_fuel_buys_is_one_whole_change_and_never_half_of_one() {
2230        // Two foldable branches and fuel for one. The half that removes the stranded blocks is
2231        // not charged for, because a limit that could stop between the two halves would leave a
2232        // block nothing reaches and the verifier would refuse the function.
2233        let mut func = graph(&[&[1, 2], &[3, 4], &[5], &[5], &[5], &[]]);
2234        let stats =
2235            SimplifyCfg.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::of(1));
2236        assert_eq!(stats.count(Kind::Optimized, super::FOLDED), 1);
2237        assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
2238        // The entry folded to its first arm, so the second arm is stranded and goes, and the
2239        // block only it reached goes with it.
2240        assert_eq!(blocks(&func), [0, 1, 3, 4, 5]);
2241    }
2242
2243    #[test]
2244    fn the_pass_leaves_the_verifier_nothing_to_complain_about() {
2245        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
2246        let mut names = Interner::new();
2247        let mut module = Module::new(names.intern("test.c"), &target);
2248        let mut func = graph(&[&[1, 2], &[3], &[3], &[4, 1], &[]]);
2249        simplify(&mut func);
2250        module.add_func(func);
2251        rucc_ir::verify(&module, &names).expect("the pass left the function verifiable");
2252    }
2253
2254    #[test]
2255    fn the_pass_says_it_preserves_nothing() {
2256        assert_eq!(SimplifyCfg.preserves(), Preserved::NONE);
2257    }
2258}