rucc_codegen/switch.rs
1//! What a `switch` becomes on the way to the machine, and how that shape is chosen.
2//!
3//! Design: `spec/optimizer/24-switch-lowering.md`. Section 24.4 puts the choice here, at the
4//! boundary into the machine level and nowhere earlier, and section 24.2 says what the choice
5//! looks like.
6//!
7//! # Why the shape is decided here and not in the front end
8//!
9//! What a `switch` should become is a target decision and not a language one. A chain of compares
10//! is right for three cases and wrong for two hundred, where the answer is a jump table, and wrong
11//! again for twenty spread over a million, where it is a binary search on the value. A front end
12//! that picked one would be picking for every target at once, and the IR would no longer hold what
13//! the program said. So the `switch` survives as far as here, and here is where it is given up.
14//!
15//! Keeping it whole that long buys something on the way as well. A `switch` is one node from which
16//! the range on each outgoing edge is exact: on the edge to case five the operand is five, and on
17//! the default edge it is outside the case set. A `switch` lowered early is a pile of branches that
18//! every pass afterwards has to work those facts back out of.
19//!
20//! # A switch is a partition and not a shape
21//!
22//! The reason to sort the cases and cut them into runs, rather than pick one shape for the whole
23//! statement, is that a real `switch` is more than one thing at once. A `switch` in a parser has a
24//! dense stretch of ASCII values best served by a jump table, a few scattered large constants best
25//! served by comparisons, and a set of aliased cases best served by a bit test, all in the same
26//! statement. A design that picks one shape for the whole of it cannot say that. So the case list
27//! is sorted, partitioned into clusters, and a decision tree is built over the clusters.
28//!
29//! Three of the four shapes are written. A `Cluster::One` is one case value and one equality test,
30//! which is what every case was before this module existed. A `Cluster::Run` is a stretch of
31//! consecutive values that all go to the same place, and it is one subtraction and one unsigned
32//! comparison however long the stretch is, which is what makes `case 'a' ... 'z'` twenty six cases
33//! in the IR and two instructions in the machine code. A `Cluster::Bits` is a set of values
34//! scattered through a span narrower than a word, each destination holding the bits of a mask, and
35//! it is one shift and one test per destination however many values are in it, which is what makes
36//! `case 'a': case 'e': case 'i': case 'o': case 'u':` five compares before this and one after.
37//!
38//! The one that is not written is the jump table, and it is a variant this enum gains rather than a
39//! rewrite of anything here. It is waiting on a read only section to put the table in. What it was
40//! also waiting on was `Opcode::IndirectBr`, which a computed goto needed as well and which
41//! tamnd/rucc#353 has since written, so what is left is the table rather than the jump.
42//!
43//! # Why the tree compares signed
44//!
45//! The IR gives a `switch` a width and not a signedness, because signedness in this IR is a
46//! property of an operation rather than of a type, so there is nothing here to ask whether the
47//! program switched on an `int` or an `unsigned`. What makes that harmless is that the sort and the
48//! tree use the same order: the cases are sorted by their signed reading and the tree splits with a
49//! signed comparison, so the tree is consistent with itself and every value comes down it to the
50//! one cluster that can hold it. Sorting one way and comparing the other is the bug this is
51//! written to not have.
52//!
53//! A run is not affected either way. Testing `x - low` against `high - low` unsigned is modular
54//! arithmetic and gives the same answer whichever way the operand is read.
55//!
56//! # What it refuses to get wrong
57//!
58//! Section 24.6 lists the ways this goes wrong and two of them are arithmetic. The width of a run
59//! is worked out in `i128`, which holds the difference of any two values of any type C can switch
60//! on, so nothing here overflows the way the same computation in the switch's own type would. A run
61//! that covers a whole type comes out as a width of every bit set, which read as an unsigned
62//! comparison is a test that is true of everything, and that is exactly right for a `switch` no
63//! value falls out of.
64//!
65//! The third is the default edge, and the rule is that it is never dropped. Every leaf of the tree
66//! ends by branching to the default, so a value that matches nothing arrives there whichever way it
67//! came down, and there is no path through any of this that leaves a block without saying where
68//! control goes next.
69//!
70//! The fourth is the bit test's shift. Shifting by more than the width of the word being shifted is
71//! undefined, and the amount is the operand, so it is the operand that has to be shown to be in
72//! range first. Section 24.6 is firm that the bound before the shift is not an optimization
73//! decision and cannot be dropped when the value looks like it has to be in range, and here it is
74//! written by the same code that writes the shift rather than added afterwards.
75//!
76//! # What it does not carry yet
77//!
78//! Section 24.5 asks for document 11's `Frequency` on every cluster from the start, so that the
79//! tree can lean towards the hot cases rather than be balanced, and so that adding it later is not
80//! a change to every place a cluster is built. It is not here because there is nowhere to read it
81//! from. Block frequencies are worked out in `rucc-opt`, which is above this crate rather than
82//! below it, and what would carry the number down is the IR, which has nowhere to put it yet.
83
84use rucc_diag::Span;
85use rucc_ir::{
86 Block, BlockCall, Builder, Extra, Flags, Func, Imm, Inst, IntPred, Opcode, Type, Value,
87};
88
89/// The most clusters a leaf of the decision tree tests one at a time, which is also the count below
90/// which nothing new is built at all. A `switch` of this many clusters or fewer stays the chain of
91/// compares it has always been, in the block it has always been in.
92///
93/// Thirty two, and the number is measured rather than picked. What it trades is not a comparison
94/// against a comparison, which is what it looks like on paper and is the reason a small number looks
95/// right. A walk of `n` clusters is `n` compares and a search is about `log2(n)`, so on paper the
96/// search wins from about five cases upward and the threshold should be about five.
97///
98/// The machine does not agree, because the two kinds of comparison do not cost the same. Every
99/// compare in a walk is a branch that is almost never taken, one case out of `n`, so the predictor
100/// gets all of them right and the front end runs through them several per cycle. Every branch in a
101/// search is a branch that goes each way about half the time, so the predictor gets a fair share of
102/// them wrong and each of those costs the whole pipeline. Twenty compares nobody mispredicts are
103/// cheaper than six branches that mispredict a third of the time, and that stays true further up
104/// than it seems it should.
105///
106/// Measured on an interpreter loop dispatching on a sparse `switch`, four million iterations picking
107/// a case at random, the walk is ahead up to about thirty two cases and the search is ahead above
108/// about thirty six. At seventeen cases a search costs sixteen percent, at twenty four it costs
109/// twenty two, at thirty six it saves nine, at fifty it saves twenty three and at a hundred it saves
110/// half. Thirty two is where those two lines cross.
111///
112/// Two things would move it. The first is a jump table, which is what a dense `switch` this large
113/// should become and which is waiting on somewhere to put the table. Once dense cases stop
114/// reaching the tree at all, what is left in it is sparser, and a sparser search may be worth
115/// starting sooner.
116/// The second is knowing which case is hot, because a walk that tests the common case first is
117/// cheaper than any search and the tree cannot use that ordering. That is document 11's `Frequency`
118/// and it is not carried here yet.
119///
120/// gcc has the same knob under the name `case-values-threshold` and a small number in it, which is
121/// the right number for gcc because gcc reaches for a jump table first and the tree is what it falls
122/// back to on cases a table cannot hold.
123pub const LINEAR: usize = 32;
124
125/// Rewrites every `switch` in the function into branches, and leaves everything else alone.
126///
127/// The function is changed in place, which is what makes this the last thing that reads the IR as
128/// the front end built it. `--emit=ir` prints before this runs, and nothing after this asks what
129/// the program said, only what the machine has to do.
130pub fn switches(func: &mut Func) {
131 let found: Vec<Inst> = func
132 .blocks()
133 .filter_map(|block| func.terminator(block))
134 .filter(|&inst| func[inst].opcode == Opcode::Switch)
135 .collect();
136 for inst in found {
137 lower(func, inst);
138 }
139}
140
141/// One `switch`, as the clusters its cases fall into and a decision tree over them.
142fn lower(func: &mut Func, inst: Inst) {
143 let block = func.block_of(inst).expect("a terminator is in a block");
144 let span = func.span(inst);
145 let Extra::Switch(info) = func[inst].extra else { return };
146 let info = func[info];
147 let Some(&value) = func[func[inst].args].first() else { return };
148 // The lane, because a `switch` on a vector is not a thing C can write and the immediates are
149 // an integer's either way.
150 let ty = func[value].ty.lane();
151 let calls: Vec<BlockCall> = func[info.targets].to_vec();
152 let cases: Vec<Imm> = func[info.cases].to_vec();
153 let Some((&default, arms)) = calls.split_first() else { return };
154 let clusters = group(func, clusters(func, &cases, arms, ty));
155
156 // Before anything is written, because the builder appends and the `switch` is where the
157 // appending has to happen.
158 func.remove_inst(inst);
159 tree(func, &Lowering { value, ty, default, span }, block, &clusters);
160}
161
162/// What every test written for one `switch` shares.
163///
164/// The tree hands the same four things down to every leaf and every leaf hands them to every test,
165/// so they travel together rather than as four more parameters at each step.
166struct Lowering {
167 /// The operand being switched on.
168 value: Value,
169 /// Its width, which every constant written here takes.
170 ty: Type,
171 /// Where a value that matches no case goes, which is every leaf's last edge.
172 default: BlockCall,
173 /// The source location of the `switch`, which everything written for it takes.
174 span: Span,
175}
176
177/// A stretch of case values that one test separates from the rest of them.
178///
179/// This is the structure `spec/optimizer/24-switch-lowering.md` section 24.2 describes, with the
180/// three variants that can be written today. It is an enum rather than a struct with a low and a
181/// high in it because the one that is missing carries something these do not: a jump table carries
182/// a table, and the point of the shape is that adding it is a variant here and an arm in [`test`]
183/// rather than a change to how a `switch` is taken apart.
184#[derive(Clone, Debug)]
185enum Cluster {
186 /// One case value, which is one equality test.
187 One {
188 /// The value the operand has to equal.
189 value: i128,
190 /// Where it goes when it does.
191 call: BlockCall,
192 },
193 /// Every value from `low` to `high`, all of which go to the same place.
194 Run {
195 /// The lowest value in the run.
196 low: i128,
197 /// The highest, which is at least one above the lowest.
198 high: i128,
199 /// Where any of them goes.
200 call: BlockCall,
201 },
202 /// Values scattered through `low` to `high` going to several places, each place being the bits
203 /// of one mask.
204 Bits {
205 /// The lowest value any of the masks names, which every bit is counted from.
206 low: i128,
207 /// The highest, which is less than a word above the lowest.
208 high: i128,
209 /// One mask per destination, in the order the destinations were first seen. Bit `n` of a
210 /// mask is set when the value `low + n` goes to that destination.
211 arms: Vec<(u64, BlockCall)>,
212 },
213}
214
215impl Cluster {
216 /// The lowest value this cluster holds.
217 fn low(&self) -> i128 {
218 match *self {
219 Self::One { value, .. } => value,
220 Self::Run { low, .. } | Self::Bits { low, .. } => low,
221 }
222 }
223
224 /// The highest value this cluster holds.
225 fn high(&self) -> i128 {
226 match *self {
227 Self::One { value, .. } => value,
228 Self::Run { high, .. } | Self::Bits { high, .. } => high,
229 }
230 }
231
232 /// Whether every value in this cluster goes where that edge goes.
233 ///
234 /// A bit test never does, because it has more than one destination and this is only asked in
235 /// order to merge two clusters into one run. Grouping happens after that merging and never
236 /// before it, so the question does not come up, and answering no is right either way.
237 fn goes_to(&self, func: &Func, call: BlockCall) -> bool {
238 match *self {
239 Self::One { call: mine, .. } | Self::Run { call: mine, .. } => same(func, mine, call),
240 Self::Bits { .. } => false,
241 }
242 }
243
244 /// Grows the cluster upwards to a value, which the caller has already checked is the one
245 /// immediately above it and goes to the same place.
246 fn grow(&mut self, value: i128) {
247 let call = match *self {
248 Self::One { call, .. } | Self::Run { call, .. } => call,
249 Self::Bits { .. } => unreachable!("a bit test is never grown into a run"),
250 };
251 *self = Self::Run { low: self.low(), high: value, call };
252 }
253}
254
255/// The widest span of values one bit test covers, which is the width of the word its mask lives in.
256///
257/// This is a correctness bound and not a tuning one, which is what section 24.6 asks it to be.
258/// `1 << (x - low)` is undefined once `x - low` reaches the width of the word being shifted, so a
259/// group is only ever formed inside this span and the range check in front of the shift is what
260/// makes the shift amount stay there. Sixty four because the mask is held in an `i64`, which every
261/// target this compiler has can shift by a register.
262const WORD: i128 = 64;
263
264/// How many more case values a group needs than it has destinations before a bit test is worth
265/// writing.
266///
267/// Three, and it comes from counting instructions rather than from anywhere else. A bit test is a
268/// subtraction, a comparison and a branch for the range check, then a shift, then a mask and a
269/// branch for each destination: five instructions and two more per destination. What it replaces is
270/// two instructions per case value. So `n` values going to `t` destinations cost `2n` as compares
271/// and `5 + 2t` as a bit test, the two are level when `n` is two and a half clear of `t`, and three
272/// is the first whole number above that.
273///
274/// Unlike [`LINEAR`] this one is not fighting the branch predictor, which is why counting is enough
275/// here and was not enough there. A walk over `n` values and a bit test over the same `n` both end
276/// in a branch that is taken about as often, so what is left between them is the instruction count.
277const MARGIN: usize = 3;
278
279/// The case list sorted and cut into clusters.
280///
281/// Sorting is what makes the rest of this possible: a decision tree needs an order to split on, and
282/// a run of consecutive values is only visible once the values are next to each other. It is
283/// `n log n` and it is the most expensive thing in the module, which section 24.7 says is fine
284/// because everything here is cheap next to the size of the construct.
285///
286/// # Panics
287///
288/// Panics on two cases of the same value. C forbids them and the front end rejects them, so
289/// everything below is written believing the clusters are disjoint, and section 24.6 asks for that
290/// belief to be recorded here rather than left implicit. Dropping the later of the pair instead
291/// would leave its arm with nothing branching to it, which is a function the IR verifier refuses,
292/// and quietly keeping both would put two clusters of the same value into a search that assumes it
293/// can tell them apart. A `switch` that arrives with a duplicate is a bug above this, and stopping
294/// on it is how it gets found.
295fn clusters(func: &Func, cases: &[Imm], arms: &[BlockCall], ty: Type) -> Vec<Cluster> {
296 let mut sorted: Vec<(i128, BlockCall)> =
297 cases.iter().zip(arms).map(|(&imm, &call)| (imm.signed(ty), call)).collect();
298 sorted.sort_by_key(|&(value, _)| value);
299 assert!(
300 sorted.windows(2).all(|pair| pair[0].0 != pair[1].0),
301 "a switch with two cases of the same value reached the back end"
302 );
303
304 let mut clusters: Vec<Cluster> = Vec::with_capacity(sorted.len());
305 for (value, call) in sorted {
306 match clusters.last_mut() {
307 // In `i128`, so that a run reaching the top of its own type is the addition it looks
308 // like rather than an overflow.
309 Some(last) if last.high() + 1 == value && last.goes_to(func, call) => {
310 last.grow(value);
311 }
312 _ => clusters.push(Cluster::One { value, call }),
313 }
314 }
315 clusters
316}
317
318/// Whether two edges go to the same block carrying the same values.
319///
320/// Both halves matter. Two cases whose arms are the same block but which pass it different
321/// arguments are two different destinations, and merging them into a run would hand the block one
322/// of the two whichever value arrived.
323fn same(func: &Func, a: BlockCall, b: BlockCall) -> bool {
324 a.block == b.block && func[a.args] == func[b.args]
325}
326
327/// The clusters again, with stretches of single values turned into bit tests where that is fewer
328/// instructions.
329///
330/// This is section 24.3's grouping phase and it is the greedy one. Each position takes the longest
331/// stretch of single values that fits inside a word, asks whether a bit test over it is worth
332/// writing, and either takes the whole stretch or takes one cluster and moves on. The document says
333/// the optimal partition is quadratic and is only justified on large switches, which are exactly the
334/// switches where compile time is already the thing being spent, so the greedy one is what is here
335/// and the other one is recorded rather than written.
336///
337/// Only single values are grouped. A run is already one subtraction and one comparison however many
338/// values it holds, so folding it into a mask replaces two instructions with two instructions and
339/// spends a word of the span doing it. That is a loss on the run and a loss on whatever the span
340/// would otherwise have reached.
341fn group(func: &Func, clusters: Vec<Cluster>) -> Vec<Cluster> {
342 let mut out: Vec<Cluster> = Vec::with_capacity(clusters.len());
343 let mut at = 0;
344 while at < clusters.len() {
345 let reach = reach(&clusters, at);
346 match bits(func, &clusters[at..at + reach]) {
347 Some(cluster) => {
348 out.push(cluster);
349 at += reach;
350 }
351 None => {
352 out.push(clusters[at].clone());
353 at += 1;
354 }
355 }
356 }
357 out
358}
359
360/// How many single values starting here sit inside one word of the first of them.
361fn reach(clusters: &[Cluster], at: usize) -> usize {
362 let Cluster::One { value: first, .. } = clusters[at] else { return 0 };
363 let mut reach = 0;
364 while let Some(Cluster::One { value, .. }) = clusters.get(at + reach) {
365 if value - first >= WORD {
366 break;
367 }
368 reach += 1;
369 }
370 reach
371}
372
373/// The masks for a stretch of single values, or nothing when the compares are the cheaper answer.
374///
375/// One mask per destination rather than one per value, which is the whole point: `case 'a': case
376/// 'e': case 'i': case 'o': case 'u':` is five values and one destination, so it is one mask and one
377/// test rather than five compares.
378fn bits(func: &Func, group: &[Cluster]) -> Option<Cluster> {
379 let low = group.first()?.low();
380 let mut arms: Vec<(u64, BlockCall)> = Vec::new();
381 for cluster in group {
382 let Cluster::One { value, call } = *cluster else { return None };
383 // Shifting is safe because `reach` only gathered values inside one word of `low`.
384 let bit = 1u64 << (value - low);
385 match arms.iter_mut().find(|&&mut (_, mine)| same(func, mine, call)) {
386 Some((mask, _)) => *mask |= bit,
387 None => arms.push((bit, call)),
388 }
389 }
390 if group.len() < arms.len() + MARGIN {
391 return None;
392 }
393 Some(Cluster::Bits { low, high: group.last()?.high(), arms })
394}
395
396/// A binary search over the clusters, ending in a chain of tests at each leaf.
397///
398/// The split is at the middle of the list and the test is whether the operand is below the lowest
399/// value of the upper half. Everything the lower half holds is below that value because the list is
400/// sorted and the clusters are disjoint, so an operand that is below it and matches anything at all
401/// matches something in the lower half, and one that is not is either in the upper half or in
402/// neither. Either way it reaches a leaf that tests what is left, and the leaf sends it to the
403/// default when none of that matches.
404fn tree(func: &mut Func, of: &Lowering, at: Block, clusters: &[Cluster]) {
405 if clusters.len() <= LINEAR {
406 chain(func, of, at, clusters);
407 return;
408 }
409 let (below, above) = clusters.split_at(clusters.len() / 2);
410 let pivot = above[0].low();
411 let left = func.create_block();
412 let right = func.create_block();
413
414 let mut build = Builder::new(func, at).at(of.span);
415 let want = build.iconst(of.ty, pivot);
416 let under = build.icmp(IntPred::Slt, of.value, want);
417 build.br_if(under, left, &[], right, &[]);
418
419 tree(func, of, left, below);
420 tree(func, of, right, above);
421}
422
423/// The clusters tested one after another, each falling to the next and the last to the default.
424///
425/// The block this starts in gets the first test, and each test after the first gets a block of its
426/// own that the one before it falls to when its test failed. The last falls to the default, so the
427/// default is not a block anything is created for and a chain of `n` clusters costs `n` less one.
428fn chain(func: &mut Func, of: &Lowering, at: Block, clusters: &[Cluster]) {
429 // A leaf with nothing in it is a jump. It is what a `switch` whose only label is `default` is,
430 // and it is also what one whose cases a later pass folded away would be.
431 let Some((last, rest)) = clusters.split_last() else {
432 let args: Vec<Value> = func[of.default.args].to_vec();
433 Builder::new(func, at).at(of.span).jump(of.default.block, &args);
434 return;
435 };
436
437 let mut at = at;
438 for cluster in rest {
439 let next = func.create_block();
440 test(func, of, at, cluster, next, &[]);
441 at = next;
442 }
443 let onward: Vec<Value> = func[of.default.args].to_vec();
444 test(func, of, at, last, of.default.block, &onward);
445}
446
447/// One cluster, as the comparison that decides it and the branch that acts on it.
448fn test(
449 func: &mut Func,
450 of: &Lowering,
451 at: Block,
452 cluster: &Cluster,
453 next: Block,
454 onward: &[Value],
455) {
456 if matches!(cluster, Cluster::Bits { .. }) {
457 scattered(func, of, at, cluster, next, onward);
458 return;
459 }
460 let call = match *cluster {
461 Cluster::One { call, .. } | Cluster::Run { call, .. } => call,
462 Cluster::Bits { .. } => unreachable!("a bit test was dealt with above"),
463 };
464 let taken: Vec<Value> = func[call.args].to_vec();
465 let mut build = Builder::new(func, at).at(of.span);
466 let matched = match *cluster {
467 Cluster::One { value, .. } => {
468 let want = build.iconst(of.ty, value);
469 build.icmp(IntPred::Eq, of.value, want)
470 }
471 Cluster::Run { low, high, .. } => {
472 let base = shifted_down(&mut build, of, low);
473 let width = build.iconst(of.ty, high - low);
474 build.icmp(IntPred::Ule, base, width)
475 }
476 Cluster::Bits { .. } => unreachable!("a bit test was dealt with above"),
477 };
478 build.br_if(matched, call.block, &taken, next, onward);
479}
480
481/// `x - low`, or `x` itself when the stretch starts at zero and there is nothing to take off it.
482///
483/// Compared unsigned against `high - low` this is one comparison covering both ends of a stretch: a
484/// value below the bottom wraps round to something enormous and fails the same test a value above
485/// the top fails. It is also what a bit test counts its bits from, which is why it is here rather
486/// than written out twice.
487fn shifted_down(build: &mut Builder<'_>, of: &Lowering, low: i128) -> Value {
488 if low == 0 {
489 return of.value;
490 }
491 let start = build.iconst(of.ty, low);
492 build.binary(Opcode::Sub, of.value, start, Flags::default())
493}
494
495/// A stretch of scattered values, as one range check and then one mask test per destination.
496///
497/// The shape is the one `gcc/tree-switch-conversion.h` states: `if ((1 << (x - low)) & mask)`. The
498/// range check comes first and is not an optimisation. It is what makes the shift defined, since a
499/// shift by the width of the word or more has no answer, and section 24.6 names this as the way a
500/// bit test goes wrong and the range check as the defence.
501///
502/// A value inside the range matching no mask goes to the default rather than on to the next test.
503/// The group is a stretch of clusters that were next to each other in the sorted list, so every case
504/// outside it is outside the range as well, and a value in the range that matched no mask has
505/// already been shown to match nothing at all.
506fn scattered(
507 func: &mut Func,
508 of: &Lowering,
509 at: Block,
510 cluster: &Cluster,
511 next: Block,
512 onward: &[Value],
513) {
514 let Cluster::Bits { low, high, arms } = cluster else {
515 unreachable!("only a bit test is written as one");
516 };
517 let (low, high) = (*low, *high);
518
519 // Every value in the range is named by some mask when the masks together cover it, and then the
520 // last destination needs no test of its own: it is where anything that got past the others goes.
521 // Asking for more than one destination is what keeps at least one test, and a lone destination
522 // covering a whole range is a run rather than a bit test anyway.
523 let all = arms.iter().fold(0u64, |seen, &(mask, _)| seen | mask);
524 let covered = arms.len() > 1 && all == span_mask(low, high);
525 let tests = arms.len() - usize::from(covered);
526 let (spare, onto_spare) = if covered {
527 let call = arms[arms.len() - 1].1;
528 (call.block, func[call.args].to_vec())
529 } else {
530 (of.default.block, func[of.default.args].to_vec())
531 };
532
533 // All of them before a builder exists, because a builder holds the function and a block cannot
534 // be made while it does.
535 let inside = func.create_block();
536 let mut blocks: Vec<Block> = vec![inside];
537 blocks.extend((1..tests).map(|_| func.create_block()));
538 let taken: Vec<Vec<Value>> = arms.iter().map(|&(_, call)| func[call.args].to_vec()).collect();
539
540 let mut build = Builder::new(func, at).at(of.span);
541 let base = shifted_down(&mut build, of, low);
542 let width = build.iconst(of.ty, high - low);
543 let ok = build.icmp(IntPred::Ule, base, width);
544 build.br_if(ok, inside, &[], next, onward);
545
546 // In a word, because that is the width the masks are and what the top of the range needs for a
547 // bit of its own. The range check above is what makes this shift amount a legal one.
548 let word = Type::int(u64::BITS);
549 let mut build = Builder::new(func, inside).at(of.span);
550 let amount = if of.ty == word { base } else { build.unary(Opcode::ZExt, base, word) };
551 let one = build.iconst(word, 1);
552 let bit = build.binary(Opcode::Shl, one, amount, Flags::default());
553
554 for (index, &(mask, call)) in arms[..tests].iter().enumerate() {
555 let want = build.iconst(word, i128::from(mask as i64));
556 let hit = build.binary(Opcode::And, bit, want, Flags::default());
557 let none = build.iconst(word, 0);
558 let matched = build.icmp(IntPred::Ne, hit, none);
559 let last = index + 1 == tests;
560 let onto = if last { spare } else { blocks[index + 1] };
561 let args = if last { &onto_spare[..] } else { &[][..] };
562 build.br_if(matched, call.block, &taken[index], onto, args);
563 if !last {
564 build = Builder::new(func, blocks[index + 1]).at(of.span);
565 }
566 }
567}
568
569/// The bits of a word that a range from `low` to `high` names, counted from `low`.
570///
571/// The width is one less than a word at most, because that is what [`reach`] gathers, so the shift
572/// below is a legal one and the answer is every bit the range can reach and no bit above it.
573fn span_mask(low: i128, high: i128) -> u64 {
574 let width = u32::try_from(high - low).expect("a group narrower than a word");
575 if width + 1 >= u64::BITS { u64::MAX } else { (1u64 << (width + 1)) - 1 }
576}
577
578/// The blocks a leaf chain of `n` clusters needs beyond the ones the program already had.
579///
580/// Here so that a test can say the number rather than count it, and so that whoever writes the jump
581/// table has one place to compare against. A `switch` that goes to a tree needs more than this,
582/// since the tree's own nodes are blocks too, and a test that cares about one of those counts them.
583#[must_use]
584pub fn blocks_for(clusters: usize) -> usize {
585 clusters.saturating_sub(1)
586}
587
588#[cfg(test)]
589mod tests {
590 use std::collections::HashMap;
591
592 use rucc_base::Interner;
593 use rucc_ir::{
594 Block, BlockCall, Builder, Extra, Func, Imm, InstData, IntPred, Module, Opcode, Signature,
595 SwitchInfo, Type, Value,
596 };
597 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
598
599 use super::{LINEAR, blocks_for, switches};
600
601 fn target() -> TargetInfo {
602 TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
603 }
604
605 /// Where a `switch` over these cases can end up, as the arms in the order given and the default.
606 struct Built {
607 names: Interner,
608 func: Func,
609 operand: Value,
610 arms: Vec<Block>,
611 default: Block,
612 }
613
614 /// `int sw(int x) { switch (x) { case 1: return 10; ... default: return 0; } }` as the walk
615 /// builds it, which is the program in issue 275.
616 ///
617 /// Every arm is a block of its own even when two cases would naturally share one, because a
618 /// test that wants two cases going to one place says so by passing the same block twice, and
619 /// [`built_sharing`] is how it does that.
620 fn built(cases: &[i128]) -> Built {
621 let arms: Vec<usize> = (0..cases.len()).collect();
622 built_sharing(cases, &arms, Type::int(32))
623 }
624
625 /// The same, with `arms[i]` saying which arm case `i` goes to, so that several cases can share.
626 fn built_sharing(cases: &[i128], arms: &[usize], ty: Type) -> Built {
627 let mut names = Interner::new();
628 let int = Type::int(32);
629 let mut func =
630 Func::new(names.intern("sw"), Signature::new().with_params(&[ty]).with_returns(&[int]));
631 let entry = func.create_block();
632 let x = func.append_param(entry, ty);
633
634 let default = func.create_block();
635 let count = arms.iter().copied().max().map_or(0, |top| top + 1);
636 let blocks: Vec<Block> = (0..count).map(|_| func.create_block()).collect();
637 let table: Vec<(i128, Block)> =
638 cases.iter().copied().zip(arms.iter().map(|&at| blocks[at])).collect();
639 Builder::new(&mut func, entry).switch(x, default, &table);
640
641 for (index, &arm) in blocks.iter().enumerate() {
642 let mut build = Builder::new(&mut func, arm);
643 let what = i128::try_from(index).expect("a small number of arms");
644 let v = build.iconst(int, (what + 1) * 10);
645 build.ret(&[v]);
646 }
647 let mut build = Builder::new(&mut func, default);
648 let v = build.iconst(int, 0);
649 build.ret(&[v]);
650 Built { names, func, operand: x, arms: blocks, default }
651 }
652
653 fn count(func: &Func) -> usize {
654 func.blocks().count()
655 }
656
657 fn printed(func: &Func, names: &mut Interner) -> String {
658 let module = Module::new(names.intern("sw.c"), &target());
659 rucc_ir::print_func(&module, func, names)
660 }
661
662 fn verified(built: &mut Built) {
663 let module = Module::new(built.names.intern("sw.c"), &target());
664 rucc_ir::verify_func(&module, &built.func, &built.names)
665 .expect("the rewrite builds valid IR");
666 }
667
668 /// Where the operand `x` ends up, worked out by running what the lowering wrote.
669 ///
670 /// This is the test the shape actually needs. Counting compares says the tree is small and says
671 /// nothing about whether it is right, and a decision tree that sends one value down the wrong
672 /// side is a miscompilation that no amount of counting finds. So the blocks the lowering built
673 /// are interpreted for a concrete operand, and the answer is the block it arrives at.
674 ///
675 /// It understands the handful of things this module writes and nothing else, which is how it
676 /// knows it has arrived: an arm ends in a `return`, so the walk stops at the block whose
677 /// instructions it cannot follow.
678 ///
679 /// Every value is held as the number its own type says it is, sign extended, rather than at the
680 /// width of the operand. A bit test computes in a word whatever the operand's width is, so an
681 /// interpreter that assumed one width would get the mask wrong and would agree with itself
682 /// while doing it.
683 fn arrives(func: &Func, operand: Value, x: i128, ty: Type) -> Block {
684 let mut at = func.entry().expect("an entry block");
685 let mut held: HashMap<Value, i128> = HashMap::new();
686 held.insert(operand, Imm::int(x, ty).signed(ty));
687 loop {
688 let mut moved = None;
689 for inst in func.insts(at).collect::<Vec<_>>() {
690 let opcode = func[inst].opcode;
691 let extra = func[inst].extra;
692 let result = func[inst].first_result;
693 let args: Vec<i128> = func[func[inst].args]
694 .iter()
695 .map(|value| held.get(value).copied().unwrap_or(0))
696 .collect();
697 let wide = |value: Option<Value>| func[value.expect("a result")].ty;
698 let mut put = |value: Option<Value>, what: i128| {
699 let value = value.expect("a result");
700 let ty = func[value].ty;
701 held.insert(value, Imm::int(what, ty).signed(ty));
702 };
703 match opcode {
704 Opcode::IConst => {
705 let Extra::Imm(imm) = extra else { return at };
706 put(result, func[imm].signed(wide(result)));
707 }
708 Opcode::Sub => put(result, args[0] - args[1]),
709 Opcode::And => put(result, args[0] & args[1]),
710 Opcode::Shl => put(result, args[0] << args[1]),
711 Opcode::ZExt => {
712 let from = func[func[func[inst].args][0]].ty;
713 let raw = Imm::int(args[0], from).unsigned();
714 put(result, i128::try_from(raw).expect("a value narrower than a word"));
715 }
716 Opcode::ICmp => {
717 let Extra::IntPred(pred) = extra else { return at };
718 let of = func[func[func[inst].args][0]].ty;
719 let unsigned = |v: i128| Imm::int(v, of).unsigned();
720 let answer = match pred {
721 IntPred::Eq => args[0] == args[1],
722 IntPred::Ne => args[0] != args[1],
723 IntPred::Slt => args[0] < args[1],
724 IntPred::Ule => unsigned(args[0]) <= unsigned(args[1]),
725 other => panic!("the lowering does not write {}", other.name()),
726 };
727 held.insert(result.expect("a comparison has a result"), i128::from(answer));
728 }
729 Opcode::Jump => {
730 let call = func.successors(inst).next().expect("a jump has a target");
731 moved = Some(call.block);
732 }
733 Opcode::BrIf => {
734 let mut targets = func.successors(inst);
735 let taken = targets.next().expect("a branch has two targets");
736 let other = targets.next().expect("a branch has two targets");
737 moved = Some(if args[0] != 0 { taken.block } else { other.block });
738 }
739 _ => return at,
740 }
741 }
742 match moved {
743 Some(next) => at = next,
744 None => return at,
745 }
746 }
747 }
748
749 /// Every probe arrives where the case list says it should, whatever shape the lowering picked.
750 fn routes(built: &mut Built, cases: &[i128], arms: &[usize], probes: &[i128], ty: Type) {
751 switches(&mut built.func);
752 verified(built);
753 for &x in probes {
754 let wanted = cases
755 .iter()
756 .position(|&case| case == x)
757 .map_or(built.default, |at| built.arms[arms[at]]);
758 let got = arrives(&built.func, built.operand, x, ty);
759 assert_eq!(got, wanted, "the operand {x} went to the wrong block");
760 }
761 }
762
763 /// Every case value, both sides of every one of them, and the ends of the type.
764 fn around(cases: &[i128], ty: Type) -> Vec<i128> {
765 let mut probes: Vec<i128> = Vec::new();
766 for &case in cases {
767 probes.extend([case - 1, case, case + 1]);
768 }
769 let bits = ty.bits();
770 probes.extend([0, -1, 1, i128::from(i32::MIN) >> (32 - bits), (1 << (bits - 1)) - 1]);
771 probes.retain(|&x| Imm::int(x, ty).signed(ty) == x);
772 probes.sort_unstable();
773 probes.dedup();
774 probes
775 }
776
777 #[test]
778 fn a_small_switch_is_a_compare_and_a_branch_for_each_case() {
779 let mut built = built(&[1, 2]);
780 let before = count(&built.func);
781 switches(&mut built.func);
782 assert_eq!(count(&built.func), before + blocks_for(2));
783
784 let text = printed(&built.func, &mut built.names);
785 assert!(!text.contains("switch"), "the switch is gone: {text}");
786 assert_eq!(text.matches("icmp eq").count(), 2, "one compare per case: {text}");
787 assert_eq!(text.matches("br_if").count(), 2, "one branch per case: {text}");
788 }
789
790 #[test]
791 fn the_last_case_falls_to_the_default_rather_than_to_a_block_of_its_own() {
792 let mut built = built(&[7]);
793 let before = count(&built.func);
794 switches(&mut built.func);
795 // One case needs no chain block at all: the one compare goes to the arm or to the default.
796 assert_eq!(count(&built.func), before);
797 assert_eq!(blocks_for(1), 0);
798 }
799
800 #[test]
801 fn a_switch_with_only_a_default_is_a_jump() {
802 let mut built = built(&[]);
803 switches(&mut built.func);
804 let entry = built.func.entry().expect("an entry block");
805 let term = built.func.terminator(entry).expect("a terminator");
806 assert_eq!(built.func[term].opcode, Opcode::Jump);
807 }
808
809 /// The rewrite has to leave a function the verifier still accepts, since every check it makes
810 /// is one the rest of the back end assumes and none of them is rechecked after this runs.
811 #[test]
812 fn what_comes_out_is_valid_ir() {
813 let mut built = built(&[1, 2, 3, 4]);
814 switches(&mut built.func);
815 verified(&mut built);
816 }
817
818 /// Nothing else is touched, which matters because this runs over every function whether or not
819 /// one has a `switch` in it.
820 #[test]
821 fn a_function_with_no_switch_is_left_exactly_as_it_was() {
822 let mut names = Interner::new();
823 let int = Type::int(32);
824 let mut func =
825 Func::new(names.intern("f"), Signature::new().with_params(&[int]).with_returns(&[int]));
826 let entry = func.create_block();
827 let x = func.append_param(entry, int);
828 Builder::new(&mut func, entry).ret(&[x]);
829
830 let before = printed(&func, &mut names);
831 switches(&mut func);
832 assert_eq!(printed(&func, &mut names), before);
833 }
834
835 #[test]
836 fn a_run_of_cases_going_to_one_place_is_one_range_test() {
837 let cases = [3, 4, 5, 6, 7, 8, 9, 10];
838 let arms = [0; 8];
839 let mut built = built_sharing(&cases, &arms, Type::int(32));
840 switches(&mut built.func);
841
842 let text = printed(&built.func, &mut built.names);
843 assert_eq!(text.matches("icmp").count(), 1, "eight cases, one test: {text}");
844 assert_eq!(text.matches("icmp ule").count(), 1, "and the test is the range: {text}");
845 assert_eq!(text.matches("sub").count(), 1, "one subtraction to bring it to zero: {text}");
846 }
847
848 #[test]
849 fn a_run_that_starts_at_zero_needs_no_subtraction() {
850 let cases = [0, 1, 2, 3, 4];
851 let arms = [0; 5];
852 let mut built = built_sharing(&cases, &arms, Type::int(32));
853 switches(&mut built.func);
854
855 let text = printed(&built.func, &mut built.names);
856 assert_eq!(text.matches("icmp ule").count(), 1, "one range test: {text}");
857 assert!(!text.contains("sub"), "nothing to subtract from zero: {text}");
858 }
859
860 /// The clusters are what the tree is built over, so a `switch` of forty cases that fall into
861 /// three runs is a `switch` of three tests and not a search.
862 #[test]
863 fn the_tree_is_built_over_the_clusters_and_not_over_the_cases() {
864 let cases: Vec<i128> = (0..30).collect();
865 let arms: Vec<usize> = (0..30).map(|at: usize| at / 10).collect();
866 let mut built = built_sharing(&cases, &arms, Type::int(32));
867 switches(&mut built.func);
868
869 let text = printed(&built.func, &mut built.names);
870 assert_eq!(text.matches("icmp").count(), 3, "three runs, three tests: {text}");
871 assert!(!text.contains("icmp slt"), "three clusters is under the leaf size: {text}");
872 }
873
874 /// The number the whole thing is for. Forty scattered cases used to be forty comparisons on the
875 /// way to the last of them, and a binary search is the difference between that and seven.
876 #[test]
877 fn a_long_sparse_switch_is_a_search_rather_than_a_walk() {
878 // Four leaves' worth, so the tree is two splits deep and the bound below is a bound on
879 // something rather than a restatement of the leaf size.
880 let count = 4 * LINEAR as i128;
881 let cases: Vec<i128> = (0..count).map(|at| at * 7).collect();
882 let mut built = built(&cases);
883 switches(&mut built.func);
884
885 let worst = deepest(&built.func);
886 assert!(worst <= LINEAR + 2, "{count} cases in {worst} comparisons at worst");
887 assert!(worst > LINEAR, "and the splits are being counted too");
888 }
889
890 /// The most comparisons on any path from the entry to an arm.
891 ///
892 /// A depth first walk over the blocks the lowering wrote, which is a directed acyclic graph
893 /// because every branch it writes goes forward, so no path is walked twice and nothing loops.
894 fn deepest(func: &Func) -> usize {
895 fn walk(func: &Func, at: Block, seen: &mut HashMap<Block, usize>) -> usize {
896 if let Some(&known) = seen.get(&at) {
897 return known;
898 }
899 let here = func.insts(at).filter(|&inst| func[inst].opcode == Opcode::ICmp).count();
900 let term = func.terminator(at).expect("a terminator");
901 let onward: Vec<Block> = match func[term].opcode {
902 Opcode::Jump | Opcode::BrIf => {
903 func.successors(term).map(|call| call.block).collect()
904 }
905 _ => Vec::new(),
906 };
907 let below =
908 onward.into_iter().map(|block| walk(func, block, seen)).max().unwrap_or_default();
909 seen.insert(at, here + below);
910 here + below
911 }
912 walk(func, func.entry().expect("an entry block"), &mut HashMap::new())
913 }
914
915 #[test]
916 fn every_value_reaches_the_arm_its_case_named_in_a_small_switch() {
917 let cases = [1, 2, 3];
918 let arms = [0, 1, 2];
919 let ty = Type::int(32);
920 let mut built = built(&cases);
921 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
922 }
923
924 #[test]
925 fn every_value_reaches_the_arm_its_case_named_in_a_search() {
926 let count = 3 * LINEAR;
927 let cases: Vec<i128> = (0..count as i128).map(|at| at * 7).collect();
928 let arms: Vec<usize> = (0..count).collect();
929 let ty = Type::int(32);
930 let mut built = built(&cases);
931 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
932 }
933
934 /// The one the signed sort and the signed split have to agree about. A `switch` whose cases sit
935 /// on both sides of zero is where sorting one way and comparing the other goes wrong.
936 #[test]
937 fn every_value_reaches_its_arm_when_the_cases_straddle_zero() {
938 let half = LINEAR as i128;
939 let cases: Vec<i128> = (-half..half).map(|at| at * 3).collect();
940 let arms: Vec<usize> = (0..2 * LINEAR).collect();
941 let ty = Type::int(32);
942 let mut built = built(&cases);
943 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
944 }
945
946 /// Runs and single values in the same statement, which is the partition the module is named
947 /// after and the thing a design that picked one shape could not say.
948 #[test]
949 fn every_value_reaches_its_arm_when_runs_and_singles_are_mixed() {
950 let cases: Vec<i128> =
951 vec![-9, -8, -7, -6, 0, 5, 6, 7, 8, 9, 10, 40, 41, 90, 91, 92, 93, 94, 95, 200];
952 let arms: Vec<usize> = vec![0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 3, 4, 5, 5, 5, 5, 5, 5, 6];
953 let ty = Type::int(32);
954 let mut built = built_sharing(&cases, &arms, ty);
955 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
956 }
957
958 /// A run that covers a whole type, where the width of it is every bit set and the comparison
959 /// against it is a test that is true of everything. Section 24.6 calls this out because the
960 /// same arithmetic in the switch's own type overflows here rather than wrapping usefully.
961 #[test]
962 fn a_run_covering_the_whole_type_matches_everything() {
963 let cases: Vec<i128> = (-128..128).collect();
964 let arms = vec![0; cases.len()];
965 let ty = Type::int(8);
966 let mut built = built_sharing(&cases, &arms, ty);
967 switches(&mut built.func);
968 verified(&mut built);
969
970 let text = printed(&built.func, &mut built.names);
971 assert_eq!(text.matches("icmp").count(), 1, "one run, one test: {text}");
972
973 let entry = built.func.entry().expect("an entry block");
974 let operand = built.func[entry].params[0];
975 for x in [-128, -1, 0, 1, 127] {
976 assert_eq!(
977 arrives(&built.func, operand, x, ty),
978 built.arms[0],
979 "every value of the type is in the run"
980 );
981 }
982 }
983
984 /// C forbids one and the front end rejects one, and everything the clusters promise each other
985 /// rests on that, so a duplicate that got this far stops the compiler rather than being guessed
986 /// at. Section 24.6 asks for the assumption to be recorded, and this is the record.
987 #[test]
988 #[should_panic(expected = "two cases of the same value")]
989 fn a_case_value_written_twice_stops_the_compiler() {
990 let cases = [4, 9, 4];
991 let arms = [0, 1, 2];
992 let mut built = built_sharing(&cases, &arms, Type::int(32));
993 switches(&mut built.func);
994 }
995
996 /// Two consecutive cases whose arms are the same block but which pass it different arguments
997 /// are two destinations, so they are two clusters and not one run. Nothing the front end writes
998 /// produces this today, which is why the `switch` has to be built by hand, and the check is
999 /// there because a run that merged them would hand the block one of the two values whichever
1000 /// case arrived.
1001 #[test]
1002 fn cases_that_share_a_block_but_not_its_arguments_are_not_a_run() {
1003 let mut names = Interner::new();
1004 let int = Type::int(32);
1005 let mut func = Func::new(
1006 names.intern("sw"),
1007 Signature::new().with_params(&[int]).with_returns(&[int]),
1008 );
1009 let entry = func.create_block();
1010 let x = func.append_param(entry, int);
1011 let default = func.create_block();
1012 let join = func.create_block();
1013 let param = func.append_param(join, int);
1014
1015 let mut build = Builder::new(&mut func, entry);
1016 let ten = build.iconst(int, 10);
1017 let twenty = build.iconst(int, 20);
1018 let none = func.push_values(&[]);
1019 let first = func.push_values(&[ten]);
1020 let second = func.push_values(&[twenty]);
1021 let targets = func.push_block_calls(&[
1022 BlockCall::new(default, none),
1023 BlockCall::new(join, first),
1024 BlockCall::new(join, second),
1025 ]);
1026 let cases = func.push_imms(&[Imm::int(1, int), Imm::int(2, int)]);
1027 let info = func.add_switch(SwitchInfo { targets, cases });
1028 let args = func.push_values(&[x]);
1029 let data = InstData { args, extra: Extra::Switch(info), ..InstData::new(Opcode::Switch) };
1030 Builder::new(&mut func, entry).inst(data, &[]);
1031
1032 let mut build = Builder::new(&mut func, join);
1033 build.ret(&[param]);
1034 let mut build = Builder::new(&mut func, default);
1035 let zero = build.iconst(int, 0);
1036 build.ret(&[zero]);
1037
1038 switches(&mut func);
1039 let text = printed(&func, &mut names);
1040 assert_eq!(text.matches("icmp eq").count(), 2, "two cases, two equality tests: {text}");
1041 assert!(!text.contains("icmp ule"), "and no range test over them: {text}");
1042 }
1043
1044 /// The leaf size is a number and not an accident, so it is worth one test that says what it is
1045 /// for: at the size itself nothing is built, and one past it the search starts.
1046 #[test]
1047 fn the_leaf_size_is_where_the_search_starts() {
1048 let flat: Vec<i128> = (0..LINEAR as i128).map(|at| at * 5).collect();
1049 let mut walked = built(&flat);
1050 switches(&mut walked.func);
1051 assert!(
1052 !printed(&walked.func, &mut walked.names).contains("icmp slt"),
1053 "a leaf's worth of clusters is still a chain"
1054 );
1055
1056 let one_more: Vec<i128> = (0..LINEAR as i128 + 1).map(|at| at * 5).collect();
1057 let mut split = built(&one_more);
1058 switches(&mut split.func);
1059 assert!(
1060 printed(&split.func, &mut split.names).contains("icmp slt"),
1061 "one more than a leaf splits"
1062 );
1063 }
1064
1065 /// The shape the bit test exists for. `case 'a': case 'e': case 'i': case 'o': case 'u':` is
1066 /// five values scattered through twenty one, all going to one place, and every one of them used
1067 /// to be a comparison of its own.
1068 #[test]
1069 fn scattered_cases_sharing_one_arm_are_one_mask_and_one_test() {
1070 let cases = [97, 101, 105, 111, 117];
1071 let arms = [0; 5];
1072 let mut built = built_sharing(&cases, &arms, Type::int(32));
1073 switches(&mut built.func);
1074
1075 let text = printed(&built.func, &mut built.names);
1076 assert!(!text.contains("icmp eq"), "no case is compared on its own: {text}");
1077 assert_eq!(text.matches("shl").count(), 1, "one bit is picked out: {text}");
1078 assert_eq!(text.matches("and").count(), 1, "and one mask is asked about it: {text}");
1079 assert_eq!(text.matches("icmp ule").count(), 1, "one bound before the shift: {text}");
1080 assert_eq!(text.matches("icmp ne").count(), 1, "one test for the one arm: {text}");
1081 }
1082
1083 /// What the counting above does not say. A mask with a bit in the wrong place still has one
1084 /// shift and one test in it, so the test that matters is where each value ends up.
1085 #[test]
1086 fn every_value_reaches_its_arm_through_a_bit_test() {
1087 let ty = Type::int(32);
1088 let cases = [97, 101, 105, 111, 117];
1089 let arms = [0; 5];
1090 let mut built = built_sharing(&cases, &arms, ty);
1091 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1092 }
1093
1094 /// One group can hold several destinations, each as the bits of a mask of its own, and they are
1095 /// asked about in turn. The shift is what is shared, and the shift is the expensive part.
1096 #[test]
1097 fn a_bit_test_carries_several_destinations_in_one_word() {
1098 let ty = Type::int(32);
1099 let cases: Vec<i128> = (0..9).map(|at| at * 3).collect();
1100 let arms: Vec<usize> = (0..9).map(|at: usize| at % 3).collect();
1101 let mut built = built_sharing(&cases, &arms, ty);
1102 switches(&mut built.func);
1103
1104 let text = printed(&built.func, &mut built.names);
1105 assert_eq!(text.matches("shl").count(), 1, "nine cases, one shift: {text}");
1106 assert_eq!(text.matches("icmp ne").count(), 3, "three arms, three masks: {text}");
1107 assert!(!text.contains("icmp eq"), "and no case compared on its own: {text}");
1108
1109 let mut built = built_sharing(&cases, &arms, ty);
1110 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1111 }
1112
1113 /// When the masks between them account for every value in the span, the last destination is
1114 /// where anything in range that matched nothing else has to go, so it needs no test of its own.
1115 #[test]
1116 fn the_last_destination_needs_no_test_when_the_masks_cover_the_span() {
1117 let ty = Type::int(32);
1118 let cases: Vec<i128> = (0..6).collect();
1119 let arms: Vec<usize> = (0..6).map(|at: usize| at % 2).collect();
1120 let mut built = built_sharing(&cases, &arms, ty);
1121 switches(&mut built.func);
1122
1123 let text = printed(&built.func, &mut built.names);
1124 assert_eq!(text.matches("icmp ne").count(), 1, "two arms, one mask asked about: {text}");
1125
1126 let mut built = built_sharing(&cases, &arms, ty);
1127 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1128 }
1129
1130 /// A bit test costs a bound, a shift and a test before the first mask is looked at, so a group
1131 /// that has barely more values in it than destinations is worse than the walk it replaces.
1132 #[test]
1133 fn a_group_that_does_not_pay_for_itself_stays_a_chain() {
1134 let cases = [0, 3, 6];
1135 let arms = [0, 1, 2];
1136 let mut built = built_sharing(&cases, &arms, Type::int(32));
1137 switches(&mut built.func);
1138
1139 let text = printed(&built.func, &mut built.names);
1140 assert!(!text.contains("shl"), "three values and three arms buys nothing: {text}");
1141 assert_eq!(text.matches("icmp eq").count(), 3, "so it stays a walk: {text}");
1142 }
1143
1144 /// The word is a correctness bound and not a tuning one. A mask holds sixty four bits, so a
1145 /// group stops at the last value within sixty four of its first, and what is left of the
1146 /// `switch` carries on without it.
1147 #[test]
1148 fn a_bit_test_never_spans_more_than_a_word() {
1149 let ty = Type::int(32);
1150 let cases = [0, 2, 4, 6, 64];
1151 let arms = [0; 5];
1152 let mut built = built_sharing(&cases, &arms, ty);
1153 switches(&mut built.func);
1154
1155 let text = printed(&built.func, &mut built.names);
1156 assert_eq!(text.matches("shl").count(), 1, "one group, not two: {text}");
1157 assert_eq!(text.matches("icmp eq").count(), 1, "and the value past it is compared: {text}");
1158
1159 let mut built = built_sharing(&cases, &arms, ty);
1160 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1161 }
1162
1163 /// The value sixty three above the first sets the top bit of the mask, which is the shift the
1164 /// span bound is there to keep legal and the one an interpreter that computed in the operand's
1165 /// width would get wrong.
1166 #[test]
1167 fn a_bit_test_reaches_the_top_of_its_word() {
1168 let ty = Type::int(32);
1169 let cases = [0, 2, 4, 63];
1170 let arms = [0; 4];
1171 let mut built = built_sharing(&cases, &arms, ty);
1172 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1173 }
1174
1175 /// A run is already one subtraction and one comparison however many values it holds, so folding
1176 /// it into a mask would replace two instructions with two instructions and spend a word of span
1177 /// doing it. Only single values are grouped.
1178 #[test]
1179 fn a_run_is_left_alone_rather_than_folded_into_a_mask() {
1180 let ty = Type::int(32);
1181 let cases = [0, 1, 2, 3, 10, 12, 14, 16];
1182 let arms = [0, 0, 0, 0, 1, 1, 1, 1];
1183 let mut built = built_sharing(&cases, &arms, ty);
1184 switches(&mut built.func);
1185
1186 let text = printed(&built.func, &mut built.names);
1187 assert_eq!(text.matches("icmp ule").count(), 2, "a run's bound and a group's: {text}");
1188 assert_eq!(text.matches("shl").count(), 1, "and only the group is a mask: {text}");
1189
1190 let mut built = built_sharing(&cases, &arms, ty);
1191 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1192 }
1193
1194 /// Negative cases are the ones a shift gets wrong if the span is measured with the wrong sign,
1195 /// so a group that starts below zero is worth its own routing check.
1196 #[test]
1197 fn every_value_reaches_its_arm_when_a_bit_test_starts_below_zero() {
1198 let ty = Type::int(32);
1199 let cases = [-20, -17, -14, -11, -8, -5];
1200 let arms = [0, 1, 0, 1, 0, 1];
1201 let mut built = built_sharing(&cases, &arms, ty);
1202 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1203 }
1204}