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