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}