1use rucc_cost::heuristics;
330use rucc_ir::{
331 Block, Builder, Def, Extra, Flags, Func, Imm, Inst, InstData, IntPred, MemInfo, MemOrder,
332 Opcode, Type, Value,
333};
334
335use crate::alias::{self, Escapes, Origin};
336use crate::cfg::Cfg;
337use crate::fold::constant;
338use crate::profile::Probability;
339use crate::range::ops::Truth;
340use crate::range::query::Ranges;
341use crate::simplify_cfg::{self, Bindings};
342use crate::{Analyses, Fuel, Pass, Preserved, Stats};
343
344const CONVERTED: &str =
346 "branch whose two arms only work out a value replaced by the value and no branch";
347
348const FACTORED: &str = "operation both arms did to different operands done once below the branch";
350
351const VALUE_IMPLIED: &str =
353 "value the two arms disagreed about settled by the condition rather than by a select";
354
355const STORE_REPLACED: &str = "store both arms made to the same place made once below the branch";
357
358const STORE_GUARDED: &str =
360 "store one path made to a local the branch had already touched made on both paths";
361
362const LOAD_SPECULATED: &str =
364 "load one path made of an address the branch had already touched made on both paths";
365
366const ARM_HAS_EFFECTS: &str =
368 "branch kept, an arm does something that only happens on the path it is on";
369
370const STORE_ON_ONE_PATH: &str =
372 "branch kept, a store only one path makes would have to be made on the other path too";
373
374const STORES_DO_NOT_MATCH: &str =
376 "branch kept, both paths store but not to one address the two of them name the same way";
377
378const ARM_MAY_TRAP: &str = "branch kept, an arm divides and doing it on both paths could trap";
380
381const NO_SELECT_AT_THAT_WIDTH: &str =
383 "branch kept, the value the arms disagree about is not a width a select is lowered at";
384
385const ARMS_TOO_LONG: &str = "branch kept, its arms are more work than doing both of them is worth";
387
388const BRANCH_IS_PREDICTED: &str =
390 "branch kept, it goes one way often enough that the machine will predict it";
391
392const CONDITION_IS_DECIDED: &str =
394 "branch kept, its condition is already known and the arm that cannot run is better deleted";
395const NO_FUEL: &str = "branch kept, the pass ran out of fuel";
396
397#[derive(Debug, Clone, Copy, PartialEq, Eq)]
399pub struct PhiOpt;
400
401impl Pass for PhiOpt {
402 fn name(&self) -> &'static str {
403 "phiopt"
404 }
405
406 fn describe(&self) -> &'static str {
407 "a branch whose two arms only work out a value becomes a select, and the branch goes"
408 }
409
410 fn preserves(&self) -> Preserved {
411 Preserved::NONE
414 }
415
416 fn run(&self, func: &mut Func, an: &mut Analyses, fuel: &mut Fuel) -> Stats {
417 let mut stats = Stats::new();
418 if func.entry().is_none() {
419 return stats;
420 }
421 for head in func.blocks().collect::<Vec<Block>>() {
422 let cfg = an.cfg(func);
423 if !cfg.reaches(head) {
424 continue;
425 }
426 let Some(shape) = diamond(func, cfg, head) else { continue };
427 let store = storing(func, &shape);
428 let implied = implied(func, an, &shape);
433 if let Some(reason) = refused(func, &shape, store.as_ref(), &implied) {
434 stats.missed(reason);
435 continue;
436 }
437 let plan = factoring(func, &shape, &implied);
438 let paired = store.as_ref().is_some_and(|one| one.insts.len() == 2);
448 let levels: usize = plan.iter().flatten().map(|one| one.levels().count()).sum();
449 let replaced = levels + usize::from(paired);
450 let saved = u32::try_from(replaced).unwrap_or(u32::MAX);
451 let work = shape
452 .arms
453 .map(|arm| arm.map_or(0, |block| work(func, head, block)).saturating_sub(saved));
454 if work.iter().any(|&count| count > 0) {
455 if work.iter().any(|&count| count > heuristics::PHIOPT_ARM_INSTRUCTIONS) {
456 stats.missed(ARMS_TOO_LONG);
457 continue;
458 }
459 if !unpredictable(an.frequencies(func).taken(head, 0)) {
464 stats.missed(BRANCH_IS_PREDICTED);
465 continue;
466 }
467 }
468 if !fuel.take() {
469 stats.missed(NO_FUEL);
473 break;
474 }
475 let loads = shape
476 .arms
477 .iter()
478 .flatten()
479 .flat_map(|&arm| func.insts(arm))
480 .filter(|&inst| func[inst].opcode == Opcode::Load)
481 .count();
482 convert(func, &shape, &plan, store.as_ref(), &implied);
483 an.clear();
486 for _ in plan.iter().flatten().flat_map(Factored::levels) {
487 stats.optimized(FACTORED);
488 }
489 for _ in implied.iter().flatten() {
490 stats.optimized(VALUE_IMPLIED);
491 }
492 match store.as_ref().map(|one| one.insts.len()) {
493 Some(2) => stats.optimized(STORE_REPLACED),
494 Some(_) => stats.optimized(STORE_GUARDED),
495 None => {}
496 }
497 for _ in 0..loads {
498 stats.optimized(LOAD_SPECULATED);
499 }
500 stats.optimized(CONVERTED);
501 }
502 stats
503 }
504}
505
506pub(crate) struct Diamond {
508 pub(crate) head: Block,
510 pub(crate) cond: Value,
512 pub(crate) join: Block,
514 pub(crate) arms: [Option<Block>; 2],
519 pub(crate) args: [Vec<Value>; 2],
521}
522
523pub(crate) fn diamond(func: &Func, cfg: &Cfg, head: Block) -> Option<Diamond> {
525 let entry = cfg.entry()?;
526 let term = func.terminator(head)?;
527 if func[term].opcode != Opcode::BrIf {
528 return None;
529 }
530 let cond = *func[func[term].args].first()?;
531 let mut targets = func.successors(term);
532 let sides = [targets.next()?, targets.next()?];
533 if sides[0].block == sides[1].block {
537 return None;
538 }
539 let through = [
540 passes_through(func, cfg, head, sides[0].block),
541 passes_through(func, cfg, head, sides[1].block),
542 ];
543 let join = match through {
546 [Some(left), Some(right)] if left == right => left,
547 [Some(left), _] if left == sides[1].block => left,
548 [_, Some(right)] if right == sides[0].block => right,
549 _ => return None,
550 };
551 if join == head || join == entry {
554 return None;
555 }
556 let arms = [
557 (sides[0].block != join).then_some(sides[0].block),
558 (sides[1].block != join).then_some(sides[1].block),
559 ];
560 let mut args = [Vec::new(), Vec::new()];
561 for (index, side) in sides.iter().enumerate() {
562 let carried = match arms[index] {
563 Some(arm) => func.successors(func.terminator(arm)?).next()?.args,
565 None => side.args,
566 };
567 args[index] = func[carried].to_vec();
568 }
569 Some(Diamond { head, cond, join, arms, args })
570}
571
572fn passes_through(func: &Func, cfg: &Cfg, head: Block, block: Block) -> Option<Block> {
580 if !func[block].params.is_empty() {
581 return None;
582 }
583 if func.block_name(block).is_some() {
587 return None;
588 }
589 match cfg.predecessors(block) {
590 [only] if *only == head => {}
591 _ => return None,
592 }
593 let term = func.terminator(block)?;
594 if func[term].opcode != Opcode::Jump {
595 return None;
596 }
597 Some(func.successors(term).next()?.block)
598}
599
600fn refused(
605 func: &Func,
606 shape: &Diamond,
607 store: Option<&Stored>,
608 implied: &[Option<usize>],
609) -> Option<&'static str> {
610 let term = func.terminator(shape.head).expect("the head of a diamond ends in its branch");
621 if simplify_cfg::taken(func, term, &Bindings::new()).is_some() {
622 return Some(CONDITION_IS_DECIDED);
623 }
624 let moving: &[Inst] = store.map_or(&[], |one| &one.insts);
625 for &arm in shape.arms.iter().flatten() {
626 for inst in func.insts(arm) {
627 if func.is_terminator(inst) || moving.contains(&inst) {
628 continue;
629 }
630 if readable(func, shape.head, inst) {
631 continue;
632 }
633 if func[inst].opcode == Opcode::Store {
634 return Some(mismatch(func, shape));
642 }
643 if func[inst].opcode.has_effects() {
644 return Some(ARM_HAS_EFFECTS);
645 }
646 if !speculatable(func, inst) {
647 return Some(ARM_MAY_TRAP);
648 }
649 }
650 }
651 let params = func[shape.join].params.to_vec();
652 for (index, ¶m) in params.iter().enumerate() {
653 if agree(func, shape.args[0][index], shape.args[1][index]) {
658 continue;
659 }
660 if implied.get(index).copied().flatten().is_some() {
661 continue;
662 }
663 if !selectable(func[param].ty) {
664 return Some(NO_SELECT_AT_THAT_WIDTH);
665 }
666 }
667 None
668}
669
670fn agree(func: &Func, then: Value, other: Value) -> bool {
679 if then == other {
680 return true;
681 }
682 let (Some((left, lty)), Some((right, rty))) = (constant(func, then), constant(func, other))
683 else {
684 return false;
685 };
686 lty == rty && left == right
687}
688
689pub(crate) fn speculatable(func: &Func, inst: Inst) -> bool {
696 let opcode = func[inst].opcode;
697 if !matches!(opcode, Opcode::SDiv | Opcode::UDiv | Opcode::SRem | Opcode::URem) {
698 return true;
699 }
700 let Some(&divisor) = func[func[inst].args].get(1) else { return false };
701 let Some((imm, ty)) = constant(func, divisor) else { return false };
702 if imm.unsigned() == 0 {
703 return false;
704 }
705 imm.signed(ty) != -1
706}
707
708struct Stored {
715 insts: Vec<Inst>,
717 values: [Option<Value>; 2],
720 addr: Value,
722 data: InstData,
724 ty: Type,
726}
727
728fn implied(func: &Func, an: &mut Analyses, shape: &Diamond) -> Vec<Option<usize>> {
751 let count = shape.args[0].len();
752 let mut answers = vec![None; count];
753 if !equality(func, shape.cond) {
754 return answers;
755 }
756 let mut asking = Vec::new();
757 for index in (0..count).filter(|&index| worth_asking(func, shape, index)) {
758 let pair = [shape.args[0][index], shape.args[1][index]];
759 match element(func, shape.cond, pair) {
760 Some(side) => answers[index] = Some(side),
761 None => asking.push(index),
762 }
763 }
764 if asking.is_empty() {
765 return answers;
766 }
767 let cfg = an.cfg(func);
771 let dom = an.dominators(func);
772 let mut ranges = Ranges::new(func, cfg, dom);
773 for index in asking {
774 let pair = [shape.args[0][index], shape.args[1][index]];
775 for side in 0..2 {
776 let Some(block) = shape.arms[side] else { continue };
777 if ranges.compare(IntPred::Eq, pair[0], pair[1], block) == Truth::Always {
778 answers[index] = Some(1 - side);
779 break;
780 }
781 }
782 }
783 answers
784}
785
786fn equality(func: &Func, cond: Value) -> bool {
788 let Def::Result { inst, .. } = func[cond].def else { return false };
789 if func[inst].opcode != Opcode::ICmp {
790 return false;
791 }
792 matches!(func[inst].extra, Extra::IntPred(IntPred::Eq | IntPred::Ne))
793}
794
795fn element(func: &Func, cond: Value, pair: [Value; 2]) -> Option<usize> {
806 let Def::Result { inst, .. } = func[cond].def else { return None };
807 let equal = match func[inst].extra {
808 Extra::IntPred(IntPred::Eq) => 0,
809 Extra::IntPred(IntPred::Ne) => 1,
810 _ => return None,
811 };
812 let &[a, b] = &func[func[inst].args] else { return None };
813 let tested = match (constant(func, a), constant(func, b)) {
814 (None, Some(c)) => (a, c),
815 (Some(c), None) => (b, c),
816 _ => return None,
817 };
818 let other = 1 - equal;
823 let settles = reduces(func, tested, pair[other], pair[equal])
824 || reduces(func, tested, pair[equal], pair[other]);
825 settles.then_some(other)
826}
827
828fn reduces(func: &Func, tested: (Value, (Imm, Type)), worked: Value, kept: Value) -> bool {
830 let (tested, (imm, ty)) = tested;
831 let Def::Result { inst: op, .. } = func[worked].def else { return false };
832 let &[left, right] = &func[func[op].args] else { return false };
833 if !func[worked].ty.is_int() {
834 return false;
835 }
836 let at = |operand: Value| -> Option<i128> {
838 if operand == tested {
839 return Some(imm.signed(ty));
840 }
841 let Def::Result { inst, .. } = func[operand].def else { return None };
842 if func[func[inst].args].first() != Some(&tested) {
843 return None;
844 }
845 match func[inst].opcode {
846 Opcode::SExt => Some(imm.signed(ty)),
847 Opcode::ZExt => i128::try_from(imm.unsigned()).ok(),
848 _ => None,
849 }
850 };
851 let neutral = |x: Value, element: i128, y: Value| at(x) == Some(element) && y == kept;
853 let absorbing = |x: Value, element: i128, gives: i128| {
855 at(x) == Some(element)
856 && constant(func, kept).is_some_and(|(number, ty)| number.signed(ty) == gives)
857 };
858 let either = |test: &dyn Fn(Value, Value) -> bool| test(left, right) || test(right, left);
859 match func[op].opcode {
860 Opcode::Add | Opcode::Xor => either(&|x, y| neutral(x, 0, y)),
861 Opcode::Or => either(&|x, y| neutral(x, 0, y) || absorbing(x, -1, -1)),
862 Opcode::Sub => neutral(right, 0, left),
863 Opcode::Mul => either(&|x, y| neutral(x, 1, y) || absorbing(x, 0, 0)),
864 Opcode::And => either(&|x, y| neutral(x, -1, y) || absorbing(x, 0, 0)),
865 Opcode::Shl | Opcode::LShr => neutral(right, 0, left) || absorbing(left, 0, 0),
866 Opcode::AShr => neutral(right, 0, left) || absorbing(left, 0, 0) || absorbing(left, -1, -1),
867 _ => false,
868 }
869}
870
871fn worth_asking(func: &Func, shape: &Diamond, index: usize) -> bool {
876 let pair = [shape.args[0][index], shape.args[1][index]];
877 if agree(func, pair[0], pair[1]) {
878 return false;
879 }
880 constant(func, pair[0]).is_none() || constant(func, pair[1]).is_none()
881}
882
883fn mismatch(func: &Func, shape: &Diamond) -> &'static str {
891 let [Some(then), Some(other)] = shape.arms else { return STORE_ON_ONE_PATH };
892 match (stored_in(func, then), stored_in(func, other)) {
893 (Some(_), Some(_)) => STORES_DO_NOT_MATCH,
894 _ => STORE_ON_ONE_PATH,
895 }
896}
897
898fn storing(func: &Func, shape: &Diamond) -> Option<Stored> {
903 let found = shape.arms.map(|arm| arm.and_then(|block| stored_in(func, block)));
904 match found {
905 [Some(then), Some(other)] => both(func, [then, other]),
906 [Some(one), None] => alone(func, shape, one, 0),
907 [None, Some(one)] => alone(func, shape, one, 1),
908 [None, None] => None,
909 }
910}
911
912fn both(func: &Func, insts: [Inst; 2]) -> Option<Stored> {
914 let data = [func[insts[0]], func[insts[1]]];
915 if data[0].flags != data[1].flags || data[0].flags.contains(Flags::VOLATILE) {
922 return None;
923 }
924 let (Extra::Mem(one), Extra::Mem(two)) = (data[0].extra, data[1].extra) else { return None };
925 if func[one] != func[two] || func[one].order != MemOrder::NotAtomic {
928 return None;
929 }
930 let &[then, addr] = func[data[0].args].first_chunk::<2>()?;
932 let &[other, addr_two] = func[data[1].args].first_chunk::<2>()?;
933 if addr != addr_two || func[then].ty != func[other].ty {
938 return None;
939 }
940 if !agree(func, then, other) && !selectable(func[then].ty) {
941 return None;
942 }
943 let ty = func[then].ty;
944 Some(Stored {
945 insts: insts.to_vec(),
946 values: [Some(then), Some(other)],
947 addr,
948 data: data[0],
949 ty,
950 })
951}
952
953fn alone(func: &Func, shape: &Diamond, inst: Inst, side: usize) -> Option<Stored> {
958 let data = func[inst];
959 if data.flags.contains(Flags::VOLATILE) {
960 return None;
961 }
962 let Extra::Mem(mem) = data.extra else { return None };
963 if func[mem].order != MemOrder::NotAtomic {
964 return None;
965 }
966 let &[value, addr] = func[data.args].first_chunk::<2>()?;
967 let ty = func[value].ty;
968 if !selectable(ty) || !touched(func, shape.head, addr, ty) {
969 return None;
970 }
971 let (Origin::Local(slot), _) = alias::origin(func, addr) else { return None };
972 let gives_back = func
973 .blocks()
974 .flat_map(|block| func.insts(block))
975 .any(|one| func[one].opcode == Opcode::StackRestore);
976 if gives_back || Escapes::of(func).escaped(slot) {
977 return None;
978 }
979 let mut values = [None, None];
980 values[side] = Some(value);
981 Some(Stored { insts: vec![inst], values, addr, data, ty })
982}
983
984fn touched(func: &Func, head: Block, addr: Value, ty: Type) -> bool {
991 let insts: Vec<Inst> = func.insts(head).collect();
992 for &inst in insts.iter().rev() {
993 let data = func[inst];
994 if func.is_terminator(inst) || !data.opcode.has_effects() {
995 continue;
996 }
997 let access = match data.opcode {
998 Opcode::Load => func[data.args].first().copied().zip(data.first_result),
999 Opcode::Store => func[data.args].first_chunk::<2>().map(|&[value, at]| (at, value)),
1000 _ => return false,
1001 };
1002 let Some((at, value)) = access else { return false };
1003 if plain(func, data) && func[value].ty == ty && same(func, at, addr, 0) {
1004 return true;
1005 }
1006 }
1007 false
1008}
1009
1010fn readable(func: &Func, head: Block, inst: Inst) -> bool {
1012 let data = func[inst];
1013 if data.opcode != Opcode::Load || !plain(func, data) {
1014 return false;
1015 }
1016 let (Some(&addr), Some(value)) = (func[data.args].first(), data.first_result) else {
1017 return false;
1018 };
1019 touched(func, head, addr, func[value].ty)
1020}
1021
1022fn plain(func: &Func, data: InstData) -> bool {
1024 let Extra::Mem(mem) = data.extra else { return false };
1025 !data.flags.contains(Flags::VOLATILE) && func[mem].order == MemOrder::NotAtomic
1026}
1027
1028const SAME_DEPTH: usize = 6;
1033
1034fn same(func: &Func, one: Value, two: Value, depth: usize) -> bool {
1040 if agree(func, one, two) {
1041 return true;
1042 }
1043 if depth == SAME_DEPTH || func[one].ty != func[two].ty {
1044 return false;
1045 }
1046 let (Def::Result { inst: left, .. }, Def::Result { inst: right, .. }) =
1047 (func[one].def, func[two].def)
1048 else {
1049 return false;
1050 };
1051 let (left, right) = (func[left], func[right]);
1052 if left.opcode != right.opcode || left.flags != right.flags || left.extra != right.extra {
1053 return false;
1054 }
1055 if left.results != 1 || right.results != 1 || !addressing(left.opcode) {
1056 return false;
1057 }
1058 let (left, right) = (&func[left.args], &func[right.args]);
1059 left.len() == right.len()
1060 && left.iter().zip(right).all(|(&one, &two)| same(func, one, two, depth + 1))
1061}
1062
1063fn addressing(opcode: Opcode) -> bool {
1065 matches!(
1066 opcode,
1067 Opcode::PtrAdd
1068 | Opcode::Add
1069 | Opcode::Sub
1070 | Opcode::Mul
1071 | Opcode::Shl
1072 | Opcode::And
1073 | Opcode::Or
1074 | Opcode::Xor
1075 | Opcode::SExt
1076 | Opcode::ZExt
1077 | Opcode::Trunc
1078 | Opcode::Bitcast
1079 | Opcode::GlobalAddr
1080 )
1081}
1082
1083fn stored_in(func: &Func, arm: Block) -> Option<Inst> {
1090 let mut store = None;
1091 for inst in func.insts(arm) {
1092 if func.is_terminator(inst) || !func[inst].opcode.has_effects() {
1093 continue;
1094 }
1095 if func[inst].opcode == Opcode::Load && store.is_none() {
1099 continue;
1100 }
1101 if func[inst].opcode != Opcode::Store || store.is_some() {
1102 return None;
1103 }
1104 store = Some(inst);
1105 }
1106 store
1107}
1108
1109struct Factored {
1115 insts: [Inst; 2],
1117 operands: Vec<Value>,
1119 differ: Option<(usize, [Value; 2])>,
1124 below: Option<Box<Factored>>,
1132 data: InstData,
1134 ty: Type,
1136}
1137
1138impl Factored {
1139 fn levels(&self) -> impl Iterator<Item = &Factored> {
1141 std::iter::successors(Some(self), |one| one.below.as_deref())
1142 }
1143}
1144
1145fn factoring(func: &Func, shape: &Diamond, implied: &[Option<usize>]) -> Vec<Option<Factored>> {
1151 let count = shape.args[0].len();
1152 let [Some(then), Some(other)] = shape.arms else {
1153 return (0..count).map(|_| None).collect();
1154 };
1155 (0..count)
1156 .map(|index| {
1157 if implied.get(index).copied().flatten().is_some() {
1160 return None;
1161 }
1162 factored(func, shape, [then, other], index)
1163 })
1164 .collect()
1165}
1166
1167fn factored(func: &Func, shape: &Diamond, arms: [Block; 2], index: usize) -> Option<Factored> {
1169 factored_at(func, arms, [shape.args[0][index], shape.args[1][index]], 0)
1170}
1171
1172fn factored_at(func: &Func, arms: [Block; 2], sides: [Value; 2], depth: u32) -> Option<Factored> {
1179 if agree(func, sides[0], sides[1]) {
1181 return None;
1182 }
1183 let insts = [written_in(func, arms[0], sides[0])?, written_in(func, arms[1], sides[1])?];
1184 let data = [func[insts[0]], func[insts[1]]];
1185 if data[0].opcode != data[1].opcode || data[0].flags != data[1].flags {
1191 return None;
1192 }
1193 if data[0].extra != data[1].extra || func[sides[0]].ty != func[sides[1]].ty {
1194 return None;
1195 }
1196 let operands = [func[data[0].args].to_vec(), func[data[1].args].to_vec()];
1197 if operands[0].len() != operands[1].len() {
1198 return None;
1199 }
1200 if depth > 0 && operands[0].len() != 1 {
1205 return None;
1206 }
1207 if depth > 0 && operands.iter().all(|side| constant(func, side[0]).is_some()) {
1211 return None;
1212 }
1213 let mut apart =
1214 operands[0].iter().zip(&operands[1]).enumerate().filter(|(_, (one, two))| one != two);
1215 let mut below = None;
1216 let differ = match (apart.next(), apart.next()) {
1217 (_, Some(_)) => return None,
1220 (Some((at, (&one, &two))), None) => {
1221 if func[one].ty != func[two].ty {
1222 return None;
1223 }
1224 let deeper = depth + 1 < heuristics::PHIOPT_FACTOR_DEPTH;
1227 below = deeper.then(|| factored_at(func, arms, [one, two], depth + 1)).flatten();
1228 if below.is_none() && !selectable(func[one].ty) {
1229 return None;
1230 }
1231 Some((at, [one, two]))
1232 }
1233 (None, None) => None,
1234 };
1235 let ty = func[sides[0]].ty;
1236 let below = below.map(Box::new);
1237 Some(Factored { insts, operands: operands[0].clone(), differ, below, data: data[0], ty })
1238}
1239
1240fn written_in(func: &Func, arm: Block, value: Value) -> Option<Inst> {
1249 let inst = func
1250 .insts(arm)
1251 .find(|&inst| func[inst].results == 1 && func[inst].first_result == Some(value))?;
1252 let mut seen = 0;
1253 for inst in func.insts(arm) {
1254 seen += func[func[inst].args].iter().filter(|&&arg| arg == value).count();
1255 for call in func.successors(inst) {
1256 seen += func[call.args].iter().filter(|&&arg| arg == value).count();
1257 }
1258 }
1259 (seen == 1).then_some(inst)
1260}
1261
1262fn selectable(ty: Type) -> bool {
1273 ty.is_scalar() && ty.is_int() && matches!(ty.bits(), 8 | 16 | 32 | 64)
1274}
1275
1276pub(crate) fn length(func: &Func, block: Block) -> u32 {
1278 let count = func.insts(block).filter(|&inst| !func.is_terminator(inst)).count();
1279 u32::try_from(count).unwrap_or(u32::MAX)
1280}
1281
1282fn work(func: &Func, head: Block, arm: Block) -> u32 {
1293 let whole = length(func, arm);
1294 if whole > heuristics::PHIOPT_ARM_SCAN_INSTRUCTIONS {
1295 return whole;
1296 }
1297 let done: Vec<Value> = func
1298 .insts(head)
1299 .filter(|&inst| func[inst].results == 1 && !func[inst].opcode.has_effects())
1300 .filter_map(|inst| func[inst].first_result)
1301 .collect();
1302 let repeated = |inst: Inst| {
1303 let data = func[inst];
1304 if data.results != 1 || data.opcode.has_effects() {
1305 return false;
1306 }
1307 let Some(value) = data.first_result else { return false };
1308 done.iter().any(|&there| same(func, there, value, 0))
1309 };
1310 let free = |inst: Inst| func[inst].opcode == Opcode::IConst;
1311 let count = func
1312 .insts(arm)
1313 .filter(|&inst| !func.is_terminator(inst) && !repeated(inst) && !free(inst))
1314 .count();
1315 u32::try_from(count).unwrap_or(u32::MAX)
1316}
1317
1318pub(crate) fn unpredictable(taken: Probability) -> bool {
1320 let margin = heuristics::PHIOPT_UNPREDICTABLE_MARGIN_PERCENT * (Probability::SCALE / 100);
1321 taken.parts() >= margin && taken.parts() <= Probability::SCALE - margin
1322}
1323
1324fn convert(
1331 func: &mut Func,
1332 shape: &Diamond,
1333 plan: &[Option<Factored>],
1334 store: Option<&Stored>,
1335 implied: &[Option<usize>],
1336) {
1337 let term = func.terminator(shape.head).expect("the head of a diamond ends in its branch");
1338 let span = func.span(term);
1339 func.remove_inst(term);
1340 let mut dropped: Vec<Inst> =
1341 plan.iter().flatten().flat_map(Factored::levels).flat_map(|one| one.insts).collect();
1342 dropped.extend(store.iter().flat_map(|one| one.insts.iter().copied()));
1343 for &arm in shape.arms.iter().flatten() {
1344 for inst in func.insts(arm).collect::<Vec<Inst>>() {
1345 if func.is_terminator(inst) {
1346 continue;
1347 }
1348 func.remove_inst(inst);
1349 if !dropped.contains(&inst) {
1352 func.append_inst(shape.head, inst);
1353 }
1354 }
1355 }
1356 let mut build = Builder::new(func, shape.head).at(span);
1357 let mut args = Vec::with_capacity(shape.args[0].len());
1358 for (index, (&then, &other)) in shape.args[0].iter().zip(&shape.args[1]).enumerate() {
1359 if let Some(side) = implied.get(index).copied().flatten() {
1362 args.push(shape.args[side][index]);
1363 continue;
1364 }
1365 if let Some(one) = &plan[index] {
1366 args.push(write_factored(&mut build, shape.cond, one));
1367 continue;
1368 }
1369 let same = agree(build.func(), then, other);
1372 args.push(if same { then } else { build.select(shape.cond, then, other) });
1373 }
1374 if let Some(one) = store {
1377 let old = one.values.contains(&None).then(|| {
1381 let Extra::Mem(mem) = one.data.extra else { unreachable!("a store says what it is") };
1382 let info = MemInfo { owns: 0, ..build.func()[mem] };
1386 build.load(one.ty, one.addr, info, one.data.flags)
1387 });
1388 let [then, other] = one
1389 .values
1390 .map(|value| value.or(old).expect("a side that stored nothing reads what is there"));
1391 let same = agree(build.func(), then, other);
1392 let what = if same { then } else { build.select(shape.cond, then, other) };
1393 let list = build.func().push_values(&[what, one.addr]);
1394 build.inst(InstData { args: list, ..one.data }, &[]);
1395 }
1396 build.jump(shape.join, &args);
1397 for &arm in shape.arms.iter().flatten() {
1401 func.remove_block(arm);
1402 }
1403}
1404
1405fn write_factored(build: &mut Builder<'_>, cond: Value, one: &Factored) -> Value {
1408 let mut operands = one.operands.clone();
1409 if let Some((at, sides)) = one.differ {
1410 operands[at] = match &one.below {
1411 Some(below) => write_factored(build, cond, below),
1412 None => build.select(cond, sides[0], sides[1]),
1413 };
1414 }
1415 let list = build.func().push_values(&operands);
1416 build.value(InstData { args: list, ..one.data }, one.ty)
1417}
1418
1419#[cfg(test)]
1420mod tests {
1421 use rucc_base::Interner;
1422 use rucc_ir::{
1423 Block, Builder, Extra, Flags, Float, Func, InstData, IntPred, MemInfo, MemOrder, Opcode,
1424 Restrict, Signature, Type, Value,
1425 };
1426
1427 use super::PhiOpt;
1428 use crate::profile::{Probability, Quality};
1429 use crate::stats::Kind;
1430 use crate::{Fuel, Pass, Stats};
1431
1432 fn phiopt(func: &mut Func) -> Stats {
1434 PhiOpt.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1435 }
1436
1437 fn blocks(func: &Func) -> Vec<usize> {
1439 func.blocks().map(Block::index).collect()
1440 }
1441
1442 fn goes_to(func: &Func, block: usize) -> Vec<usize> {
1444 let block = Block::from_usize(block);
1445 let term = func.terminator(block).expect("every block here has one");
1446 func.successors(term).map(|call| call.block.index()).collect()
1447 }
1448
1449 fn opcodes(func: &Func, block: usize) -> Vec<Opcode> {
1451 let block = Block::from_usize(block);
1452 func.insts(block).map(|inst| func[inst].opcode).collect()
1453 }
1454
1455 fn carries(func: &Func, block: usize) -> Vec<Value> {
1457 let block = Block::from_usize(block);
1458 let term = func.terminator(block).expect("every block here has one");
1459 let call = func.successors(term).next().expect("a terminator here has an edge");
1460 func[call.args].to_vec()
1461 }
1462
1463 fn plain() -> MemInfo {
1465 MemInfo {
1466 size: 4,
1467 align: 4,
1468 order: MemOrder::NotAtomic,
1469 tbaa: None,
1470 owns: 0,
1471 restrict: Restrict::NONE,
1472 }
1473 }
1474
1475 fn store_something(build: &mut Builder<'_>) {
1477 let what = build.iconst(Type::int(32), 7);
1478 let address = build.iconst(Type::int(64), 16);
1479 let address = build.unary(Opcode::IntToPtr, address, Type::PTR);
1480 build.store(what, address, plain(), Flags::NONE);
1481 }
1482
1483 fn both_arms_store(info: MemInfo, flags: [Flags; 2], addresses: bool) -> Func {
1489 let mut names = Interner::new();
1490 let ints = [Type::PTR, Type::int(32), Type::int(32), Type::PTR];
1491 let signature = Signature::new().with_params(&ints);
1492 let mut func = Func::new(names.intern("f"), signature);
1493 let head = func.create_block();
1494 let address = func.append_param(head, Type::PTR);
1495 let written =
1496 [func.append_param(head, Type::int(32)), func.append_param(head, Type::int(32))];
1497 let elsewhere = func.append_param(head, Type::PTR);
1498 let arms = [func.create_block(), func.create_block()];
1499 let join = func.create_block();
1500
1501 let mut build = Builder::new(&mut func, head);
1502 let zero = build.iconst(Type::int(32), 0);
1503 let test = build.icmp(IntPred::Slt, written[0], zero);
1504 build.br_if(test, arms[0], &[], arms[1], &[]);
1505 for (index, arm) in arms.iter().enumerate() {
1506 let mut build = Builder::new(&mut func, *arm);
1507 let where_to = if addresses && index == 1 { elsewhere } else { address };
1508 build.store(written[index], where_to, info, flags[index]);
1509 build.jump(join, &[]);
1510 }
1511 let mut build = Builder::new(&mut func, join);
1512 build.ret(&[]);
1513 func
1514 }
1515
1516 fn empty_arms() -> Func {
1522 let mut names = Interner::new();
1523 let signature = Signature::new().with_params(&[Type::int(32), Type::int(32)]);
1524 let mut func = Func::new(names.intern("f"), signature);
1525 let head = func.create_block();
1526 let left = func.append_param(head, Type::int(32));
1527 let right = func.append_param(head, Type::int(32));
1528 let arms = [func.create_block(), func.create_block()];
1529 let join = func.create_block();
1530 let param = func.append_param(join, Type::int(32));
1531
1532 let mut build = Builder::new(&mut func, head);
1533 let test = build.icmp(IntPred::Slt, left, right);
1534 build.br_if(test, arms[0], &[], arms[1], &[]);
1535 for (arm, value) in arms.iter().zip([1, 2]) {
1536 let mut build = Builder::new(&mut func, *arm);
1537 let it = build.iconst(Type::int(32), value);
1538 build.jump(join, &[it]);
1539 }
1540 let mut build = Builder::new(&mut func, join);
1541 build.ret(&[param]);
1542 func
1543 }
1544
1545 #[test]
1546 fn a_branch_that_is_already_decided_is_left_for_simplify_cfg() {
1547 let mut names = Interner::new();
1551 let mut func = Func::new(names.intern("f"), Signature::new());
1552 let head = func.create_block();
1553 let arms = [func.create_block(), func.create_block()];
1554 let join = func.create_block();
1555 let param = func.append_param(join, Type::int(32));
1556
1557 let mut build = Builder::new(&mut func, head);
1558 let one = build.iconst(Type::int(32), 1);
1561 let zero = build.iconst(Type::int(32), 0);
1562 let test = build.icmp(IntPred::Ne, one, zero);
1563 build.br_if(test, arms[0], &[], arms[1], &[]);
1564 for (arm, value) in arms.iter().zip([1, 2]) {
1565 let mut build = Builder::new(&mut func, *arm);
1566 let it = build.iconst(Type::int(32), value);
1567 build.jump(join, &[it]);
1568 }
1569 let mut build = Builder::new(&mut func, join);
1570 build.ret(&[param]);
1571
1572 let stats = phiopt(&mut func);
1573 assert_eq!(stats.count(Kind::Missed, super::CONDITION_IS_DECIDED), 1);
1574 assert_eq!(blocks(&func), vec![0, 1, 2, 3]);
1575 }
1576
1577 #[test]
1578 fn a_diamond_whose_arms_are_empty_becomes_a_select() {
1579 let mut func = empty_arms();
1580 let stats = phiopt(&mut func);
1581 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1582 assert_eq!(
1584 opcodes(&func, 0),
1585 vec![Opcode::ICmp, Opcode::IConst, Opcode::IConst, Opcode::Select, Opcode::Jump]
1586 );
1587 assert_eq!(goes_to(&func, 0), vec![3]);
1588 assert_eq!(blocks(&func), vec![0, 3]);
1589 }
1590
1591 #[test]
1592 fn the_side_the_condition_holds_on_is_the_side_the_select_takes_first() {
1593 let mut func = empty_arms();
1594 phiopt(&mut func);
1595 let select = func
1596 .insts(Block::from_usize(0))
1597 .find(|&inst| func[inst].opcode == Opcode::Select)
1598 .expect("the select the pass just built");
1599 let args = func[func[select].args].to_vec();
1600 let one = crate::fold::constant(&func, args[1]).expect("the true arm carried a constant");
1601 let two = crate::fold::constant(&func, args[2]).expect("the false arm carried a constant");
1602 assert_eq!(one.0.unsigned(), 1, "the arm the branch named first");
1603 assert_eq!(two.0.unsigned(), 2, "the arm the branch named second");
1604 }
1605
1606 #[test]
1608 fn a_triangle_whose_empty_side_goes_straight_to_the_join_is_converted() {
1609 let mut names = Interner::new();
1610 let signature = Signature::new().with_params(&[Type::int(32)]);
1611 let mut func = Func::new(names.intern("f"), signature);
1612 let head = func.create_block();
1613 let outside = func.append_param(head, Type::int(32));
1614 let arm = func.create_block();
1615 let join = func.create_block();
1616 let param = func.append_param(join, Type::int(32));
1617
1618 let mut build = Builder::new(&mut func, head);
1619 let zero = build.iconst(Type::int(32), 0);
1620 let test = build.icmp(IntPred::Slt, outside, zero);
1621 build.br_if(test, arm, &[], join, &[outside]);
1622 let mut build = Builder::new(&mut func, arm);
1623 let it = build.iconst(Type::int(32), 0);
1624 build.jump(join, &[it]);
1625 let mut build = Builder::new(&mut func, join);
1626 build.ret(&[param]);
1627
1628 let stats = phiopt(&mut func);
1629 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1630 assert_eq!(blocks(&func), vec![0, 2]);
1631 assert_eq!(goes_to(&func, 0), vec![2]);
1632 assert_eq!(opcodes(&func, 0).last(), Some(&Opcode::Jump));
1633 }
1634
1635 #[test]
1636 fn a_parameter_both_sides_agree_about_needs_no_select() {
1637 let mut names = Interner::new();
1638 let signature = Signature::new().with_params(&[Type::int(32)]);
1639 let mut func = Func::new(names.intern("f"), signature);
1640 let head = func.create_block();
1641 let outside = func.append_param(head, Type::int(32));
1642 let arms = [func.create_block(), func.create_block()];
1643 let join = func.create_block();
1644 let param = func.append_param(join, Type::int(32));
1645
1646 let mut build = Builder::new(&mut func, head);
1647 let zero = build.iconst(Type::int(32), 0);
1648 let test = build.icmp(IntPred::Slt, outside, zero);
1649 build.br_if(test, arms[0], &[], arms[1], &[]);
1650 for arm in arms {
1651 let mut build = Builder::new(&mut func, arm);
1652 build.jump(join, &[outside]);
1653 }
1654 let mut build = Builder::new(&mut func, join);
1655 build.ret(&[param]);
1656
1657 let stats = phiopt(&mut func);
1658 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1659 assert!(!opcodes(&func, 0).contains(&Opcode::Select), "both sides carried the same value");
1660 assert_eq!(carries(&func, 0), vec![outside]);
1661 }
1662
1663 #[test]
1664 fn two_sides_carrying_the_same_number_need_no_select_either() {
1665 let mut names = Interner::new();
1669 let signature = Signature::new().with_params(&[Type::int(32)]);
1670 let mut func = Func::new(names.intern("f"), signature);
1671 let head = func.create_block();
1672 let outside = func.append_param(head, Type::int(32));
1673 let arms = [func.create_block(), func.create_block()];
1674 let join = func.create_block();
1675 let param = func.append_param(join, Type::int(32));
1676
1677 let mut build = Builder::new(&mut func, head);
1678 let zero = build.iconst(Type::int(32), 0);
1679 let test = build.icmp(IntPred::Slt, outside, zero);
1680 build.br_if(test, arms[0], &[], arms[1], &[]);
1681 for arm in arms {
1682 let mut build = Builder::new(&mut func, arm);
1683 let seven = build.iconst(Type::int(32), 7);
1684 build.jump(join, &[seven]);
1685 }
1686 let mut build = Builder::new(&mut func, join);
1687 build.ret(&[param]);
1688
1689 let stats = phiopt(&mut func);
1690 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1691 assert!(!opcodes(&func, 0).contains(&Opcode::Select), "both sides carried a seven");
1692 }
1693
1694 #[test]
1695 fn two_sides_carrying_different_numbers_still_get_a_select() {
1696 let mut func = empty_arms();
1697 let stats = phiopt(&mut func);
1698 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1699 assert!(opcodes(&func, 0).contains(&Opcode::Select), "one and two are not the same number");
1700 }
1701
1702 fn condition_settles_it(pred: IntPred, ty: Type) -> Func {
1708 let mut names = Interner::new();
1709 let signature = Signature::new().with_params(&[ty, ty]);
1710 let mut func = Func::new(names.intern("f"), signature);
1711 let head = func.create_block();
1712 let left = func.append_param(head, ty);
1713 let right = func.append_param(head, ty);
1714 let arms = [func.create_block(), func.create_block()];
1715 let join = func.create_block();
1716 let param = func.append_param(join, ty);
1717
1718 let mut build = Builder::new(&mut func, head);
1719 let test = build.icmp(pred, left, right);
1720 build.br_if(test, arms[0], &[], arms[1], &[]);
1721 for (arm, value) in arms.iter().zip([right, left]) {
1725 let mut build = Builder::new(&mut func, *arm);
1726 build.jump(join, &[value]);
1727 }
1728 let mut build = Builder::new(&mut func, join);
1729 build.ret(&[param]);
1730 func
1731 }
1732
1733 #[test]
1734 fn a_value_the_condition_says_is_the_other_one_needs_no_select() {
1735 let mut func = condition_settles_it(IntPred::Eq, Type::int(32));
1736 let stats = phiopt(&mut func);
1737 assert_eq!(stats.count(Kind::Optimized, super::VALUE_IMPLIED), 1);
1738 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1739 assert_eq!(opcodes(&func, 0), vec![Opcode::ICmp, Opcode::Jump]);
1740 let params = func[Block::from_usize(0)].params.to_vec();
1742 assert_eq!(carries(&func, 0), vec![params[0]]);
1743 assert_eq!(blocks(&func), vec![0, 3]);
1744 }
1745
1746 #[test]
1748 fn an_inequality_settles_it_from_the_other_side() {
1749 let mut func = condition_settles_it(IntPred::Ne, Type::int(32));
1750 let stats = phiopt(&mut func);
1751 assert_eq!(stats.count(Kind::Optimized, super::VALUE_IMPLIED), 1);
1752 let params = func[Block::from_usize(0)].params.to_vec();
1753 assert_eq!(carries(&func, 0), vec![params[1]]);
1754 }
1755
1756 #[test]
1758 fn a_value_of_a_type_with_no_select_is_still_settled_by_the_condition() {
1759 let mut func = condition_settles_it(IntPred::Eq, Type::PTR);
1760 let stats = phiopt(&mut func);
1761 assert_eq!(stats.count(Kind::Missed, super::NO_SELECT_AT_THAT_WIDTH), 0);
1762 assert_eq!(stats.count(Kind::Optimized, super::VALUE_IMPLIED), 1);
1763 assert_eq!(opcodes(&func, 0), vec![Opcode::ICmp, Opcode::Jump]);
1764 }
1765
1766 #[test]
1768 fn a_branch_that_is_not_an_equality_gets_its_select() {
1769 let mut func = condition_settles_it(IntPred::Slt, Type::int(32));
1770 let stats = phiopt(&mut func);
1771 assert_eq!(stats.count(Kind::Optimized, super::VALUE_IMPLIED), 0);
1772 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1773 assert!(opcodes(&func, 0).contains(&Opcode::Select));
1774 }
1775
1776 #[derive(Clone, Copy)]
1778 enum Order {
1779 TestedRight,
1781 TestedLeft,
1783 }
1784
1785 #[derive(Clone, Copy, PartialEq, Eq)]
1787 enum Shape {
1788 Plain,
1790 Widened,
1792 Swapped,
1794 }
1795
1796 fn element_diamond(
1802 pred: IntPred,
1803 c: i128,
1804 opcode: Opcode,
1805 order: Order,
1806 kept: Option<i128>,
1807 shape: Shape,
1808 ) -> Func {
1809 let narrow = if shape == Shape::Widened { Type::int(32) } else { Type::int(64) };
1810 let ty = Type::int(64);
1811 let mut names = Interner::new();
1812 let signature = Signature::new().with_params(&[narrow, ty]);
1813 let mut func = Func::new(names.intern("f"), signature);
1814 let head = func.create_block();
1815 let x = func.append_param(head, narrow);
1816 let y = func.append_param(head, ty);
1817 let arms = [func.create_block(), func.create_block()];
1818 let join = func.create_block();
1819 let param = func.append_param(join, ty);
1820
1821 let mut build = Builder::new(&mut func, head);
1822 let c = build.iconst(narrow, c);
1823 let test = build.icmp(pred, x, c);
1824 build.br_if(test, arms[0], &[], arms[1], &[]);
1825 let mut equal = usize::from(pred == IntPred::Ne);
1826 if shape == Shape::Swapped {
1827 equal = 1 - equal;
1828 }
1829 let mut build = Builder::new(&mut func, arms[equal]);
1830 let kept = kept.map_or(y, |number| build.iconst(ty, number));
1831 build.jump(join, &[kept]);
1832 let mut build = Builder::new(&mut func, arms[1 - equal]);
1833 let x = if shape == Shape::Widened { build.unary(Opcode::SExt, x, ty) } else { x };
1834 let worked = match order {
1835 Order::TestedRight => build.binary(opcode, y, x, Flags::NONE),
1836 Order::TestedLeft => build.binary(opcode, x, y, Flags::NONE),
1837 };
1838 build.jump(join, &[worked]);
1839 let mut build = Builder::new(&mut func, join);
1840 build.ret(&[param]);
1841 func
1842 }
1843
1844 fn settled(pred: IntPred, c: i128, opcode: Opcode, order: Order, kept: Option<i128>) -> bool {
1846 let mut func = element_diamond(pred, c, opcode, order, kept, Shape::Plain);
1847 let stats = phiopt(&mut func);
1848 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1849 let implied = stats.count(Kind::Optimized, super::VALUE_IMPLIED) == 1;
1850 assert_eq!(implied, !opcodes(&func, 0).contains(&Opcode::Select));
1851 implied
1852 }
1853
1854 #[test]
1857 fn an_operation_the_tested_constant_leaves_alone_needs_no_select() {
1858 use Order::{TestedLeft, TestedRight};
1859 for opcode in [Opcode::Add, Opcode::Or, Opcode::Xor] {
1860 assert!(settled(IntPred::Eq, 0, opcode, TestedRight, None), "{opcode:?}");
1861 assert!(settled(IntPred::Eq, 0, opcode, TestedLeft, None), "{opcode:?}");
1862 }
1863 for opcode in [Opcode::Sub, Opcode::Shl, Opcode::LShr, Opcode::AShr] {
1864 assert!(settled(IntPred::Eq, 0, opcode, TestedRight, None), "{opcode:?}");
1865 }
1866 assert!(settled(IntPred::Eq, 1, Opcode::Mul, TestedRight, None));
1867 assert!(settled(IntPred::Eq, -1, Opcode::And, TestedLeft, None));
1868 assert!(settled(IntPred::Ne, 0, Opcode::Add, TestedRight, None));
1869 }
1870
1871 #[test]
1874 fn an_operation_the_tested_constant_decides_needs_no_select() {
1875 use Order::{TestedLeft, TestedRight};
1876 assert!(settled(IntPred::Ne, 0, Opcode::Mul, TestedRight, Some(0)));
1877 assert!(settled(IntPred::Eq, 0, Opcode::And, TestedLeft, Some(0)));
1878 assert!(settled(IntPred::Eq, -1, Opcode::Or, TestedRight, Some(-1)));
1879 assert!(settled(IntPred::Eq, 0, Opcode::Shl, TestedLeft, Some(0)));
1880 assert!(settled(IntPred::Eq, -1, Opcode::AShr, TestedLeft, Some(-1)));
1881 }
1882
1883 #[test]
1885 fn an_operation_the_tested_constant_does_not_settle_keeps_its_select() {
1886 use Order::{TestedLeft, TestedRight};
1887 assert!(!settled(IntPred::Eq, 0, Opcode::Sub, TestedLeft, None));
1889 assert!(!settled(IntPred::Eq, 0, Opcode::Shl, TestedLeft, None));
1890 assert!(!settled(IntPred::Eq, 1, Opcode::Add, TestedRight, None));
1892 assert!(!settled(IntPred::Eq, 0, Opcode::Mul, TestedRight, None));
1893 assert!(!settled(IntPred::Eq, 0, Opcode::Mul, TestedRight, Some(1)));
1895 }
1896
1897 #[test]
1900 fn an_operation_on_the_side_the_test_holds_on_gives_way_to_the_other_side() {
1901 let mut func =
1902 element_diamond(IntPred::Eq, 0, Opcode::Add, Order::TestedRight, None, Shape::Swapped);
1903 let stats = phiopt(&mut func);
1904 assert_eq!(stats.count(Kind::Optimized, super::VALUE_IMPLIED), 1);
1905 let params = func[Block::from_usize(0)].params.to_vec();
1906 assert_eq!(carries(&func, 0), vec![params[1]]);
1907 }
1908
1909 #[test]
1912 fn a_division_is_not_an_operation_the_constant_settles() {
1913 let mut func =
1914 element_diamond(IntPred::Eq, 1, Opcode::SDiv, Order::TestedRight, None, Shape::Plain);
1915 let stats = phiopt(&mut func);
1916 assert_eq!(stats.count(Kind::Optimized, super::VALUE_IMPLIED), 0);
1917 }
1918
1919 #[test]
1922 fn a_widened_tested_value_is_still_the_tested_value() {
1923 let mut func =
1924 element_diamond(IntPred::Eq, 0, Opcode::Add, Order::TestedRight, None, Shape::Widened);
1925 let stats = phiopt(&mut func);
1926 assert_eq!(stats.count(Kind::Optimized, super::VALUE_IMPLIED), 1);
1927 assert!(!opcodes(&func, 0).contains(&Opcode::Select));
1928 }
1929
1930 #[test]
1932 fn two_different_constants_are_not_asked_about() {
1933 let mut func = empty_arms();
1934 let stats = phiopt(&mut func);
1935 assert_eq!(stats.count(Kind::Optimized, super::VALUE_IMPLIED), 0);
1936 assert!(opcodes(&func, 0).contains(&Opcode::Select));
1937 }
1938
1939 #[test]
1940 fn a_store_both_arms_make_to_one_place_is_made_once_below_the_branch() {
1941 let mut func = both_arms_store(plain(), [Flags::NONE; 2], false);
1942 let stats = phiopt(&mut func);
1943 assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 1);
1944 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1945 assert_eq!(
1947 opcodes(&func, 0),
1948 vec![Opcode::IConst, Opcode::ICmp, Opcode::Select, Opcode::Store, Opcode::Jump]
1949 );
1950 assert_eq!(blocks(&func), vec![0, 3]);
1951 assert_eq!(goes_to(&func, 0), vec![3]);
1952 }
1953
1954 #[test]
1955 fn the_one_store_writes_what_the_side_the_condition_holds_on_was_writing() {
1956 let mut func = both_arms_store(plain(), [Flags::NONE; 2], false);
1957 phiopt(&mut func);
1958 let head = Block::from_usize(0);
1959 let select = func
1960 .insts(head)
1961 .find(|&inst| func[inst].opcode == Opcode::Select)
1962 .expect("the select the pass just built");
1963 let store = func
1964 .insts(head)
1965 .find(|&inst| func[inst].opcode == Opcode::Store)
1966 .expect("the one store that is left");
1967 let chosen = func[func[select].args].to_vec();
1968 let written = func[func[store].args].to_vec();
1969 let params = func[head].params.to_vec();
1971 assert_eq!(chosen[1], params[1], "the arm the branch named first");
1972 assert_eq!(chosen[2], params[2], "the arm the branch named second");
1973 assert_eq!(written[0], func[select].first_result.expect("a select produces one value"));
1974 assert_eq!(written[1], params[0], "the address both arms named");
1975 }
1976
1977 #[test]
1979 fn two_arms_that_write_the_same_thing_get_a_store_and_no_select() {
1980 let mut func = both_arms_store(plain(), [Flags::NONE; 2], false);
1981 let head = Block::from_usize(0);
1984 let params = func[head].params.to_vec();
1985 let store = func
1986 .insts(Block::from_usize(2))
1987 .find(|&inst| func[inst].opcode == Opcode::Store)
1988 .expect("the second arm's store");
1989 let args = func.push_values(&[params[1], params[0]]);
1990 func[store].args = args;
1991
1992 let stats = phiopt(&mut func);
1993 assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 1);
1994 assert_eq!(
1995 opcodes(&func, 0),
1996 vec![Opcode::IConst, Opcode::ICmp, Opcode::Store, Opcode::Jump]
1997 );
1998 }
1999
2000 #[test]
2002 fn two_arms_that_store_to_different_addresses_keep_their_branch() {
2003 let mut func = both_arms_store(plain(), [Flags::NONE; 2], true);
2004 let stats = phiopt(&mut func);
2005 assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 0);
2006 assert_eq!(stats.count(Kind::Missed, super::STORES_DO_NOT_MATCH), 1);
2007 assert_eq!(goes_to(&func, 0), vec![1, 2]);
2008 }
2009
2010 #[test]
2011 fn a_volatile_store_keeps_its_branch_even_when_both_arms_make_it() {
2012 let mut func = both_arms_store(plain(), [Flags::VOLATILE; 2], false);
2013 let stats = phiopt(&mut func);
2014 assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 0);
2015 assert_eq!(stats.count(Kind::Missed, super::STORES_DO_NOT_MATCH), 1);
2016 assert_eq!(goes_to(&func, 0), vec![1, 2]);
2017 }
2018
2019 #[test]
2020 fn an_atomic_store_keeps_its_branch_even_when_both_arms_make_it() {
2021 let mut func = both_arms_store(
2022 MemInfo { order: MemOrder::SeqCst, ..plain() },
2023 [Flags::NONE; 2],
2024 false,
2025 );
2026 let stats = phiopt(&mut func);
2027 assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 0);
2028 assert_eq!(stats.count(Kind::Missed, super::STORES_DO_NOT_MATCH), 1);
2029 }
2030
2031 #[test]
2033 fn two_stores_that_disagree_about_the_access_keep_their_branch() {
2034 let mut func = both_arms_store(plain(), [Flags::NONE; 2], false);
2035 let store = func
2036 .insts(Block::from_usize(2))
2037 .find(|&inst| func[inst].opcode == Opcode::Store)
2038 .expect("the second arm's store");
2039 let mem = func.add_mem(MemInfo { align: 1, ..plain() });
2040 func[store].extra = Extra::Mem(mem);
2041
2042 let stats = phiopt(&mut func);
2043 assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 0);
2044 assert_eq!(stats.count(Kind::Missed, super::STORES_DO_NOT_MATCH), 1);
2045 }
2046
2047 #[test]
2052 fn a_store_each_way_does_not_count_against_how_long_the_arms_may_be() {
2053 let mut func = both_arms_store(plain(), [Flags::NONE; 2], false);
2054 let params = func[Block::from_usize(0)].params.to_vec();
2055 for arm in [1, 2] {
2056 let block = Block::from_usize(arm);
2057 let term = func.terminator(block).expect("an arm ends in its jump");
2058 func.remove_inst(term);
2059 let mut build = Builder::new(&mut func, block);
2060 let mut value = params[1];
2061 for _ in 0..rucc_cost::heuristics::PHIOPT_ARM_INSTRUCTIONS {
2062 value = build.binary(Opcode::Add, value, params[2], Flags::NONE);
2063 }
2064 func.append_inst(block, term);
2065 }
2066
2067 let stats = phiopt(&mut func);
2068 assert_eq!(stats.count(Kind::Missed, super::ARMS_TOO_LONG), 0);
2069 assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 1);
2070 }
2071
2072 #[test]
2074 fn an_arm_that_stores_where_the_other_does_not_keeps_its_branch() {
2075 let mut names = Interner::new();
2076 let signature = Signature::new().with_params(&[Type::int(32)]);
2077 let mut func = Func::new(names.intern("f"), signature);
2078 let head = func.create_block();
2079 let outside = func.append_param(head, Type::int(32));
2080 let arms = [func.create_block(), func.create_block()];
2081 let join = func.create_block();
2082 let param = func.append_param(join, Type::int(32));
2083
2084 let mut build = Builder::new(&mut func, head);
2085 let zero = build.iconst(Type::int(32), 0);
2086 let test = build.icmp(IntPred::Slt, outside, zero);
2087 build.br_if(test, arms[0], &[], arms[1], &[]);
2088 let mut build = Builder::new(&mut func, arms[0]);
2089 store_something(&mut build);
2090 let it = build.iconst(Type::int(32), 1);
2091 build.jump(join, &[it]);
2092 let mut build = Builder::new(&mut func, arms[1]);
2093 let it = build.iconst(Type::int(32), 2);
2094 build.jump(join, &[it]);
2095 let mut build = Builder::new(&mut func, join);
2096 build.ret(&[param]);
2097
2098 let stats = phiopt(&mut func);
2099 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
2100 assert_eq!(stats.count(Kind::Missed, super::STORE_ON_ONE_PATH), 1);
2101 assert_eq!(goes_to(&func, 0), vec![1, 2]);
2102 }
2103
2104 fn one_arm_stores(escape: bool, read: bool) -> Func {
2112 let mut names = Interner::new();
2113 let signature = Signature::new().with_params(&[Type::int(32)]);
2114 let mut func = Func::new(names.intern("f"), signature);
2115 let head = func.create_block();
2116 let written = func.append_param(head, Type::int(32));
2117 let arm = func.create_block();
2118 let join = func.create_block();
2119
2120 let mut build = Builder::new(&mut func, head);
2121 let mem = build.func().add_mem(MemInfo { size: 32, align: 16, ..plain() });
2122 let alloca = InstData { extra: Extra::Mem(mem), ..InstData::new(Opcode::Alloca) };
2123 let local = build.value(alloca, Type::PTR);
2124 if escape {
2125 let somewhere = build.iconst(Type::int(64), 16);
2126 let somewhere = build.unary(Opcode::IntToPtr, somewhere, Type::PTR);
2127 build.store(local, somewhere, MemInfo { size: 8, align: 8, ..plain() }, Flags::NONE);
2128 }
2129 let eight = build.iconst(Type::int(64), 8);
2130 let slot = build.binary(Opcode::PtrAdd, local, eight, Flags::NONE);
2131 let against = if read {
2132 build.load(Type::int(32), slot, plain(), Flags::NONE)
2133 } else {
2134 build.iconst(Type::int(32), 0)
2135 };
2136 let test = build.icmp(IntPred::Slt, written, against);
2137 build.br_if(test, arm, &[], join, &[]);
2138 let mut build = Builder::new(&mut func, arm);
2139 let eight = build.iconst(Type::int(64), 8);
2140 let slot = build.binary(Opcode::PtrAdd, local, eight, Flags::NONE);
2141 build.store(written, slot, plain(), Flags::NONE);
2142 build.jump(join, &[]);
2143 let mut build = Builder::new(&mut func, join);
2144 build.ret(&[]);
2145 func
2146 }
2147
2148 #[test]
2151 fn a_store_one_arm_makes_to_a_local_the_head_read_is_made_on_both_paths() {
2152 let mut func = one_arm_stores(false, true);
2153 let stats = phiopt(&mut func);
2154 assert_eq!(stats.count(Kind::Optimized, super::STORE_GUARDED), 1);
2155 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
2156 assert_eq!(blocks(&func), vec![0, 2]);
2157 let ops = opcodes(&func, 0);
2158 assert_eq!(ops.iter().filter(|&&op| op == Opcode::Load).count(), 2);
2159 assert_eq!(ops[ops.len() - 3..], [Opcode::Select, Opcode::Store, Opcode::Jump]);
2160 }
2161
2162 #[test]
2167 fn the_read_a_one_armed_store_is_given_owns_no_padding() {
2168 let mut func = one_arm_stores(false, true);
2169 let arm = Block::from_usize(1);
2170 let store = func.insts(arm).find(|&inst| func[inst].opcode == Opcode::Store).unwrap();
2171 let Extra::Mem(mem) = func[store].extra else { panic!("a store says what it is") };
2172 let owning = func.add_mem(MemInfo { owns: 2, ..func[mem] });
2173 func[store].extra = Extra::Mem(owning);
2174 let stats = phiopt(&mut func);
2175 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
2176 let head = Block::from_usize(0);
2177 let mut owns = Vec::new();
2178 for inst in func.insts(head).collect::<Vec<_>>() {
2179 if let Extra::Mem(mem) = func[inst].extra {
2180 owns.push((func[inst].opcode, func[mem].owns));
2181 }
2182 }
2183 assert!(owns.contains(&(Opcode::Store, 2)), "{owns:?}");
2184 assert!(owns.iter().all(|&(op, owns)| op == Opcode::Store || owns == 0), "{owns:?}");
2185 }
2186
2187 #[test]
2189 fn a_store_one_arm_makes_to_a_local_that_escaped_keeps_its_branch() {
2190 let mut func = one_arm_stores(true, true);
2191 let stats = phiopt(&mut func);
2192 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
2193 assert_eq!(stats.count(Kind::Missed, super::STORE_ON_ONE_PATH), 1);
2194 assert_eq!(goes_to(&func, 0), vec![1, 2]);
2195 }
2196
2197 #[test]
2200 fn a_store_one_arm_makes_to_a_slot_the_head_never_touched_keeps_its_branch() {
2201 let mut func = one_arm_stores(false, false);
2202 let stats = phiopt(&mut func);
2203 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
2204 assert_eq!(stats.count(Kind::Missed, super::STORE_ON_ONE_PATH), 1);
2205 assert_eq!(goes_to(&func, 0), vec![1, 2]);
2206 }
2207
2208 fn arm_reads(flags: Flags) -> Func {
2210 let mut names = Interner::new();
2211 let signature = Signature::new().with_params(&[Type::PTR]);
2212 let mut func = Func::new(names.intern("f"), signature);
2213 let head = func.create_block();
2214 let address = func.append_param(head, Type::PTR);
2215 let arm = func.create_block();
2216 let join = func.create_block();
2217 let param = func.append_param(join, Type::int(32));
2218
2219 let mut build = Builder::new(&mut func, head);
2220 let first = build.load(Type::int(32), address, plain(), Flags::NONE);
2221 let zero = build.iconst(Type::int(32), 0);
2222 let test = build.icmp(IntPred::Slt, first, zero);
2223 build.br_if(test, arm, &[], join, &[zero]);
2224 let mut build = Builder::new(&mut func, arm);
2225 let again = build.load(Type::int(32), address, plain(), flags);
2226 build.jump(join, &[again]);
2227 let mut build = Builder::new(&mut func, join);
2228 build.ret(&[param]);
2229 func
2230 }
2231
2232 #[test]
2233 fn a_load_in_an_arm_of_an_address_the_head_read_is_made_on_both_paths() {
2234 let mut func = arm_reads(Flags::NONE);
2235 let stats = phiopt(&mut func);
2236 assert_eq!(stats.count(Kind::Optimized, super::LOAD_SPECULATED), 1);
2237 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
2238 assert_eq!(blocks(&func), vec![0, 2]);
2239 }
2240
2241 #[test]
2244 fn a_volatile_load_in_an_arm_keeps_its_branch() {
2245 let mut func = arm_reads(Flags::VOLATILE);
2246 let stats = phiopt(&mut func);
2247 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
2248 assert_eq!(stats.count(Kind::Missed, super::ARM_HAS_EFFECTS), 1);
2249 assert_eq!(goes_to(&func, 0), vec![1, 2]);
2250 }
2251
2252 #[test]
2254 fn an_arm_that_does_something_else_keeps_its_branch() {
2255 let mut names = Interner::new();
2256 let signature = Signature::new().with_params(&[Type::int(32)]);
2257 let mut func = Func::new(names.intern("f"), signature);
2258 let head = func.create_block();
2259 let outside = func.append_param(head, Type::int(32));
2260 let arms = [func.create_block(), func.create_block()];
2261 let join = func.create_block();
2262 let param = func.append_param(join, Type::int(32));
2263
2264 let mut build = Builder::new(&mut func, head);
2265 let zero = build.iconst(Type::int(32), 0);
2266 let test = build.icmp(IntPred::Slt, outside, zero);
2267 build.br_if(test, arms[0], &[], arms[1], &[]);
2268 let mut build = Builder::new(&mut func, arms[0]);
2269 let address = build.iconst(Type::int(64), 16);
2270 let address = build.unary(Opcode::IntToPtr, address, Type::PTR);
2271 let it = build.load(Type::int(32), address, plain(), Flags::NONE);
2272 build.jump(join, &[it]);
2273 let mut build = Builder::new(&mut func, arms[1]);
2274 let it = build.iconst(Type::int(32), 2);
2275 build.jump(join, &[it]);
2276 let mut build = Builder::new(&mut func, join);
2277 build.ret(&[param]);
2278
2279 let stats = phiopt(&mut func);
2280 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
2281 assert_eq!(stats.count(Kind::Missed, super::ARM_HAS_EFFECTS), 1);
2282 assert_eq!(goes_to(&func, 0), vec![1, 2]);
2283 }
2284
2285 #[test]
2287 fn an_arm_that_divides_by_something_unknown_keeps_its_branch() {
2288 let mut names = Interner::new();
2289 let signature = Signature::new().with_params(&[Type::int(32), Type::int(32)]);
2290 let mut func = Func::new(names.intern("f"), signature);
2291 let head = func.create_block();
2292 let left = func.append_param(head, Type::int(32));
2293 let right = func.append_param(head, Type::int(32));
2294 let arms = [func.create_block(), func.create_block()];
2295 let join = func.create_block();
2296 let param = func.append_param(join, Type::int(32));
2297
2298 let mut build = Builder::new(&mut func, head);
2299 let zero = build.iconst(Type::int(32), 0);
2300 let test = build.icmp(IntPred::Ne, right, zero);
2301 build.br_if(test, arms[0], &[], arms[1], &[]);
2302 let mut build = Builder::new(&mut func, arms[0]);
2303 let it = build.binary(Opcode::SDiv, left, right, Flags::NONE);
2304 build.jump(join, &[it]);
2305 let mut build = Builder::new(&mut func, arms[1]);
2306 let it = build.iconst(Type::int(32), 0);
2307 build.jump(join, &[it]);
2308 let mut build = Builder::new(&mut func, join);
2309 build.ret(&[param]);
2310
2311 let stats = phiopt(&mut func);
2312 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
2313 assert_eq!(stats.count(Kind::Missed, super::ARM_MAY_TRAP), 1);
2314 assert_eq!(goes_to(&func, 0), vec![1, 2]);
2315 }
2316
2317 #[test]
2318 fn a_division_by_a_constant_that_is_not_zero_or_minus_one_is_moved() {
2319 let mut names = Interner::new();
2320 let signature = Signature::new().with_params(&[Type::int(32)]);
2321 let mut func = Func::new(names.intern("f"), signature);
2322 let head = func.create_block();
2323 let outside = func.append_param(head, Type::int(32));
2324 let arms = [func.create_block(), func.create_block()];
2325 let join = func.create_block();
2326 let param = func.append_param(join, Type::int(32));
2327
2328 let mut build = Builder::new(&mut func, head);
2329 let zero = build.iconst(Type::int(32), 0);
2330 let test = build.icmp(IntPred::Slt, outside, zero);
2331 build.br_if(test, arms[0], &[], arms[1], &[]);
2332 let mut build = Builder::new(&mut func, arms[0]);
2333 let three = build.iconst(Type::int(32), 3);
2334 let it = build.binary(Opcode::SDiv, outside, three, Flags::NONE);
2335 build.jump(join, &[it]);
2336 let mut build = Builder::new(&mut func, arms[1]);
2337 let it = build.iconst(Type::int(32), 0);
2338 build.jump(join, &[it]);
2339 let mut build = Builder::new(&mut func, join);
2340 build.ret(&[param]);
2341
2342 let stats = phiopt(&mut func);
2343 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
2344 assert!(opcodes(&func, 0).contains(&Opcode::SDiv));
2345 }
2346
2347 #[test]
2349 fn a_value_no_select_is_lowered_for_keeps_its_branch() {
2350 let mut names = Interner::new();
2351 let signature = Signature::new().with_params(&[Type::int(32)]);
2352 let mut func = Func::new(names.intern("f"), signature);
2353 let head = func.create_block();
2354 let outside = func.append_param(head, Type::int(32));
2355 let arms = [func.create_block(), func.create_block()];
2356 let join = func.create_block();
2357 func.append_param(join, Type::PTR);
2358
2359 let mut build = Builder::new(&mut func, head);
2360 let zero = build.iconst(Type::int(32), 0);
2361 let test = build.icmp(IntPred::Slt, outside, zero);
2362 build.br_if(test, arms[0], &[], arms[1], &[]);
2363 for (arm, value) in arms.iter().zip([16, 32]) {
2364 let mut build = Builder::new(&mut func, *arm);
2365 let it = build.iconst(Type::int(64), value);
2366 let it = build.unary(Opcode::IntToPtr, it, Type::PTR);
2367 build.jump(join, &[it]);
2368 }
2369 let mut build = Builder::new(&mut func, join);
2370 build.ret(&[]);
2371
2372 let stats = phiopt(&mut func);
2373 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
2374 assert_eq!(stats.count(Kind::Missed, super::NO_SELECT_AT_THAT_WIDTH), 1);
2375 }
2376
2377 #[test]
2386 fn a_choice_between_two_quads_keeps_its_branch_as_well() {
2387 let mut names = Interner::new();
2388 let quad = Type::float(Float::F128);
2389 let signature = Signature::new().with_params(&[Type::int(32)]);
2390 let mut func = Func::new(names.intern("f"), signature);
2391 let head = func.create_block();
2392 let outside = func.append_param(head, Type::int(32));
2393 let arms = [func.create_block(), func.create_block()];
2394 let join = func.create_block();
2395 func.append_param(join, quad);
2396
2397 let mut build = Builder::new(&mut func, head);
2398 let zero = build.iconst(Type::int(32), 0);
2399 let test = build.icmp(IntPred::Slt, outside, zero);
2400 build.br_if(test, arms[0], &[], arms[1], &[]);
2401 for (arm, bits) in arms.iter().zip([0x3fff_u128 << 112, 0x4000_u128 << 112]) {
2402 let mut build = Builder::new(&mut func, *arm);
2403 let it = build.fconst(quad, bits);
2404 build.jump(join, &[it]);
2405 }
2406 let mut build = Builder::new(&mut func, join);
2407 build.ret(&[]);
2408
2409 let stats = phiopt(&mut func);
2410 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
2411 assert_eq!(stats.count(Kind::Missed, super::NO_SELECT_AT_THAT_WIDTH), 1);
2412 }
2413
2414 #[test]
2415 fn arms_with_more_work_in_them_than_the_budget_keep_their_branch() {
2416 let mut names = Interner::new();
2417 let signature = Signature::new().with_params(&[Type::int(32)]);
2418 let mut func = Func::new(names.intern("f"), signature);
2419 let head = func.create_block();
2420 let outside = func.append_param(head, Type::int(32));
2421 let arms = [func.create_block(), func.create_block()];
2422 let join = func.create_block();
2423 let param = func.append_param(join, Type::int(32));
2424
2425 let mut build = Builder::new(&mut func, head);
2426 let zero = build.iconst(Type::int(32), 0);
2427 let test = build.icmp(IntPred::Slt, outside, zero);
2428 build.br_if(test, arms[0], &[], arms[1], &[]);
2429 let mut build = Builder::new(&mut func, arms[0]);
2430 let mut it = outside;
2432 for _ in 0..4 {
2433 it = build.binary(Opcode::Add, it, outside, Flags::NONE);
2434 }
2435 build.jump(join, &[it]);
2436 let mut build = Builder::new(&mut func, arms[1]);
2437 let it = build.iconst(Type::int(32), 0);
2438 build.jump(join, &[it]);
2439 let mut build = Builder::new(&mut func, join);
2440 build.ret(&[param]);
2441
2442 let stats = phiopt(&mut func);
2443 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
2444 assert_eq!(stats.count(Kind::Missed, super::ARMS_TOO_LONG), 1);
2445 }
2446
2447 #[test]
2453 fn constants_in_an_arm_are_not_counted_as_work() {
2454 let mut names = Interner::new();
2455 let signature = Signature::new().with_params(&[Type::int(32), Type::int(64)]);
2456 let mut func = Func::new(names.intern("f"), signature);
2457 let head = func.create_block();
2458 let outside = func.append_param(head, Type::int(32));
2459 let acc = func.append_param(head, Type::int(64));
2460 let arms = [func.create_block(), func.create_block()];
2461 let join = func.create_block();
2462 let param = func.append_param(join, Type::int(64));
2463
2464 let mut build = Builder::new(&mut func, head);
2465 let zero = build.iconst(Type::int(32), 0);
2466 let test = build.icmp(IntPred::Slt, outside, zero);
2467 build.br_if(test, arms[0], &[], arms[1], &[]);
2468 let mut build = Builder::new(&mut func, arms[0]);
2469 build.iconst(Type::int(32), 1);
2470 let one = build.iconst(Type::int(64), 1);
2471 let it = build.binary(Opcode::Add, acc, one, Flags::NONE);
2472 build.jump(join, &[it]);
2473 let mut build = Builder::new(&mut func, arms[1]);
2474 build.jump(join, &[acc]);
2475 let mut build = Builder::new(&mut func, join);
2476 build.ret(&[param]);
2477
2478 let stats = phiopt(&mut func);
2479 assert_eq!(stats.count(Kind::Missed, super::ARMS_TOO_LONG), 0);
2480 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
2481 }
2482
2483 #[test]
2485 fn an_arm_of_three_operations_is_too_long_with_its_constants_free() {
2486 let mut names = Interner::new();
2487 let signature = Signature::new().with_params(&[Type::int(32), Type::int(64)]);
2488 let mut func = Func::new(names.intern("f"), signature);
2489 let head = func.create_block();
2490 let outside = func.append_param(head, Type::int(32));
2491 let acc = func.append_param(head, Type::int(64));
2492 let arms = [func.create_block(), func.create_block()];
2493 let join = func.create_block();
2494 let param = func.append_param(join, Type::int(64));
2495
2496 let mut build = Builder::new(&mut func, head);
2497 let zero = build.iconst(Type::int(32), 0);
2498 let test = build.icmp(IntPred::Slt, outside, zero);
2499 build.br_if(test, arms[0], &[], arms[1], &[]);
2500 let mut build = Builder::new(&mut func, arms[0]);
2501 let mut it = acc;
2502 for step in 1..=3 {
2503 let by = build.iconst(Type::int(64), step);
2504 it = build.binary(Opcode::Mul, it, by, Flags::NONE);
2505 }
2506 build.jump(join, &[it]);
2507 let mut build = Builder::new(&mut func, arms[1]);
2508 build.jump(join, &[acc]);
2509 let mut build = Builder::new(&mut func, join);
2510 build.ret(&[param]);
2511
2512 let stats = phiopt(&mut func);
2513 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
2514 assert_eq!(stats.count(Kind::Missed, super::ARMS_TOO_LONG), 1);
2515 }
2516
2517 #[test]
2519 fn the_margin_is_three_in_from_each_end() {
2520 let guessed = |percent: u32| Probability::percent(percent, Quality::Guessed);
2521 assert!(super::unpredictable(Probability::even()));
2522 assert!(super::unpredictable(guessed(3)));
2523 assert!(super::unpredictable(guessed(97)));
2524 assert!(!super::unpredictable(guessed(2)));
2525 assert!(!super::unpredictable(guessed(98)));
2526 assert!(!super::unpredictable(Probability::always()));
2527 assert!(!super::unpredictable(Probability::never()));
2528 }
2529
2530 fn hinted_diamond(parts: u32) -> Stats {
2534 let mut names = Interner::new();
2535 let signature = Signature::new().with_params(&[Type::int(32)]);
2536 let mut func = Func::new(names.intern("f"), signature);
2537 let head = func.create_block();
2538 let outside = func.append_param(head, Type::int(32));
2539 let arms = [func.create_block(), func.create_block()];
2540 let join = func.create_block();
2541 let param = func.append_param(join, Type::int(32));
2542
2543 let mut build = Builder::new(&mut func, head);
2544 let zero = build.iconst(Type::int(32), 0);
2545 let test = build.icmp(IntPred::Slt, outside, zero);
2546 build.br_if(test, arms[0], &[], arms[1], &[]);
2547 for (arm, opcode) in arms.into_iter().zip([Opcode::Add, Opcode::Sub]) {
2548 let mut build = Builder::new(&mut func, arm);
2549 let it = build.binary(opcode, outside, outside, Flags::NONE);
2550 build.jump(join, &[it]);
2551 }
2552 let mut build = Builder::new(&mut func, join);
2553 build.ret(&[param]);
2554
2555 let term = func.terminator(head).expect("a branch");
2556 let hint = rucc_ir::Hint::parts(parts);
2557 for (at, hint) in func.target_list(term).iter().zip([hint, hint.complement()]) {
2558 let call = func[at];
2559 func.set_block_call(at, rucc_ir::BlockCall { hint, ..call });
2560 }
2561 phiopt(&mut func)
2562 }
2563
2564 #[test]
2566 fn a_diamond_hinted_past_the_margin_keeps_its_branch() {
2567 for parts in [9_900, 100] {
2568 let stats = hinted_diamond(parts);
2569 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0, "{parts}");
2570 assert_eq!(stats.count(Kind::Missed, super::BRANCH_IS_PREDICTED), 1, "{parts}");
2571 }
2572 }
2573
2574 #[test]
2577 fn a_diamond_hinted_inside_the_margin_converts() {
2578 for parts in [9_000, 1_000, 5_000] {
2579 let stats = hinted_diamond(parts);
2580 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1, "{parts}");
2581 assert_eq!(stats.count(Kind::Missed, super::BRANCH_IS_PREDICTED), 0, "{parts}");
2582 }
2583 }
2584
2585 #[test]
2586 fn an_arm_that_two_edges_reach_is_not_an_arm() {
2587 let mut names = Interner::new();
2588 let signature = Signature::new().with_params(&[Type::int(32)]);
2589 let mut func = Func::new(names.intern("f"), signature);
2590 let head = func.create_block();
2591 let outside = func.append_param(head, Type::int(32));
2592 let above = func.create_block();
2593 let arms = [func.create_block(), func.create_block()];
2594 let join = func.create_block();
2595 let param = func.append_param(join, Type::int(32));
2596
2597 let mut build = Builder::new(&mut func, head);
2600 let zero = build.iconst(Type::int(32), 0);
2601 let first = build.icmp(IntPred::Slt, outside, zero);
2602 build.br_if(first, above, &[], arms[0], &[]);
2603 let mut build = Builder::new(&mut func, above);
2604 let one = build.iconst(Type::int(32), 1);
2605 let second = build.icmp(IntPred::Slt, outside, one);
2606 build.br_if(second, arms[0], &[], arms[1], &[]);
2607 for (arm, value) in arms.iter().zip([1, 2]) {
2608 let mut build = Builder::new(&mut func, *arm);
2609 let it = build.iconst(Type::int(32), value);
2610 build.jump(join, &[it]);
2611 }
2612 let mut build = Builder::new(&mut func, join);
2613 build.ret(&[param]);
2614
2615 let stats = phiopt(&mut func);
2616 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
2620 assert_eq!(goes_to(&func, 0), vec![1, 2]);
2621 assert_eq!(goes_to(&func, 1), vec![2, 3]);
2622 }
2623
2624 #[test]
2625 fn fuel_stops_the_conversion_where_it_stands() {
2626 let mut func = empty_arms();
2627 let mut fuel = Fuel::of(0);
2628 let stats = PhiOpt.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut fuel);
2629 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
2630 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
2631 assert_eq!(goes_to(&func, 0), vec![1, 2]);
2632 }
2633
2634 fn same_operation(steps: &[Opcode]) -> Func {
2642 let mut names = Interner::new();
2643 let int = Type::int(32);
2644 let signature = Signature::new().with_params(&[int, int, int, int]);
2645 let mut func = Func::new(names.intern("f"), signature);
2646 let head = func.create_block();
2647 let left = func.append_param(head, int);
2648 let right = func.append_param(head, int);
2649 let operands = [func.append_param(head, int), func.append_param(head, int)];
2650 let arms = [func.create_block(), func.create_block()];
2651 let join = func.create_block();
2652 let params: Vec<Value> = steps.iter().map(|_| func.append_param(join, int)).collect();
2653
2654 let mut build = Builder::new(&mut func, head);
2655 let shared = build.iconst(int, 3);
2658 let test = build.icmp(IntPred::Slt, left, right);
2659 build.br_if(test, arms[0], &[], arms[1], &[]);
2660 for (&arm, operand) in arms.iter().zip(operands) {
2661 let mut build = Builder::new(&mut func, arm);
2662 let carried: Vec<Value> = steps
2663 .iter()
2664 .map(|&opcode| build.binary(opcode, operand, shared, Flags::default()))
2665 .collect();
2666 build.jump(join, &carried);
2667 }
2668 let mut build = Builder::new(&mut func, join);
2669 build.ret(¶ms);
2670 func
2671 }
2672
2673 #[test]
2675 fn an_operation_both_arms_did_is_done_once_below_the_branch() {
2676 let mut func = same_operation(&[Opcode::Add]);
2677 let stats = phiopt(&mut func);
2678 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 1);
2679 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
2680 assert_eq!(
2681 opcodes(&func, 0),
2682 vec![Opcode::IConst, Opcode::ICmp, Opcode::Select, Opcode::Add, Opcode::Jump],
2683 "the select chooses the operand and the add happens once"
2684 );
2685 assert_eq!(blocks(&func), vec![0, 3]);
2686 }
2687
2688 #[test]
2692 fn the_select_chooses_the_operands_and_not_the_answers() {
2693 let mut func = same_operation(&[Opcode::Add]);
2694 phiopt(&mut func);
2695 let head = Block::from_usize(0);
2696 let select = func
2697 .insts(head)
2698 .find(|&inst| func[inst].opcode == Opcode::Select)
2699 .expect("the select the pass just built");
2700 let add = func
2701 .insts(head)
2702 .find(|&inst| func[inst].opcode == Opcode::Add)
2703 .expect("the add the pass just wrote");
2704 let chosen = func[func[select].args].to_vec();
2705 let params = func[head].params.to_vec();
2706 assert_eq!(&chosen[1..], ¶ms[2..], "the two operands the arms differed in");
2707 let added = func[func[add].args].to_vec();
2708 assert_eq!(added[0], func[select].first_result.expect("a select has a result"));
2709 assert_eq!(carries(&func, 0), vec![func[add].first_result.expect("an add has a result")]);
2710 }
2711
2712 #[test]
2716 fn arms_that_factor_away_entirely_are_not_too_long() {
2717 let steps = [Opcode::Add, Opcode::Sub, Opcode::Mul];
2718 let mut func = same_operation(&steps);
2719 let stats = phiopt(&mut func);
2720 assert_eq!(stats.count(Kind::Missed, super::ARMS_TOO_LONG), 0);
2721 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 3);
2722 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
2723 let written = opcodes(&func, 0);
2724 assert_eq!(written.iter().filter(|&&op| op == Opcode::Select).count(), 3);
2725 for step in steps {
2726 assert_eq!(written.iter().filter(|&&op| op == step).count(), 1, "{step:?} once");
2727 }
2728 }
2729
2730 #[test]
2733 fn arms_that_agree_in_every_operand_need_no_select() {
2734 let mut names = Interner::new();
2735 let int = Type::int(32);
2736 let signature = Signature::new().with_params(&[int, int, int]);
2737 let mut func = Func::new(names.intern("f"), signature);
2738 let head = func.create_block();
2739 let left = func.append_param(head, int);
2740 let right = func.append_param(head, int);
2741 let operand = func.append_param(head, int);
2742 let arms = [func.create_block(), func.create_block()];
2743 let join = func.create_block();
2744 let param = func.append_param(join, int);
2745
2746 let mut build = Builder::new(&mut func, head);
2747 let shared = build.iconst(int, 3);
2748 let test = build.icmp(IntPred::Slt, left, right);
2749 build.br_if(test, arms[0], &[], arms[1], &[]);
2750 for &arm in &arms {
2751 let mut build = Builder::new(&mut func, arm);
2752 let it = build.binary(Opcode::Add, operand, shared, Flags::default());
2753 build.jump(join, &[it]);
2754 }
2755 let mut build = Builder::new(&mut func, join);
2756 build.ret(&[param]);
2757
2758 let stats = phiopt(&mut func);
2759 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 1);
2760 assert_eq!(
2761 opcodes(&func, 0),
2762 vec![Opcode::IConst, Opcode::ICmp, Opcode::Add, Opcode::Jump],
2763 "one add and nothing to choose between"
2764 );
2765 }
2766
2767 fn chain(steps: [&[Opcode]; 2]) -> Func {
2772 let mut names = Interner::new();
2773 let int = Type::int(32);
2774 let signature = Signature::new().with_params(&[int, int, int, int]);
2775 let mut func = Func::new(names.intern("f"), signature);
2776 let head = func.create_block();
2777 let left = func.append_param(head, int);
2778 let right = func.append_param(head, int);
2779 let operands = [func.append_param(head, int), func.append_param(head, int)];
2780 let arms = [func.create_block(), func.create_block()];
2781 let join = func.create_block();
2782 let param = func.append_param(join, int);
2783
2784 let mut build = Builder::new(&mut func, head);
2785 let shared = build.iconst(int, 3);
2786 let test = build.icmp(IntPred::Slt, left, right);
2787 build.br_if(test, arms[0], &[], arms[1], &[]);
2788 for ((&arm, operand), steps) in arms.iter().zip(operands).zip(steps) {
2789 let mut build = Builder::new(&mut func, arm);
2790 let mut value = operand;
2791 for &opcode in steps {
2792 value = build.binary(opcode, value, shared, Flags::default());
2793 }
2794 build.jump(join, &[value]);
2795 }
2796 let mut build = Builder::new(&mut func, join);
2797 build.ret(&[param]);
2798 func
2799 }
2800
2801 fn counted(func: &Func, wanted: &[Opcode]) -> Vec<usize> {
2803 let written = opcodes(func, 0);
2804 wanted.iter().map(|&op| written.iter().filter(|&&one| one == op).count()).collect()
2805 }
2806
2807 fn converted(steps: [&[(Opcode, u32)]; 2]) -> Func {
2813 let mut names = Interner::new();
2814 let (int, wide) = (Type::int(32), Type::int(64));
2815 let signature = Signature::new().with_params(&[int, int, int, int, wide]);
2816 let mut func = Func::new(names.intern("f"), signature);
2817 let head = func.create_block();
2818 let left = func.append_param(head, int);
2819 let right = func.append_param(head, int);
2820 let operands = [func.append_param(head, int), func.append_param(head, int)];
2821 let total = func.append_param(head, wide);
2822 let arms = [func.create_block(), func.create_block()];
2823 let join = func.create_block();
2824 let param = func.append_param(join, wide);
2825
2826 let mut build = Builder::new(&mut func, head);
2827 let test = build.icmp(IntPred::Slt, left, right);
2828 build.br_if(test, arms[0], &[], arms[1], &[]);
2829 for ((&arm, operand), steps) in arms.iter().zip(operands).zip(steps) {
2830 let mut build = Builder::new(&mut func, arm);
2831 let mut value = operand;
2832 for &(opcode, bits) in steps {
2833 value = build.unary(opcode, value, Type::int(bits));
2834 }
2835 let sum = build.binary(Opcode::Add, total, value, Flags::default());
2836 build.jump(join, &[sum]);
2837 }
2838 let mut build = Builder::new(&mut func, join);
2839 build.ret(&[param]);
2840 func
2841 }
2842
2843 fn selected(func: &Func) -> Type {
2845 let select = func
2846 .insts(Block::from_usize(0))
2847 .find(|&inst| func[inst].opcode == Opcode::Select)
2848 .expect("the select the pass just built");
2849 func[func[select].first_result.expect("a select has a result")].ty
2850 }
2851
2852 #[test]
2855 fn a_chain_of_conversions_is_factored_all_the_way_down() {
2856 let steps: &[(Opcode, u32)] = &[(Opcode::Trunc, 16), (Opcode::SExt, 64)];
2857 let mut func = converted([steps, steps]);
2858 let stats = phiopt(&mut func);
2859 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 3);
2860 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
2861 let head = [Opcode::Select, Opcode::Trunc, Opcode::SExt, Opcode::Add];
2862 assert_eq!(counted(&func, &head), vec![1, 1, 1, 1]);
2863 assert_eq!(selected(&func), Type::int(32), "the select is under the whole chain");
2864 assert_eq!(blocks(&func), vec![0, 3]);
2865 }
2866
2867 #[test]
2870 fn a_chain_that_differs_at_the_second_level_is_factored_one_deep() {
2871 let mut func = converted([&[(Opcode::SExt, 64)], &[(Opcode::ZExt, 64)]]);
2872 let stats = phiopt(&mut func);
2873 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 1);
2874 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
2875 let head = [Opcode::Select, Opcode::SExt, Opcode::ZExt, Opcode::Add];
2876 assert_eq!(counted(&func, &head), vec![1, 1, 1, 1]);
2877 assert_eq!(selected(&func), Type::int(64));
2878 }
2879
2880 #[test]
2885 fn an_operation_of_two_operands_below_the_first_level_is_not_factored() {
2886 let steps: &[Opcode] = &[Opcode::Add, Opcode::Mul];
2887 let mut func = chain([steps, steps]);
2888 let stats = phiopt(&mut func);
2889 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 1);
2890 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
2891 let head = [Opcode::Select, Opcode::Add, Opcode::Mul];
2892 assert_eq!(counted(&func, &head), vec![1, 2, 1]);
2893 }
2894
2895 #[test]
2898 fn a_chain_is_factored_only_as_deep_as_the_bound() {
2899 let bound = rucc_cost::heuristics::PHIOPT_FACTOR_DEPTH;
2900 let depth = usize::try_from(bound).unwrap();
2901 let steps: Vec<(Opcode, u32)> = (0..=depth)
2902 .map(|at| if at % 2 == 0 { (Opcode::SExt, 64) } else { (Opcode::Trunc, 32) })
2903 .collect();
2904 let steps = &steps[..depth + usize::from(depth % 2 == 0)];
2905 let mut func = converted([steps, steps]);
2906 let stats = phiopt(&mut func);
2907 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), bound);
2908 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
2909 }
2910
2911 #[test]
2919 fn a_widening_of_a_constant_in_each_arm_is_left_to_fold() {
2920 let mut names = Interner::new();
2921 let (narrow, wide) = (Type::int(32), Type::int(64));
2922 let signature = Signature::new().with_params(&[narrow, narrow, wide]);
2923 let mut func = Func::new(names.intern("f"), signature);
2924 let head = func.create_block();
2925 let left = func.append_param(head, narrow);
2926 let right = func.append_param(head, narrow);
2927 let total = func.append_param(head, wide);
2928 let arms = [func.create_block(), func.create_block()];
2929 let join = func.create_block();
2930 let param = func.append_param(join, wide);
2931
2932 let mut build = Builder::new(&mut func, head);
2933 let test = build.icmp(IntPred::Slt, left, right);
2934 build.br_if(test, arms[0], &[], arms[1], &[]);
2935 for (&arm, value) in arms.iter().zip([0, 1]) {
2936 let mut build = Builder::new(&mut func, arm);
2937 let value = build.iconst(narrow, value);
2938 let widened = build.unary(Opcode::SExt, value, wide);
2939 let sum = build.binary(Opcode::Add, total, widened, Flags::default());
2940 build.jump(join, &[sum]);
2941 }
2942 let mut build = Builder::new(&mut func, join);
2943 build.ret(&[param]);
2944
2945 let stats = phiopt(&mut func);
2946 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 1);
2947 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
2948 assert_eq!(selected(&func), wide, "the select is between the two widened constants");
2949 }
2950
2951 #[test]
2952 fn a_widening_both_arms_share_under_an_operation_is_factored_too() {
2953 let mut names = Interner::new();
2954 let (narrow, wide) = (Type::int(32), Type::int(64));
2955 let signature = Signature::new().with_params(&[narrow, narrow, narrow, wide]);
2956 let mut func = Func::new(names.intern("f"), signature);
2957 let head = func.create_block();
2958 let left = func.append_param(head, narrow);
2959 let right = func.append_param(head, narrow);
2960 let x = func.append_param(head, narrow);
2961 let total = func.append_param(head, wide);
2962 let arms = [func.create_block(), func.create_block()];
2963 let join = func.create_block();
2964 let param = func.append_param(join, wide);
2965
2966 let mut build = Builder::new(&mut func, head);
2967 let test = build.icmp(IntPred::Slt, left, right);
2968 build.br_if(test, arms[0], &[], arms[1], &[]);
2969 for (&arm, (opcode, by)) in arms.iter().zip([(Opcode::Mul, 2), (Opcode::Add, 1)]) {
2970 let mut build = Builder::new(&mut func, arm);
2971 let by = build.iconst(narrow, by);
2972 let step = build.binary(opcode, x, by, Flags::default());
2973 let widened = build.unary(Opcode::SExt, step, wide);
2974 let sum = build.binary(Opcode::Add, total, widened, Flags::default());
2975 build.jump(join, &[sum]);
2976 }
2977 let mut build = Builder::new(&mut func, join);
2978 build.ret(&[param]);
2979
2980 let stats = phiopt(&mut func);
2981 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 2);
2982 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
2983 let head = [Opcode::Select, Opcode::SExt, Opcode::Add, Opcode::Mul];
2984 assert_eq!(counted(&func, &head), vec![1, 1, 2, 1]);
2985 let select = func
2986 .insts(Block::from_usize(0))
2987 .find(|&inst| func[inst].opcode == Opcode::Select)
2988 .unwrap();
2989 let chosen = func[select].first_result.unwrap();
2990 assert_eq!(func[chosen].ty, narrow, "the select is under the widening");
2991 }
2992
2993 #[test]
2996 fn arms_that_do_different_things_are_not_factored() {
2997 let mut names = Interner::new();
2998 let int = Type::int(32);
2999 let signature = Signature::new().with_params(&[int, int, int, int]);
3000 let mut func = Func::new(names.intern("f"), signature);
3001 let head = func.create_block();
3002 let left = func.append_param(head, int);
3003 let right = func.append_param(head, int);
3004 let operands = [func.append_param(head, int), func.append_param(head, int)];
3005 let arms = [func.create_block(), func.create_block()];
3006 let join = func.create_block();
3007 let param = func.append_param(join, int);
3008
3009 let mut build = Builder::new(&mut func, head);
3010 let shared = build.iconst(int, 3);
3011 let test = build.icmp(IntPred::Slt, left, right);
3012 build.br_if(test, arms[0], &[], arms[1], &[]);
3013 for ((&arm, operand), opcode) in arms.iter().zip(operands).zip([Opcode::Add, Opcode::Sub]) {
3014 let mut build = Builder::new(&mut func, arm);
3015 let it = build.binary(opcode, operand, shared, Flags::default());
3016 build.jump(join, &[it]);
3017 }
3018 let mut build = Builder::new(&mut func, join);
3019 build.ret(&[param]);
3020
3021 let stats = phiopt(&mut func);
3022 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 0);
3023 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
3024 assert_eq!(
3025 opcodes(&func, 0),
3026 vec![
3027 Opcode::IConst,
3028 Opcode::ICmp,
3029 Opcode::Add,
3030 Opcode::Sub,
3031 Opcode::Select,
3032 Opcode::Jump
3033 ],
3034 "both operations hoisted and a select between their answers"
3035 );
3036 }
3037
3038 #[test]
3041 fn arms_that_differ_in_two_operands_are_not_factored() {
3042 let mut names = Interner::new();
3043 let int = Type::int(32);
3044 let signature = Signature::new().with_params(&[int, int, int, int, int, int]);
3045 let mut func = Func::new(names.intern("f"), signature);
3046 let head = func.create_block();
3047 let left = func.append_param(head, int);
3048 let right = func.append_param(head, int);
3049 let first = [func.append_param(head, int), func.append_param(head, int)];
3050 let second = [func.append_param(head, int), func.append_param(head, int)];
3051 let arms = [func.create_block(), func.create_block()];
3052 let join = func.create_block();
3053 let param = func.append_param(join, int);
3054
3055 let mut build = Builder::new(&mut func, head);
3056 let test = build.icmp(IntPred::Slt, left, right);
3057 build.br_if(test, arms[0], &[], arms[1], &[]);
3058 for ((&arm, one), two) in arms.iter().zip(first).zip(second) {
3059 let mut build = Builder::new(&mut func, arm);
3060 let it = build.binary(Opcode::Add, one, two, Flags::default());
3061 build.jump(join, &[it]);
3062 }
3063 let mut build = Builder::new(&mut func, join);
3064 build.ret(&[param]);
3065
3066 let stats = phiopt(&mut func);
3067 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 0);
3068 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
3069 assert_eq!(opcodes(&func, 0).iter().filter(|&&op| op == Opcode::Add).count(), 2);
3070 }
3071
3072 #[test]
3076 fn an_operation_read_more_than_once_is_not_factored() {
3077 let mut names = Interner::new();
3078 let int = Type::int(32);
3079 let signature = Signature::new().with_params(&[int, int, int, int]);
3080 let mut func = Func::new(names.intern("f"), signature);
3081 let head = func.create_block();
3082 let left = func.append_param(head, int);
3083 let right = func.append_param(head, int);
3084 let operands = [func.append_param(head, int), func.append_param(head, int)];
3085 let arms = [func.create_block(), func.create_block()];
3086 let join = func.create_block();
3087 let params = [func.append_param(join, int), func.append_param(join, int)];
3088
3089 let mut build = Builder::new(&mut func, head);
3090 let shared = build.iconst(int, 3);
3091 let test = build.icmp(IntPred::Slt, left, right);
3092 build.br_if(test, arms[0], &[], arms[1], &[]);
3093 for (&arm, operand) in arms.iter().zip(operands) {
3094 let mut build = Builder::new(&mut func, arm);
3095 let it = build.binary(Opcode::Add, operand, shared, Flags::default());
3096 build.jump(join, &[it, it]);
3097 }
3098 let mut build = Builder::new(&mut func, join);
3099 build.ret(¶ms);
3100
3101 let stats = phiopt(&mut func);
3102 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 0);
3103 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
3104 assert_eq!(opcodes(&func, 0).iter().filter(|&&op| op == Opcode::Add).count(), 2);
3105 }
3106
3107 #[test]
3110 fn a_triangle_factors_nothing() {
3111 let mut names = Interner::new();
3112 let int = Type::int(32);
3113 let signature = Signature::new().with_params(&[int, int, int]);
3114 let mut func = Func::new(names.intern("f"), signature);
3115 let head = func.create_block();
3116 let left = func.append_param(head, int);
3117 let right = func.append_param(head, int);
3118 let operand = func.append_param(head, int);
3119 let arm = func.create_block();
3120 let join = func.create_block();
3121 let param = func.append_param(join, int);
3122
3123 let mut build = Builder::new(&mut func, head);
3124 let shared = build.iconst(int, 3);
3125 let test = build.icmp(IntPred::Slt, left, right);
3126 build.br_if(test, arm, &[], join, &[operand]);
3127 let mut build = Builder::new(&mut func, arm);
3128 let it = build.binary(Opcode::Add, operand, shared, Flags::default());
3129 build.jump(join, &[it]);
3130 let mut build = Builder::new(&mut func, join);
3131 build.ret(&[param]);
3132
3133 let stats = phiopt(&mut func);
3134 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 0);
3135 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
3136 }
3137}