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