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 rucc_base::Interner;
987 use rucc_base::hash::Map;
988 use rucc_ir::{
989 Block, BlockCall, Builder, Extra, Func, Hint, Imm, InstData, IntPred, Module, Opcode,
990 Signature, SwitchInfo, Type, Value,
991 };
992 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
993
994 use super::{Force, Goal, LINEAR, Lowered, SWITCH_PEEL_PERCENT, blocks_for, lowered, switches};
995
996 fn target() -> TargetInfo {
997 TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
998 }
999
1000 struct Built {
1002 names: Interner,
1003 func: Func,
1004 operand: Value,
1005 arms: Vec<Block>,
1006 default: Block,
1007 }
1008
1009 fn built(cases: &[i128]) -> Built {
1016 let arms: Vec<usize> = (0..cases.len()).collect();
1017 built_sharing(cases, &arms, Type::int(32))
1018 }
1019
1020 fn built_sharing(cases: &[i128], arms: &[usize], ty: Type) -> Built {
1022 let mut names = Interner::new();
1023 let int = Type::int(32);
1024 let mut func =
1025 Func::new(names.intern("sw"), Signature::new().with_params(&[ty]).with_returns(&[int]));
1026 let entry = func.create_block();
1027 let x = func.append_param(entry, ty);
1028
1029 let default = func.create_block();
1030 let count = arms.iter().copied().max().map_or(0, |top| top + 1);
1031 let blocks: Vec<Block> = (0..count).map(|_| func.create_block()).collect();
1032 let table: Vec<(i128, Block)> =
1033 cases.iter().copied().zip(arms.iter().map(|&at| blocks[at])).collect();
1034 Builder::new(&mut func, entry).switch(x, default, &table);
1035
1036 for (index, &arm) in blocks.iter().enumerate() {
1037 let mut build = Builder::new(&mut func, arm);
1038 let what = i128::try_from(index).expect("a small number of arms");
1039 let v = build.iconst(int, (what + 1) * 10);
1040 build.ret(&[v]);
1041 }
1042 let mut build = Builder::new(&mut func, default);
1043 let v = build.iconst(int, 0);
1044 build.ret(&[v]);
1045 Built { names, func, operand: x, arms: blocks, default }
1046 }
1047
1048 fn count(func: &Func) -> usize {
1049 func.blocks().count()
1050 }
1051
1052 fn printed(func: &Func, names: &mut Interner) -> String {
1053 let module = Module::new(names.intern("sw.c"), &target());
1054 rucc_ir::print_func(&module, func, names)
1055 }
1056
1057 fn verified(built: &mut Built) {
1058 let module = Module::new(built.names.intern("sw.c"), &target());
1059 rucc_ir::verify_func(&module, &built.func, &built.names)
1060 .expect("the rewrite builds valid IR");
1061 }
1062
1063 fn arrives(func: &Func, operand: Value, x: i128, ty: Type) -> Block {
1079 let mut at = func.entry().expect("an entry block");
1080 let mut held: Map<Value, i128> = Map::default();
1081 held.insert(operand, Imm::int(x, ty).signed(ty));
1082 loop {
1083 let mut moved = None;
1084 for inst in func.insts(at).collect::<Vec<_>>() {
1085 let opcode = func[inst].opcode;
1086 let extra = func[inst].extra;
1087 let result = func[inst].first_result;
1088 let args: Vec<i128> = func[func[inst].args]
1089 .iter()
1090 .map(|value| held.get(value).copied().unwrap_or(0))
1091 .collect();
1092 let wide = |value: Option<Value>| func[value.expect("a result")].ty;
1093 let mut put = |value: Option<Value>, what: i128| {
1094 let value = value.expect("a result");
1095 let ty = func[value].ty;
1096 held.insert(value, Imm::int(what, ty).signed(ty));
1097 };
1098 match opcode {
1099 Opcode::IConst => {
1100 let Extra::Imm(imm) = extra else { return at };
1101 put(result, func[imm].signed(wide(result)));
1102 }
1103 Opcode::Sub => put(result, args[0] - args[1]),
1104 Opcode::And => put(result, args[0] & args[1]),
1105 Opcode::Shl => put(result, args[0] << args[1]),
1106 Opcode::ZExt => {
1107 let from = func[func[func[inst].args][0]].ty;
1108 let raw = Imm::int(args[0], from).unsigned();
1109 put(result, i128::try_from(raw).expect("a value narrower than a word"));
1110 }
1111 Opcode::ICmp => {
1112 let Extra::IntPred(pred) = extra else { return at };
1113 let of = func[func[func[inst].args][0]].ty;
1114 let unsigned = |v: i128| Imm::int(v, of).unsigned();
1115 let answer = match pred {
1116 IntPred::Eq => args[0] == args[1],
1117 IntPred::Ne => args[0] != args[1],
1118 IntPred::Slt => args[0] < args[1],
1119 IntPred::Ule => unsigned(args[0]) <= unsigned(args[1]),
1120 other => panic!("the lowering does not write {}", other.name()),
1121 };
1122 held.insert(result.expect("a comparison has a result"), i128::from(answer));
1123 }
1124 Opcode::Jump => {
1125 let call = func.successors(inst).next().expect("a jump has a target");
1126 moved = Some(call.block);
1127 }
1128 Opcode::BrIf => {
1129 let mut targets = func.successors(inst);
1130 let taken = targets.next().expect("a branch has two targets");
1131 let other = targets.next().expect("a branch has two targets");
1132 moved = Some(if args[0] != 0 { taken.block } else { other.block });
1133 }
1134 Opcode::Switch => {
1137 let Extra::Switch(info) = extra else { return at };
1138 let of = func[func[func[inst].args][0]].ty;
1139 let targets: Vec<BlockCall> = func.successors(inst).collect();
1140 let found = func[func[info].cases]
1141 .iter()
1142 .position(|case| case.signed(of) == args[0])
1143 .map_or(targets[0], |arm| targets[arm + 1]);
1144 moved = Some(found.block);
1145 }
1146 _ => return at,
1147 }
1148 }
1149 match moved {
1150 Some(next) => at = next,
1151 None => return at,
1152 }
1153 }
1154 }
1155
1156 fn routes(built: &mut Built, cases: &[i128], arms: &[usize], probes: &[i128], ty: Type) {
1158 switches(&mut built.func, Goal::Speed);
1159 lands(built, cases, arms, probes, ty);
1160 }
1161
1162 fn lands(built: &mut Built, cases: &[i128], arms: &[usize], probes: &[i128], ty: Type) {
1164 verified(built);
1165 for &x in probes {
1166 let wanted = cases
1167 .iter()
1168 .position(|&case| case == x)
1169 .map_or(built.default, |at| built.arms[arms[at]]);
1170 let got = arrives(&built.func, built.operand, x, ty);
1171 assert_eq!(got, wanted, "the operand {x} went to the wrong block");
1172 }
1173 }
1174
1175 fn forcing(cases: &[i128], force: Force) -> Lowered {
1178 let arms: Vec<usize> = (0..cases.len()).collect();
1179 let mut built = built(cases);
1180 let said = lowered(&mut built.func, Goal::Speed, Some(force));
1181 lands(&mut built, cases, &arms, &around(cases, Type::int(32)), Type::int(32));
1182 assert_eq!(said.len(), 1);
1183 said[0]
1184 }
1185
1186 #[test]
1187 fn each_forced_shape_is_the_shape_it_says_and_still_routes_every_value() {
1188 let sparse: Vec<i128> = (0..40).map(|at| at * 17).collect();
1189 let table = forcing(&sparse, Force::Table);
1190 assert_eq!((table.shape(), table.tables, table.clusters), ("table", 1, 1));
1191 let tree = forcing(&sparse, Force::Tree);
1192 assert_eq!((tree.shape(), tree.clusters), ("tree", 40));
1193 let dense: Vec<i128> = (0..40).collect();
1194 let walk = forcing(&dense, Force::Walk);
1195 assert_eq!((walk.shape(), walk.tables, walk.clusters), ("walk", 0, 40));
1196 }
1197
1198 #[test]
1199 fn a_forced_table_too_wide_to_be_worth_it_keeps_the_shape_it_had() {
1200 let wide: Vec<i128> = (0..40).map(|at| at * 1000).collect();
1201 assert_eq!(forcing(&wide, Force::Table).shape(), "tree");
1202 }
1203
1204 #[test]
1205 fn what_a_switch_became_is_said_in_one_line() {
1206 let dense: Vec<i128> = (0..40).collect();
1207 let mut built = built(&dense);
1208 let said = lowered(&mut built.func, Goal::Speed, None);
1209 assert_eq!(
1210 said.iter().map(Lowered::describe).collect::<Vec<_>>(),
1211 ["switch of 40 cases lowered as a table; clusters 1, tables 1, bit tests 0"]
1212 );
1213 let three = forcing(&[1, 5, 9], Force::Walk);
1214 assert_eq!(three.shape(), "walk");
1215 assert_eq!(Force::named("tree"), Some(Force::Tree));
1216 assert_eq!(Force::named("bit-test"), None);
1217 }
1218
1219 fn around(cases: &[i128], ty: Type) -> Vec<i128> {
1221 let mut probes: Vec<i128> = Vec::new();
1222 for &case in cases {
1223 probes.extend([case - 1, case, case + 1]);
1224 }
1225 let bits = ty.bits();
1226 probes.extend([0, -1, 1, i128::from(i32::MIN) >> (32 - bits), (1 << (bits - 1)) - 1]);
1227 probes.retain(|&x| Imm::int(x, ty).signed(ty) == x);
1228 probes.sort_unstable();
1229 probes.dedup();
1230 probes
1231 }
1232
1233 #[test]
1234 fn a_small_switch_is_a_compare_and_a_branch_for_each_case() {
1235 let mut built = built(&[1, 2]);
1236 let before = count(&built.func);
1237 switches(&mut built.func, Goal::Speed);
1238 assert_eq!(count(&built.func), before + blocks_for(2));
1239
1240 let text = printed(&built.func, &mut built.names);
1241 assert!(!text.contains("switch"), "the switch is gone: {text}");
1242 assert_eq!(text.matches("icmp eq").count(), 2, "one compare per case: {text}");
1243 assert_eq!(text.matches("br_if").count(), 2, "one branch per case: {text}");
1244 }
1245
1246 #[test]
1247 fn the_last_case_falls_to_the_default_rather_than_to_a_block_of_its_own() {
1248 let mut built = built(&[7]);
1249 let before = count(&built.func);
1250 switches(&mut built.func, Goal::Speed);
1251 assert_eq!(count(&built.func), before);
1253 assert_eq!(blocks_for(1), 0);
1254 }
1255
1256 #[test]
1257 fn a_switch_with_only_a_default_is_a_jump() {
1258 let mut built = built(&[]);
1259 switches(&mut built.func, Goal::Speed);
1260 let entry = built.func.entry().expect("an entry block");
1261 let term = built.func.terminator(entry).expect("a terminator");
1262 assert_eq!(built.func[term].opcode, Opcode::Jump);
1263 }
1264
1265 #[test]
1268 fn what_comes_out_is_valid_ir() {
1269 let mut built = built(&[1, 2, 3, 4]);
1270 switches(&mut built.func, Goal::Speed);
1271 verified(&mut built);
1272 }
1273
1274 #[test]
1277 fn a_function_with_no_switch_is_left_exactly_as_it_was() {
1278 let mut names = Interner::new();
1279 let int = Type::int(32);
1280 let mut func =
1281 Func::new(names.intern("f"), Signature::new().with_params(&[int]).with_returns(&[int]));
1282 let entry = func.create_block();
1283 let x = func.append_param(entry, int);
1284 Builder::new(&mut func, entry).ret(&[x]);
1285
1286 let before = printed(&func, &mut names);
1287 switches(&mut func, Goal::Speed);
1288 assert_eq!(printed(&func, &mut names), before);
1289 }
1290
1291 #[test]
1292 fn a_run_of_cases_going_to_one_place_is_one_range_test() {
1293 let cases = [3, 4, 5, 6, 7, 8, 9, 10];
1294 let arms = [0; 8];
1295 let mut built = built_sharing(&cases, &arms, Type::int(32));
1296 switches(&mut built.func, Goal::Speed);
1297
1298 let text = printed(&built.func, &mut built.names);
1299 assert_eq!(text.matches("icmp").count(), 1, "eight cases, one test: {text}");
1300 assert_eq!(text.matches("icmp ule").count(), 1, "and the test is the range: {text}");
1301 assert_eq!(text.matches("sub").count(), 1, "one subtraction to bring it to zero: {text}");
1302 }
1303
1304 #[test]
1305 fn a_run_that_starts_at_zero_needs_no_subtraction() {
1306 let cases = [0, 1, 2, 3, 4];
1307 let arms = [0; 5];
1308 let mut built = built_sharing(&cases, &arms, Type::int(32));
1309 switches(&mut built.func, Goal::Speed);
1310
1311 let text = printed(&built.func, &mut built.names);
1312 assert_eq!(text.matches("icmp ule").count(), 1, "one range test: {text}");
1313 assert!(!text.contains("sub"), "nothing to subtract from zero: {text}");
1314 }
1315
1316 #[test]
1319 fn the_tree_is_built_over_the_clusters_and_not_over_the_cases() {
1320 let cases: Vec<i128> = (0..30).collect();
1321 let arms: Vec<usize> = (0..30).map(|at: usize| at / 10).collect();
1322 let mut built = built_sharing(&cases, &arms, Type::int(32));
1323 switches(&mut built.func, Goal::Speed);
1324
1325 let text = printed(&built.func, &mut built.names);
1326 assert_eq!(text.matches("icmp").count(), 3, "three runs, three tests: {text}");
1327 assert!(!text.contains("icmp slt"), "three clusters is under the leaf size: {text}");
1328 }
1329
1330 fn hint(built: &mut Built, parts: &[u32]) {
1334 let func = &mut built.func;
1335 let entry = func.blocks().next().expect("an entry");
1336 let term = func.terminator(entry).expect("the switch");
1337 for (at, &parts) in func.target_list(term).iter().zip(parts) {
1338 let call = func[at];
1339 func.set_block_call(at, BlockCall { hint: Hint::parts(parts), ..call });
1340 }
1341 }
1342
1343 fn first(func: &Func) -> (Block, [Option<u32>; 2]) {
1346 let entry = func.blocks().next().expect("an entry");
1347 let term = func.terminator(entry).expect("a branch");
1348 assert_eq!(func[term].opcode, Opcode::BrIf);
1349 let calls: Vec<BlockCall> = func.target_list(term).iter().map(|at| func[at]).collect();
1350 (calls[0].block, [calls[0].hint.taken(), calls[1].hint.taken()])
1351 }
1352
1353 fn leaning(cases: usize, hot: usize, parts: u32) -> Vec<u32> {
1356 let rest = (10_000 - parts) / u32::try_from(cases).expect("a small switch");
1357 (0..=cases).map(|at| if at == hot + 1 { parts } else { rest }).collect()
1358 }
1359
1360 #[test]
1361 fn a_case_hinted_hot_is_tested_first_with_the_hint_on_its_branch() {
1362 let cases: Vec<i128> = (0..40).map(|at| at * SPARSE).collect();
1364 let arms: Vec<usize> = (0..cases.len()).collect();
1365 let mut built = built(&cases);
1366 hint(&mut built, &leaning(cases.len(), 7, 9_000));
1367 let hot = built.arms[7];
1368 routes(&mut built, &cases, &arms, &around(&cases, Type::int(32)), Type::int(32));
1369 assert_eq!(first(&built.func), (hot, [Some(9_000), Some(1_000)]));
1370 }
1371
1372 #[test]
1373 fn a_hot_case_in_a_dense_stretch_is_taken_out_of_the_table() {
1374 let cases: Vec<i128> = (0..20).collect();
1375 let arms: Vec<usize> = (0..cases.len()).collect();
1376 let mut built = built(&cases);
1377 hint(&mut built, &leaning(cases.len(), 5, 9_000));
1378 let hot = built.arms[5];
1379 routes(&mut built, &cases, &arms, &around(&cases, Type::int(32)), Type::int(32));
1380 assert_eq!(first(&built.func).0, hot);
1381 }
1382
1383 #[test]
1384 fn a_hint_under_the_threshold_or_on_the_default_leaves_the_tree_as_it_was() {
1385 let cases: Vec<i128> = (0..40).map(|at| at * SPARSE).collect();
1386 let mut plain = built(&cases);
1387 switches(&mut plain.func, Goal::Speed);
1388 let want = printed(&plain.func, &mut plain.names);
1389 let bar = SWITCH_PEEL_PERCENT * 100;
1390 let mut on_the_default = leaning(cases.len(), 0, 1_000);
1391 on_the_default[0] = 9_000;
1392 for parts in [leaning(cases.len(), 7, bar - 1), on_the_default] {
1393 let mut built = built(&cases);
1394 hint(&mut built, &parts);
1395 switches(&mut built.func, Goal::Speed);
1396 assert_eq!(printed(&built.func, &mut built.names), want);
1397 }
1398 }
1399
1400 #[test]
1401 fn a_long_sparse_switch_is_a_search_rather_than_a_walk() {
1402 let count = 4 * LINEAR as i128;
1406 let cases: Vec<i128> = (0..count).map(|at| at * SPARSE).collect();
1407 let mut built = built(&cases);
1408 switches(&mut built.func, Goal::Speed);
1409
1410 let worst = deepest(&built.func);
1411 assert!(worst <= LINEAR + 2, "{count} cases in {worst} comparisons at worst");
1412 assert!(worst > LINEAR, "and the splits are being counted too");
1413 }
1414
1415 const SPARSE: i128 = 17;
1418
1419 fn deepest(func: &Func) -> usize {
1424 fn walk(func: &Func, at: Block, seen: &mut Map<Block, usize>) -> usize {
1425 if let Some(&known) = seen.get(&at) {
1426 return known;
1427 }
1428 let here = func.insts(at).filter(|&inst| func[inst].opcode == Opcode::ICmp).count();
1429 let term = func.terminator(at).expect("a terminator");
1430 let onward: Vec<Block> = match func[term].opcode {
1431 Opcode::Jump | Opcode::BrIf => {
1432 func.successors(term).map(|call| call.block).collect()
1433 }
1434 _ => Vec::new(),
1435 };
1436 let below =
1437 onward.into_iter().map(|block| walk(func, block, seen)).max().unwrap_or_default();
1438 seen.insert(at, here + below);
1439 here + below
1440 }
1441 walk(func, func.entry().expect("an entry block"), &mut Map::default())
1442 }
1443
1444 #[test]
1445 fn every_value_reaches_the_arm_its_case_named_in_a_small_switch() {
1446 let cases = [1, 2, 3];
1447 let arms = [0, 1, 2];
1448 let ty = Type::int(32);
1449 let mut built = built(&cases);
1450 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1451 }
1452
1453 #[test]
1454 fn every_value_reaches_the_arm_its_case_named_in_a_search() {
1455 let count = 3 * LINEAR;
1456 let cases: Vec<i128> = (0..count as i128).map(|at| at * SPARSE).collect();
1457 let arms: Vec<usize> = (0..count).collect();
1458 let ty = Type::int(32);
1459 let mut built = built(&cases);
1460 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1461 }
1462
1463 #[test]
1466 fn every_value_reaches_its_arm_when_the_cases_straddle_zero() {
1467 let half = LINEAR as i128;
1468 let cases: Vec<i128> = (-half..half).map(|at| at * SPARSE).collect();
1469 let arms: Vec<usize> = (0..2 * LINEAR).collect();
1470 let ty = Type::int(32);
1471 let mut built = built(&cases);
1472 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1473 }
1474
1475 #[test]
1478 fn every_value_reaches_its_arm_when_runs_and_singles_are_mixed() {
1479 let cases: Vec<i128> =
1480 vec![-9, -8, -7, -6, 0, 5, 6, 7, 8, 9, 10, 40, 41, 90, 91, 92, 93, 94, 95, 200];
1481 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];
1482 let ty = Type::int(32);
1483 let mut built = built_sharing(&cases, &arms, ty);
1484 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1485 }
1486
1487 #[test]
1491 fn a_run_covering_the_whole_type_matches_everything() {
1492 let cases: Vec<i128> = (-128..128).collect();
1493 let arms = vec![0; cases.len()];
1494 let ty = Type::int(8);
1495 let mut built = built_sharing(&cases, &arms, ty);
1496 switches(&mut built.func, Goal::Speed);
1497 verified(&mut built);
1498
1499 let text = printed(&built.func, &mut built.names);
1500 assert_eq!(text.matches("icmp").count(), 1, "one run, one test: {text}");
1501
1502 let entry = built.func.entry().expect("an entry block");
1503 let operand = built.func[entry].params[0];
1504 for x in [-128, -1, 0, 1, 127] {
1505 assert_eq!(
1506 arrives(&built.func, operand, x, ty),
1507 built.arms[0],
1508 "every value of the type is in the run"
1509 );
1510 }
1511 }
1512
1513 #[test]
1517 #[should_panic(expected = "two cases of the same value")]
1518 fn a_case_value_written_twice_stops_the_compiler() {
1519 let cases = [4, 9, 4];
1520 let arms = [0, 1, 2];
1521 let mut built = built_sharing(&cases, &arms, Type::int(32));
1522 switches(&mut built.func, Goal::Speed);
1523 }
1524
1525 #[test]
1531 fn cases_that_share_a_block_but_not_its_arguments_are_not_a_run() {
1532 let mut names = Interner::new();
1533 let int = Type::int(32);
1534 let mut func = Func::new(
1535 names.intern("sw"),
1536 Signature::new().with_params(&[int]).with_returns(&[int]),
1537 );
1538 let entry = func.create_block();
1539 let x = func.append_param(entry, int);
1540 let default = func.create_block();
1541 let join = func.create_block();
1542 let param = func.append_param(join, int);
1543
1544 let mut build = Builder::new(&mut func, entry);
1545 let ten = build.iconst(int, 10);
1546 let twenty = build.iconst(int, 20);
1547 let none = func.push_values(&[]);
1548 let first = func.push_values(&[ten]);
1549 let second = func.push_values(&[twenty]);
1550 let targets = func.push_block_calls(&[
1551 BlockCall::new(default, none),
1552 BlockCall::new(join, first),
1553 BlockCall::new(join, second),
1554 ]);
1555 let cases = func.push_imms(&[Imm::int(1, int), Imm::int(2, int)]);
1556 let info = func.add_switch(SwitchInfo { targets, cases });
1557 let args = func.push_values(&[x]);
1558 let data = InstData { args, extra: Extra::Switch(info), ..InstData::new(Opcode::Switch) };
1559 Builder::new(&mut func, entry).inst(data, &[]);
1560
1561 let mut build = Builder::new(&mut func, join);
1562 build.ret(&[param]);
1563 let mut build = Builder::new(&mut func, default);
1564 let zero = build.iconst(int, 0);
1565 build.ret(&[zero]);
1566
1567 switches(&mut func, Goal::Speed);
1568 let text = printed(&func, &mut names);
1569 assert_eq!(text.matches("icmp eq").count(), 2, "two cases, two equality tests: {text}");
1570 assert!(!text.contains("icmp ule"), "and no range test over them: {text}");
1571 }
1572
1573 #[test]
1576 fn the_leaf_size_is_where_the_search_starts() {
1577 let flat: Vec<i128> = (0..LINEAR as i128).map(|at| at * SPARSE).collect();
1578 let mut walked = built(&flat);
1579 switches(&mut walked.func, Goal::Speed);
1580 assert!(
1581 !printed(&walked.func, &mut walked.names).contains("icmp slt"),
1582 "a leaf's worth of clusters is still a chain"
1583 );
1584
1585 let one_more: Vec<i128> = (0..LINEAR as i128 + 1).map(|at| at * SPARSE).collect();
1586 let mut split = built(&one_more);
1587 switches(&mut split.func, Goal::Speed);
1588 assert!(
1589 printed(&split.func, &mut split.names).contains("icmp slt"),
1590 "one more than a leaf splits"
1591 );
1592 }
1593
1594 #[test]
1598 fn scattered_cases_sharing_one_arm_are_one_mask_and_one_test() {
1599 let cases = [97, 101, 105, 111, 117];
1600 let arms = [0; 5];
1601 let mut built = built_sharing(&cases, &arms, Type::int(32));
1602 switches(&mut built.func, Goal::Speed);
1603
1604 let text = printed(&built.func, &mut built.names);
1605 assert!(!text.contains("icmp eq"), "no case is compared on its own: {text}");
1606 assert_eq!(text.matches("shl").count(), 1, "one bit is picked out: {text}");
1607 assert_eq!(text.matches("and").count(), 1, "and one mask is asked about it: {text}");
1608 assert_eq!(text.matches("icmp ule").count(), 1, "one bound before the shift: {text}");
1609 assert_eq!(text.matches("icmp ne").count(), 1, "one test for the one arm: {text}");
1610 }
1611
1612 #[test]
1615 fn every_value_reaches_its_arm_through_a_bit_test() {
1616 let ty = Type::int(32);
1617 let cases = [97, 101, 105, 111, 117];
1618 let arms = [0; 5];
1619 let mut built = built_sharing(&cases, &arms, ty);
1620 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1621 }
1622
1623 #[test]
1626 fn a_bit_test_carries_several_destinations_in_one_word() {
1627 let ty = Type::int(32);
1628 let cases: Vec<i128> = (0..9).map(|at| at * 3).collect();
1629 let arms: Vec<usize> = (0..9).map(|at: usize| at % 3).collect();
1630 let mut built = built_sharing(&cases, &arms, ty);
1631 switches(&mut built.func, Goal::Speed);
1632
1633 let text = printed(&built.func, &mut built.names);
1634 assert_eq!(text.matches("shl").count(), 1, "nine cases, one shift: {text}");
1635 assert_eq!(text.matches("icmp ne").count(), 3, "three arms, three masks: {text}");
1636 assert!(!text.contains("icmp eq"), "and no case compared on its own: {text}");
1637
1638 let mut built = built_sharing(&cases, &arms, ty);
1639 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1640 }
1641
1642 #[test]
1645 fn the_last_destination_needs_no_test_when_the_masks_cover_the_span() {
1646 let ty = Type::int(32);
1647 let cases: Vec<i128> = (0..6).collect();
1648 let arms: Vec<usize> = (0..6).map(|at: usize| at % 2).collect();
1649 let mut built = built_sharing(&cases, &arms, ty);
1650 switches(&mut built.func, Goal::Speed);
1651
1652 let text = printed(&built.func, &mut built.names);
1653 assert_eq!(text.matches("icmp ne").count(), 1, "two arms, one mask asked about: {text}");
1654
1655 let mut built = built_sharing(&cases, &arms, ty);
1656 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1657 }
1658
1659 #[test]
1662 fn a_group_that_does_not_pay_for_itself_stays_a_chain() {
1663 let cases = [0, 3, 6];
1664 let arms = [0, 1, 2];
1665 let mut built = built_sharing(&cases, &arms, Type::int(32));
1666 switches(&mut built.func, Goal::Speed);
1667
1668 let text = printed(&built.func, &mut built.names);
1669 assert!(!text.contains("shl"), "three values and three arms buys nothing: {text}");
1670 assert_eq!(text.matches("icmp eq").count(), 3, "so it stays a walk: {text}");
1671 }
1672
1673 #[test]
1677 fn a_bit_test_never_spans_more_than_a_word() {
1678 let ty = Type::int(32);
1679 let cases = [0, 2, 4, 6, 64];
1680 let arms = [0; 5];
1681 let mut built = built_sharing(&cases, &arms, ty);
1682 switches(&mut built.func, Goal::Speed);
1683
1684 let text = printed(&built.func, &mut built.names);
1685 assert_eq!(text.matches("shl").count(), 1, "one group, not two: {text}");
1686 assert_eq!(text.matches("icmp eq").count(), 1, "and the value past it is compared: {text}");
1687
1688 let mut built = built_sharing(&cases, &arms, ty);
1689 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1690 }
1691
1692 #[test]
1696 fn a_bit_test_reaches_the_top_of_its_word() {
1697 let ty = Type::int(32);
1698 let cases = [0, 2, 4, 63];
1699 let arms = [0; 4];
1700 let mut built = built_sharing(&cases, &arms, ty);
1701 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1702 }
1703
1704 #[test]
1708 fn a_run_is_left_alone_rather_than_folded_into_a_mask() {
1709 let ty = Type::int(32);
1710 let cases = [0, 1, 2, 3, 10, 12, 14, 16];
1711 let arms = [0, 0, 0, 0, 1, 1, 1, 1];
1712 let mut built = built_sharing(&cases, &arms, ty);
1713 switches(&mut built.func, Goal::Speed);
1714
1715 let text = printed(&built.func, &mut built.names);
1716 assert_eq!(text.matches("icmp ule").count(), 2, "a run's bound and a group's: {text}");
1717 assert_eq!(text.matches("shl").count(), 1, "and only the group is a mask: {text}");
1718
1719 let mut built = built_sharing(&cases, &arms, ty);
1720 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1721 }
1722
1723 #[test]
1726 fn every_value_reaches_its_arm_when_a_bit_test_starts_below_zero() {
1727 let ty = Type::int(32);
1728 let cases = [-20, -17, -14, -11, -8, -5];
1729 let arms = [0, 1, 0, 1, 0, 1];
1730 let mut built = built_sharing(&cases, &arms, ty);
1731 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1732 }
1733
1734 #[test]
1737 fn a_dense_switch_is_one_bound_and_a_table() {
1738 let cases: Vec<i128> = (0..13).collect();
1739 let mut built = built(&cases);
1740 switches(&mut built.func, Goal::Speed);
1741 verified(&mut built);
1742
1743 let text = printed(&built.func, &mut built.names);
1744 assert_eq!(text.matches("icmp ule").count(), 1, "one bound over the span: {text}");
1745 assert_eq!(text.matches("switch").count(), 1, "and one table inside it: {text}");
1746 assert!(!text.contains("icmp eq"), "and no case compared on its own: {text}");
1747 }
1748
1749 #[test]
1752 fn every_value_reaches_its_arm_through_a_table_with_holes() {
1753 let ty = Type::int(32);
1754 let cases = [3, 4, 5, 7, 8, 10, 11, 13, 14, 15, 19];
1755 let arms: Vec<usize> = (0..cases.len()).collect();
1756 let mut built = built_sharing(&cases, &arms, ty);
1757 routes(&mut built, &cases, &arms, &around(&cases, ty), ty);
1758 let text = printed(&built.func, &mut built.names);
1759 assert_eq!(text.matches("switch").count(), 1, "the cases are one table: {text}");
1760 }
1761
1762 #[test]
1766 fn every_value_reaches_its_arm_through_a_table_that_straddles_zero() {
1767 let ty = Type::int(8);
1768 let cases: Vec<i128> = (-7..8).filter(|x| x % 4 != 0).collect();
1769 let arms: Vec<usize> = (0..cases.len()).map(|at| at % 5).collect();
1770 let mut built = built_sharing(&cases, &arms, ty);
1771 let probes: Vec<i128> = (-128..128).collect();
1772 routes(&mut built, &cases, &arms, &probes, ty);
1773 let text = printed(&built.func, &mut built.names);
1774 assert_eq!(text.matches("switch").count(), 1, "the cases are one table: {text}");
1775 }
1776
1777 #[test]
1780 fn a_few_destinations_stay_a_bit_test_and_more_become_a_table() {
1781 let ty = Type::int(32);
1782 let cases: Vec<i128> = (0..12).map(|at| at * 3).collect();
1783 let few: Vec<usize> = (0..12).map(|at: usize| at % 3).collect();
1784 let mut built = built_sharing(&cases, &few, ty);
1785 switches(&mut built.func, Goal::Speed);
1786 let text = printed(&built.func, &mut built.names);
1787 assert!(!text.contains("switch"), "three arms are masks: {text}");
1788
1789 let many: Vec<usize> = (0..12).map(|at: usize| at % 5).collect();
1790 let mut built = built_sharing(&cases, &many, ty);
1791 switches(&mut built.func, Goal::Speed);
1792 let text = printed(&built.func, &mut built.names);
1793 assert_eq!(text.matches("switch").count(), 1, "five arms are a table: {text}");
1794 let mut built = built_sharing(&cases, &many, ty);
1795 routes(&mut built, &cases, &many, &around(&cases, ty), ty);
1796 }
1797
1798 #[test]
1801 fn too_few_cases_for_a_table_are_compared() {
1802 let cases: Vec<i128> = (0..10).collect();
1803 let mut built = built(&cases);
1804 switches(&mut built.func, Goal::Speed);
1805 let text = printed(&built.func, &mut built.names);
1806 assert!(!text.contains("switch"), "ten cases are not a table: {text}");
1807 }
1808
1809 #[test]
1812 fn for_size_a_table_starts_at_six_cases() {
1813 let tabled = |count: i128, goal: Goal| {
1814 let cases: Vec<i128> = (0..count).collect();
1815 let mut built = built(&cases);
1816 switches(&mut built.func, goal);
1817 printed(&built.func, &mut built.names).contains("switch")
1818 };
1819 assert!(!tabled(5, Goal::Size), "five cases are compared");
1820 assert!(tabled(6, Goal::Size), "six are a table");
1821 assert!(!tabled(6, Goal::Speed), "which for speed they are not");
1822 }
1823
1824 #[test]
1827 fn a_table_for_speed_can_be_too_sparse_for_size() {
1828 let ty = Type::int(32);
1829 let cases: Vec<i128> = (0..12).map(|at| at * 8).collect();
1830 let arms: Vec<usize> = (0..cases.len()).collect();
1831 let mut built = built_sharing(&cases, &arms, ty);
1832 switches(&mut built.func, Goal::Speed);
1833 let text = printed(&built.func, &mut built.names);
1834 assert_eq!(text.matches("switch").count(), 1, "at speed a span of 89 is a table: {text}");
1835
1836 let mut built = built_sharing(&cases, &arms, ty);
1837 switches(&mut built.func, Goal::Size);
1838 let text = printed(&built.func, &mut built.names);
1839 assert!(!text.contains("switch"), "at size it is searched: {text}");
1840 }
1841
1842 #[test]
1845 fn an_operand_wider_than_a_word_gets_no_table() {
1846 let ty = Type::int(128);
1847 let cases: Vec<i128> = (0..13).collect();
1848 let arms: Vec<usize> = (0..cases.len()).collect();
1849 let mut built = built_sharing(&cases, &arms, ty);
1850 let probes: Vec<i128> = (-2..16).collect();
1851 routes(&mut built, &cases, &arms, &probes, ty);
1852 let text = printed(&built.func, &mut built.names);
1853 assert!(!text.contains("switch"), "a wide operand is searched: {text}");
1854 }
1855
1856 #[test]
1860 fn arms_that_carry_values_are_reached_through_blocks_of_their_own() {
1861 let mut names = Interner::new();
1862 let int = Type::int(32);
1863 let mut func = Func::new(
1864 names.intern("sw"),
1865 Signature::new().with_params(&[int]).with_returns(&[int]),
1866 );
1867 let entry = func.create_block();
1868 let x = func.append_param(entry, int);
1869 let default = func.create_block();
1870 let join = func.create_block();
1871 let param = func.append_param(join, int);
1872
1873 let mut build = Builder::new(&mut func, entry);
1874 let values: Vec<Value> = (0..12).map(|at| build.iconst(int, 100 + at)).collect();
1875 let none = func.push_values(&[]);
1876 let mut calls = vec![BlockCall::new(default, none)];
1877 for &value in &values {
1878 let args = func.push_values(&[value]);
1879 calls.push(BlockCall::new(join, args));
1880 }
1881 let targets = func.push_block_calls(&calls);
1882 let imms: Vec<Imm> = (0..12).map(|at| Imm::int(at, int)).collect();
1883 let cases = func.push_imms(&imms);
1884 let info = func.add_switch(SwitchInfo { targets, cases });
1885 let args = func.push_values(&[x]);
1886 let data = InstData { args, extra: Extra::Switch(info), ..InstData::new(Opcode::Switch) };
1887 Builder::new(&mut func, entry).inst(data, &[]);
1888
1889 let mut build = Builder::new(&mut func, join);
1890 build.ret(&[param]);
1891 let mut build = Builder::new(&mut func, default);
1892 let zero = build.iconst(int, 0);
1893 build.ret(&[zero]);
1894
1895 switches(&mut func, Goal::Speed);
1896 let module = Module::new(names.intern("sw.c"), &target());
1897 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1898 let text = printed(&func, &mut names);
1899 assert_eq!(text.matches("switch").count(), 1, "the cases are one table: {text}");
1900 let table = func
1901 .blocks()
1902 .flat_map(|block| func.insts(block).collect::<Vec<_>>())
1903 .find(|&inst| func[inst].opcode == Opcode::Switch)
1904 .expect("a table");
1905 for call in func.successors(table).skip(1) {
1906 assert!(func[call.args].is_empty(), "a cell passes nothing itself: {text}");
1907 assert_ne!(call.block, join, "a cell goes to a block of its own: {text}");
1908 }
1909 for at in 0..12 {
1910 assert_eq!(arrives(&func, x, at, int), join, "case {at} reaches the join");
1911 }
1912 assert_eq!(arrives(&func, x, 12, int), default, "and a value past the end does not");
1913 }
1914}