Skip to main content

rucc_opt/
prune.rs

1//! What the ranges prove cannot happen, taken out of the graph.
2//!
3//! Two transformations, and they are here together because section 24.4 says so: the one on
4//! switches "belongs in the same pass as the range-based branch simplification of document 21".
5//! Both ask document 10's machinery one question and act on a yes, and neither of them can do
6//! anything the other could not have set up, so one walk asking both is cheaper than two walks
7//! asking one each.
8//!
9//! # The branch the ranges decide
10//!
11//! Section 21.1's branch simplification, the third of the three forms it takes. A branch whose
12//! two arms go to the same place is a jump, and [`crate::simplify_cfg`] does that one. A branch on
13//! a constant is a jump, and that one is there too. A branch on something that is not a constant
14//! but that cannot come out any way other than one is a jump as well, and that is this one, because
15//! it is the form that needs an analysis rather than a look at the operand.
16//!
17//! ```c
18//! if (x > 10) { if (x > 5) { f(); } }
19//! ```
20//!
21//! The inner condition is not a constant and no rewrite rule can see it is settled, because what
22//! settles it is the edge the block was reached by rather than anything in the expression. The
23//! ranges see it: on the edge out of `x > 10` where the branch was taken, `x` is in
24//! `[11, INT_MAX]`, and `x > 5` over that range is always true. So the inner branch is a jump to
25//! its taken arm, and the other arm stops being reachable and goes with it.
26//!
27//! # Why this is not jump threading
28//!
29//! [`crate::thread`] asks a related question and gets a different answer. It asks whether the
30//! branch at the end of a block is settled by which edge control arrived on, and its answer is per
31//! edge: an edge whose arrival settles the branch is redirected past it, and the other edges into
32//! the same block are left alone. This asks whether the branch is settled at the block whatever
33//! edge control arrived on, and its answer is per block. Neither subsumes the other. Threading
34//! handles the case where one predecessor knows something the others do not, and pays for it with
35//! a redirected edge or a copied block. This handles the case where the fact holds on every path
36//! in, and pays nothing, but it will not fire where only one path establishes the fact.
37//!
38//! The two also read facts from different distances. Threading looks at the block control came
39//! from. The ranges walk up the dominator tree collecting what every branch above narrowed, so the
40//! `if (x > 10)` above can be any number of blocks away from the `if (x > 5)` and the answer is the
41//! same.
42//!
43//! # The case the ranges rule out
44//!
45//! Section 24.4's one middle end transformation on switches, and the reason document 24 keeps a
46//! `switch` whole through the entire middle end rather than lowering it early. A switch that
47//! survives is a single node whose operand has one range, and a case value outside that range
48//! names an arm nothing can reach.
49//!
50//! ```c
51//! switch (x & 3) { case 0: ...; case 2: ...; case 7: ...; }
52//! ```
53//!
54//! The operand is in `[0, 3]`, so `case 7` is dead. What that buys is more than the compare it
55//! removes. Document 24's lowering decides between a walk, a binary search, a bit test and a jump
56//! table by how dense the case values are, and dropping the outlier is what turns a switch that
57//! looked sparse into one that is dense enough for a table. Section 24.4 puts it this way: it "can
58//! turn a sparse switch into a dense one and change the lowering decision entirely".
59//!
60//! A switch every one of whose cases is ruled out becomes a jump to its default, which is the
61//! same thing happening to the whole node rather than to one arm of it.
62//!
63//! # The default the cases cover
64//!
65//! The mirror of the case the ranges rule out. Where the cases that are left name every value the
66//! operand can hold, nothing is left over for the default, and its edge is one nothing takes.
67//!
68//! ```c
69//! switch (x & 3) { case 0: ...; case 1: ...; case 2: ...; case 3: ...; default: ...; }
70//! ```
71//!
72//! The operand is in `[0, 3]` and all four are cases, so the default is dead code with an edge
73//! into it. The edge cannot simply go, because a `switch` always has a default, so one of the cases
74//! becomes the default instead and leaves the case list. The one picked is the place the most cases
75//! go, and every case going there leaves with it, so the switch that is left is as short as it can
76//! be. The check costs nothing: the cases that are left are all in the range and no two are the
77//! same, so they cover it exactly when there are as many of them as the range has values.
78//!
79//! What that buys depends on what the cases were. A switch whose arms do work loses a compare and
80//! the default's block. A switch [`crate::switch_conv`] made a table of has every case going to the
81//! one load, so all of them leave, the switch is a jump to the load, and the range check in front of
82//! the table goes too. That is the check gcc leaves out when it can see the default is unreachable.
83//!
84//! # Answers first, then rewrites
85//!
86//! [`Ranges`] borrows the function, so nothing can be changed while it is alive. The walk
87//! therefore collects every answer, drops the oracle, and then applies them all, rather than the
88//! block at a time shape [`crate::phiopt`] uses.
89//!
90//! That is not only a borrow checker accommodation, it is also cheaper, and the reason it is
91//! sound is worth stating. An answer here is a fact that holds at a block. Applying another answer
92//! removes a branch, which removes edges, and a value's range at a block is the union over the
93//! paths that reach it, so removing a path can only narrow a range and never widen one. A fact
94//! proved before the rewrites is therefore still a fact after them. What can happen is that a
95//! block an answer was about stops being reachable, and an answer about a block nothing reaches is
96//! harmless because the block is about to be swept.
97//!
98//! # What it refuses
99//!
100//! A condition that is already a constant, because that is [`crate::simplify_cfg`]'s branch fold
101//! and two passes doing the same rewrite is two answers to check rather than one. A branch whose
102//! arms go to the same block, for the same reason.
103//!
104//! Everything else it refuses is the oracle saying it does not know, which is not a refusal so
105//! much as the answer, and it is recorded as a miss so that `-fopt-info-all` shows how often the
106//! question was asked and came back empty.
107//!
108//! # Which level
109//!
110//! `-O1` and above. Section 24.4 calls the switch half "cheap, it uses machinery that exists", and
111//! the branch half asks one question per conditional branch rather than one per value, so the
112//! query count is bounded by the number of branches rather than by the size of the function. Both
113//! halves only ever remove code, so `-Os` and `-Oz` want them as much as `-O2` does.
114//!
115//! It runs after [`crate::thread`] and [`crate::phiopt`] and before [`crate::simplify_cfg`], which
116//! is where it has to be at both ends. After, because both of those change the graph and the facts
117//! this reads are about the graph. Before, because what this leaves is a jump where a branch was
118//! and a block with one predecessor where there were two, and forwarding the first and merging the
119//! second is [`crate::simplify_cfg`]'s work rather than a second copy of it here.
120//!
121//! The blocks that stop being reachable are this pass's own problem rather than the cleanup
122//! pass's, because section 6.5 puts that obligation on whichever pass stranded them and the
123//! verifier holds every pass to it. So the walk that takes them out is called from here, and it is
124//! [`crate::simplify_cfg`]'s walk rather than a second one written next door.
125
126use std::collections::HashMap;
127
128use rucc_ir::{Block, BlockCall, Def, Extra, Func, Imm, Inst, IntPred, Opcode, SwitchInfo, Value};
129
130use crate::fold::constant;
131use crate::range::ops::Truth;
132use crate::range::query::Ranges;
133use crate::simplify_cfg;
134use crate::{Analyses, Fuel, Pass, Preserved, Stats};
135
136/// Recorded once for each branch the ranges settled.
137const BRANCH_DECIDED: &str =
138    "branch the value ranges settle whichever way control reached it replaced by a jump";
139
140/// Recorded once for each case value the ranges ruled out.
141const CASE_REMOVED: &str =
142    "case whose value the switched value cannot hold taken out of the switch";
143
144/// Recorded once for a switch none of whose cases can be reached.
145const SWITCH_REMOVED: &str =
146    "switch none of whose cases the switched value can reach replaced by a jump to its default";
147
148/// Recorded once for a switch whose cases cover every value the switched value can hold.
149const DEFAULT_REMOVED: &str =
150    "default no value can reach replaced by the place the most cases go to";
151
152/// Recorded once for each branch the ranges were asked about and could not settle.
153const BRANCH_UNDECIDED: &str = "branch kept, the value ranges do not settle which way it goes";
154
155/// Recorded once for each switch the ranges ruled no case out of.
156const NO_CASE_REMOVED: &str = "switch kept whole, the value ranges rule none of its cases out";
157const NO_FUEL: &str = "branch or switch kept, the pass ran out of fuel";
158
159/// The pass.
160#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161pub struct Prune;
162
163impl Pass for Prune {
164    fn name(&self) -> &'static str {
165        "prune"
166    }
167
168    fn describe(&self) -> &'static str {
169        "a branch the ranges settle becomes a jump, and a case they rule out leaves its switch"
170    }
171
172    fn preserves(&self) -> Preserved {
173        // Nothing. An arm that stops being reachable is an edge that stops existing, so every
174        // analysis built on the graph was built on a different graph.
175        Preserved::NONE
176    }
177
178    fn run(&self, func: &mut Func, an: &mut Analyses, fuel: &mut Fuel) -> Stats {
179        let mut stats = Stats::new();
180        if func.entry().is_none() {
181            return stats;
182        }
183        let plan = answers(func, an, &mut stats);
184        if plan.branches.is_empty() && plan.switches.is_empty() {
185            return stats;
186        }
187        'apply: {
188            for (term, call) in plan.branches {
189                if !fuel.take() {
190                    stats.missed(NO_FUEL);
191                    break 'apply;
192                }
193                simplify_cfg::jump_to(func, term, call);
194                stats.optimized(BRANCH_DECIDED);
195            }
196            for (term, keep) in plan.switches {
197                if !fuel.take() {
198                    stats.missed(NO_FUEL);
199                    break 'apply;
200                }
201                let removed = shrink(func, term, &keep);
202                for _ in 0..removed {
203                    stats.optimized(CASE_REMOVED);
204                }
205                if keep.covered {
206                    stats.optimized(DEFAULT_REMOVED);
207                } else if keep.cases.is_empty() {
208                    stats.optimized(SWITCH_REMOVED);
209                }
210            }
211        }
212        // The graph was about the function as it was a moment ago, and the manager clears the
213        // cache after the pass returns, which is too late for the pass itself.
214        an.clear();
215        // Section 6.5 makes taking the stranded blocks out an obligation of whichever pass
216        // stranded them rather than a favour the cleanup pass does, and the verifier holds every
217        // pass to it under `-fverify-each`. An arm that stops being reachable is exactly that, so
218        // the walk is here, and it is [`crate::simplify_cfg`]'s walk rather than a second one
219        // written next door, because two answers about what reachable means is two compilers.
220        simplify_cfg::sweep(func, an, &mut stats);
221        stats
222    }
223}
224
225/// Every rewrite the ranges license, worked out against the function as it stands.
226#[derive(Debug, Default)]
227struct Plan {
228    /// The branches that only go one way, and the edge each of them goes by.
229    branches: Vec<(Inst, BlockCall)>,
230    /// The switches that lose a case or their default, and what each of them keeps.
231    switches: Vec<(Inst, Keep)>,
232}
233
234/// What is left of a switch the ranges say something about.
235#[derive(Debug)]
236struct Keep {
237    /// The places the surviving cases are in, in the order they were in.
238    cases: Vec<usize>,
239    /// Whether those cases are every value the switched value can hold, which leaves nothing for
240    /// the default.
241    covered: bool,
242}
243
244/// Everything the ranges license, worked out against the function as it stands.
245///
246/// One [`Ranges`] for the whole walk rather than one per block, because the oracle caches what it
247/// has worked out and a second one would start from nothing.
248fn answers(func: &Func, an: &mut Analyses, stats: &mut Stats) -> Plan {
249    let mut plan = Plan::default();
250    let cfg = an.cfg(func);
251    let dom = an.dominators(func);
252    let mut ranges = Ranges::new(func, cfg, dom);
253    for block in func.blocks() {
254        if !cfg.reaches(block) {
255            continue;
256        }
257        let Some(term) = func.terminator(block) else { continue };
258        match func[term].opcode {
259            Opcode::BrIf => match decided(func, &mut ranges, block, term) {
260                Answer::Jump(call) => plan.branches.push((term, call)),
261                Answer::Unsettled => stats.missed(BRANCH_UNDECIDED),
262                Answer::NotAsked => (),
263            },
264            Opcode::Switch => match reachable(func, &mut ranges, block, term) {
265                Some(keeping) => plan.switches.push((term, keeping)),
266                None => stats.missed(NO_CASE_REMOVED),
267            },
268            _ => (),
269        }
270    }
271    plan
272}
273
274/// What came back about a conditional branch.
275///
276/// Three outcomes rather than two, because a branch this pass declines to look at and a branch it
277/// looked at and could not settle say different things under `-fopt-info-all`. The first is not a
278/// missed optimization at all, it is another pass's fold, and counting it as one would put a
279/// remark on every constant branch in the program saying the ranges failed at something they were
280/// never asked.
281enum Answer {
282    /// The one edge the branch takes.
283    Jump(BlockCall),
284    /// Asked, and the oracle does not know.
285    Unsettled,
286    /// Not this pass's question.
287    NotAsked,
288}
289
290/// The one edge a conditional branch takes, when the ranges say it only has one.
291///
292/// The first target is the one taken when the condition is one, which is what `Builder::br_if`
293/// writes and what the printer reads back, so a condition that always holds takes target zero.
294fn decided(func: &Func, ranges: &mut Ranges<'_>, block: Block, term: Inst) -> Answer {
295    let data = &func[term];
296    let Extra::Targets(targets) = data.extra else { return Answer::NotAsked };
297    let Some(&cond) = func[data.args].first() else { return Answer::NotAsked };
298    // Already a constant, or both arms in one place. Both are [`crate::simplify_cfg`]'s fold and
299    // it runs right after this one, so answering them here would be a second answer to the same
300    // question rather than an answer to one nothing else has.
301    if constant(func, cond).is_some() {
302        return Answer::NotAsked;
303    }
304    let calls = &func[targets];
305    let together =
306        |two: &[BlockCall]| two[0].block == two[1].block && func[two[0].args] == func[two[1].args];
307    if calls.len() == 2 && together(calls) {
308        return Answer::NotAsked;
309    }
310    let arm = match settled(func, ranges, block, cond) {
311        Some(true) => 0,
312        Some(false) => 1,
313        None => return Answer::Unsettled,
314    };
315    func[targets].get(arm).copied().map_or(Answer::NotAsked, Answer::Jump)
316}
317
318/// Whether this condition can only come out one way at this block, and which way that is.
319///
320/// A comparison is asked about through [`Ranges::compare`], which is the entry point section 10.3
321/// puts the relational oracle behind, so a branch on `a < b` under a dominating `a < b` is settled
322/// even where neither value is pinned down to a range that settles it. Anything else is asked as a
323/// range: a one bit value that cannot be zero is true and one that can only be zero is false.
324///
325/// [`crate::header_copy`] asks this too, about the loop entry test it has just put in front of a
326/// loop, because by then this pass has run and the test it wants an answer about did not exist yet.
327/// Section 26.6 wanted that answer from document 10's ranges, and one function answering for both
328/// is what keeps the two passes from disagreeing about the same branch.
329pub(crate) fn settled(
330    func: &Func,
331    ranges: &mut Ranges<'_>,
332    block: Block,
333    cond: Value,
334) -> Option<bool> {
335    if let Some((pred, lhs, rhs)) = comparison(func, cond) {
336        return match ranges.compare(pred, lhs, rhs, block) {
337            Truth::Always => Some(true),
338            Truth::Never => Some(false),
339            Truth::Either => None,
340        };
341    }
342    let range = ranges.at(cond, block);
343    if range.nonzero() {
344        return Some(true);
345    }
346    (range.singleton() == Some(0)).then_some(false)
347}
348
349/// The comparison behind this value, if it is one.
350fn comparison(func: &Func, value: Value) -> Option<(IntPred, Value, Value)> {
351    let Def::Result { inst, .. } = func[value].def else { return None };
352    if func[inst].opcode != Opcode::ICmp {
353        return None;
354    }
355    let Extra::IntPred(pred) = func[inst].extra else { return None };
356    let &[lhs, rhs] = func[func[inst].args].first_chunk::<2>()?;
357    Some((pred, lhs, rhs))
358}
359
360/// Which of a switch's cases the switched value can still hold, and whether the default can be
361/// reached, when either says something.
362///
363/// `None` is the switch that keeps every case and its default. An empty list of cases is the
364/// switch none of whose cases can be reached, which becomes a jump to its default.
365fn reachable(func: &Func, ranges: &mut Ranges<'_>, block: Block, term: Inst) -> Option<Keep> {
366    let Extra::Switch(at) = func[term].extra else { return None };
367    let info = func[at];
368    let arg = *func[func[term].args].first()?;
369    let range = ranges.at(arg, block);
370    let cases = &func[info.cases];
371    let keeping: Vec<usize> =
372        (0..cases.len()).filter(|&at| range.contains(cases[at].unsigned())).collect();
373    // Every case left is in the range and no two are the same, so they are all of it exactly when
374    // there are as many of them as it has values. Saturating, because the full range of a 128 bit
375    // value has one more value than a `u128` can count.
376    let values = range
377        .pairs()
378        .iter()
379        .fold(0u128, |sum, &(lo, hi)| sum.saturating_add((hi - lo).saturating_add(1)));
380    let covered = !keeping.is_empty() && values == keeping.len() as u128;
381    (keeping.len() < cases.len() || covered).then_some(Keep { cases: keeping, covered })
382}
383
384/// Rewrites a switch to what it keeps, and says how many cases the ranges ruled out.
385///
386/// A switch whose default nothing reaches takes the place the most cases go to as its default,
387/// and those cases leave. A switch left with no cases is a jump to its default, because a decision
388/// tree over nothing is the default arm and document 24's lowering would rather not be handed one.
389fn shrink(func: &mut Func, term: Inst, keep: &Keep) -> usize {
390    let Extra::Switch(at) = func[term].extra else { return 0 };
391    let info = func[at];
392    let all = func[info.targets].to_vec();
393    let values = func[info.cases].to_vec();
394    let removed = values.len() - keep.cases.len();
395    // The default is the first target and the cases follow it in the order their values are in,
396    // so the target for the case in place `at` is one past it.
397    let mut arms: Vec<(Imm, BlockCall)> =
398        keep.cases.iter().map(|&at| (values[at], all[at + 1])).collect();
399    let default = if keep.covered {
400        let most = busiest(func, &arms);
401        arms.retain(|&(_, call)| !same(func, call, most));
402        most
403    } else {
404        all[0]
405    };
406    if arms.is_empty() {
407        simplify_cfg::jump_to(func, term, default);
408        return removed;
409    }
410    let targets: Vec<BlockCall> =
411        std::iter::once(default).chain(arms.iter().map(|&(_, call)| call)).collect();
412    let cases: Vec<Imm> = arms.iter().map(|&(value, _)| value).collect();
413    let targets = func.push_block_calls(&targets);
414    let cases = func.push_imms(&cases);
415    let fresh = func.add_switch(SwitchInfo { targets, cases });
416    func[term].extra = Extra::Switch(fresh);
417    removed
418}
419
420/// The place the most of these cases go to, the first of them where two tie.
421///
422/// A place is a block and what is passed to it, because two edges into one block passing
423/// different values are two places and only one of them can be the default.
424fn busiest(func: &Func, arms: &[(Imm, BlockCall)]) -> BlockCall {
425    let mut counts: HashMap<(Block, &[Value]), usize> = HashMap::new();
426    for &(_, call) in arms {
427        *counts.entry((call.block, &func[call.args])).or_default() += 1;
428    }
429    let mut best = arms[0].1;
430    let mut most = 0;
431    for &(_, call) in arms {
432        let count = counts[&(call.block, &func[call.args])];
433        if count > most {
434            best = call;
435            most = count;
436        }
437    }
438    best
439}
440
441/// Whether two edges go to the same block with the same values.
442fn same(func: &Func, a: BlockCall, b: BlockCall) -> bool {
443    a.block == b.block && func[a.args] == func[b.args]
444}
445
446#[cfg(test)]
447mod tests {
448    use rucc_base::Interner;
449    use rucc_ir::{Block, Builder, Flags, Func, IntPred, Opcode, Signature, Type};
450
451    use super::Prune;
452    use crate::stats::Kind;
453    use crate::{Fuel, Pass, Stats};
454
455    /// Runs the pass with as much fuel as it wants.
456    fn prune(func: &mut Func) -> Stats {
457        Prune.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
458    }
459
460    /// The opcode of a block's terminator.
461    fn terminator(func: &Func, block: usize) -> Opcode {
462        let block = Block::from_usize(block);
463        func[func.terminator(block).expect("every block here has one")].opcode
464    }
465
466    /// The blocks a block's terminator names, in the order it names them.
467    fn goes_to(func: &Func, block: usize) -> Vec<usize> {
468        let block = Block::from_usize(block);
469        let term = func.terminator(block).expect("every block here has one");
470        func.successors(term).map(|call| call.block.index()).collect()
471    }
472
473    /// Two nested branches on the same value, the outer one narrowing it for the inner one.
474    ///
475    /// Block 0 branches on `x <outer> bound`, block 1 branches on `x <inner> 5`, and blocks 2 and
476    /// 3 are the inner branch's two arms. Block 4 is where the outer branch goes when it does not
477    /// hold, and it is there so that the inner block has one predecessor rather than being the
478    /// entry's only successor.
479    fn nested(outer: IntPred, bound: i128, inner: IntPred) -> Func {
480        let mut names = Interner::new();
481        let ty = Type::int(32);
482        let mut func = Func::new(names.intern("f"), Signature::new().with_params(&[ty]));
483        let entry = func.create_block();
484        let middle = func.create_block();
485        let arms = [func.create_block(), func.create_block()];
486        let away = func.create_block();
487        let x = func.append_param(entry, ty);
488        let mut build = Builder::new(&mut func, entry);
489        let edge = build.iconst(ty, bound);
490        let first = build.icmp(outer, x, edge);
491        build.br_if(first, middle, &[], away, &[]);
492        let mut build = Builder::new(&mut func, middle);
493        let five = build.iconst(ty, 5);
494        let second = build.icmp(inner, x, five);
495        build.br_if(second, arms[0], &[], arms[1], &[]);
496        for block in [arms[0], arms[1], away] {
497            let mut build = Builder::new(&mut func, block);
498            build.ret(&[]);
499        }
500        func
501    }
502
503    #[test]
504    fn a_branch_the_ranges_settle_becomes_a_jump_to_the_arm_they_settle_on() {
505        // `if (x > 10) { if (x > 5) ... }`. On the edge into block 1 the value is at least 11, so
506        // the second comparison holds there whatever else is true, and the arm it does not take
507        // stops being reachable.
508        let mut func = nested(IntPred::Sgt, 10, IntPred::Sgt);
509        let stats = prune(&mut func);
510        assert!(stats.changed());
511        assert_eq!(terminator(&func, 1), Opcode::Jump);
512        assert_eq!(goes_to(&func, 1), [2]);
513        assert_eq!(stats.count(Kind::Optimized, super::BRANCH_DECIDED), 1);
514    }
515
516    #[test]
517    fn a_branch_the_ranges_settle_the_other_way_jumps_to_the_other_arm() {
518        // `if (x > 10) { if (x < 5) ... }`, where the inner comparison cannot hold. The pass has
519        // to name the second target rather than the first, and getting that backwards would build
520        // a compiler that quietly runs the wrong arm.
521        let mut func = nested(IntPred::Sgt, 10, IntPred::Slt);
522        let stats = prune(&mut func);
523        assert!(stats.changed());
524        assert_eq!(terminator(&func, 1), Opcode::Jump);
525        assert_eq!(goes_to(&func, 1), [3]);
526    }
527
528    #[test]
529    fn a_branch_the_ranges_do_not_settle_keeps_its_two_arms() {
530        // `if (x > 10) { if (x > 20) ... }` the other way round. Being over 10 says nothing about
531        // being over 20, so both arms are still reachable and the branch stays.
532        let mut func = nested(IntPred::Sgt, 3, IntPred::Sgt);
533        let stats = prune(&mut func);
534        assert!(!stats.changed());
535        assert_eq!(terminator(&func, 1), Opcode::BrIf);
536        assert_eq!(stats.count(Kind::Missed, super::BRANCH_UNDECIDED), 2);
537    }
538
539    #[test]
540    fn a_branch_on_a_constant_is_left_for_the_control_flow_pass() {
541        // Two passes writing the same rewrite is two answers to check, and this one is section
542        // 21.1's rather than section 10's. It is refused before the oracle is asked at all.
543        let mut names = Interner::new();
544        let mut func = Func::new(names.intern("f"), Signature::new());
545        let entry = func.create_block();
546        let arms = [func.create_block(), func.create_block()];
547        let mut build = Builder::new(&mut func, entry);
548        let always = build.iconst(Type::int(1), 1);
549        build.br_if(always, arms[0], &[], arms[1], &[]);
550        for block in arms {
551            let mut build = Builder::new(&mut func, block);
552            build.ret(&[]);
553        }
554        let stats = prune(&mut func);
555        assert!(!stats.changed());
556        assert_eq!(terminator(&func, 0), Opcode::BrIf);
557        assert_eq!(stats.count(Kind::Missed, super::BRANCH_UNDECIDED), 0);
558    }
559
560    /// A switch on `x & mask`, with those case values and a default.
561    fn masked(mask: i128, cases: &[i128]) -> Func {
562        let places: Vec<usize> = (0..cases.len()).collect();
563        onto(mask, cases, &places)
564    }
565
566    /// A switch on `x & mask` whose case in place `i` goes to arm `places[i]`.
567    ///
568    /// Block 0 is the entry, block 1 the default, and the arms are blocks 2 onwards.
569    fn onto(mask: i128, cases: &[i128], places: &[usize]) -> Func {
570        let mut names = Interner::new();
571        let ty = Type::int(32);
572        let mut func = Func::new(names.intern("f"), Signature::new().with_params(&[ty]));
573        let entry = func.create_block();
574        let default = func.create_block();
575        let count = places.iter().max().map_or(0, |&last| last + 1);
576        let arms: Vec<Block> = (0..count).map(|_| func.create_block()).collect();
577        let x = func.append_param(entry, ty);
578        let mut build = Builder::new(&mut func, entry);
579        let bits = build.iconst(ty, mask);
580        let narrowed = build.binary(Opcode::And, x, bits, Flags::NONE);
581        let pairs: Vec<(i128, Block)> =
582            cases.iter().copied().zip(places.iter().map(|&at| arms[at])).collect::<Vec<_>>();
583        build.switch(narrowed, default, &pairs);
584        for block in std::iter::once(default).chain(arms) {
585            let mut build = Builder::new(&mut func, block);
586            build.ret(&[]);
587        }
588        func
589    }
590
591    #[test]
592    fn a_case_the_switched_value_cannot_hold_leaves_the_switch() {
593        // `switch (x & 3) { case 0: case 2: case 7: }`. The operand is in [0, 3], so the last
594        // case names an arm nothing reaches, and the two that are left are what document 24's
595        // lowering gets to decide over.
596        let mut func = masked(3, &[0, 2, 7]);
597        let stats = prune(&mut func);
598        assert!(stats.changed());
599        assert_eq!(terminator(&func, 0), Opcode::Switch);
600        // The default first and the surviving cases after it, in the order they were in.
601        assert_eq!(goes_to(&func, 0), [1, 2, 3]);
602        assert_eq!(stats.count(Kind::Optimized, super::CASE_REMOVED), 1);
603    }
604
605    #[test]
606    fn a_switch_no_case_of_which_can_be_reached_jumps_to_its_default() {
607        // Every case is outside the operand's range, so the whole node goes rather than an arm
608        // of it. What is left is the default, which is where control was always going.
609        let mut func = masked(1, &[5, 9]);
610        let stats = prune(&mut func);
611        assert!(stats.changed());
612        assert_eq!(terminator(&func, 0), Opcode::Jump);
613        assert_eq!(goes_to(&func, 0), [1]);
614        assert_eq!(stats.count(Kind::Optimized, super::SWITCH_REMOVED), 1);
615    }
616
617    #[test]
618    fn a_default_the_cases_cover_gives_way_to_the_first_case() {
619        // `switch (x & 3)` with all four values as cases. Nothing is left for the default, so the
620        // first case takes its place and leaves the case list, and three compares are left of four.
621        let mut func = masked(3, &[0, 1, 2, 3]);
622        let stats = prune(&mut func);
623        assert!(stats.changed());
624        assert_eq!(terminator(&func, 0), Opcode::Switch);
625        assert_eq!(goes_to(&func, 0), [2, 3, 4, 5]);
626        assert_eq!(stats.count(Kind::Optimized, super::DEFAULT_REMOVED), 1);
627        assert_eq!(stats.count(Kind::Optimized, super::CASE_REMOVED), 0);
628    }
629
630    #[test]
631    fn a_default_the_cases_cover_gives_way_to_the_place_most_of_them_go() {
632        // Cases 1 and 2 share an arm, so that arm is the default and both of them leave.
633        let mut func = onto(3, &[0, 1, 2, 3], &[0, 1, 1, 2]);
634        let stats = prune(&mut func);
635        assert!(stats.changed());
636        assert_eq!(goes_to(&func, 0), [3, 2, 4]);
637        assert_eq!(stats.count(Kind::Optimized, super::DEFAULT_REMOVED), 1);
638    }
639
640    #[test]
641    fn a_switch_whose_cases_all_go_one_way_and_cover_the_range_is_a_jump() {
642        // What `switch_conv` leaves for a table: every case at the one load. With the default gone
643        // there is nothing to decide, and the range check in front of the table goes with it.
644        let mut func = onto(3, &[0, 1, 2, 3], &[0, 0, 0, 0]);
645        let stats = prune(&mut func);
646        assert!(stats.changed());
647        assert_eq!(terminator(&func, 0), Opcode::Jump);
648        assert_eq!(goes_to(&func, 0), [2]);
649        assert_eq!(stats.count(Kind::Optimized, super::SWITCH_REMOVED), 0);
650    }
651
652    #[test]
653    fn a_default_is_covered_once_the_cases_outside_the_range_are_gone() {
654        // Case 9 is ruled out and the four left cover `[0, 3]`, so both happen to one switch.
655        let mut func = masked(3, &[0, 1, 9, 2, 3]);
656        let stats = prune(&mut func);
657        assert_eq!(goes_to(&func, 0), [2, 3, 5, 6]);
658        assert_eq!(stats.count(Kind::Optimized, super::CASE_REMOVED), 1);
659        assert_eq!(stats.count(Kind::Optimized, super::DEFAULT_REMOVED), 1);
660    }
661
662    #[test]
663    fn a_default_one_value_can_still_reach_is_kept() {
664        let mut func = masked(3, &[0, 1, 3]);
665        let stats = prune(&mut func);
666        assert!(!stats.changed());
667        assert_eq!(goes_to(&func, 0), [1, 2, 3, 4]);
668    }
669
670    #[test]
671    fn a_switch_whose_cases_the_operand_can_all_hold_is_kept_whole() {
672        let mut func = masked(7, &[0, 2, 7]);
673        let stats = prune(&mut func);
674        assert!(!stats.changed());
675        assert_eq!(terminator(&func, 0), Opcode::Switch);
676        assert_eq!(stats.count(Kind::Missed, super::NO_CASE_REMOVED), 1);
677    }
678
679    #[test]
680    fn a_branch_two_values_are_related_on_settles_without_either_being_pinned_down() {
681        // The oracle's half rather than the ranges' half, and the case section 10.3 says it is
682        // for. Nothing here says what `a` or `b` can be, so no interval settles the second
683        // comparison. What settles it is that the edge into block 1 recorded that `a < b`.
684        let mut names = Interner::new();
685        let ty = Type::int(32);
686        let mut func = Func::new(names.intern("f"), Signature::new().with_params(&[ty, ty]));
687        let entry = func.create_block();
688        let middle = func.create_block();
689        let arms = [func.create_block(), func.create_block()];
690        let away = func.create_block();
691        let a = func.append_param(entry, ty);
692        let b = func.append_param(entry, ty);
693        let mut build = Builder::new(&mut func, entry);
694        let first = build.icmp(IntPred::Slt, a, b);
695        build.br_if(first, middle, &[], away, &[]);
696        let mut build = Builder::new(&mut func, middle);
697        // A second comparison of the same two values, written again rather than reused, which is
698        // what a program with the test in two places hands the optimizer.
699        let second = build.icmp(IntPred::Sle, a, b);
700        build.br_if(second, arms[0], &[], arms[1], &[]);
701        for block in [arms[0], arms[1], away] {
702            let mut build = Builder::new(&mut func, block);
703            build.ret(&[]);
704        }
705        let stats = prune(&mut func);
706        assert!(stats.changed());
707        assert_eq!(terminator(&func, 1), Opcode::Jump);
708        assert_eq!(goes_to(&func, 1), [2]);
709    }
710
711    #[test]
712    fn a_value_that_cannot_be_zero_is_a_branch_that_always_holds() {
713        // `if (x == 5) { if ((_Bool)x) ... }`. The condition is a truncation rather than a
714        // comparison, so what answers is the range on its own: on the edge into block 1 the value
715        // is exactly five, five truncated to one bit is one, and one is not zero.
716        let mut names = Interner::new();
717        let wide = Type::int(32);
718        let mut func = Func::new(names.intern("f"), Signature::new().with_params(&[wide]));
719        let entry = func.create_block();
720        let middle = func.create_block();
721        let arms = [func.create_block(), func.create_block()];
722        let away = func.create_block();
723        let x = func.append_param(entry, wide);
724        let mut build = Builder::new(&mut func, entry);
725        let five = build.iconst(wide, 5);
726        let is_five = build.icmp(IntPred::Eq, x, five);
727        build.br_if(is_five, middle, &[], away, &[]);
728        let mut build = Builder::new(&mut func, middle);
729        let bit = build.unary(Opcode::Trunc, x, Type::int(1));
730        build.br_if(bit, arms[0], &[], arms[1], &[]);
731        for block in [arms[0], arms[1], away] {
732            let mut build = Builder::new(&mut func, block);
733            build.ret(&[]);
734        }
735        let stats = prune(&mut func);
736        assert!(stats.changed());
737        assert_eq!(terminator(&func, 1), Opcode::Jump);
738        assert_eq!(goes_to(&func, 1), [2]);
739    }
740
741    #[test]
742    fn a_function_with_no_body_is_left_alone() {
743        let mut names = Interner::new();
744        let mut func = Func::new(names.intern("f"), Signature::new());
745        let stats = prune(&mut func);
746        assert!(!stats.changed());
747    }
748
749    #[test]
750    fn no_fuel_leaves_the_branch_where_it_is() {
751        let mut func = nested(IntPred::Sgt, 10, IntPred::Sgt);
752        let stats =
753            Prune.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::of(0));
754        assert!(!stats.changed());
755        assert_eq!(terminator(&func, 1), Opcode::BrIf);
756        assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
757    }
758}