1use std::collections::HashSet;
71
72use rucc_ir::{Block, BlockCall, Builder, Extra, Flags, Func, Imm, Inst, Opcode, Type, Value};
73
74use crate::cfg::Cfg;
75use crate::{Analyses, Fuel, Pass, Preserved, Stats};
76
77const CONVERTED: &str = "switch replaced by a range check and the arithmetic its arms were doing";
79
80const NO_FUEL: &str = "switch left alone, the pass ran out of fuel";
82
83const TOO_FEW: &str = "switch left alone, it has too few labels for arithmetic to be cheaper";
85
86const NOT_CONSECUTIVE: &str = "switch left alone, its labels are not consecutive";
88
89const ARM_IS_SHARED: &str = "switch left alone, an arm is reached from somewhere other than it";
91
92const ARM_DOES_WORK: &str = "switch left alone, an arm does more than work out a constant";
94
95const ARMS_DIFFER: &str = "switch left alone, its arms do not all hand on the same thing";
97
98const NOT_AFFINE: &str = "switch left alone, its answers are not a fixed multiple of the label \
100 plus a constant";
101
102const WIDTHS_DIFFER: &str = "switch left alone, its answers are not as wide as its labels";
104
105const LABELS: usize = 3;
107
108#[derive(Debug)]
110pub struct SwitchConv;
111
112impl Pass for SwitchConv {
113 fn name(&self) -> &'static str {
114 "switch-conv"
115 }
116
117 fn describe(&self) -> &'static str {
118 "a switch whose arms are a fixed multiple of the label becomes a range check and arithmetic"
119 }
120
121 fn preserves(&self) -> Preserved {
122 Preserved::NONE
124 }
125
126 fn run(&self, func: &mut Func, an: &mut Analyses, fuel: &mut Fuel) -> Stats {
127 let mut stats = Stats::new();
128 if func.entry().is_none() {
129 return stats;
130 }
131 let cfg = an.cfg(func);
132 let found: Vec<Inst> = func
133 .blocks()
134 .filter_map(|block| func.terminator(block))
135 .filter(|&inst| func[inst].opcode == Opcode::Switch)
136 .collect();
137
138 let mut plans = Vec::new();
139 for inst in found {
140 match plan(func, cfg, inst) {
141 Ok(plan) => plans.push(plan),
142 Err(why) => stats.missed(why),
143 }
144 }
145
146 let mut changed = false;
147 for plan in plans {
148 if !fuel.take() {
149 stats.missed(NO_FUEL);
150 continue;
151 }
152 apply(func, &plan);
153 stats.optimized(CONVERTED);
154 changed = true;
155 }
156 if changed {
157 an.clear();
158 }
159 stats
160 }
161}
162
163#[derive(Clone, Copy, Debug, PartialEq, Eq)]
165enum Hands {
166 On(Block),
168 Back,
170}
171
172#[derive(Debug)]
174struct Plan {
175 inst: Inst,
177 value: Value,
179 ty: Type,
181 hands: Hands,
183 args: Vec<Value>,
186 answer: usize,
188 scale: i128,
190 offset: i128,
192 arms: Vec<Block>,
194}
195
196fn plan(func: &Func, cfg: &Cfg, inst: Inst) -> Result<Plan, &'static str> {
198 let Extra::Switch(info) = func[inst].extra else { return Err(ARMS_DIFFER) };
199 let info = func[info];
200 let Some(&value) = func[func[inst].args].first() else { return Err(ARMS_DIFFER) };
201 let ty = func[value].ty;
202 if !ty.is_int() {
203 return Err(WIDTHS_DIFFER);
204 }
205 let calls: Vec<BlockCall> = func[info.targets].to_vec();
206 let labels: Vec<i128> = func[info.cases].iter().map(|imm| imm.signed(ty)).collect();
207 let Some((&default, arms)) = calls.split_first() else { return Err(ARMS_DIFFER) };
208 if arms.len() != labels.len() || arms.len() < LABELS {
209 return Err(TOO_FEW);
210 }
211 if arms.iter().any(|call| call.block == default.block) {
215 return Err(ARM_IS_SHARED);
216 }
217
218 for pair in labels.windows(2) {
223 if pair[1].checked_sub(pair[0]) != Some(1) {
224 return Err(NOT_CONSECUTIVE);
225 }
226 }
227
228 let mut hands = None;
231 let mut shared: Option<Vec<Value>> = None;
232 let mut answer = None;
233 let mut answers = Vec::new();
234 for call in arms {
235 if !call.args.is_empty() {
236 return Err(ARM_DOES_WORK);
237 }
238 if cfg.predecessors(call.block).len() != 1 {
239 return Err(ARM_IS_SHARED);
240 }
241 if func.block_name(call.block).is_some() {
244 return Err(ARM_IS_SHARED);
245 }
246 let (way, args) = tail(func, call.block)?;
247 if *hands.get_or_insert(way) != way {
248 return Err(ARMS_DIFFER);
249 }
250 let previous = shared.get_or_insert_with(|| args.clone());
251 if previous.len() != args.len() {
252 return Err(ARMS_DIFFER);
253 }
254 for (index, (&mine, &theirs)) in previous.iter().zip(&args).enumerate() {
257 if mine == theirs {
258 continue;
259 }
260 if *answer.get_or_insert(index) != index {
261 return Err(ARMS_DIFFER);
262 }
263 }
264 let at = answer.unwrap_or(0);
265 let Some(&handed) = args.get(at) else { return Err(ARMS_DIFFER) };
266 if func[handed].ty != ty {
267 return Err(WIDTHS_DIFFER);
268 }
269 let Some(number) = constant(func, handed) else { return Err(NOT_AFFINE) };
270 answers.push(number);
271 }
272 let (Some(hands), Some(args)) = (hands, shared) else { return Err(ARMS_DIFFER) };
273 let answer = answer.ok_or(NOT_AFFINE)?;
274
275 let (scale, offset) = line(&labels, &answers, ty).ok_or(NOT_AFFINE)?;
276 Ok(Plan {
277 inst,
278 value,
279 ty,
280 hands,
281 args,
282 answer,
283 scale,
284 offset,
285 arms: arms.iter().map(|call| call.block).collect(),
286 })
287}
288
289fn tail(func: &Func, block: Block) -> Result<(Hands, Vec<Value>), &'static str> {
295 let Some(last) = func.terminator(block) else { return Err(ARM_DOES_WORK) };
296 for inst in func.insts(block) {
297 if inst != last && func[inst].opcode != Opcode::IConst {
298 return Err(ARM_DOES_WORK);
299 }
300 }
301 let args: Vec<Value> = match func[last].opcode {
302 Opcode::Jump => {
303 let Some(call) = func.successors(last).next() else { return Err(ARM_DOES_WORK) };
304 let args = func[call.args].to_vec();
305 return Ok((Hands::On(call.block), args));
306 }
307 Opcode::Return => func[func[last].args].to_vec(),
308 _ => return Err(ARM_DOES_WORK),
309 };
310 Ok((Hands::Back, args))
311}
312
313fn constant(func: &Func, value: Value) -> Option<i128> {
315 crate::discharge::constant(func, value)
316}
317
318fn line(labels: &[i128], answers: &[i128], ty: Type) -> Option<(i128, i128)> {
325 let [first, second, ..] = *labels else { return None };
326 let [low, high, ..] = *answers else { return None };
327 debug_assert_eq!(second - first, 1, "the labels were checked to be consecutive");
328 let scale = high.checked_sub(low)?;
329 let offset = low.checked_sub(scale.checked_mul(first)?)?;
330 for (&label, &answer) in labels.iter().zip(answers) {
331 let want = scale.checked_mul(label)?.checked_add(offset)?;
332 if wrap(want, ty) != answer {
333 return None;
334 }
335 }
336 Some((scale, offset))
337}
338
339fn wrap(value: i128, ty: Type) -> i128 {
344 Imm::int(value, ty).signed(ty)
345}
346
347fn apply(func: &mut Func, plan: &Plan) {
349 let span = func.span(plan.inst);
350 let hit = func.create_block();
351 let mut builder = Builder::new(func, hit).at(span);
352 let scaled = match plan.scale {
353 0 => builder.iconst(plan.ty, plan.offset),
354 1 => plan.value,
355 scale => {
356 let by = builder.iconst(plan.ty, scale);
357 builder.binary(Opcode::Mul, plan.value, by, Flags::NONE)
358 }
359 };
360 let answer = if plan.offset == 0 || plan.scale == 0 {
361 scaled
362 } else {
363 let by = builder.iconst(plan.ty, plan.offset);
364 builder.binary(Opcode::Add, scaled, by, Flags::NONE)
365 };
366 let mut args = plan.args.clone();
367 args[plan.answer] = answer;
368 match plan.hands {
369 Hands::On(block) => builder.jump(block, &args),
370 Hands::Back => builder.ret(&args),
371 };
372
373 let Extra::Switch(info) = func[plan.inst].extra else { return };
376 let empty = func.push_values(&[]);
377 let mut calls: Vec<BlockCall> = func[func[info].targets].to_vec();
378 for call in &mut calls[1..] {
379 *call = BlockCall::new(hit, empty);
382 }
383 let targets = func.push_block_calls(&calls);
384 let cases = func[info].cases;
385 let info = func.add_switch(rucc_ir::SwitchInfo { targets, cases });
386 func[plan.inst].extra = Extra::Switch(info);
387
388 let mut gone = HashSet::new();
391 for &arm in &plan.arms {
392 if gone.insert(arm) {
393 func.remove_block(arm);
394 }
395 }
396}
397
398#[cfg(test)]
399mod tests {
400 use std::collections::HashMap;
401
402 use rucc_base::Interner;
403 use rucc_ir::{Block, Builder, Func, Opcode, Signature, Type, Value};
404
405 use super::SwitchConv;
406 use crate::stats::Kind;
407 use crate::{Fuel, Pass, Stats};
408
409 fn i32() -> Type {
411 Type::int(32)
412 }
413
414 fn convert(func: &mut Func) -> Stats {
416 SwitchConv.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
417 }
418
419 fn returning(ty: Type, labels: &[i128], answers: &[i128]) -> Func {
424 let mut names = Interner::new();
425 let mut func = Func::new(names.intern("f"), Signature::new());
426 let head = func.create_block();
427 let value = func.append_param(head, ty);
428 let default = func.create_block();
429 let arms: Vec<Block> = answers.iter().map(|_| func.create_block()).collect();
430 for (&arm, &answer) in arms.iter().zip(answers) {
431 let mut build = Builder::new(&mut func, arm);
432 let it = build.iconst(ty, answer);
433 build.ret(&[it]);
434 }
435 let mut build = Builder::new(&mut func, default);
436 let it = build.iconst(ty, 999);
437 build.ret(&[it]);
438 let cases: Vec<(i128, Block)> = labels.iter().copied().zip(arms.iter().copied()).collect();
439 Builder::new(&mut func, head).switch(value, default, &cases);
440 func
441 }
442
443 fn cases(func: &Func) -> Vec<usize> {
445 let head = func.entry().expect("a function with blocks in it");
446 let term = func.terminator(head).expect("a head block has one");
447 func.successors(term).skip(1).map(|call| call.block.index()).collect()
448 }
449
450 fn arm(func: &Func) -> Block {
452 let blocks = cases(func);
453 let first = blocks[0];
454 assert!(blocks.iter().all(|&block| block == first), "the case edges did not all move");
455 Block::from_usize(first)
456 }
457
458 fn opcodes(func: &Func, block: Block) -> Vec<Opcode> {
460 func.insts(block).map(|inst| func[inst].opcode).collect()
461 }
462
463 fn answer(func: &Func, block: Block, label: i128) -> i128 {
469 let head = func.entry().expect("a function with blocks in it");
470 let mut values: HashMap<Value, i128> = HashMap::new();
471 values.insert(func[head].params[0], label);
472 for inst in func.insts(block) {
473 let data = func[inst];
474 let Some(result) = data.first_result else {
475 let args = func[data.args].to_vec();
476 let handed = match data.opcode {
477 Opcode::Return => args[0],
478 Opcode::Jump => {
479 func[func.successors(inst).next().expect("a jump goes").args][0]
480 }
481 other => panic!("a block this pass wrote ends in {other:?}"),
482 };
483 return values[&handed];
484 };
485 let args: Vec<i128> = func[data.args].iter().map(|arg| values[arg]).collect();
486 let it = match data.opcode {
487 Opcode::IConst => {
488 let (imm, ty) = crate::fold::constant(func, result).expect("a constant is one");
489 imm.signed(ty)
490 }
491 Opcode::Mul => args[0].wrapping_mul(args[1]),
492 Opcode::Add => args[0].wrapping_add(args[1]),
493 other => panic!("this pass does not write {other:?}"),
494 };
495 values.insert(result, super::wrap(it, func[result].ty));
496 }
497 panic!("a block with no terminator");
498 }
499
500 fn fired(stats: &Stats) -> bool {
502 stats.total(Kind::Optimized) > 0
503 }
504
505 #[test]
506 fn labels_that_run_with_their_answers_become_one_addition() {
507 let mut func = returning(i32(), &[0, 1, 2, 3], &[1, 2, 3, 4]);
508 assert!(fired(&convert(&mut func)));
509 let arm = arm(&func);
510 assert_eq!(opcodes(&func, arm), [Opcode::IConst, Opcode::Add, Opcode::Return]);
511 for label in 0..4 {
512 assert_eq!(answer(&func, arm, label), label + 1);
513 }
514 }
515
516 #[test]
517 fn answers_that_are_a_multiple_of_the_label_become_a_multiplication() {
518 let mut func = returning(i32(), &[3, 4, 5, 6], &[30, 40, 50, 60]);
519 assert!(fired(&convert(&mut func)));
520 let arm = arm(&func);
521 assert_eq!(opcodes(&func, arm), [Opcode::IConst, Opcode::Mul, Opcode::Return]);
522 for label in 3..7 {
523 assert_eq!(answer(&func, arm, label), label * 10);
524 }
525 }
526
527 #[test]
528 fn answers_that_are_all_the_same_become_the_constant_they_all_were() {
529 let mut func = returning(i32(), &[7, 8, 9, 10], &[9, 9, 9, 9]);
530 assert!(fired(&convert(&mut func)));
531 let arm = arm(&func);
532 assert_eq!(opcodes(&func, arm), [Opcode::IConst, Opcode::Return]);
533 assert_eq!(answer(&func, arm, 8), 9);
534 }
535
536 #[test]
537 fn labels_that_run_below_zero_are_a_run_like_any_other() {
538 let mut func = returning(i32(), &[-2, -1, 0, 1], &[-4, -2, 0, 2]);
539 assert!(fired(&convert(&mut func)));
540 let arm = arm(&func);
541 for label in -2..2 {
542 assert_eq!(answer(&func, arm, label), label * 2);
543 }
544 }
545
546 #[test]
553 fn a_line_that_only_holds_by_wrapping_still_holds() {
554 let ty = Type::int(8);
555 let mut func = returning(ty, &[0, 1, 2], &[0, 100, -56]);
556 assert!(fired(&convert(&mut func)));
557 let arm = arm(&func);
558 assert_eq!(answer(&func, arm, 2), -56);
559 }
560
561 #[test]
562 fn labels_with_a_hole_in_them_are_left_alone() {
563 let mut func = returning(i32(), &[0, 1, 3], &[1, 2, 4]);
564 assert!(!fired(&convert(&mut func)));
565 assert_eq!(cases(&func).len(), 3);
566 }
567
568 #[test]
569 fn answers_that_are_not_a_line_are_left_alone() {
570 let mut func = returning(i32(), &[0, 1, 2], &[5, 9, 2]);
571 assert!(!fired(&convert(&mut func)));
572 }
573
574 #[test]
575 fn two_labels_are_not_enough_to_pay_for_the_arithmetic() {
576 let mut func = returning(i32(), &[0, 1], &[1, 2]);
577 assert!(!fired(&convert(&mut func)));
578 }
579
580 #[test]
581 fn an_answer_wider_than_its_label_is_left_alone() {
582 let mut names = Interner::new();
583 let mut func = Func::new(names.intern("f"), Signature::new());
584 let head = func.create_block();
585 let value = func.append_param(head, i32());
586 let default = func.create_block();
587 let arms: Vec<Block> = (0..3).map(|_| func.create_block()).collect();
588 for (index, &arm) in arms.iter().enumerate() {
589 let mut build = Builder::new(&mut func, arm);
590 let it = build.iconst(Type::int(64), index as i128 + 1);
591 build.ret(&[it]);
592 }
593 let mut build = Builder::new(&mut func, default);
594 let it = build.iconst(Type::int(64), 0);
595 build.ret(&[it]);
596 let cases: Vec<(i128, Block)> = (0..3).zip(arms.iter().copied()).collect();
597 Builder::new(&mut func, head).switch(value, default, &cases);
598 assert!(!fired(&convert(&mut func)));
599 }
600
601 #[test]
602 fn an_arm_something_else_reaches_is_left_alone() {
603 let mut func = returning(i32(), &[0, 1, 2], &[1, 2, 3]);
604 let default = Block::from_usize(1);
607 let arm = Block::from_usize(2);
608 let term = func.terminator(default).expect("the default returns");
609 func.remove_inst(term);
610 Builder::new(&mut func, default).jump(arm, &[]);
611 assert!(!fired(&convert(&mut func)));
612 }
613
614 #[test]
615 fn an_arm_that_is_also_the_default_is_left_alone() {
616 let mut names = Interner::new();
617 let mut func = Func::new(names.intern("f"), Signature::new());
618 let head = func.create_block();
619 let value = func.append_param(head, i32());
620 let shared = func.create_block();
621 let mut build = Builder::new(&mut func, shared);
622 let it = build.iconst(i32(), 1);
623 build.ret(&[it]);
624 let others: Vec<Block> = (0..2).map(|_| func.create_block()).collect();
625 for (index, &arm) in others.iter().enumerate() {
626 let mut build = Builder::new(&mut func, arm);
627 let it = build.iconst(i32(), index as i128 + 2);
628 build.ret(&[it]);
629 }
630 let cases = [(0, shared), (1, others[0]), (2, others[1])];
631 Builder::new(&mut func, head).switch(value, shared, &cases);
632 assert!(!fired(&convert(&mut func)));
633 }
634
635 #[test]
636 fn arms_that_join_keep_what_they_pass_beside_the_answer() {
637 let mut names = Interner::new();
638 let mut func = Func::new(names.intern("f"), Signature::new());
639 let head = func.create_block();
640 let value = func.append_param(head, i32());
641 let alongside = func.append_param(head, i32());
642 let join = func.create_block();
643 let handed = func.append_param(join, i32());
644 let carried = func.append_param(join, i32());
645 Builder::new(&mut func, join).ret(&[handed, carried]);
646 let default = func.create_block();
647 let mut build = Builder::new(&mut func, default);
648 let it = build.iconst(i32(), 999);
649 build.jump(join, &[it, alongside]);
650 let arms: Vec<Block> = (0..3).map(|_| func.create_block()).collect();
651 for (index, &arm) in arms.iter().enumerate() {
652 let mut build = Builder::new(&mut func, arm);
653 let it = build.iconst(i32(), index as i128 + 1);
654 build.jump(join, &[it, alongside]);
655 }
656 let cases: Vec<(i128, Block)> = (0..3).zip(arms.iter().copied()).collect();
657 Builder::new(&mut func, head).switch(value, default, &cases);
658 assert!(fired(&convert(&mut func)));
659
660 let arm = arm(&func);
661 assert_eq!(answer(&func, arm, 2), 3);
662 let term = func.terminator(arm).expect("the block ends in a jump");
664 let call = func.successors(term).next().expect("a jump goes somewhere");
665 assert_eq!(func[call.args][1], alongside);
666 }
667
668 #[test]
669 fn arms_that_hand_on_two_different_things_are_left_alone() {
670 let mut names = Interner::new();
671 let mut func = Func::new(names.intern("f"), Signature::new());
672 let head = func.create_block();
673 let value = func.append_param(head, i32());
674 let join = func.create_block();
675 let first = func.append_param(join, i32());
676 let second = func.append_param(join, i32());
677 Builder::new(&mut func, join).ret(&[first, second]);
678 let default = func.create_block();
679 let mut build = Builder::new(&mut func, default);
680 let it = build.iconst(i32(), 999);
681 build.jump(join, &[it, it]);
682 let arms: Vec<Block> = (0..3).map(|_| func.create_block()).collect();
683 for (index, &arm) in arms.iter().enumerate() {
684 let mut build = Builder::new(&mut func, arm);
685 let one = build.iconst(i32(), index as i128 + 1);
686 let two = build.iconst(i32(), index as i128 + 10);
687 build.jump(join, &[one, two]);
688 }
689 let cases: Vec<(i128, Block)> = (0..3).zip(arms.iter().copied()).collect();
690 Builder::new(&mut func, head).switch(value, default, &cases);
691 assert!(!fired(&convert(&mut func)));
692 }
693
694 #[test]
695 fn an_arm_that_does_something_is_left_alone() {
696 let mut names = Interner::new();
697 let mut func = Func::new(names.intern("f"), Signature::new());
698 let head = func.create_block();
699 let value = func.append_param(head, i32());
700 let default = func.create_block();
701 let mut build = Builder::new(&mut func, default);
702 let it = build.iconst(i32(), 999);
703 build.ret(&[it]);
704 let arms: Vec<Block> = (0..3).map(|_| func.create_block()).collect();
705 for (index, &arm) in arms.iter().enumerate() {
706 let mut build = Builder::new(&mut func, arm);
707 let it = build.iconst(i32(), index as i128 + 1);
708 let sum = build.binary(Opcode::Add, it, value, rucc_ir::Flags::NONE);
710 build.ret(&[sum]);
711 }
712 let cases: Vec<(i128, Block)> = (0..3).zip(arms.iter().copied()).collect();
713 Builder::new(&mut func, head).switch(value, default, &cases);
714 assert!(!fired(&convert(&mut func)));
715 }
716
717 #[test]
718 fn the_default_goes_where_it_went() {
719 let mut func = returning(i32(), &[0, 1, 2, 3], &[1, 2, 3, 4]);
720 let head = func.entry().expect("a function with blocks in it");
721 let before = func.terminator(head).expect("a head block has one");
722 let was = func.successors(before).next().expect("a switch has a default").block;
723 assert!(fired(&convert(&mut func)));
724 let after = func.terminator(head).expect("a head block has one");
725 let now = func.successors(after).next().expect("a switch has a default").block;
726 assert_eq!(was, now, "the default moved");
727 }
728}