1use std::fmt::Write as _;
103
104use rucc_cost::Goal;
105use rucc_cost::heuristics::{
106 JUMP_TABLE_MIN_TARGETS, JUMP_TABLE_MIN_TARGETS_FOR_SIZE, SWITCH_PEEL_PERCENT,
107};
108use rucc_diag::Span;
109use rucc_ir::{
110 Block, BlockCall, Builder, Extra, Flags, Func, Hint, Imm, Inst, IntPred, Opcode, Type, Value,
111};
112
113pub const LINEAR: usize = 32;
148
149pub fn switches(func: &mut Func, goal: Goal) {
158 let _ = lowered(func, goal, None);
159}
160
161#[must_use]
164pub fn lowered(func: &mut Func, goal: Goal, force: Option<Force>) -> Vec<Lowered> {
165 let found: Vec<Inst> = func
166 .blocks()
167 .filter_map(|block| func.terminator(block))
168 .filter(|&inst| func[inst].opcode == Opcode::Switch)
169 .collect();
170 found.into_iter().filter_map(|inst| lower(func, inst, goal, force)).collect()
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq)]
180pub enum Force {
181 Table,
183 Tree,
185 Walk,
187}
188
189impl Force {
190 #[must_use]
192 pub fn named(name: &str) -> Option<Self> {
193 match name {
194 "table" => Some(Self::Table),
195 "tree" => Some(Self::Tree),
196 "walk" => Some(Self::Walk),
197 _ => None,
198 }
199 }
200}
201
202pub const FORCED_CELLS: i128 = 4096;
205
206#[derive(Debug, Clone, Copy, PartialEq, Eq)]
208pub struct Lowered {
209 pub cases: usize,
211 pub clusters: usize,
213 pub tables: usize,
215 pub bits: usize,
217 pub searched: bool,
219 pub peeled: bool,
221}
222
223impl Lowered {
224 #[must_use]
231 pub fn shape(&self) -> &'static str {
232 if self.tables > 0 {
233 "table"
234 } else if self.bits > 0 {
235 "bit-test"
236 } else if self.searched {
237 "tree"
238 } else {
239 "walk"
240 }
241 }
242
243 #[must_use]
245 pub fn describe(&self) -> String {
246 let mut out = format!(
247 "switch of {} cases lowered as a {}; clusters {}, tables {}, bit tests {}",
248 self.cases,
249 self.shape(),
250 self.clusters,
251 self.tables,
252 self.bits
253 );
254 if self.peeled {
255 let _ = write!(out, ", hot case first");
256 }
257 out
258 }
259}
260
261fn lower(func: &mut Func, inst: Inst, goal: Goal, force: Option<Force>) -> Option<Lowered> {
263 let block = func.block_of(inst).expect("a terminator is in a block");
264 let span = func.span(inst);
265 let Extra::Switch(info) = func[inst].extra else { return None };
266 let info = func[info];
267 let &value = func[func[inst].args].first()?;
268 let ty = func[value].ty.lane();
271 let calls: Vec<BlockCall> = func[info.targets].to_vec();
272 let mut cases: Vec<Imm> = func[info.cases].to_vec();
273 let count = cases.len();
274 let (&default, arms) = calls.split_first()?;
275 let mut arms = arms.to_vec();
276 let hot = hottest(&arms).map(|at| (cases.remove(at).signed(ty), arms.remove(at)));
277 let found = clusters(func, &cases, &arms, ty);
278 let clusters = match force {
279 None => group(func, tables(func, found, ty, goal)),
280 Some(Force::Table) => forced(found, ty),
281 Some(Force::Tree | Force::Walk) => found,
282 };
283 let leaf = match force {
284 Some(Force::Tree) => 1,
285 Some(Force::Walk) => usize::MAX,
286 Some(Force::Table) | None => LINEAR,
287 };
288 let lowered = Lowered {
289 cases: count,
290 clusters: clusters.len(),
291 tables: clusters.iter().filter(|one| matches!(one, Cluster::Table { .. })).count(),
292 bits: clusters.iter().filter(|one| matches!(one, Cluster::Bits { .. })).count(),
293 searched: clusters.len() > leaf,
294 peeled: hot.is_some(),
295 };
296
297 func.remove_inst(inst);
300 let of = Lowering { value, ty, default, span };
301 let rest = match hot {
302 Some((case, call)) => peel(func, &of, block, case, call),
303 None => block,
304 };
305 tree(func, &of, rest, &clusters, leaf);
306 Some(lowered)
307}
308
309fn forced(clusters: Vec<Cluster>, ty: Type) -> Vec<Cluster> {
312 let (Some(first), Some(last)) = (clusters.first(), clusters.last()) else { return clusters };
313 if ty.bits() == 0 || ty.bits() > u64::BITS || last.high() - first.low() >= FORCED_CELLS {
314 return clusters;
315 }
316 vec![table(&clusters)]
317}
318
319fn hottest(arms: &[BlockCall]) -> Option<usize> {
324 let (at, parts) = arms
325 .iter()
326 .enumerate()
327 .filter_map(|(at, call)| Some((at, call.hint.taken()?)))
328 .max_by_key(|&(_, parts)| parts)?;
329 (parts >= SWITCH_PEEL_PERCENT * Hint::SCALE / 100).then_some(at)
330}
331
332fn peel(func: &mut Func, of: &Lowering, at: Block, case: i128, call: BlockCall) -> Block {
335 let rest = func.create_block();
336 let taken: Vec<Value> = func[call.args].to_vec();
337 let mut build = Builder::new(func, at).at(of.span);
338 let want = build.iconst(of.ty, case);
339 let matched = build.icmp(IntPred::Eq, of.value, want);
340 build.br_if(matched, call.block, &taken, rest, &[]);
341 let term = func.terminator(at).expect("the branch just written");
342 for (slot, hint) in func.target_list(term).iter().zip([call.hint, call.hint.complement()]) {
343 let written = func[slot];
344 func.set_block_call(slot, BlockCall { hint, ..written });
345 }
346 rest
347}
348
349struct Lowering {
354 value: Value,
356 ty: Type,
358 default: BlockCall,
360 span: Span,
362}
363
364#[derive(Clone, Debug)]
371enum Cluster {
372 One {
374 value: i128,
376 call: BlockCall,
378 },
379 Run {
381 low: i128,
383 high: i128,
385 call: BlockCall,
387 },
388 Table {
391 low: i128,
393 high: i128,
395 arms: Vec<(i128, BlockCall)>,
398 },
399 Bits {
402 low: i128,
404 high: i128,
406 arms: Vec<(u64, BlockCall)>,
409 },
410}
411
412impl Cluster {
413 fn low(&self) -> i128 {
415 match *self {
416 Self::One { value, .. } => value,
417 Self::Run { low, .. } | Self::Bits { low, .. } | Self::Table { low, .. } => low,
418 }
419 }
420
421 fn high(&self) -> i128 {
423 match *self {
424 Self::One { value, .. } => value,
425 Self::Run { high, .. } | Self::Bits { high, .. } | Self::Table { high, .. } => high,
426 }
427 }
428
429 fn goes_to(&self, func: &Func, call: BlockCall) -> bool {
435 match *self {
436 Self::One { call: mine, .. } | Self::Run { call: mine, .. } => same(func, mine, call),
437 Self::Bits { .. } | Self::Table { .. } => false,
438 }
439 }
440
441 fn grow(&mut self, value: i128) {
444 let call = match *self {
445 Self::One { call, .. } | Self::Run { call, .. } => call,
446 Self::Bits { .. } | Self::Table { .. } => {
447 unreachable!("a bit test or a table is never grown into a run")
448 }
449 };
450 *self = Self::Run { low: self.low(), high: value, call };
451 }
452}
453
454const JUMP_TABLE_GROWTH: i128 = 8;
465
466const JUMP_TABLE_GROWTH_FOR_SIZE: i128 = 3;
472
473fn tables(func: &Func, clusters: Vec<Cluster>, ty: Type, goal: Goal) -> Vec<Cluster> {
486 if ty.bits() == 0 || ty.bits() > u64::BITS {
487 return clusters;
488 }
489 let mut out: Vec<Cluster> = Vec::with_capacity(clusters.len());
490 let mut at = 0;
491 while at < clusters.len() {
492 match dense(func, &clusters[at..], goal) {
493 Some(end) => {
494 out.push(table(&clusters[at..at + end]));
495 at += end;
496 }
497 None => {
498 out.push(clusters[at].clone());
499 at += 1;
500 }
501 }
502 }
503 out
504}
505
506fn dense(func: &Func, clusters: &[Cluster], goal: Goal) -> Option<usize> {
521 let (growth, least) = match goal {
522 Goal::Speed => (JUMP_TABLE_GROWTH, JUMP_TABLE_MIN_TARGETS),
523 Goal::Size => (JUMP_TABLE_GROWTH_FOR_SIZE, JUMP_TABLE_MIN_TARGETS_FOR_SIZE),
524 };
525 let low = clusters.first()?.low();
526 let most = 2 * i128::try_from(clusters.len()).ok()?;
527 let least = usize::try_from(least).ok()?;
528 let mut compares: i128 = 0;
529 let mut places: Vec<BlockCall> = Vec::new();
530 let mut best = None;
531 for (index, cluster) in clusters.iter().enumerate() {
532 let call = match *cluster {
533 Cluster::One { call, .. } => {
534 compares += 1;
535 call
536 }
537 Cluster::Run { call, .. } => {
538 compares += 2;
539 call
540 }
541 Cluster::Bits { .. } | Cluster::Table { .. } => return best,
542 };
543 if places.len() <= BIT_TEST_TARGETS && !places.iter().any(|&seen| same(func, seen, call)) {
544 places.push(call);
545 }
546 let span = cluster.high() - low + 1;
547 if span > growth * most {
548 break;
549 }
550 let masks = span <= WORD && places.len() <= BIT_TEST_TARGETS;
551 if index + 1 >= least && span <= growth * compares && !masks {
552 best = Some(index + 1);
553 }
554 }
555 best
556}
557
558const BIT_TEST_TARGETS: usize = 3;
562
563fn table(stretch: &[Cluster]) -> Cluster {
565 let mut arms = Vec::new();
566 for cluster in stretch {
567 match *cluster {
568 Cluster::One { value, call } => arms.push((value, call)),
569 Cluster::Run { low, high, call } => {
570 arms.extend((low..=high).map(|value| (value, call)))
571 }
572 Cluster::Bits { .. } | Cluster::Table { .. } => {
573 unreachable!("tables are found before anything is grouped")
574 }
575 }
576 }
577 let low = stretch.first().map_or(0, Cluster::low);
578 let high = stretch.last().map_or(0, Cluster::high);
579 Cluster::Table { low, high, arms }
580}
581
582const WORD: i128 = 64;
590
591const MARGIN: usize = 3;
605
606fn clusters(func: &Func, cases: &[Imm], arms: &[BlockCall], ty: Type) -> Vec<Cluster> {
623 let mut sorted: Vec<(i128, BlockCall)> =
624 cases.iter().zip(arms).map(|(&imm, &call)| (imm.signed(ty), call)).collect();
625 sorted.sort_by_key(|&(value, _)| value);
626 assert!(
627 sorted.windows(2).all(|pair| pair[0].0 != pair[1].0),
628 "a switch with two cases of the same value reached the back end"
629 );
630
631 let mut clusters: Vec<Cluster> = Vec::with_capacity(sorted.len());
632 for (value, call) in sorted {
633 match clusters.last_mut() {
634 Some(last) if last.high() + 1 == value && last.goes_to(func, call) => {
637 last.grow(value);
638 }
639 _ => clusters.push(Cluster::One { value, call }),
640 }
641 }
642 clusters
643}
644
645fn same(func: &Func, a: BlockCall, b: BlockCall) -> bool {
651 a.block == b.block && func[a.args] == func[b.args]
652}
653
654fn group(func: &Func, clusters: Vec<Cluster>) -> Vec<Cluster> {
669 let mut out: Vec<Cluster> = Vec::with_capacity(clusters.len());
670 let mut at = 0;
671 while at < clusters.len() {
672 let reach = reach(&clusters, at);
673 match bits(func, &clusters[at..at + reach]) {
674 Some(cluster) => {
675 out.push(cluster);
676 at += reach;
677 }
678 None => {
679 out.push(clusters[at].clone());
680 at += 1;
681 }
682 }
683 }
684 out
685}
686
687fn reach(clusters: &[Cluster], at: usize) -> usize {
689 let Cluster::One { value: first, .. } = clusters[at] else { return 0 };
690 let mut reach = 0;
691 while let Some(Cluster::One { value, .. }) = clusters.get(at + reach) {
692 if value - first >= WORD {
693 break;
694 }
695 reach += 1;
696 }
697 reach
698}
699
700fn bits(func: &Func, group: &[Cluster]) -> Option<Cluster> {
706 let low = group.first()?.low();
707 let mut arms: Vec<(u64, BlockCall)> = Vec::new();
708 for cluster in group {
709 let Cluster::One { value, call } = *cluster else { return None };
710 let bit = 1u64 << (value - low);
712 match arms.iter_mut().find(|&&mut (_, mine)| same(func, mine, call)) {
713 Some((mask, _)) => *mask |= bit,
714 None => arms.push((bit, call)),
715 }
716 }
717 if group.len() < arms.len() + MARGIN {
718 return None;
719 }
720 Some(Cluster::Bits { low, high: group.last()?.high(), arms })
721}
722
723fn tree(func: &mut Func, of: &Lowering, at: Block, clusters: &[Cluster], leaf: usize) {
733 if clusters.len() <= leaf {
734 chain(func, of, at, clusters);
735 return;
736 }
737 let (below, above) = clusters.split_at(clusters.len() / 2);
738 let pivot = above[0].low();
739 let left = func.create_block();
740 let right = func.create_block();
741
742 let mut build = Builder::new(func, at).at(of.span);
743 let want = build.iconst(of.ty, pivot);
744 let under = build.icmp(IntPred::Slt, of.value, want);
745 build.br_if(under, left, &[], right, &[]);
746
747 tree(func, of, left, below, leaf);
748 tree(func, of, right, above, leaf);
749}
750
751fn chain(func: &mut Func, of: &Lowering, at: Block, clusters: &[Cluster]) {
757 let Some((last, rest)) = clusters.split_last() else {
760 let args: Vec<Value> = func[of.default.args].to_vec();
761 Builder::new(func, at).at(of.span).jump(of.default.block, &args);
762 return;
763 };
764
765 let mut at = at;
766 for cluster in rest {
767 let next = func.create_block();
768 test(func, of, at, cluster, next, &[]);
769 at = next;
770 }
771 let onward: Vec<Value> = func[of.default.args].to_vec();
772 test(func, of, at, last, of.default.block, &onward);
773}
774
775fn test(
777 func: &mut Func,
778 of: &Lowering,
779 at: Block,
780 cluster: &Cluster,
781 next: Block,
782 onward: &[Value],
783) {
784 if matches!(cluster, Cluster::Bits { .. }) {
785 scattered(func, of, at, cluster, next, onward);
786 return;
787 }
788 if matches!(cluster, Cluster::Table { .. }) {
789 looked_up(func, of, at, cluster, next, onward);
790 return;
791 }
792 let call = match *cluster {
793 Cluster::One { call, .. } | Cluster::Run { call, .. } => call,
794 Cluster::Bits { .. } | Cluster::Table { .. } => unreachable!("dealt with above"),
795 };
796 let taken: Vec<Value> = func[call.args].to_vec();
797 let mut build = Builder::new(func, at).at(of.span);
798 let matched = match *cluster {
799 Cluster::One { value, .. } => {
800 let want = build.iconst(of.ty, value);
801 build.icmp(IntPred::Eq, of.value, want)
802 }
803 Cluster::Run { low, high, .. } => {
804 let base = shifted_down(&mut build, of, low);
805 let width = build.iconst(of.ty, high - low);
806 build.icmp(IntPred::Ule, base, width)
807 }
808 Cluster::Bits { .. } | Cluster::Table { .. } => unreachable!("dealt with above"),
809 };
810 build.br_if(matched, call.block, &taken, next, onward);
811}
812
813fn looked_up(
829 func: &mut Func,
830 of: &Lowering,
831 at: Block,
832 cluster: &Cluster,
833 next: Block,
834 onward: &[Value],
835) {
836 let Cluster::Table { low, high, arms } = cluster else {
837 unreachable!("only a table is written as one");
838 };
839 let (low, high) = (*low, *high);
840 let inside = func.create_block();
841 let mut hops: Vec<(BlockCall, Block)> = Vec::new();
842 let mut hop = |func: &mut Func, call: BlockCall| -> Block {
843 if func[call.args].is_empty() {
844 return call.block;
845 }
846 if let Some(&(_, block)) = hops.iter().find(|&&(mine, _)| same(func, mine, call)) {
847 return block;
848 }
849 let block = func.create_block();
850 hops.push((call, block));
851 block
852 };
853 let default = hop(func, of.default);
854 let cases: Vec<(i128, Block)> =
855 arms.iter().map(|&(value, call)| (value - low, hop(func, call))).collect();
856
857 let mut build = Builder::new(func, at).at(of.span);
858 let base = shifted_down(&mut build, of, low);
859 let width = build.iconst(of.ty, high - low);
860 let ok = build.icmp(IntPred::Ule, base, width);
861 build.br_if(ok, inside, &[], next, onward);
862
863 let word = Type::int(u64::BITS);
867 let mut build = Builder::new(func, inside).at(of.span);
868 let index = if of.ty == word { base } else { build.unary(Opcode::ZExt, base, word) };
869 build.switch(index, default, &cases);
870
871 for (call, block) in hops {
872 let args: Vec<Value> = func[call.args].to_vec();
873 Builder::new(func, block).at(of.span).jump(call.block, &args);
874 }
875}
876
877fn shifted_down(build: &mut Builder<'_>, of: &Lowering, low: i128) -> Value {
884 if low == 0 {
885 return of.value;
886 }
887 let start = build.iconst(of.ty, low);
888 build.binary(Opcode::Sub, of.value, start, Flags::default())
889}
890
891fn scattered(
903 func: &mut Func,
904 of: &Lowering,
905 at: Block,
906 cluster: &Cluster,
907 next: Block,
908 onward: &[Value],
909) {
910 let Cluster::Bits { low, high, arms } = cluster else {
911 unreachable!("only a bit test is written as one");
912 };
913 let (low, high) = (*low, *high);
914
915 let all = arms.iter().fold(0u64, |seen, &(mask, _)| seen | mask);
920 let covered = arms.len() > 1 && all == span_mask(low, high);
921 let tests = arms.len() - usize::from(covered);
922 let (spare, onto_spare) = if covered {
923 let call = arms[arms.len() - 1].1;
924 (call.block, func[call.args].to_vec())
925 } else {
926 (of.default.block, func[of.default.args].to_vec())
927 };
928
929 let inside = func.create_block();
932 let mut blocks: Vec<Block> = vec![inside];
933 blocks.extend((1..tests).map(|_| func.create_block()));
934 let taken: Vec<Vec<Value>> = arms.iter().map(|&(_, call)| func[call.args].to_vec()).collect();
935
936 let mut build = Builder::new(func, at).at(of.span);
937 let base = shifted_down(&mut build, of, low);
938 let width = build.iconst(of.ty, high - low);
939 let ok = build.icmp(IntPred::Ule, base, width);
940 build.br_if(ok, inside, &[], next, onward);
941
942 let word = Type::int(u64::BITS);
945 let mut build = Builder::new(func, inside).at(of.span);
946 let amount = if of.ty == word { base } else { build.unary(Opcode::ZExt, base, word) };
947 let one = build.iconst(word, 1);
948 let bit = build.binary(Opcode::Shl, one, amount, Flags::default());
949
950 for (index, &(mask, call)) in arms[..tests].iter().enumerate() {
951 let want = build.iconst(word, i128::from(mask as i64));
952 let hit = build.binary(Opcode::And, bit, want, Flags::default());
953 let none = build.iconst(word, 0);
954 let matched = build.icmp(IntPred::Ne, hit, none);
955 let last = index + 1 == tests;
956 let onto = if last { spare } else { blocks[index + 1] };
957 let args = if last { &onto_spare[..] } else { &[][..] };
958 build.br_if(matched, call.block, &taken[index], onto, args);
959 if !last {
960 build = Builder::new(func, blocks[index + 1]).at(of.span);
961 }
962 }
963}
964
965fn span_mask(low: i128, high: i128) -> u64 {
970 let width = u32::try_from(high - low).expect("a group narrower than a word");
971 if width + 1 >= u64::BITS { u64::MAX } else { (1u64 << (width + 1)) - 1 }
972}
973
974#[must_use]
980pub fn blocks_for(clusters: usize) -> usize {
981 clusters.saturating_sub(1)
982}
983
984#[cfg(test)]
985mod tests {
986 use std::collections::HashMap;
987
988 use rucc_base::Interner;
989 use rucc_ir::{
990 Block, BlockCall, Builder, Extra, Func, Hint, Imm, InstData, IntPred, Module, Opcode,
991 Signature, SwitchInfo, Type, Value,
992 };
993 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
994
995 use super::{Force, Goal, LINEAR, Lowered, SWITCH_PEEL_PERCENT, blocks_for, lowered, switches};
996
997 fn target() -> TargetInfo {
998 TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
999 }
1000
1001 struct Built {
1003 names: Interner,
1004 func: Func,
1005 operand: Value,
1006 arms: Vec<Block>,
1007 default: Block,
1008 }
1009
1010 fn built(cases: &[i128]) -> Built {
1017 let arms: Vec<usize> = (0..cases.len()).collect();
1018 built_sharing(cases, &arms, Type::int(32))
1019 }
1020
1021 fn built_sharing(cases: &[i128], arms: &[usize], ty: Type) -> Built {
1023 let mut names = Interner::new();
1024 let int = Type::int(32);
1025 let mut func =
1026 Func::new(names.intern("sw"), Signature::new().with_params(&[ty]).with_returns(&[int]));
1027 let entry = func.create_block();
1028 let x = func.append_param(entry, ty);
1029
1030 let default = func.create_block();
1031 let count = arms.iter().copied().max().map_or(0, |top| top + 1);
1032 let blocks: Vec<Block> = (0..count).map(|_| func.create_block()).collect();
1033 let table: Vec<(i128, Block)> =
1034 cases.iter().copied().zip(arms.iter().map(|&at| blocks[at])).collect();
1035 Builder::new(&mut func, entry).switch(x, default, &table);
1036
1037 for (index, &arm) in blocks.iter().enumerate() {
1038 let mut build = Builder::new(&mut func, arm);
1039 let what = i128::try_from(index).expect("a small number of arms");
1040 let v = build.iconst(int, (what + 1) * 10);
1041 build.ret(&[v]);
1042 }
1043 let mut build = Builder::new(&mut func, default);
1044 let v = build.iconst(int, 0);
1045 build.ret(&[v]);
1046 Built { names, func, operand: x, arms: blocks, default }
1047 }
1048
1049 fn count(func: &Func) -> usize {
1050 func.blocks().count()
1051 }
1052
1053 fn printed(func: &Func, names: &mut Interner) -> String {
1054 let module = Module::new(names.intern("sw.c"), &target());
1055 rucc_ir::print_func(&module, func, names)
1056 }
1057
1058 fn verified(built: &mut Built) {
1059 let module = Module::new(built.names.intern("sw.c"), &target());
1060 rucc_ir::verify_func(&module, &built.func, &built.names)
1061 .expect("the rewrite builds valid IR");
1062 }
1063
1064 fn arrives(func: &Func, operand: Value, x: i128, ty: Type) -> Block {
1080 let mut at = func.entry().expect("an entry block");
1081 let mut held: HashMap<Value, i128> = HashMap::new();
1082 held.insert(operand, Imm::int(x, ty).signed(ty));
1083 loop {
1084 let mut moved = None;
1085 for inst in func.insts(at).collect::<Vec<_>>() {
1086 let opcode = func[inst].opcode;
1087 let extra = func[inst].extra;
1088 let result = func[inst].first_result;
1089 let args: Vec<i128> = func[func[inst].args]
1090 .iter()
1091 .map(|value| held.get(value).copied().unwrap_or(0))
1092 .collect();
1093 let wide = |value: Option<Value>| func[value.expect("a result")].ty;
1094 let mut put = |value: Option<Value>, what: i128| {
1095 let value = value.expect("a result");
1096 let ty = func[value].ty;
1097 held.insert(value, Imm::int(what, ty).signed(ty));
1098 };
1099 match opcode {
1100 Opcode::IConst => {
1101 let Extra::Imm(imm) = extra else { return at };
1102 put(result, func[imm].signed(wide(result)));
1103 }
1104 Opcode::Sub => put(result, args[0] - args[1]),
1105 Opcode::And => put(result, args[0] & args[1]),
1106 Opcode::Shl => put(result, args[0] << args[1]),
1107 Opcode::ZExt => {
1108 let from = func[func[func[inst].args][0]].ty;
1109 let raw = Imm::int(args[0], from).unsigned();
1110 put(result, i128::try_from(raw).expect("a value narrower than a word"));
1111 }
1112 Opcode::ICmp => {
1113 let Extra::IntPred(pred) = extra else { return at };
1114 let of = func[func[func[inst].args][0]].ty;
1115 let unsigned = |v: i128| Imm::int(v, of).unsigned();
1116 let answer = match pred {
1117 IntPred::Eq => args[0] == args[1],
1118 IntPred::Ne => args[0] != args[1],
1119 IntPred::Slt => args[0] < args[1],
1120 IntPred::Ule => unsigned(args[0]) <= unsigned(args[1]),
1121 other => panic!("the lowering does not write {}", other.name()),
1122 };
1123 held.insert(result.expect("a comparison has a result"), i128::from(answer));
1124 }
1125 Opcode::Jump => {
1126 let call = func.successors(inst).next().expect("a jump has a target");
1127 moved = Some(call.block);
1128 }
1129 Opcode::BrIf => {
1130 let mut targets = func.successors(inst);
1131 let taken = targets.next().expect("a branch has two targets");
1132 let other = targets.next().expect("a branch has two targets");
1133 moved = Some(if args[0] != 0 { taken.block } else { other.block });
1134 }
1135 Opcode::Switch => {
1138 let Extra::Switch(info) = extra else { return at };
1139 let of = func[func[func[inst].args][0]].ty;
1140 let targets: Vec<BlockCall> = func.successors(inst).collect();
1141 let found = func[func[info].cases]
1142 .iter()
1143 .position(|case| case.signed(of) == args[0])
1144 .map_or(targets[0], |arm| targets[arm + 1]);
1145 moved = Some(found.block);
1146 }
1147 _ => return at,
1148 }
1149 }
1150 match moved {
1151 Some(next) => at = next,
1152 None => return at,
1153 }
1154 }
1155 }
1156
1157 fn routes(built: &mut Built, cases: &[i128], arms: &[usize], probes: &[i128], ty: Type) {
1159 switches(&mut built.func, Goal::Speed);
1160 lands(built, cases, arms, probes, ty);
1161 }
1162
1163 fn lands(built: &mut Built, cases: &[i128], arms: &[usize], probes: &[i128], ty: Type) {
1165 verified(built);
1166 for &x in probes {
1167 let wanted = cases
1168 .iter()
1169 .position(|&case| case == x)
1170 .map_or(built.default, |at| built.arms[arms[at]]);
1171 let got = arrives(&built.func, built.operand, x, ty);
1172 assert_eq!(got, wanted, "the operand {x} went to the wrong block");
1173 }
1174 }
1175
1176 fn forcing(cases: &[i128], force: Force) -> Lowered {
1179 let arms: Vec<usize> = (0..cases.len()).collect();
1180 let mut built = built(cases);
1181 let said = lowered(&mut built.func, Goal::Speed, Some(force));
1182 lands(&mut built, cases, &arms, &around(cases, Type::int(32)), Type::int(32));
1183 assert_eq!(said.len(), 1);
1184 said[0]
1185 }
1186
1187 #[test]
1188 fn each_forced_shape_is_the_shape_it_says_and_still_routes_every_value() {
1189 let sparse: Vec<i128> = (0..40).map(|at| at * 17).collect();
1190 let table = forcing(&sparse, Force::Table);
1191 assert_eq!((table.shape(), table.tables, table.clusters), ("table", 1, 1));
1192 let tree = forcing(&sparse, Force::Tree);
1193 assert_eq!((tree.shape(), tree.clusters), ("tree", 40));
1194 let dense: Vec<i128> = (0..40).collect();
1195 let walk = forcing(&dense, Force::Walk);
1196 assert_eq!((walk.shape(), walk.tables, walk.clusters), ("walk", 0, 40));
1197 }
1198
1199 #[test]
1200 fn a_forced_table_too_wide_to_be_worth_it_keeps_the_shape_it_had() {
1201 let wide: Vec<i128> = (0..40).map(|at| at * 1000).collect();
1202 assert_eq!(forcing(&wide, Force::Table).shape(), "tree");
1203 }
1204
1205 #[test]
1206 fn what_a_switch_became_is_said_in_one_line() {
1207 let dense: Vec<i128> = (0..40).collect();
1208 let mut built = built(&dense);
1209 let said = lowered(&mut built.func, Goal::Speed, None);
1210 assert_eq!(
1211 said.iter().map(Lowered::describe).collect::<Vec<_>>(),
1212 ["switch of 40 cases lowered as a table; clusters 1, tables 1, bit tests 0"]
1213 );
1214 let three = forcing(&[1, 5, 9], Force::Walk);
1215 assert_eq!(three.shape(), "walk");
1216 assert_eq!(Force::named("tree"), Some(Force::Tree));
1217 assert_eq!(Force::named("bit-test"), None);
1218 }
1219
1220 fn around(cases: &[i128], ty: Type) -> Vec<i128> {
1222 let mut probes: Vec<i128> = Vec::new();
1223 for &case in cases {
1224 probes.extend([case - 1, case, case + 1]);
1225 }
1226 let bits = ty.bits();
1227 probes.extend([0, -1, 1, i128::from(i32::MIN) >> (32 - bits), (1 << (bits - 1)) - 1]);
1228 probes.retain(|&x| Imm::int(x, ty).signed(ty) == x);
1229 probes.sort_unstable();
1230 probes.dedup();
1231 probes
1232 }
1233
1234 #[test]
1235 fn a_small_switch_is_a_compare_and_a_branch_for_each_case() {
1236 let mut built = built(&[1, 2]);
1237 let before = count(&built.func);
1238 switches(&mut built.func, Goal::Speed);
1239 assert_eq!(count(&built.func), before + blocks_for(2));
1240
1241 let text = printed(&built.func, &mut built.names);
1242 assert!(!text.contains("switch"), "the switch is gone: {text}");
1243 assert_eq!(text.matches("icmp eq").count(), 2, "one compare per case: {text}");
1244 assert_eq!(text.matches("br_if").count(), 2, "one branch per case: {text}");
1245 }
1246
1247 #[test]
1248 fn the_last_case_falls_to_the_default_rather_than_to_a_block_of_its_own() {
1249 let mut built = built(&[7]);
1250 let before = count(&built.func);
1251 switches(&mut built.func, Goal::Speed);
1252 assert_eq!(count(&built.func), before);
1254 assert_eq!(blocks_for(1), 0);
1255 }
1256
1257 #[test]
1258 fn a_switch_with_only_a_default_is_a_jump() {
1259 let mut built = built(&[]);
1260 switches(&mut built.func, Goal::Speed);
1261 let entry = built.func.entry().expect("an entry block");
1262 let term = built.func.terminator(entry).expect("a terminator");
1263 assert_eq!(built.func[term].opcode, Opcode::Jump);
1264 }
1265
1266 #[test]
1269 fn what_comes_out_is_valid_ir() {
1270 let mut built = built(&[1, 2, 3, 4]);
1271 switches(&mut built.func, Goal::Speed);
1272 verified(&mut built);
1273 }
1274
1275 #[test]
1278 fn a_function_with_no_switch_is_left_exactly_as_it_was() {
1279 let mut names = Interner::new();
1280 let int = Type::int(32);
1281 let mut func =
1282 Func::new(names.intern("f"), Signature::new().with_params(&[int]).with_returns(&[int]));
1283 let entry = func.create_block();
1284 let x = func.append_param(entry, int);
1285 Builder::new(&mut func, entry).ret(&[x]);
1286
1287 let before = printed(&func, &mut names);
1288 switches(&mut func, Goal::Speed);
1289 assert_eq!(printed(&func, &mut names), before);
1290 }
1291
1292 #[test]
1293 fn a_run_of_cases_going_to_one_place_is_one_range_test() {
1294 let cases = [3, 4, 5, 6, 7, 8, 9, 10];
1295 let arms = [0; 8];
1296 let mut built = built_sharing(&cases, &arms, Type::int(32));
1297 switches(&mut built.func, Goal::Speed);
1298
1299 let text = printed(&built.func, &mut built.names);
1300 assert_eq!(text.matches("icmp").count(), 1, "eight cases, one test: {text}");
1301 assert_eq!(text.matches("icmp ule").count(), 1, "and the test is the range: {text}");
1302 assert_eq!(text.matches("sub").count(), 1, "one subtraction to bring it to zero: {text}");
1303 }
1304
1305 #[test]
1306 fn a_run_that_starts_at_zero_needs_no_subtraction() {
1307 let cases = [0, 1, 2, 3, 4];
1308 let arms = [0; 5];
1309 let mut built = built_sharing(&cases, &arms, Type::int(32));
1310 switches(&mut built.func, Goal::Speed);
1311
1312 let text = printed(&built.func, &mut built.names);
1313 assert_eq!(text.matches("icmp ule").count(), 1, "one range test: {text}");
1314 assert!(!text.contains("sub"), "nothing to subtract from zero: {text}");
1315 }
1316
1317 #[test]
1320 fn the_tree_is_built_over_the_clusters_and_not_over_the_cases() {
1321 let cases: Vec<i128> = (0..30).collect();
1322 let arms: Vec<usize> = (0..30).map(|at: usize| at / 10).collect();
1323 let mut built = built_sharing(&cases, &arms, Type::int(32));
1324 switches(&mut built.func, Goal::Speed);
1325
1326 let text = printed(&built.func, &mut built.names);
1327 assert_eq!(text.matches("icmp").count(), 3, "three runs, three tests: {text}");
1328 assert!(!text.contains("icmp slt"), "three clusters is under the leaf size: {text}");
1329 }
1330
1331 fn hint(built: &mut Built, parts: &[u32]) {
1335 let func = &mut built.func;
1336 let entry = func.blocks().next().expect("an entry");
1337 let term = func.terminator(entry).expect("the switch");
1338 for (at, &parts) in func.target_list(term).iter().zip(parts) {
1339 let call = func[at];
1340 func.set_block_call(at, BlockCall { hint: Hint::parts(parts), ..call });
1341 }
1342 }
1343
1344 fn first(func: &Func) -> (Block, [Option<u32>; 2]) {
1347 let entry = func.blocks().next().expect("an entry");
1348 let term = func.terminator(entry).expect("a branch");
1349 assert_eq!(func[term].opcode, Opcode::BrIf);
1350 let calls: Vec<BlockCall> = func.target_list(term).iter().map(|at| func[at]).collect();
1351 (calls[0].block, [calls[0].hint.taken(), calls[1].hint.taken()])
1352 }
1353
1354 fn leaning(cases: usize, hot: usize, parts: u32) -> Vec<u32> {
1357 let rest = (10_000 - parts) / u32::try_from(cases).expect("a small switch");
1358 (0..=cases).map(|at| if at == hot + 1 { parts } else { rest }).collect()
1359 }
1360
1361 #[test]
1362 fn a_case_hinted_hot_is_tested_first_with_the_hint_on_its_branch() {
1363 let cases: Vec<i128> = (0..40).map(|at| at * SPARSE).collect();
1365 let arms: Vec<usize> = (0..cases.len()).collect();
1366 let mut built = built(&cases);
1367 hint(&mut built, &leaning(cases.len(), 7, 9_000));
1368 let hot = built.arms[7];
1369 routes(&mut built, &cases, &arms, &around(&cases, Type::int(32)), Type::int(32));
1370 assert_eq!(first(&built.func), (hot, [Some(9_000), Some(1_000)]));
1371 }
1372
1373 #[test]
1374 fn a_hot_case_in_a_dense_stretch_is_taken_out_of_the_table() {
1375 let cases: Vec<i128> = (0..20).collect();
1376 let arms: Vec<usize> = (0..cases.len()).collect();
1377 let mut built = built(&cases);
1378 hint(&mut built, &leaning(cases.len(), 5, 9_000));
1379 let hot = built.arms[5];
1380 routes(&mut built, &cases, &arms, &around(&cases, Type::int(32)), Type::int(32));
1381 assert_eq!(first(&built.func).0, hot);
1382 }
1383
1384 #[test]
1385 fn a_hint_under_the_threshold_or_on_the_default_leaves_the_tree_as_it_was() {
1386 let cases: Vec<i128> = (0..40).map(|at| at * SPARSE).collect();
1387 let mut plain = built(&cases);
1388 switches(&mut plain.func, Goal::Speed);
1389 let want = printed(&plain.func, &mut plain.names);
1390 let bar = SWITCH_PEEL_PERCENT * 100;
1391 let mut on_the_default = leaning(cases.len(), 0, 1_000);
1392 on_the_default[0] = 9_000;
1393 for parts in [leaning(cases.len(), 7, bar - 1), on_the_default] {
1394 let mut built = built(&cases);
1395 hint(&mut built, &parts);
1396 switches(&mut built.func, Goal::Speed);
1397 assert_eq!(printed(&built.func, &mut built.names), want);
1398 }
1399 }
1400
1401 #[test]
1402 fn a_long_sparse_switch_is_a_search_rather_than_a_walk() {
1403 let count = 4 * LINEAR as i128;
1407 let cases: Vec<i128> = (0..count).map(|at| at * SPARSE).collect();
1408 let mut built = built(&cases);
1409 switches(&mut built.func, Goal::Speed);
1410
1411 let worst = deepest(&built.func);
1412 assert!(worst <= LINEAR + 2, "{count} cases in {worst} comparisons at worst");
1413 assert!(worst > LINEAR, "and the splits are being counted too");
1414 }
1415
1416 const SPARSE: i128 = 17;
1419
1420 fn deepest(func: &Func) -> usize {
1425 fn walk(func: &Func, at: Block, seen: &mut HashMap<Block, usize>) -> usize {
1426 if let Some(&known) = seen.get(&at) {
1427 return known;
1428 }
1429 let here = func.insts(at).filter(|&inst| func[inst].opcode == Opcode::ICmp).count();
1430 let term = func.terminator(at).expect("a terminator");
1431 let onward: Vec<Block> = match func[term].opcode {
1432 Opcode::Jump | Opcode::BrIf => {
1433 func.successors(term).map(|call| call.block).collect()
1434 }
1435 _ => Vec::new(),
1436 };
1437 let below =
1438 onward.into_iter().map(|block| walk(func, block, seen)).max().unwrap_or_default();
1439 seen.insert(at, here + below);
1440 here + below
1441 }
1442 walk(func, func.entry().expect("an entry block"), &mut HashMap::new())
1443 }
1444
1445 #[test]
1446 fn every_value_reaches_the_arm_its_case_named_in_a_small_switch() {
1447 let cases = [1, 2, 3];
1448 let arms = [0, 1, 2];
1449 let ty = Type::int(32);
1450 let mut built = built(&cases);
1451 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1452 }
1453
1454 #[test]
1455 fn every_value_reaches_the_arm_its_case_named_in_a_search() {
1456 let count = 3 * LINEAR;
1457 let cases: Vec<i128> = (0..count as i128).map(|at| at * SPARSE).collect();
1458 let arms: Vec<usize> = (0..count).collect();
1459 let ty = Type::int(32);
1460 let mut built = built(&cases);
1461 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1462 }
1463
1464 #[test]
1467 fn every_value_reaches_its_arm_when_the_cases_straddle_zero() {
1468 let half = LINEAR as i128;
1469 let cases: Vec<i128> = (-half..half).map(|at| at * SPARSE).collect();
1470 let arms: Vec<usize> = (0..2 * LINEAR).collect();
1471 let ty = Type::int(32);
1472 let mut built = built(&cases);
1473 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1474 }
1475
1476 #[test]
1479 fn every_value_reaches_its_arm_when_runs_and_singles_are_mixed() {
1480 let cases: Vec<i128> =
1481 vec![-9, -8, -7, -6, 0, 5, 6, 7, 8, 9, 10, 40, 41, 90, 91, 92, 93, 94, 95, 200];
1482 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];
1483 let ty = Type::int(32);
1484 let mut built = built_sharing(&cases, &arms, ty);
1485 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1486 }
1487
1488 #[test]
1492 fn a_run_covering_the_whole_type_matches_everything() {
1493 let cases: Vec<i128> = (-128..128).collect();
1494 let arms = vec![0; cases.len()];
1495 let ty = Type::int(8);
1496 let mut built = built_sharing(&cases, &arms, ty);
1497 switches(&mut built.func, Goal::Speed);
1498 verified(&mut built);
1499
1500 let text = printed(&built.func, &mut built.names);
1501 assert_eq!(text.matches("icmp").count(), 1, "one run, one test: {text}");
1502
1503 let entry = built.func.entry().expect("an entry block");
1504 let operand = built.func[entry].params[0];
1505 for x in [-128, -1, 0, 1, 127] {
1506 assert_eq!(
1507 arrives(&built.func, operand, x, ty),
1508 built.arms[0],
1509 "every value of the type is in the run"
1510 );
1511 }
1512 }
1513
1514 #[test]
1518 #[should_panic(expected = "two cases of the same value")]
1519 fn a_case_value_written_twice_stops_the_compiler() {
1520 let cases = [4, 9, 4];
1521 let arms = [0, 1, 2];
1522 let mut built = built_sharing(&cases, &arms, Type::int(32));
1523 switches(&mut built.func, Goal::Speed);
1524 }
1525
1526 #[test]
1532 fn cases_that_share_a_block_but_not_its_arguments_are_not_a_run() {
1533 let mut names = Interner::new();
1534 let int = Type::int(32);
1535 let mut func = Func::new(
1536 names.intern("sw"),
1537 Signature::new().with_params(&[int]).with_returns(&[int]),
1538 );
1539 let entry = func.create_block();
1540 let x = func.append_param(entry, int);
1541 let default = func.create_block();
1542 let join = func.create_block();
1543 let param = func.append_param(join, int);
1544
1545 let mut build = Builder::new(&mut func, entry);
1546 let ten = build.iconst(int, 10);
1547 let twenty = build.iconst(int, 20);
1548 let none = func.push_values(&[]);
1549 let first = func.push_values(&[ten]);
1550 let second = func.push_values(&[twenty]);
1551 let targets = func.push_block_calls(&[
1552 BlockCall::new(default, none),
1553 BlockCall::new(join, first),
1554 BlockCall::new(join, second),
1555 ]);
1556 let cases = func.push_imms(&[Imm::int(1, int), Imm::int(2, int)]);
1557 let info = func.add_switch(SwitchInfo { targets, cases });
1558 let args = func.push_values(&[x]);
1559 let data = InstData { args, extra: Extra::Switch(info), ..InstData::new(Opcode::Switch) };
1560 Builder::new(&mut func, entry).inst(data, &[]);
1561
1562 let mut build = Builder::new(&mut func, join);
1563 build.ret(&[param]);
1564 let mut build = Builder::new(&mut func, default);
1565 let zero = build.iconst(int, 0);
1566 build.ret(&[zero]);
1567
1568 switches(&mut func, Goal::Speed);
1569 let text = printed(&func, &mut names);
1570 assert_eq!(text.matches("icmp eq").count(), 2, "two cases, two equality tests: {text}");
1571 assert!(!text.contains("icmp ule"), "and no range test over them: {text}");
1572 }
1573
1574 #[test]
1577 fn the_leaf_size_is_where_the_search_starts() {
1578 let flat: Vec<i128> = (0..LINEAR as i128).map(|at| at * SPARSE).collect();
1579 let mut walked = built(&flat);
1580 switches(&mut walked.func, Goal::Speed);
1581 assert!(
1582 !printed(&walked.func, &mut walked.names).contains("icmp slt"),
1583 "a leaf's worth of clusters is still a chain"
1584 );
1585
1586 let one_more: Vec<i128> = (0..LINEAR as i128 + 1).map(|at| at * SPARSE).collect();
1587 let mut split = built(&one_more);
1588 switches(&mut split.func, Goal::Speed);
1589 assert!(
1590 printed(&split.func, &mut split.names).contains("icmp slt"),
1591 "one more than a leaf splits"
1592 );
1593 }
1594
1595 #[test]
1599 fn scattered_cases_sharing_one_arm_are_one_mask_and_one_test() {
1600 let cases = [97, 101, 105, 111, 117];
1601 let arms = [0; 5];
1602 let mut built = built_sharing(&cases, &arms, Type::int(32));
1603 switches(&mut built.func, Goal::Speed);
1604
1605 let text = printed(&built.func, &mut built.names);
1606 assert!(!text.contains("icmp eq"), "no case is compared on its own: {text}");
1607 assert_eq!(text.matches("shl").count(), 1, "one bit is picked out: {text}");
1608 assert_eq!(text.matches("and").count(), 1, "and one mask is asked about it: {text}");
1609 assert_eq!(text.matches("icmp ule").count(), 1, "one bound before the shift: {text}");
1610 assert_eq!(text.matches("icmp ne").count(), 1, "one test for the one arm: {text}");
1611 }
1612
1613 #[test]
1616 fn every_value_reaches_its_arm_through_a_bit_test() {
1617 let ty = Type::int(32);
1618 let cases = [97, 101, 105, 111, 117];
1619 let arms = [0; 5];
1620 let mut built = built_sharing(&cases, &arms, ty);
1621 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1622 }
1623
1624 #[test]
1627 fn a_bit_test_carries_several_destinations_in_one_word() {
1628 let ty = Type::int(32);
1629 let cases: Vec<i128> = (0..9).map(|at| at * 3).collect();
1630 let arms: Vec<usize> = (0..9).map(|at: usize| at % 3).collect();
1631 let mut built = built_sharing(&cases, &arms, ty);
1632 switches(&mut built.func, Goal::Speed);
1633
1634 let text = printed(&built.func, &mut built.names);
1635 assert_eq!(text.matches("shl").count(), 1, "nine cases, one shift: {text}");
1636 assert_eq!(text.matches("icmp ne").count(), 3, "three arms, three masks: {text}");
1637 assert!(!text.contains("icmp eq"), "and no case compared on its own: {text}");
1638
1639 let mut built = built_sharing(&cases, &arms, ty);
1640 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1641 }
1642
1643 #[test]
1646 fn the_last_destination_needs_no_test_when_the_masks_cover_the_span() {
1647 let ty = Type::int(32);
1648 let cases: Vec<i128> = (0..6).collect();
1649 let arms: Vec<usize> = (0..6).map(|at: usize| at % 2).collect();
1650 let mut built = built_sharing(&cases, &arms, ty);
1651 switches(&mut built.func, Goal::Speed);
1652
1653 let text = printed(&built.func, &mut built.names);
1654 assert_eq!(text.matches("icmp ne").count(), 1, "two arms, one mask asked about: {text}");
1655
1656 let mut built = built_sharing(&cases, &arms, ty);
1657 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1658 }
1659
1660 #[test]
1663 fn a_group_that_does_not_pay_for_itself_stays_a_chain() {
1664 let cases = [0, 3, 6];
1665 let arms = [0, 1, 2];
1666 let mut built = built_sharing(&cases, &arms, Type::int(32));
1667 switches(&mut built.func, Goal::Speed);
1668
1669 let text = printed(&built.func, &mut built.names);
1670 assert!(!text.contains("shl"), "three values and three arms buys nothing: {text}");
1671 assert_eq!(text.matches("icmp eq").count(), 3, "so it stays a walk: {text}");
1672 }
1673
1674 #[test]
1678 fn a_bit_test_never_spans_more_than_a_word() {
1679 let ty = Type::int(32);
1680 let cases = [0, 2, 4, 6, 64];
1681 let arms = [0; 5];
1682 let mut built = built_sharing(&cases, &arms, ty);
1683 switches(&mut built.func, Goal::Speed);
1684
1685 let text = printed(&built.func, &mut built.names);
1686 assert_eq!(text.matches("shl").count(), 1, "one group, not two: {text}");
1687 assert_eq!(text.matches("icmp eq").count(), 1, "and the value past it is compared: {text}");
1688
1689 let mut built = built_sharing(&cases, &arms, ty);
1690 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1691 }
1692
1693 #[test]
1697 fn a_bit_test_reaches_the_top_of_its_word() {
1698 let ty = Type::int(32);
1699 let cases = [0, 2, 4, 63];
1700 let arms = [0; 4];
1701 let mut built = built_sharing(&cases, &arms, ty);
1702 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1703 }
1704
1705 #[test]
1709 fn a_run_is_left_alone_rather_than_folded_into_a_mask() {
1710 let ty = Type::int(32);
1711 let cases = [0, 1, 2, 3, 10, 12, 14, 16];
1712 let arms = [0, 0, 0, 0, 1, 1, 1, 1];
1713 let mut built = built_sharing(&cases, &arms, ty);
1714 switches(&mut built.func, Goal::Speed);
1715
1716 let text = printed(&built.func, &mut built.names);
1717 assert_eq!(text.matches("icmp ule").count(), 2, "a run's bound and a group's: {text}");
1718 assert_eq!(text.matches("shl").count(), 1, "and only the group is a mask: {text}");
1719
1720 let mut built = built_sharing(&cases, &arms, ty);
1721 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1722 }
1723
1724 #[test]
1727 fn every_value_reaches_its_arm_when_a_bit_test_starts_below_zero() {
1728 let ty = Type::int(32);
1729 let cases = [-20, -17, -14, -11, -8, -5];
1730 let arms = [0, 1, 0, 1, 0, 1];
1731 let mut built = built_sharing(&cases, &arms, ty);
1732 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1733 }
1734
1735 #[test]
1738 fn a_dense_switch_is_one_bound_and_a_table() {
1739 let cases: Vec<i128> = (0..13).collect();
1740 let mut built = built(&cases);
1741 switches(&mut built.func, Goal::Speed);
1742 verified(&mut built);
1743
1744 let text = printed(&built.func, &mut built.names);
1745 assert_eq!(text.matches("icmp ule").count(), 1, "one bound over the span: {text}");
1746 assert_eq!(text.matches("switch").count(), 1, "and one table inside it: {text}");
1747 assert!(!text.contains("icmp eq"), "and no case compared on its own: {text}");
1748 }
1749
1750 #[test]
1753 fn every_value_reaches_its_arm_through_a_table_with_holes() {
1754 let ty = Type::int(32);
1755 let cases = [3, 4, 5, 7, 8, 10, 11, 13, 14, 15, 19];
1756 let arms: Vec<usize> = (0..cases.len()).collect();
1757 let mut built = built_sharing(&cases, &arms, ty);
1758 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1759 let text = printed(&built.func, &mut built.names);
1760 assert_eq!(text.matches("switch").count(), 1, "the cases are one table: {text}");
1761 }
1762
1763 #[test]
1767 fn every_value_reaches_its_arm_through_a_table_that_straddles_zero() {
1768 let ty = Type::int(8);
1769 let cases: Vec<i128> = (-7..8).filter(|x| x % 4 != 0).collect();
1770 let arms: Vec<usize> = (0..cases.len()).map(|at| at % 5).collect();
1771 let mut built = built_sharing(&cases, &arms, ty);
1772 let probes: Vec<i128> = (-128..128).collect();
1773 routes(&mut built, &cases, &arms, &probes, ty);
1774 let text = printed(&built.func, &mut built.names);
1775 assert_eq!(text.matches("switch").count(), 1, "the cases are one table: {text}");
1776 }
1777
1778 #[test]
1781 fn a_few_destinations_stay_a_bit_test_and_more_become_a_table() {
1782 let ty = Type::int(32);
1783 let cases: Vec<i128> = (0..12).map(|at| at * 3).collect();
1784 let few: Vec<usize> = (0..12).map(|at: usize| at % 3).collect();
1785 let mut built = built_sharing(&cases, &few, ty);
1786 switches(&mut built.func, Goal::Speed);
1787 let text = printed(&built.func, &mut built.names);
1788 assert!(!text.contains("switch"), "three arms are masks: {text}");
1789
1790 let many: Vec<usize> = (0..12).map(|at: usize| at % 5).collect();
1791 let mut built = built_sharing(&cases, &many, ty);
1792 switches(&mut built.func, Goal::Speed);
1793 let text = printed(&built.func, &mut built.names);
1794 assert_eq!(text.matches("switch").count(), 1, "five arms are a table: {text}");
1795 let mut built = built_sharing(&cases, &many, ty);
1796 routes(&mut built, &cases, &many, &around(&cases, ty), ty);
1797 }
1798
1799 #[test]
1802 fn too_few_cases_for_a_table_are_compared() {
1803 let cases: Vec<i128> = (0..10).collect();
1804 let mut built = built(&cases);
1805 switches(&mut built.func, Goal::Speed);
1806 let text = printed(&built.func, &mut built.names);
1807 assert!(!text.contains("switch"), "ten cases are not a table: {text}");
1808 }
1809
1810 #[test]
1813 fn for_size_a_table_starts_at_six_cases() {
1814 let tabled = |count: i128, goal: Goal| {
1815 let cases: Vec<i128> = (0..count).collect();
1816 let mut built = built(&cases);
1817 switches(&mut built.func, goal);
1818 printed(&built.func, &mut built.names).contains("switch")
1819 };
1820 assert!(!tabled(5, Goal::Size), "five cases are compared");
1821 assert!(tabled(6, Goal::Size), "six are a table");
1822 assert!(!tabled(6, Goal::Speed), "which for speed they are not");
1823 }
1824
1825 #[test]
1828 fn a_table_for_speed_can_be_too_sparse_for_size() {
1829 let ty = Type::int(32);
1830 let cases: Vec<i128> = (0..12).map(|at| at * 8).collect();
1831 let arms: Vec<usize> = (0..cases.len()).collect();
1832 let mut built = built_sharing(&cases, &arms, ty);
1833 switches(&mut built.func, Goal::Speed);
1834 let text = printed(&built.func, &mut built.names);
1835 assert_eq!(text.matches("switch").count(), 1, "at speed a span of 89 is a table: {text}");
1836
1837 let mut built = built_sharing(&cases, &arms, ty);
1838 switches(&mut built.func, Goal::Size);
1839 let text = printed(&built.func, &mut built.names);
1840 assert!(!text.contains("switch"), "at size it is searched: {text}");
1841 }
1842
1843 #[test]
1846 fn an_operand_wider_than_a_word_gets_no_table() {
1847 let ty = Type::int(128);
1848 let cases: Vec<i128> = (0..13).collect();
1849 let arms: Vec<usize> = (0..cases.len()).collect();
1850 let mut built = built_sharing(&cases, &arms, ty);
1851 let probes: Vec<i128> = (-2..16).collect();
1852 routes(&mut built, &cases, &arms, &probes, ty);
1853 let text = printed(&built.func, &mut built.names);
1854 assert!(!text.contains("switch"), "a wide operand is searched: {text}");
1855 }
1856
1857 #[test]
1861 fn arms_that_carry_values_are_reached_through_blocks_of_their_own() {
1862 let mut names = Interner::new();
1863 let int = Type::int(32);
1864 let mut func = Func::new(
1865 names.intern("sw"),
1866 Signature::new().with_params(&[int]).with_returns(&[int]),
1867 );
1868 let entry = func.create_block();
1869 let x = func.append_param(entry, int);
1870 let default = func.create_block();
1871 let join = func.create_block();
1872 let param = func.append_param(join, int);
1873
1874 let mut build = Builder::new(&mut func, entry);
1875 let values: Vec<Value> = (0..12).map(|at| build.iconst(int, 100 + at)).collect();
1876 let none = func.push_values(&[]);
1877 let mut calls = vec![BlockCall::new(default, none)];
1878 for &value in &values {
1879 let args = func.push_values(&[value]);
1880 calls.push(BlockCall::new(join, args));
1881 }
1882 let targets = func.push_block_calls(&calls);
1883 let imms: Vec<Imm> = (0..12).map(|at| Imm::int(at, int)).collect();
1884 let cases = func.push_imms(&imms);
1885 let info = func.add_switch(SwitchInfo { targets, cases });
1886 let args = func.push_values(&[x]);
1887 let data = InstData { args, extra: Extra::Switch(info), ..InstData::new(Opcode::Switch) };
1888 Builder::new(&mut func, entry).inst(data, &[]);
1889
1890 let mut build = Builder::new(&mut func, join);
1891 build.ret(&[param]);
1892 let mut build = Builder::new(&mut func, default);
1893 let zero = build.iconst(int, 0);
1894 build.ret(&[zero]);
1895
1896 switches(&mut func, Goal::Speed);
1897 let module = Module::new(names.intern("sw.c"), &target());
1898 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1899 let text = printed(&func, &mut names);
1900 assert_eq!(text.matches("switch").count(), 1, "the cases are one table: {text}");
1901 let table = func
1902 .blocks()
1903 .flat_map(|block| func.insts(block).collect::<Vec<_>>())
1904 .find(|&inst| func[inst].opcode == Opcode::Switch)
1905 .expect("a table");
1906 for call in func.successors(table).skip(1) {
1907 assert!(func[call.args].is_empty(), "a cell passes nothing itself: {text}");
1908 assert_ne!(call.block, join, "a cell goes to a block of its own: {text}");
1909 }
1910 for at in 0..12 {
1911 assert_eq!(arrives(&func, x, at, int), join, "case {at} reaches the join");
1912 }
1913 assert_eq!(arrives(&func, x, 12, int), default, "and a value past the end does not");
1914 }
1915}