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