1use rucc_cost::heuristics;
317use rucc_ir::{
318 Block, Builder, Def, Extra, Flags, Func, Inst, InstData, IntPred, MemOrder, Opcode, Type, Value,
319};
320
321use crate::alias::{self, Escapes, Origin};
322use crate::cfg::Cfg;
323use crate::fold::constant;
324use crate::profile::Probability;
325use crate::range::ops::Truth;
326use crate::range::query::Ranges;
327use crate::simplify_cfg::{self, Bindings};
328use crate::{Analyses, Fuel, Pass, Preserved, Stats};
329
330const CONVERTED: &str =
332 "branch whose two arms only work out a value replaced by the value and no branch";
333
334const FACTORED: &str = "operation both arms did to different operands done once below the branch";
336
337const VALUE_IMPLIED: &str =
339 "value the two arms disagreed about settled by the condition rather than by a select";
340
341const STORE_REPLACED: &str = "store both arms made to the same place made once below the branch";
343
344const STORE_GUARDED: &str =
346 "store one path made to a local the branch had already touched made on both paths";
347
348const LOAD_SPECULATED: &str =
350 "load one path made of an address the branch had already touched made on both paths";
351
352const ARM_HAS_EFFECTS: &str =
354 "branch kept, an arm does something that only happens on the path it is on";
355
356const STORE_ON_ONE_PATH: &str =
358 "branch kept, a store only one path makes would have to be made on the other path too";
359
360const STORES_DO_NOT_MATCH: &str =
362 "branch kept, both paths store but not to one address the two of them name the same way";
363
364const ARM_MAY_TRAP: &str = "branch kept, an arm divides and doing it on both paths could trap";
366
367const NO_SELECT_AT_THAT_WIDTH: &str =
369 "branch kept, the value the arms disagree about is not a width a select is lowered at";
370
371const ARMS_TOO_LONG: &str = "branch kept, its arms are more work than doing both of them is worth";
373
374const BRANCH_IS_PREDICTED: &str =
376 "branch kept, it goes one way often enough that the machine will predict it";
377
378const CONDITION_IS_DECIDED: &str =
380 "branch kept, its condition is already known and the arm that cannot run is better deleted";
381const NO_FUEL: &str = "branch kept, the pass ran out of fuel";
382
383#[derive(Debug, Clone, Copy, PartialEq, Eq)]
385pub struct PhiOpt;
386
387impl Pass for PhiOpt {
388 fn name(&self) -> &'static str {
389 "phiopt"
390 }
391
392 fn describe(&self) -> &'static str {
393 "a branch whose two arms only work out a value becomes a select, and the branch goes"
394 }
395
396 fn preserves(&self) -> Preserved {
397 Preserved::NONE
400 }
401
402 fn run(&self, func: &mut Func, an: &mut Analyses, fuel: &mut Fuel) -> Stats {
403 let mut stats = Stats::new();
404 if func.entry().is_none() {
405 return stats;
406 }
407 for head in func.blocks().collect::<Vec<Block>>() {
408 let cfg = an.cfg(func);
409 if !cfg.reaches(head) {
410 continue;
411 }
412 let Some(shape) = diamond(func, cfg, head) else { continue };
413 let store = storing(func, &shape);
414 let implied = implied(func, an, &shape);
419 if let Some(reason) = refused(func, &shape, store.as_ref(), &implied) {
420 stats.missed(reason);
421 continue;
422 }
423 let plan = factoring(func, &shape, &implied);
424 let paired = store.as_ref().is_some_and(|one| one.insts.len() == 2);
434 let replaced = plan.iter().flatten().count() + usize::from(paired);
435 let saved = u32::try_from(replaced).unwrap_or(u32::MAX);
436 let work = shape
437 .arms
438 .map(|arm| arm.map_or(0, |block| work(func, head, block)).saturating_sub(saved));
439 if work.iter().any(|&count| count > 0) {
440 if work.iter().any(|&count| count > heuristics::PHIOPT_ARM_INSTRUCTIONS) {
441 stats.missed(ARMS_TOO_LONG);
442 continue;
443 }
444 if !unpredictable(an.frequencies(func).taken(head, 0)) {
449 stats.missed(BRANCH_IS_PREDICTED);
450 continue;
451 }
452 }
453 if !fuel.take() {
454 stats.missed(NO_FUEL);
458 break;
459 }
460 let loads = shape
461 .arms
462 .iter()
463 .flatten()
464 .flat_map(|&arm| func.insts(arm))
465 .filter(|&inst| func[inst].opcode == Opcode::Load)
466 .count();
467 convert(func, &shape, &plan, store.as_ref(), &implied);
468 an.clear();
471 for _ in plan.iter().flatten() {
472 stats.optimized(FACTORED);
473 }
474 for _ in implied.iter().flatten() {
475 stats.optimized(VALUE_IMPLIED);
476 }
477 match store.as_ref().map(|one| one.insts.len()) {
478 Some(2) => stats.optimized(STORE_REPLACED),
479 Some(_) => stats.optimized(STORE_GUARDED),
480 None => {}
481 }
482 for _ in 0..loads {
483 stats.optimized(LOAD_SPECULATED);
484 }
485 stats.optimized(CONVERTED);
486 }
487 stats
488 }
489}
490
491pub(crate) struct Diamond {
493 pub(crate) head: Block,
495 pub(crate) cond: Value,
497 pub(crate) join: Block,
499 pub(crate) arms: [Option<Block>; 2],
504 pub(crate) args: [Vec<Value>; 2],
506}
507
508pub(crate) fn diamond(func: &Func, cfg: &Cfg, head: Block) -> Option<Diamond> {
510 let entry = cfg.entry()?;
511 let term = func.terminator(head)?;
512 if func[term].opcode != Opcode::BrIf {
513 return None;
514 }
515 let cond = *func[func[term].args].first()?;
516 let mut targets = func.successors(term);
517 let sides = [targets.next()?, targets.next()?];
518 if sides[0].block == sides[1].block {
522 return None;
523 }
524 let through = [
525 passes_through(func, cfg, head, sides[0].block),
526 passes_through(func, cfg, head, sides[1].block),
527 ];
528 let join = match through {
531 [Some(left), Some(right)] if left == right => left,
532 [Some(left), _] if left == sides[1].block => left,
533 [_, Some(right)] if right == sides[0].block => right,
534 _ => return None,
535 };
536 if join == head || join == entry {
539 return None;
540 }
541 let arms = [
542 (sides[0].block != join).then_some(sides[0].block),
543 (sides[1].block != join).then_some(sides[1].block),
544 ];
545 let mut args = [Vec::new(), Vec::new()];
546 for (index, side) in sides.iter().enumerate() {
547 let carried = match arms[index] {
548 Some(arm) => func.successors(func.terminator(arm)?).next()?.args,
550 None => side.args,
551 };
552 args[index] = func[carried].to_vec();
553 }
554 Some(Diamond { head, cond, join, arms, args })
555}
556
557fn passes_through(func: &Func, cfg: &Cfg, head: Block, block: Block) -> Option<Block> {
565 if !func[block].params.is_empty() {
566 return None;
567 }
568 if func.block_name(block).is_some() {
572 return None;
573 }
574 match cfg.predecessors(block) {
575 [only] if *only == head => {}
576 _ => return None,
577 }
578 let term = func.terminator(block)?;
579 if func[term].opcode != Opcode::Jump {
580 return None;
581 }
582 Some(func.successors(term).next()?.block)
583}
584
585fn refused(
590 func: &Func,
591 shape: &Diamond,
592 store: Option<&Stored>,
593 implied: &[Option<usize>],
594) -> Option<&'static str> {
595 let term = func.terminator(shape.head).expect("the head of a diamond ends in its branch");
606 if simplify_cfg::taken(func, term, &Bindings::new()).is_some() {
607 return Some(CONDITION_IS_DECIDED);
608 }
609 let moving: &[Inst] = store.map_or(&[], |one| &one.insts);
610 for &arm in shape.arms.iter().flatten() {
611 for inst in func.insts(arm) {
612 if func.is_terminator(inst) || moving.contains(&inst) {
613 continue;
614 }
615 if readable(func, shape.head, inst) {
616 continue;
617 }
618 if func[inst].opcode == Opcode::Store {
619 return Some(mismatch(func, shape));
627 }
628 if func[inst].opcode.has_effects() {
629 return Some(ARM_HAS_EFFECTS);
630 }
631 if !speculatable(func, inst) {
632 return Some(ARM_MAY_TRAP);
633 }
634 }
635 }
636 let params = func[shape.join].params.to_vec();
637 for (index, ¶m) in params.iter().enumerate() {
638 if agree(func, shape.args[0][index], shape.args[1][index]) {
643 continue;
644 }
645 if implied.get(index).copied().flatten().is_some() {
646 continue;
647 }
648 if !selectable(func[param].ty) {
649 return Some(NO_SELECT_AT_THAT_WIDTH);
650 }
651 }
652 None
653}
654
655fn agree(func: &Func, then: Value, other: Value) -> bool {
664 if then == other {
665 return true;
666 }
667 let (Some((left, lty)), Some((right, rty))) = (constant(func, then), constant(func, other))
668 else {
669 return false;
670 };
671 lty == rty && left == right
672}
673
674pub(crate) fn speculatable(func: &Func, inst: Inst) -> bool {
681 let opcode = func[inst].opcode;
682 if !matches!(opcode, Opcode::SDiv | Opcode::UDiv | Opcode::SRem | Opcode::URem) {
683 return true;
684 }
685 let Some(&divisor) = func[func[inst].args].get(1) else { return false };
686 let Some((imm, ty)) = constant(func, divisor) else { return false };
687 if imm.unsigned() == 0 {
688 return false;
689 }
690 imm.signed(ty) != -1
691}
692
693struct Stored {
700 insts: Vec<Inst>,
702 values: [Option<Value>; 2],
705 addr: Value,
707 data: InstData,
709 ty: Type,
711}
712
713fn implied(func: &Func, an: &mut Analyses, shape: &Diamond) -> Vec<Option<usize>> {
736 let count = shape.args[0].len();
737 let mut answers = vec![None; count];
738 if !equality(func, shape.cond) {
739 return answers;
740 }
741 let asking: Vec<usize> = (0..count).filter(|&index| worth_asking(func, shape, index)).collect();
742 if asking.is_empty() {
743 return answers;
744 }
745 let cfg = an.cfg(func);
749 let dom = an.dominators(func);
750 let mut ranges = Ranges::new(func, cfg, dom);
751 for index in asking {
752 let pair = [shape.args[0][index], shape.args[1][index]];
753 for side in 0..2 {
754 let Some(block) = shape.arms[side] else { continue };
755 if ranges.compare(IntPred::Eq, pair[0], pair[1], block) == Truth::Always {
756 answers[index] = Some(1 - side);
757 break;
758 }
759 }
760 }
761 answers
762}
763
764fn equality(func: &Func, cond: Value) -> bool {
766 let Def::Result { inst, .. } = func[cond].def else { return false };
767 if func[inst].opcode != Opcode::ICmp {
768 return false;
769 }
770 matches!(func[inst].extra, Extra::IntPred(IntPred::Eq | IntPred::Ne))
771}
772
773fn worth_asking(func: &Func, shape: &Diamond, index: usize) -> bool {
778 let pair = [shape.args[0][index], shape.args[1][index]];
779 if agree(func, pair[0], pair[1]) {
780 return false;
781 }
782 constant(func, pair[0]).is_none() || constant(func, pair[1]).is_none()
783}
784
785fn mismatch(func: &Func, shape: &Diamond) -> &'static str {
793 let [Some(then), Some(other)] = shape.arms else { return STORE_ON_ONE_PATH };
794 match (stored_in(func, then), stored_in(func, other)) {
795 (Some(_), Some(_)) => STORES_DO_NOT_MATCH,
796 _ => STORE_ON_ONE_PATH,
797 }
798}
799
800fn storing(func: &Func, shape: &Diamond) -> Option<Stored> {
805 let found = shape.arms.map(|arm| arm.and_then(|block| stored_in(func, block)));
806 match found {
807 [Some(then), Some(other)] => both(func, [then, other]),
808 [Some(one), None] => alone(func, shape, one, 0),
809 [None, Some(one)] => alone(func, shape, one, 1),
810 [None, None] => None,
811 }
812}
813
814fn both(func: &Func, insts: [Inst; 2]) -> Option<Stored> {
816 let data = [func[insts[0]], func[insts[1]]];
817 if data[0].flags != data[1].flags || data[0].flags.contains(Flags::VOLATILE) {
824 return None;
825 }
826 let (Extra::Mem(one), Extra::Mem(two)) = (data[0].extra, data[1].extra) else { return None };
827 if func[one] != func[two] || func[one].order != MemOrder::NotAtomic {
830 return None;
831 }
832 let &[then, addr] = func[data[0].args].first_chunk::<2>()?;
834 let &[other, addr_two] = func[data[1].args].first_chunk::<2>()?;
835 if addr != addr_two || func[then].ty != func[other].ty {
840 return None;
841 }
842 if !agree(func, then, other) && !selectable(func[then].ty) {
843 return None;
844 }
845 let ty = func[then].ty;
846 Some(Stored {
847 insts: insts.to_vec(),
848 values: [Some(then), Some(other)],
849 addr,
850 data: data[0],
851 ty,
852 })
853}
854
855fn alone(func: &Func, shape: &Diamond, inst: Inst, side: usize) -> Option<Stored> {
860 let data = func[inst];
861 if data.flags.contains(Flags::VOLATILE) {
862 return None;
863 }
864 let Extra::Mem(mem) = data.extra else { return None };
865 if func[mem].order != MemOrder::NotAtomic {
866 return None;
867 }
868 let &[value, addr] = func[data.args].first_chunk::<2>()?;
869 let ty = func[value].ty;
870 if !selectable(ty) || !touched(func, shape.head, addr, ty) {
871 return None;
872 }
873 let (Origin::Local(slot), _) = alias::origin(func, addr) else { return None };
874 let gives_back = func
875 .blocks()
876 .flat_map(|block| func.insts(block))
877 .any(|one| func[one].opcode == Opcode::StackRestore);
878 if gives_back || Escapes::of(func).escaped(slot) {
879 return None;
880 }
881 let mut values = [None, None];
882 values[side] = Some(value);
883 Some(Stored { insts: vec![inst], values, addr, data, ty })
884}
885
886fn touched(func: &Func, head: Block, addr: Value, ty: Type) -> bool {
893 let insts: Vec<Inst> = func.insts(head).collect();
894 for &inst in insts.iter().rev() {
895 let data = func[inst];
896 if func.is_terminator(inst) || !data.opcode.has_effects() {
897 continue;
898 }
899 let access = match data.opcode {
900 Opcode::Load => func[data.args].first().copied().zip(data.first_result),
901 Opcode::Store => func[data.args].first_chunk::<2>().map(|&[value, at]| (at, value)),
902 _ => return false,
903 };
904 let Some((at, value)) = access else { return false };
905 if plain(func, data) && func[value].ty == ty && same(func, at, addr, 0) {
906 return true;
907 }
908 }
909 false
910}
911
912fn readable(func: &Func, head: Block, inst: Inst) -> bool {
914 let data = func[inst];
915 if data.opcode != Opcode::Load || !plain(func, data) {
916 return false;
917 }
918 let (Some(&addr), Some(value)) = (func[data.args].first(), data.first_result) else {
919 return false;
920 };
921 touched(func, head, addr, func[value].ty)
922}
923
924fn plain(func: &Func, data: InstData) -> bool {
926 let Extra::Mem(mem) = data.extra else { return false };
927 !data.flags.contains(Flags::VOLATILE) && func[mem].order == MemOrder::NotAtomic
928}
929
930const SAME_DEPTH: usize = 6;
935
936fn same(func: &Func, one: Value, two: Value, depth: usize) -> bool {
942 if agree(func, one, two) {
943 return true;
944 }
945 if depth == SAME_DEPTH || func[one].ty != func[two].ty {
946 return false;
947 }
948 let (Def::Result { inst: left, .. }, Def::Result { inst: right, .. }) =
949 (func[one].def, func[two].def)
950 else {
951 return false;
952 };
953 let (left, right) = (func[left], func[right]);
954 if left.opcode != right.opcode || left.flags != right.flags || left.extra != right.extra {
955 return false;
956 }
957 if left.results != 1 || right.results != 1 || !addressing(left.opcode) {
958 return false;
959 }
960 let (left, right) = (&func[left.args], &func[right.args]);
961 left.len() == right.len()
962 && left.iter().zip(right).all(|(&one, &two)| same(func, one, two, depth + 1))
963}
964
965fn addressing(opcode: Opcode) -> bool {
967 matches!(
968 opcode,
969 Opcode::PtrAdd
970 | Opcode::Add
971 | Opcode::Sub
972 | Opcode::Mul
973 | Opcode::Shl
974 | Opcode::And
975 | Opcode::Or
976 | Opcode::Xor
977 | Opcode::SExt
978 | Opcode::ZExt
979 | Opcode::Trunc
980 | Opcode::Bitcast
981 | Opcode::GlobalAddr
982 )
983}
984
985fn stored_in(func: &Func, arm: Block) -> Option<Inst> {
992 let mut store = None;
993 for inst in func.insts(arm) {
994 if func.is_terminator(inst) || !func[inst].opcode.has_effects() {
995 continue;
996 }
997 if func[inst].opcode == Opcode::Load && store.is_none() {
1001 continue;
1002 }
1003 if func[inst].opcode != Opcode::Store || store.is_some() {
1004 return None;
1005 }
1006 store = Some(inst);
1007 }
1008 store
1009}
1010
1011struct Factored {
1017 insts: [Inst; 2],
1019 operands: Vec<Value>,
1021 differ: Option<(usize, [Value; 2])>,
1026 data: InstData,
1028 ty: Type,
1030}
1031
1032fn factoring(func: &Func, shape: &Diamond, implied: &[Option<usize>]) -> Vec<Option<Factored>> {
1038 let count = shape.args[0].len();
1039 let [Some(then), Some(other)] = shape.arms else {
1040 return (0..count).map(|_| None).collect();
1041 };
1042 (0..count)
1043 .map(|index| {
1044 if implied.get(index).copied().flatten().is_some() {
1047 return None;
1048 }
1049 factored(func, shape, [then, other], index)
1050 })
1051 .collect()
1052}
1053
1054fn factored(func: &Func, shape: &Diamond, arms: [Block; 2], index: usize) -> Option<Factored> {
1056 let sides = [shape.args[0][index], shape.args[1][index]];
1057 if agree(func, sides[0], sides[1]) {
1059 return None;
1060 }
1061 let insts = [written_in(func, arms[0], sides[0])?, written_in(func, arms[1], sides[1])?];
1062 let data = [func[insts[0]], func[insts[1]]];
1063 if data[0].opcode != data[1].opcode || data[0].flags != data[1].flags {
1069 return None;
1070 }
1071 if data[0].extra != data[1].extra || func[sides[0]].ty != func[sides[1]].ty {
1072 return None;
1073 }
1074 let operands = [func[data[0].args].to_vec(), func[data[1].args].to_vec()];
1075 if operands[0].len() != operands[1].len() {
1076 return None;
1077 }
1078 let mut apart =
1079 operands[0].iter().zip(&operands[1]).enumerate().filter(|(_, (one, two))| one != two);
1080 let differ = match (apart.next(), apart.next()) {
1081 (_, Some(_)) => return None,
1084 (Some((at, (&one, &two))), None) => {
1085 if func[one].ty != func[two].ty || !selectable(func[one].ty) {
1086 return None;
1087 }
1088 Some((at, [one, two]))
1089 }
1090 (None, None) => None,
1091 };
1092 let ty = func[sides[0]].ty;
1093 Some(Factored { insts, operands: operands[0].clone(), differ, data: data[0], ty })
1094}
1095
1096fn written_in(func: &Func, arm: Block, value: Value) -> Option<Inst> {
1105 let inst = func
1106 .insts(arm)
1107 .find(|&inst| func[inst].results == 1 && func[inst].first_result == Some(value))?;
1108 let mut seen = 0;
1109 for inst in func.insts(arm) {
1110 seen += func[func[inst].args].iter().filter(|&&arg| arg == value).count();
1111 for call in func.successors(inst) {
1112 seen += func[call.args].iter().filter(|&&arg| arg == value).count();
1113 }
1114 }
1115 (seen == 1).then_some(inst)
1116}
1117
1118fn selectable(ty: Type) -> bool {
1129 ty.is_scalar() && ty.is_int() && matches!(ty.bits(), 8 | 16 | 32 | 64)
1130}
1131
1132pub(crate) fn length(func: &Func, block: Block) -> u32 {
1134 let count = func.insts(block).filter(|&inst| !func.is_terminator(inst)).count();
1135 u32::try_from(count).unwrap_or(u32::MAX)
1136}
1137
1138fn work(func: &Func, head: Block, arm: Block) -> u32 {
1149 let whole = length(func, arm);
1150 if whole > heuristics::PHIOPT_ARM_SCAN_INSTRUCTIONS {
1151 return whole;
1152 }
1153 let done: Vec<Value> = func
1154 .insts(head)
1155 .filter(|&inst| func[inst].results == 1 && !func[inst].opcode.has_effects())
1156 .filter_map(|inst| func[inst].first_result)
1157 .collect();
1158 let repeated = |inst: Inst| {
1159 let data = func[inst];
1160 if data.results != 1 || data.opcode.has_effects() {
1161 return false;
1162 }
1163 let Some(value) = data.first_result else { return false };
1164 done.iter().any(|&there| same(func, there, value, 0))
1165 };
1166 let free = |inst: Inst| func[inst].opcode == Opcode::IConst;
1167 let count = func
1168 .insts(arm)
1169 .filter(|&inst| !func.is_terminator(inst) && !repeated(inst) && !free(inst))
1170 .count();
1171 u32::try_from(count).unwrap_or(u32::MAX)
1172}
1173
1174pub(crate) fn unpredictable(taken: Probability) -> bool {
1176 let margin = heuristics::PHIOPT_UNPREDICTABLE_MARGIN_PERCENT * (Probability::SCALE / 100);
1177 taken.parts() >= margin && taken.parts() <= Probability::SCALE - margin
1178}
1179
1180fn convert(
1187 func: &mut Func,
1188 shape: &Diamond,
1189 plan: &[Option<Factored>],
1190 store: Option<&Stored>,
1191 implied: &[Option<usize>],
1192) {
1193 let term = func.terminator(shape.head).expect("the head of a diamond ends in its branch");
1194 let span = func.span(term);
1195 func.remove_inst(term);
1196 let mut dropped: Vec<Inst> = plan.iter().flatten().flat_map(|one| one.insts).collect();
1197 dropped.extend(store.iter().flat_map(|one| one.insts.iter().copied()));
1198 for &arm in shape.arms.iter().flatten() {
1199 for inst in func.insts(arm).collect::<Vec<Inst>>() {
1200 if func.is_terminator(inst) {
1201 continue;
1202 }
1203 func.remove_inst(inst);
1204 if !dropped.contains(&inst) {
1207 func.append_inst(shape.head, inst);
1208 }
1209 }
1210 }
1211 let mut build = Builder::new(func, shape.head).at(span);
1212 let mut args = Vec::with_capacity(shape.args[0].len());
1213 for (index, (&then, &other)) in shape.args[0].iter().zip(&shape.args[1]).enumerate() {
1214 if let Some(side) = implied.get(index).copied().flatten() {
1217 args.push(shape.args[side][index]);
1218 continue;
1219 }
1220 if let Some(one) = &plan[index] {
1221 let mut operands = one.operands.clone();
1222 if let Some((at, sides)) = one.differ {
1223 operands[at] = build.select(shape.cond, sides[0], sides[1]);
1224 }
1225 let list = build.func().push_values(&operands);
1226 args.push(build.value(InstData { args: list, ..one.data }, one.ty));
1227 continue;
1228 }
1229 let same = agree(build.func(), then, other);
1232 args.push(if same { then } else { build.select(shape.cond, then, other) });
1233 }
1234 if let Some(one) = store {
1237 let old = one.values.contains(&None).then(|| {
1241 let Extra::Mem(mem) = one.data.extra else { unreachable!("a store says what it is") };
1242 let info = build.func()[mem];
1243 build.load(one.ty, one.addr, info, one.data.flags)
1244 });
1245 let [then, other] = one
1246 .values
1247 .map(|value| value.or(old).expect("a side that stored nothing reads what is there"));
1248 let same = agree(build.func(), then, other);
1249 let what = if same { then } else { build.select(shape.cond, then, other) };
1250 let list = build.func().push_values(&[what, one.addr]);
1251 build.inst(InstData { args: list, ..one.data }, &[]);
1252 }
1253 build.jump(shape.join, &args);
1254 for &arm in shape.arms.iter().flatten() {
1258 func.remove_block(arm);
1259 }
1260}
1261
1262#[cfg(test)]
1263mod tests {
1264 use rucc_base::Interner;
1265 use rucc_ir::{
1266 Block, Builder, Extra, Flags, Float, Func, InstData, IntPred, MemInfo, MemOrder, Opcode,
1267 Restrict, Signature, Type, Value,
1268 };
1269
1270 use super::PhiOpt;
1271 use crate::profile::{Probability, Quality};
1272 use crate::stats::Kind;
1273 use crate::{Fuel, Pass, Stats};
1274
1275 fn phiopt(func: &mut Func) -> Stats {
1277 PhiOpt.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1278 }
1279
1280 fn blocks(func: &Func) -> Vec<usize> {
1282 func.blocks().map(Block::index).collect()
1283 }
1284
1285 fn goes_to(func: &Func, block: usize) -> Vec<usize> {
1287 let block = Block::from_usize(block);
1288 let term = func.terminator(block).expect("every block here has one");
1289 func.successors(term).map(|call| call.block.index()).collect()
1290 }
1291
1292 fn opcodes(func: &Func, block: usize) -> Vec<Opcode> {
1294 let block = Block::from_usize(block);
1295 func.insts(block).map(|inst| func[inst].opcode).collect()
1296 }
1297
1298 fn carries(func: &Func, block: usize) -> Vec<Value> {
1300 let block = Block::from_usize(block);
1301 let term = func.terminator(block).expect("every block here has one");
1302 let call = func.successors(term).next().expect("a terminator here has an edge");
1303 func[call.args].to_vec()
1304 }
1305
1306 fn plain() -> MemInfo {
1308 MemInfo {
1309 size: 4,
1310 align: 4,
1311 order: MemOrder::NotAtomic,
1312 tbaa: None,
1313 owns: 0,
1314 restrict: Restrict::NONE,
1315 }
1316 }
1317
1318 fn store_something(build: &mut Builder<'_>) {
1320 let what = build.iconst(Type::int(32), 7);
1321 let address = build.iconst(Type::int(64), 16);
1322 let address = build.unary(Opcode::IntToPtr, address, Type::PTR);
1323 build.store(what, address, plain(), Flags::NONE);
1324 }
1325
1326 fn both_arms_store(info: MemInfo, flags: [Flags; 2], addresses: bool) -> Func {
1332 let mut names = Interner::new();
1333 let ints = [Type::PTR, Type::int(32), Type::int(32), Type::PTR];
1334 let signature = Signature::new().with_params(&ints);
1335 let mut func = Func::new(names.intern("f"), signature);
1336 let head = func.create_block();
1337 let address = func.append_param(head, Type::PTR);
1338 let written =
1339 [func.append_param(head, Type::int(32)), func.append_param(head, Type::int(32))];
1340 let elsewhere = func.append_param(head, Type::PTR);
1341 let arms = [func.create_block(), func.create_block()];
1342 let join = func.create_block();
1343
1344 let mut build = Builder::new(&mut func, head);
1345 let zero = build.iconst(Type::int(32), 0);
1346 let test = build.icmp(IntPred::Slt, written[0], zero);
1347 build.br_if(test, arms[0], &[], arms[1], &[]);
1348 for (index, arm) in arms.iter().enumerate() {
1349 let mut build = Builder::new(&mut func, *arm);
1350 let where_to = if addresses && index == 1 { elsewhere } else { address };
1351 build.store(written[index], where_to, info, flags[index]);
1352 build.jump(join, &[]);
1353 }
1354 let mut build = Builder::new(&mut func, join);
1355 build.ret(&[]);
1356 func
1357 }
1358
1359 fn empty_arms() -> Func {
1365 let mut names = Interner::new();
1366 let signature = Signature::new().with_params(&[Type::int(32), Type::int(32)]);
1367 let mut func = Func::new(names.intern("f"), signature);
1368 let head = func.create_block();
1369 let left = func.append_param(head, Type::int(32));
1370 let right = func.append_param(head, Type::int(32));
1371 let arms = [func.create_block(), func.create_block()];
1372 let join = func.create_block();
1373 let param = func.append_param(join, Type::int(32));
1374
1375 let mut build = Builder::new(&mut func, head);
1376 let test = build.icmp(IntPred::Slt, left, right);
1377 build.br_if(test, arms[0], &[], arms[1], &[]);
1378 for (arm, value) in arms.iter().zip([1, 2]) {
1379 let mut build = Builder::new(&mut func, *arm);
1380 let it = build.iconst(Type::int(32), value);
1381 build.jump(join, &[it]);
1382 }
1383 let mut build = Builder::new(&mut func, join);
1384 build.ret(&[param]);
1385 func
1386 }
1387
1388 #[test]
1389 fn a_branch_that_is_already_decided_is_left_for_simplify_cfg() {
1390 let mut names = Interner::new();
1394 let mut func = Func::new(names.intern("f"), Signature::new());
1395 let head = func.create_block();
1396 let arms = [func.create_block(), func.create_block()];
1397 let join = func.create_block();
1398 let param = func.append_param(join, Type::int(32));
1399
1400 let mut build = Builder::new(&mut func, head);
1401 let one = build.iconst(Type::int(32), 1);
1404 let zero = build.iconst(Type::int(32), 0);
1405 let test = build.icmp(IntPred::Ne, one, zero);
1406 build.br_if(test, arms[0], &[], arms[1], &[]);
1407 for (arm, value) in arms.iter().zip([1, 2]) {
1408 let mut build = Builder::new(&mut func, *arm);
1409 let it = build.iconst(Type::int(32), value);
1410 build.jump(join, &[it]);
1411 }
1412 let mut build = Builder::new(&mut func, join);
1413 build.ret(&[param]);
1414
1415 let stats = phiopt(&mut func);
1416 assert_eq!(stats.count(Kind::Missed, super::CONDITION_IS_DECIDED), 1);
1417 assert_eq!(blocks(&func), vec![0, 1, 2, 3]);
1418 }
1419
1420 #[test]
1421 fn a_diamond_whose_arms_are_empty_becomes_a_select() {
1422 let mut func = empty_arms();
1423 let stats = phiopt(&mut func);
1424 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1425 assert_eq!(
1427 opcodes(&func, 0),
1428 vec![Opcode::ICmp, Opcode::IConst, Opcode::IConst, Opcode::Select, Opcode::Jump]
1429 );
1430 assert_eq!(goes_to(&func, 0), vec![3]);
1431 assert_eq!(blocks(&func), vec![0, 3]);
1432 }
1433
1434 #[test]
1435 fn the_side_the_condition_holds_on_is_the_side_the_select_takes_first() {
1436 let mut func = empty_arms();
1437 phiopt(&mut func);
1438 let select = func
1439 .insts(Block::from_usize(0))
1440 .find(|&inst| func[inst].opcode == Opcode::Select)
1441 .expect("the select the pass just built");
1442 let args = func[func[select].args].to_vec();
1443 let one = crate::fold::constant(&func, args[1]).expect("the true arm carried a constant");
1444 let two = crate::fold::constant(&func, args[2]).expect("the false arm carried a constant");
1445 assert_eq!(one.0.unsigned(), 1, "the arm the branch named first");
1446 assert_eq!(two.0.unsigned(), 2, "the arm the branch named second");
1447 }
1448
1449 #[test]
1451 fn a_triangle_whose_empty_side_goes_straight_to_the_join_is_converted() {
1452 let mut names = Interner::new();
1453 let signature = Signature::new().with_params(&[Type::int(32)]);
1454 let mut func = Func::new(names.intern("f"), signature);
1455 let head = func.create_block();
1456 let outside = func.append_param(head, Type::int(32));
1457 let arm = func.create_block();
1458 let join = func.create_block();
1459 let param = func.append_param(join, Type::int(32));
1460
1461 let mut build = Builder::new(&mut func, head);
1462 let zero = build.iconst(Type::int(32), 0);
1463 let test = build.icmp(IntPred::Slt, outside, zero);
1464 build.br_if(test, arm, &[], join, &[outside]);
1465 let mut build = Builder::new(&mut func, arm);
1466 let it = build.iconst(Type::int(32), 0);
1467 build.jump(join, &[it]);
1468 let mut build = Builder::new(&mut func, join);
1469 build.ret(&[param]);
1470
1471 let stats = phiopt(&mut func);
1472 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1473 assert_eq!(blocks(&func), vec![0, 2]);
1474 assert_eq!(goes_to(&func, 0), vec![2]);
1475 assert_eq!(opcodes(&func, 0).last(), Some(&Opcode::Jump));
1476 }
1477
1478 #[test]
1479 fn a_parameter_both_sides_agree_about_needs_no_select() {
1480 let mut names = Interner::new();
1481 let signature = Signature::new().with_params(&[Type::int(32)]);
1482 let mut func = Func::new(names.intern("f"), signature);
1483 let head = func.create_block();
1484 let outside = func.append_param(head, Type::int(32));
1485 let arms = [func.create_block(), func.create_block()];
1486 let join = func.create_block();
1487 let param = func.append_param(join, Type::int(32));
1488
1489 let mut build = Builder::new(&mut func, head);
1490 let zero = build.iconst(Type::int(32), 0);
1491 let test = build.icmp(IntPred::Slt, outside, zero);
1492 build.br_if(test, arms[0], &[], arms[1], &[]);
1493 for arm in arms {
1494 let mut build = Builder::new(&mut func, arm);
1495 build.jump(join, &[outside]);
1496 }
1497 let mut build = Builder::new(&mut func, join);
1498 build.ret(&[param]);
1499
1500 let stats = phiopt(&mut func);
1501 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1502 assert!(!opcodes(&func, 0).contains(&Opcode::Select), "both sides carried the same value");
1503 assert_eq!(carries(&func, 0), vec![outside]);
1504 }
1505
1506 #[test]
1507 fn two_sides_carrying_the_same_number_need_no_select_either() {
1508 let mut names = Interner::new();
1512 let signature = Signature::new().with_params(&[Type::int(32)]);
1513 let mut func = Func::new(names.intern("f"), signature);
1514 let head = func.create_block();
1515 let outside = func.append_param(head, Type::int(32));
1516 let arms = [func.create_block(), func.create_block()];
1517 let join = func.create_block();
1518 let param = func.append_param(join, Type::int(32));
1519
1520 let mut build = Builder::new(&mut func, head);
1521 let zero = build.iconst(Type::int(32), 0);
1522 let test = build.icmp(IntPred::Slt, outside, zero);
1523 build.br_if(test, arms[0], &[], arms[1], &[]);
1524 for arm in arms {
1525 let mut build = Builder::new(&mut func, arm);
1526 let seven = build.iconst(Type::int(32), 7);
1527 build.jump(join, &[seven]);
1528 }
1529 let mut build = Builder::new(&mut func, join);
1530 build.ret(&[param]);
1531
1532 let stats = phiopt(&mut func);
1533 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1534 assert!(!opcodes(&func, 0).contains(&Opcode::Select), "both sides carried a seven");
1535 }
1536
1537 #[test]
1538 fn two_sides_carrying_different_numbers_still_get_a_select() {
1539 let mut func = empty_arms();
1540 let stats = phiopt(&mut func);
1541 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1542 assert!(opcodes(&func, 0).contains(&Opcode::Select), "one and two are not the same number");
1543 }
1544
1545 fn condition_settles_it(pred: IntPred, ty: Type) -> Func {
1551 let mut names = Interner::new();
1552 let signature = Signature::new().with_params(&[ty, ty]);
1553 let mut func = Func::new(names.intern("f"), signature);
1554 let head = func.create_block();
1555 let left = func.append_param(head, ty);
1556 let right = func.append_param(head, ty);
1557 let arms = [func.create_block(), func.create_block()];
1558 let join = func.create_block();
1559 let param = func.append_param(join, ty);
1560
1561 let mut build = Builder::new(&mut func, head);
1562 let test = build.icmp(pred, left, right);
1563 build.br_if(test, arms[0], &[], arms[1], &[]);
1564 for (arm, value) in arms.iter().zip([right, left]) {
1568 let mut build = Builder::new(&mut func, *arm);
1569 build.jump(join, &[value]);
1570 }
1571 let mut build = Builder::new(&mut func, join);
1572 build.ret(&[param]);
1573 func
1574 }
1575
1576 #[test]
1577 fn a_value_the_condition_says_is_the_other_one_needs_no_select() {
1578 let mut func = condition_settles_it(IntPred::Eq, Type::int(32));
1579 let stats = phiopt(&mut func);
1580 assert_eq!(stats.count(Kind::Optimized, super::VALUE_IMPLIED), 1);
1581 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1582 assert_eq!(opcodes(&func, 0), vec![Opcode::ICmp, Opcode::Jump]);
1583 let params = func[Block::from_usize(0)].params.to_vec();
1585 assert_eq!(carries(&func, 0), vec![params[0]]);
1586 assert_eq!(blocks(&func), vec![0, 3]);
1587 }
1588
1589 #[test]
1591 fn an_inequality_settles_it_from_the_other_side() {
1592 let mut func = condition_settles_it(IntPred::Ne, Type::int(32));
1593 let stats = phiopt(&mut func);
1594 assert_eq!(stats.count(Kind::Optimized, super::VALUE_IMPLIED), 1);
1595 let params = func[Block::from_usize(0)].params.to_vec();
1596 assert_eq!(carries(&func, 0), vec![params[1]]);
1597 }
1598
1599 #[test]
1601 fn a_value_of_a_type_with_no_select_is_still_settled_by_the_condition() {
1602 let mut func = condition_settles_it(IntPred::Eq, Type::PTR);
1603 let stats = phiopt(&mut func);
1604 assert_eq!(stats.count(Kind::Missed, super::NO_SELECT_AT_THAT_WIDTH), 0);
1605 assert_eq!(stats.count(Kind::Optimized, super::VALUE_IMPLIED), 1);
1606 assert_eq!(opcodes(&func, 0), vec![Opcode::ICmp, Opcode::Jump]);
1607 }
1608
1609 #[test]
1611 fn a_branch_that_is_not_an_equality_gets_its_select() {
1612 let mut func = condition_settles_it(IntPred::Slt, Type::int(32));
1613 let stats = phiopt(&mut func);
1614 assert_eq!(stats.count(Kind::Optimized, super::VALUE_IMPLIED), 0);
1615 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1616 assert!(opcodes(&func, 0).contains(&Opcode::Select));
1617 }
1618
1619 #[test]
1621 fn two_different_constants_are_not_asked_about() {
1622 let mut func = empty_arms();
1623 let stats = phiopt(&mut func);
1624 assert_eq!(stats.count(Kind::Optimized, super::VALUE_IMPLIED), 0);
1625 assert!(opcodes(&func, 0).contains(&Opcode::Select));
1626 }
1627
1628 #[test]
1629 fn a_store_both_arms_make_to_one_place_is_made_once_below_the_branch() {
1630 let mut func = both_arms_store(plain(), [Flags::NONE; 2], false);
1631 let stats = phiopt(&mut func);
1632 assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 1);
1633 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1634 assert_eq!(
1636 opcodes(&func, 0),
1637 vec![Opcode::IConst, Opcode::ICmp, Opcode::Select, Opcode::Store, Opcode::Jump]
1638 );
1639 assert_eq!(blocks(&func), vec![0, 3]);
1640 assert_eq!(goes_to(&func, 0), vec![3]);
1641 }
1642
1643 #[test]
1644 fn the_one_store_writes_what_the_side_the_condition_holds_on_was_writing() {
1645 let mut func = both_arms_store(plain(), [Flags::NONE; 2], false);
1646 phiopt(&mut func);
1647 let head = Block::from_usize(0);
1648 let select = func
1649 .insts(head)
1650 .find(|&inst| func[inst].opcode == Opcode::Select)
1651 .expect("the select the pass just built");
1652 let store = func
1653 .insts(head)
1654 .find(|&inst| func[inst].opcode == Opcode::Store)
1655 .expect("the one store that is left");
1656 let chosen = func[func[select].args].to_vec();
1657 let written = func[func[store].args].to_vec();
1658 let params = func[head].params.to_vec();
1660 assert_eq!(chosen[1], params[1], "the arm the branch named first");
1661 assert_eq!(chosen[2], params[2], "the arm the branch named second");
1662 assert_eq!(written[0], func[select].first_result.expect("a select produces one value"));
1663 assert_eq!(written[1], params[0], "the address both arms named");
1664 }
1665
1666 #[test]
1668 fn two_arms_that_write_the_same_thing_get_a_store_and_no_select() {
1669 let mut func = both_arms_store(plain(), [Flags::NONE; 2], false);
1670 let head = Block::from_usize(0);
1673 let params = func[head].params.to_vec();
1674 let store = func
1675 .insts(Block::from_usize(2))
1676 .find(|&inst| func[inst].opcode == Opcode::Store)
1677 .expect("the second arm's store");
1678 let args = func.push_values(&[params[1], params[0]]);
1679 func[store].args = args;
1680
1681 let stats = phiopt(&mut func);
1682 assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 1);
1683 assert_eq!(
1684 opcodes(&func, 0),
1685 vec![Opcode::IConst, Opcode::ICmp, Opcode::Store, Opcode::Jump]
1686 );
1687 }
1688
1689 #[test]
1691 fn two_arms_that_store_to_different_addresses_keep_their_branch() {
1692 let mut func = both_arms_store(plain(), [Flags::NONE; 2], true);
1693 let stats = phiopt(&mut func);
1694 assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 0);
1695 assert_eq!(stats.count(Kind::Missed, super::STORES_DO_NOT_MATCH), 1);
1696 assert_eq!(goes_to(&func, 0), vec![1, 2]);
1697 }
1698
1699 #[test]
1700 fn a_volatile_store_keeps_its_branch_even_when_both_arms_make_it() {
1701 let mut func = both_arms_store(plain(), [Flags::VOLATILE; 2], false);
1702 let stats = phiopt(&mut func);
1703 assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 0);
1704 assert_eq!(stats.count(Kind::Missed, super::STORES_DO_NOT_MATCH), 1);
1705 assert_eq!(goes_to(&func, 0), vec![1, 2]);
1706 }
1707
1708 #[test]
1709 fn an_atomic_store_keeps_its_branch_even_when_both_arms_make_it() {
1710 let mut func = both_arms_store(
1711 MemInfo { order: MemOrder::SeqCst, ..plain() },
1712 [Flags::NONE; 2],
1713 false,
1714 );
1715 let stats = phiopt(&mut func);
1716 assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 0);
1717 assert_eq!(stats.count(Kind::Missed, super::STORES_DO_NOT_MATCH), 1);
1718 }
1719
1720 #[test]
1722 fn two_stores_that_disagree_about_the_access_keep_their_branch() {
1723 let mut func = both_arms_store(plain(), [Flags::NONE; 2], false);
1724 let store = func
1725 .insts(Block::from_usize(2))
1726 .find(|&inst| func[inst].opcode == Opcode::Store)
1727 .expect("the second arm's store");
1728 let mem = func.add_mem(MemInfo { align: 1, ..plain() });
1729 func[store].extra = Extra::Mem(mem);
1730
1731 let stats = phiopt(&mut func);
1732 assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 0);
1733 assert_eq!(stats.count(Kind::Missed, super::STORES_DO_NOT_MATCH), 1);
1734 }
1735
1736 #[test]
1741 fn a_store_each_way_does_not_count_against_how_long_the_arms_may_be() {
1742 let mut func = both_arms_store(plain(), [Flags::NONE; 2], false);
1743 let params = func[Block::from_usize(0)].params.to_vec();
1744 for arm in [1, 2] {
1745 let block = Block::from_usize(arm);
1746 let term = func.terminator(block).expect("an arm ends in its jump");
1747 func.remove_inst(term);
1748 let mut build = Builder::new(&mut func, block);
1749 let mut value = params[1];
1750 for _ in 0..rucc_cost::heuristics::PHIOPT_ARM_INSTRUCTIONS {
1751 value = build.binary(Opcode::Add, value, params[2], Flags::NONE);
1752 }
1753 func.append_inst(block, term);
1754 }
1755
1756 let stats = phiopt(&mut func);
1757 assert_eq!(stats.count(Kind::Missed, super::ARMS_TOO_LONG), 0);
1758 assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 1);
1759 }
1760
1761 #[test]
1763 fn an_arm_that_stores_where_the_other_does_not_keeps_its_branch() {
1764 let mut names = Interner::new();
1765 let signature = Signature::new().with_params(&[Type::int(32)]);
1766 let mut func = Func::new(names.intern("f"), signature);
1767 let head = func.create_block();
1768 let outside = func.append_param(head, Type::int(32));
1769 let arms = [func.create_block(), func.create_block()];
1770 let join = func.create_block();
1771 let param = func.append_param(join, Type::int(32));
1772
1773 let mut build = Builder::new(&mut func, head);
1774 let zero = build.iconst(Type::int(32), 0);
1775 let test = build.icmp(IntPred::Slt, outside, zero);
1776 build.br_if(test, arms[0], &[], arms[1], &[]);
1777 let mut build = Builder::new(&mut func, arms[0]);
1778 store_something(&mut build);
1779 let it = build.iconst(Type::int(32), 1);
1780 build.jump(join, &[it]);
1781 let mut build = Builder::new(&mut func, arms[1]);
1782 let it = build.iconst(Type::int(32), 2);
1783 build.jump(join, &[it]);
1784 let mut build = Builder::new(&mut func, join);
1785 build.ret(&[param]);
1786
1787 let stats = phiopt(&mut func);
1788 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
1789 assert_eq!(stats.count(Kind::Missed, super::STORE_ON_ONE_PATH), 1);
1790 assert_eq!(goes_to(&func, 0), vec![1, 2]);
1791 }
1792
1793 fn one_arm_stores(escape: bool, read: bool) -> Func {
1801 let mut names = Interner::new();
1802 let signature = Signature::new().with_params(&[Type::int(32)]);
1803 let mut func = Func::new(names.intern("f"), signature);
1804 let head = func.create_block();
1805 let written = func.append_param(head, Type::int(32));
1806 let arm = func.create_block();
1807 let join = func.create_block();
1808
1809 let mut build = Builder::new(&mut func, head);
1810 let mem = build.func().add_mem(MemInfo { size: 32, align: 16, ..plain() });
1811 let alloca = InstData { extra: Extra::Mem(mem), ..InstData::new(Opcode::Alloca) };
1812 let local = build.value(alloca, Type::PTR);
1813 if escape {
1814 let somewhere = build.iconst(Type::int(64), 16);
1815 let somewhere = build.unary(Opcode::IntToPtr, somewhere, Type::PTR);
1816 build.store(local, somewhere, MemInfo { size: 8, align: 8, ..plain() }, Flags::NONE);
1817 }
1818 let eight = build.iconst(Type::int(64), 8);
1819 let slot = build.binary(Opcode::PtrAdd, local, eight, Flags::NONE);
1820 let against = if read {
1821 build.load(Type::int(32), slot, plain(), Flags::NONE)
1822 } else {
1823 build.iconst(Type::int(32), 0)
1824 };
1825 let test = build.icmp(IntPred::Slt, written, against);
1826 build.br_if(test, arm, &[], join, &[]);
1827 let mut build = Builder::new(&mut func, arm);
1828 let eight = build.iconst(Type::int(64), 8);
1829 let slot = build.binary(Opcode::PtrAdd, local, eight, Flags::NONE);
1830 build.store(written, slot, plain(), Flags::NONE);
1831 build.jump(join, &[]);
1832 let mut build = Builder::new(&mut func, join);
1833 build.ret(&[]);
1834 func
1835 }
1836
1837 #[test]
1840 fn a_store_one_arm_makes_to_a_local_the_head_read_is_made_on_both_paths() {
1841 let mut func = one_arm_stores(false, true);
1842 let stats = phiopt(&mut func);
1843 assert_eq!(stats.count(Kind::Optimized, super::STORE_GUARDED), 1);
1844 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1845 assert_eq!(blocks(&func), vec![0, 2]);
1846 let ops = opcodes(&func, 0);
1847 assert_eq!(ops.iter().filter(|&&op| op == Opcode::Load).count(), 2);
1848 assert_eq!(ops[ops.len() - 3..], [Opcode::Select, Opcode::Store, Opcode::Jump]);
1849 }
1850
1851 #[test]
1853 fn a_store_one_arm_makes_to_a_local_that_escaped_keeps_its_branch() {
1854 let mut func = one_arm_stores(true, true);
1855 let stats = phiopt(&mut func);
1856 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
1857 assert_eq!(stats.count(Kind::Missed, super::STORE_ON_ONE_PATH), 1);
1858 assert_eq!(goes_to(&func, 0), vec![1, 2]);
1859 }
1860
1861 #[test]
1864 fn a_store_one_arm_makes_to_a_slot_the_head_never_touched_keeps_its_branch() {
1865 let mut func = one_arm_stores(false, false);
1866 let stats = phiopt(&mut func);
1867 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
1868 assert_eq!(stats.count(Kind::Missed, super::STORE_ON_ONE_PATH), 1);
1869 assert_eq!(goes_to(&func, 0), vec![1, 2]);
1870 }
1871
1872 fn arm_reads(flags: Flags) -> Func {
1874 let mut names = Interner::new();
1875 let signature = Signature::new().with_params(&[Type::PTR]);
1876 let mut func = Func::new(names.intern("f"), signature);
1877 let head = func.create_block();
1878 let address = func.append_param(head, Type::PTR);
1879 let arm = func.create_block();
1880 let join = func.create_block();
1881 let param = func.append_param(join, Type::int(32));
1882
1883 let mut build = Builder::new(&mut func, head);
1884 let first = build.load(Type::int(32), address, plain(), Flags::NONE);
1885 let zero = build.iconst(Type::int(32), 0);
1886 let test = build.icmp(IntPred::Slt, first, zero);
1887 build.br_if(test, arm, &[], join, &[zero]);
1888 let mut build = Builder::new(&mut func, arm);
1889 let again = build.load(Type::int(32), address, plain(), flags);
1890 build.jump(join, &[again]);
1891 let mut build = Builder::new(&mut func, join);
1892 build.ret(&[param]);
1893 func
1894 }
1895
1896 #[test]
1897 fn a_load_in_an_arm_of_an_address_the_head_read_is_made_on_both_paths() {
1898 let mut func = arm_reads(Flags::NONE);
1899 let stats = phiopt(&mut func);
1900 assert_eq!(stats.count(Kind::Optimized, super::LOAD_SPECULATED), 1);
1901 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1902 assert_eq!(blocks(&func), vec![0, 2]);
1903 }
1904
1905 #[test]
1908 fn a_volatile_load_in_an_arm_keeps_its_branch() {
1909 let mut func = arm_reads(Flags::VOLATILE);
1910 let stats = phiopt(&mut func);
1911 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
1912 assert_eq!(stats.count(Kind::Missed, super::ARM_HAS_EFFECTS), 1);
1913 assert_eq!(goes_to(&func, 0), vec![1, 2]);
1914 }
1915
1916 #[test]
1918 fn an_arm_that_does_something_else_keeps_its_branch() {
1919 let mut names = Interner::new();
1920 let signature = Signature::new().with_params(&[Type::int(32)]);
1921 let mut func = Func::new(names.intern("f"), signature);
1922 let head = func.create_block();
1923 let outside = func.append_param(head, Type::int(32));
1924 let arms = [func.create_block(), func.create_block()];
1925 let join = func.create_block();
1926 let param = func.append_param(join, Type::int(32));
1927
1928 let mut build = Builder::new(&mut func, head);
1929 let zero = build.iconst(Type::int(32), 0);
1930 let test = build.icmp(IntPred::Slt, outside, zero);
1931 build.br_if(test, arms[0], &[], arms[1], &[]);
1932 let mut build = Builder::new(&mut func, arms[0]);
1933 let address = build.iconst(Type::int(64), 16);
1934 let address = build.unary(Opcode::IntToPtr, address, Type::PTR);
1935 let it = build.load(Type::int(32), address, plain(), Flags::NONE);
1936 build.jump(join, &[it]);
1937 let mut build = Builder::new(&mut func, arms[1]);
1938 let it = build.iconst(Type::int(32), 2);
1939 build.jump(join, &[it]);
1940 let mut build = Builder::new(&mut func, join);
1941 build.ret(&[param]);
1942
1943 let stats = phiopt(&mut func);
1944 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
1945 assert_eq!(stats.count(Kind::Missed, super::ARM_HAS_EFFECTS), 1);
1946 assert_eq!(goes_to(&func, 0), vec![1, 2]);
1947 }
1948
1949 #[test]
1951 fn an_arm_that_divides_by_something_unknown_keeps_its_branch() {
1952 let mut names = Interner::new();
1953 let signature = Signature::new().with_params(&[Type::int(32), Type::int(32)]);
1954 let mut func = Func::new(names.intern("f"), signature);
1955 let head = func.create_block();
1956 let left = func.append_param(head, Type::int(32));
1957 let right = func.append_param(head, Type::int(32));
1958 let arms = [func.create_block(), func.create_block()];
1959 let join = func.create_block();
1960 let param = func.append_param(join, Type::int(32));
1961
1962 let mut build = Builder::new(&mut func, head);
1963 let zero = build.iconst(Type::int(32), 0);
1964 let test = build.icmp(IntPred::Ne, right, zero);
1965 build.br_if(test, arms[0], &[], arms[1], &[]);
1966 let mut build = Builder::new(&mut func, arms[0]);
1967 let it = build.binary(Opcode::SDiv, left, right, Flags::NONE);
1968 build.jump(join, &[it]);
1969 let mut build = Builder::new(&mut func, arms[1]);
1970 let it = build.iconst(Type::int(32), 0);
1971 build.jump(join, &[it]);
1972 let mut build = Builder::new(&mut func, join);
1973 build.ret(&[param]);
1974
1975 let stats = phiopt(&mut func);
1976 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
1977 assert_eq!(stats.count(Kind::Missed, super::ARM_MAY_TRAP), 1);
1978 assert_eq!(goes_to(&func, 0), vec![1, 2]);
1979 }
1980
1981 #[test]
1982 fn a_division_by_a_constant_that_is_not_zero_or_minus_one_is_moved() {
1983 let mut names = Interner::new();
1984 let signature = Signature::new().with_params(&[Type::int(32)]);
1985 let mut func = Func::new(names.intern("f"), signature);
1986 let head = func.create_block();
1987 let outside = func.append_param(head, Type::int(32));
1988 let arms = [func.create_block(), func.create_block()];
1989 let join = func.create_block();
1990 let param = func.append_param(join, Type::int(32));
1991
1992 let mut build = Builder::new(&mut func, head);
1993 let zero = build.iconst(Type::int(32), 0);
1994 let test = build.icmp(IntPred::Slt, outside, zero);
1995 build.br_if(test, arms[0], &[], arms[1], &[]);
1996 let mut build = Builder::new(&mut func, arms[0]);
1997 let three = build.iconst(Type::int(32), 3);
1998 let it = build.binary(Opcode::SDiv, outside, three, Flags::NONE);
1999 build.jump(join, &[it]);
2000 let mut build = Builder::new(&mut func, arms[1]);
2001 let it = build.iconst(Type::int(32), 0);
2002 build.jump(join, &[it]);
2003 let mut build = Builder::new(&mut func, join);
2004 build.ret(&[param]);
2005
2006 let stats = phiopt(&mut func);
2007 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
2008 assert!(opcodes(&func, 0).contains(&Opcode::SDiv));
2009 }
2010
2011 #[test]
2013 fn a_value_no_select_is_lowered_for_keeps_its_branch() {
2014 let mut names = Interner::new();
2015 let signature = Signature::new().with_params(&[Type::int(32)]);
2016 let mut func = Func::new(names.intern("f"), signature);
2017 let head = func.create_block();
2018 let outside = func.append_param(head, Type::int(32));
2019 let arms = [func.create_block(), func.create_block()];
2020 let join = func.create_block();
2021 func.append_param(join, Type::PTR);
2022
2023 let mut build = Builder::new(&mut func, head);
2024 let zero = build.iconst(Type::int(32), 0);
2025 let test = build.icmp(IntPred::Slt, outside, zero);
2026 build.br_if(test, arms[0], &[], arms[1], &[]);
2027 for (arm, value) in arms.iter().zip([16, 32]) {
2028 let mut build = Builder::new(&mut func, *arm);
2029 let it = build.iconst(Type::int(64), value);
2030 let it = build.unary(Opcode::IntToPtr, it, Type::PTR);
2031 build.jump(join, &[it]);
2032 }
2033 let mut build = Builder::new(&mut func, join);
2034 build.ret(&[]);
2035
2036 let stats = phiopt(&mut func);
2037 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
2038 assert_eq!(stats.count(Kind::Missed, super::NO_SELECT_AT_THAT_WIDTH), 1);
2039 }
2040
2041 #[test]
2050 fn a_choice_between_two_quads_keeps_its_branch_as_well() {
2051 let mut names = Interner::new();
2052 let quad = Type::float(Float::F128);
2053 let signature = Signature::new().with_params(&[Type::int(32)]);
2054 let mut func = Func::new(names.intern("f"), signature);
2055 let head = func.create_block();
2056 let outside = func.append_param(head, Type::int(32));
2057 let arms = [func.create_block(), func.create_block()];
2058 let join = func.create_block();
2059 func.append_param(join, quad);
2060
2061 let mut build = Builder::new(&mut func, head);
2062 let zero = build.iconst(Type::int(32), 0);
2063 let test = build.icmp(IntPred::Slt, outside, zero);
2064 build.br_if(test, arms[0], &[], arms[1], &[]);
2065 for (arm, bits) in arms.iter().zip([0x3fff_u128 << 112, 0x4000_u128 << 112]) {
2066 let mut build = Builder::new(&mut func, *arm);
2067 let it = build.fconst(quad, bits);
2068 build.jump(join, &[it]);
2069 }
2070 let mut build = Builder::new(&mut func, join);
2071 build.ret(&[]);
2072
2073 let stats = phiopt(&mut func);
2074 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
2075 assert_eq!(stats.count(Kind::Missed, super::NO_SELECT_AT_THAT_WIDTH), 1);
2076 }
2077
2078 #[test]
2079 fn arms_with_more_work_in_them_than_the_budget_keep_their_branch() {
2080 let mut names = Interner::new();
2081 let signature = Signature::new().with_params(&[Type::int(32)]);
2082 let mut func = Func::new(names.intern("f"), signature);
2083 let head = func.create_block();
2084 let outside = func.append_param(head, Type::int(32));
2085 let arms = [func.create_block(), func.create_block()];
2086 let join = func.create_block();
2087 let param = func.append_param(join, Type::int(32));
2088
2089 let mut build = Builder::new(&mut func, head);
2090 let zero = build.iconst(Type::int(32), 0);
2091 let test = build.icmp(IntPred::Slt, outside, zero);
2092 build.br_if(test, arms[0], &[], arms[1], &[]);
2093 let mut build = Builder::new(&mut func, arms[0]);
2094 let mut it = outside;
2096 for _ in 0..4 {
2097 it = build.binary(Opcode::Add, it, outside, Flags::NONE);
2098 }
2099 build.jump(join, &[it]);
2100 let mut build = Builder::new(&mut func, arms[1]);
2101 let it = build.iconst(Type::int(32), 0);
2102 build.jump(join, &[it]);
2103 let mut build = Builder::new(&mut func, join);
2104 build.ret(&[param]);
2105
2106 let stats = phiopt(&mut func);
2107 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
2108 assert_eq!(stats.count(Kind::Missed, super::ARMS_TOO_LONG), 1);
2109 }
2110
2111 #[test]
2117 fn constants_in_an_arm_are_not_counted_as_work() {
2118 let mut names = Interner::new();
2119 let signature = Signature::new().with_params(&[Type::int(32), Type::int(64)]);
2120 let mut func = Func::new(names.intern("f"), signature);
2121 let head = func.create_block();
2122 let outside = func.append_param(head, Type::int(32));
2123 let acc = func.append_param(head, Type::int(64));
2124 let arms = [func.create_block(), func.create_block()];
2125 let join = func.create_block();
2126 let param = func.append_param(join, Type::int(64));
2127
2128 let mut build = Builder::new(&mut func, head);
2129 let zero = build.iconst(Type::int(32), 0);
2130 let test = build.icmp(IntPred::Slt, outside, zero);
2131 build.br_if(test, arms[0], &[], arms[1], &[]);
2132 let mut build = Builder::new(&mut func, arms[0]);
2133 build.iconst(Type::int(32), 1);
2134 let one = build.iconst(Type::int(64), 1);
2135 let it = build.binary(Opcode::Add, acc, one, Flags::NONE);
2136 build.jump(join, &[it]);
2137 let mut build = Builder::new(&mut func, arms[1]);
2138 build.jump(join, &[acc]);
2139 let mut build = Builder::new(&mut func, join);
2140 build.ret(&[param]);
2141
2142 let stats = phiopt(&mut func);
2143 assert_eq!(stats.count(Kind::Missed, super::ARMS_TOO_LONG), 0);
2144 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
2145 }
2146
2147 #[test]
2149 fn an_arm_of_three_operations_is_too_long_with_its_constants_free() {
2150 let mut names = Interner::new();
2151 let signature = Signature::new().with_params(&[Type::int(32), Type::int(64)]);
2152 let mut func = Func::new(names.intern("f"), signature);
2153 let head = func.create_block();
2154 let outside = func.append_param(head, Type::int(32));
2155 let acc = func.append_param(head, Type::int(64));
2156 let arms = [func.create_block(), func.create_block()];
2157 let join = func.create_block();
2158 let param = func.append_param(join, Type::int(64));
2159
2160 let mut build = Builder::new(&mut func, head);
2161 let zero = build.iconst(Type::int(32), 0);
2162 let test = build.icmp(IntPred::Slt, outside, zero);
2163 build.br_if(test, arms[0], &[], arms[1], &[]);
2164 let mut build = Builder::new(&mut func, arms[0]);
2165 let mut it = acc;
2166 for step in 1..=3 {
2167 let by = build.iconst(Type::int(64), step);
2168 it = build.binary(Opcode::Mul, it, by, Flags::NONE);
2169 }
2170 build.jump(join, &[it]);
2171 let mut build = Builder::new(&mut func, arms[1]);
2172 build.jump(join, &[acc]);
2173 let mut build = Builder::new(&mut func, join);
2174 build.ret(&[param]);
2175
2176 let stats = phiopt(&mut func);
2177 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
2178 assert_eq!(stats.count(Kind::Missed, super::ARMS_TOO_LONG), 1);
2179 }
2180
2181 #[test]
2188 fn the_margin_is_a_quarter_in_from_each_end() {
2189 let guessed = |percent: u32| Probability::percent(percent, Quality::Guessed);
2190 assert!(super::unpredictable(Probability::even()));
2191 assert!(super::unpredictable(guessed(25)));
2192 assert!(super::unpredictable(guessed(75)));
2193 assert!(!super::unpredictable(guessed(24)));
2194 assert!(!super::unpredictable(guessed(76)));
2195 assert!(!super::unpredictable(Probability::always()));
2196 assert!(!super::unpredictable(Probability::never()));
2197 }
2198
2199 #[test]
2200 fn an_arm_that_two_edges_reach_is_not_an_arm() {
2201 let mut names = Interner::new();
2202 let signature = Signature::new().with_params(&[Type::int(32)]);
2203 let mut func = Func::new(names.intern("f"), signature);
2204 let head = func.create_block();
2205 let outside = func.append_param(head, Type::int(32));
2206 let above = func.create_block();
2207 let arms = [func.create_block(), func.create_block()];
2208 let join = func.create_block();
2209 let param = func.append_param(join, Type::int(32));
2210
2211 let mut build = Builder::new(&mut func, head);
2214 let zero = build.iconst(Type::int(32), 0);
2215 let first = build.icmp(IntPred::Slt, outside, zero);
2216 build.br_if(first, above, &[], arms[0], &[]);
2217 let mut build = Builder::new(&mut func, above);
2218 let one = build.iconst(Type::int(32), 1);
2219 let second = build.icmp(IntPred::Slt, outside, one);
2220 build.br_if(second, arms[0], &[], arms[1], &[]);
2221 for (arm, value) in arms.iter().zip([1, 2]) {
2222 let mut build = Builder::new(&mut func, *arm);
2223 let it = build.iconst(Type::int(32), value);
2224 build.jump(join, &[it]);
2225 }
2226 let mut build = Builder::new(&mut func, join);
2227 build.ret(&[param]);
2228
2229 let stats = phiopt(&mut func);
2230 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
2234 assert_eq!(goes_to(&func, 0), vec![1, 2]);
2235 assert_eq!(goes_to(&func, 1), vec![2, 3]);
2236 }
2237
2238 #[test]
2239 fn fuel_stops_the_conversion_where_it_stands() {
2240 let mut func = empty_arms();
2241 let mut fuel = Fuel::of(0);
2242 let stats = PhiOpt.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut fuel);
2243 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
2244 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
2245 assert_eq!(goes_to(&func, 0), vec![1, 2]);
2246 }
2247
2248 fn same_operation(steps: &[Opcode]) -> Func {
2256 let mut names = Interner::new();
2257 let int = Type::int(32);
2258 let signature = Signature::new().with_params(&[int, int, int, int]);
2259 let mut func = Func::new(names.intern("f"), signature);
2260 let head = func.create_block();
2261 let left = func.append_param(head, int);
2262 let right = func.append_param(head, int);
2263 let operands = [func.append_param(head, int), func.append_param(head, int)];
2264 let arms = [func.create_block(), func.create_block()];
2265 let join = func.create_block();
2266 let params: Vec<Value> = steps.iter().map(|_| func.append_param(join, int)).collect();
2267
2268 let mut build = Builder::new(&mut func, head);
2269 let shared = build.iconst(int, 3);
2272 let test = build.icmp(IntPred::Slt, left, right);
2273 build.br_if(test, arms[0], &[], arms[1], &[]);
2274 for (&arm, operand) in arms.iter().zip(operands) {
2275 let mut build = Builder::new(&mut func, arm);
2276 let carried: Vec<Value> = steps
2277 .iter()
2278 .map(|&opcode| build.binary(opcode, operand, shared, Flags::default()))
2279 .collect();
2280 build.jump(join, &carried);
2281 }
2282 let mut build = Builder::new(&mut func, join);
2283 build.ret(¶ms);
2284 func
2285 }
2286
2287 #[test]
2289 fn an_operation_both_arms_did_is_done_once_below_the_branch() {
2290 let mut func = same_operation(&[Opcode::Add]);
2291 let stats = phiopt(&mut func);
2292 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 1);
2293 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
2294 assert_eq!(
2295 opcodes(&func, 0),
2296 vec![Opcode::IConst, Opcode::ICmp, Opcode::Select, Opcode::Add, Opcode::Jump],
2297 "the select chooses the operand and the add happens once"
2298 );
2299 assert_eq!(blocks(&func), vec![0, 3]);
2300 }
2301
2302 #[test]
2306 fn the_select_chooses_the_operands_and_not_the_answers() {
2307 let mut func = same_operation(&[Opcode::Add]);
2308 phiopt(&mut func);
2309 let head = Block::from_usize(0);
2310 let select = func
2311 .insts(head)
2312 .find(|&inst| func[inst].opcode == Opcode::Select)
2313 .expect("the select the pass just built");
2314 let add = func
2315 .insts(head)
2316 .find(|&inst| func[inst].opcode == Opcode::Add)
2317 .expect("the add the pass just wrote");
2318 let chosen = func[func[select].args].to_vec();
2319 let params = func[head].params.to_vec();
2320 assert_eq!(&chosen[1..], ¶ms[2..], "the two operands the arms differed in");
2321 let added = func[func[add].args].to_vec();
2322 assert_eq!(added[0], func[select].first_result.expect("a select has a result"));
2323 assert_eq!(carries(&func, 0), vec![func[add].first_result.expect("an add has a result")]);
2324 }
2325
2326 #[test]
2330 fn arms_that_factor_away_entirely_are_not_too_long() {
2331 let steps = [Opcode::Add, Opcode::Sub, Opcode::Mul];
2332 let mut func = same_operation(&steps);
2333 let stats = phiopt(&mut func);
2334 assert_eq!(stats.count(Kind::Missed, super::ARMS_TOO_LONG), 0);
2335 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 3);
2336 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
2337 let written = opcodes(&func, 0);
2338 assert_eq!(written.iter().filter(|&&op| op == Opcode::Select).count(), 3);
2339 for step in steps {
2340 assert_eq!(written.iter().filter(|&&op| op == step).count(), 1, "{step:?} once");
2341 }
2342 }
2343
2344 #[test]
2347 fn arms_that_agree_in_every_operand_need_no_select() {
2348 let mut names = Interner::new();
2349 let int = Type::int(32);
2350 let signature = Signature::new().with_params(&[int, int, int]);
2351 let mut func = Func::new(names.intern("f"), signature);
2352 let head = func.create_block();
2353 let left = func.append_param(head, int);
2354 let right = func.append_param(head, int);
2355 let operand = func.append_param(head, int);
2356 let arms = [func.create_block(), func.create_block()];
2357 let join = func.create_block();
2358 let param = func.append_param(join, int);
2359
2360 let mut build = Builder::new(&mut func, head);
2361 let shared = build.iconst(int, 3);
2362 let test = build.icmp(IntPred::Slt, left, right);
2363 build.br_if(test, arms[0], &[], arms[1], &[]);
2364 for &arm in &arms {
2365 let mut build = Builder::new(&mut func, arm);
2366 let it = build.binary(Opcode::Add, operand, shared, Flags::default());
2367 build.jump(join, &[it]);
2368 }
2369 let mut build = Builder::new(&mut func, join);
2370 build.ret(&[param]);
2371
2372 let stats = phiopt(&mut func);
2373 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 1);
2374 assert_eq!(
2375 opcodes(&func, 0),
2376 vec![Opcode::IConst, Opcode::ICmp, Opcode::Add, Opcode::Jump],
2377 "one add and nothing to choose between"
2378 );
2379 }
2380
2381 #[test]
2384 fn arms_that_do_different_things_are_not_factored() {
2385 let mut names = Interner::new();
2386 let int = Type::int(32);
2387 let signature = Signature::new().with_params(&[int, int, int, int]);
2388 let mut func = Func::new(names.intern("f"), signature);
2389 let head = func.create_block();
2390 let left = func.append_param(head, int);
2391 let right = func.append_param(head, int);
2392 let operands = [func.append_param(head, int), func.append_param(head, int)];
2393 let arms = [func.create_block(), func.create_block()];
2394 let join = func.create_block();
2395 let param = func.append_param(join, int);
2396
2397 let mut build = Builder::new(&mut func, head);
2398 let shared = build.iconst(int, 3);
2399 let test = build.icmp(IntPred::Slt, left, right);
2400 build.br_if(test, arms[0], &[], arms[1], &[]);
2401 for ((&arm, operand), opcode) in arms.iter().zip(operands).zip([Opcode::Add, Opcode::Sub]) {
2402 let mut build = Builder::new(&mut func, arm);
2403 let it = build.binary(opcode, operand, shared, Flags::default());
2404 build.jump(join, &[it]);
2405 }
2406 let mut build = Builder::new(&mut func, join);
2407 build.ret(&[param]);
2408
2409 let stats = phiopt(&mut func);
2410 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 0);
2411 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
2412 assert_eq!(
2413 opcodes(&func, 0),
2414 vec![
2415 Opcode::IConst,
2416 Opcode::ICmp,
2417 Opcode::Add,
2418 Opcode::Sub,
2419 Opcode::Select,
2420 Opcode::Jump
2421 ],
2422 "both operations hoisted and a select between their answers"
2423 );
2424 }
2425
2426 #[test]
2429 fn arms_that_differ_in_two_operands_are_not_factored() {
2430 let mut names = Interner::new();
2431 let int = Type::int(32);
2432 let signature = Signature::new().with_params(&[int, int, int, int, int, int]);
2433 let mut func = Func::new(names.intern("f"), signature);
2434 let head = func.create_block();
2435 let left = func.append_param(head, int);
2436 let right = func.append_param(head, int);
2437 let first = [func.append_param(head, int), func.append_param(head, int)];
2438 let second = [func.append_param(head, int), func.append_param(head, int)];
2439 let arms = [func.create_block(), func.create_block()];
2440 let join = func.create_block();
2441 let param = func.append_param(join, int);
2442
2443 let mut build = Builder::new(&mut func, head);
2444 let test = build.icmp(IntPred::Slt, left, right);
2445 build.br_if(test, arms[0], &[], arms[1], &[]);
2446 for ((&arm, one), two) in arms.iter().zip(first).zip(second) {
2447 let mut build = Builder::new(&mut func, arm);
2448 let it = build.binary(Opcode::Add, one, two, Flags::default());
2449 build.jump(join, &[it]);
2450 }
2451 let mut build = Builder::new(&mut func, join);
2452 build.ret(&[param]);
2453
2454 let stats = phiopt(&mut func);
2455 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 0);
2456 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
2457 assert_eq!(opcodes(&func, 0).iter().filter(|&&op| op == Opcode::Add).count(), 2);
2458 }
2459
2460 #[test]
2464 fn an_operation_read_more_than_once_is_not_factored() {
2465 let mut names = Interner::new();
2466 let int = Type::int(32);
2467 let signature = Signature::new().with_params(&[int, int, int, int]);
2468 let mut func = Func::new(names.intern("f"), signature);
2469 let head = func.create_block();
2470 let left = func.append_param(head, int);
2471 let right = func.append_param(head, int);
2472 let operands = [func.append_param(head, int), func.append_param(head, int)];
2473 let arms = [func.create_block(), func.create_block()];
2474 let join = func.create_block();
2475 let params = [func.append_param(join, int), func.append_param(join, int)];
2476
2477 let mut build = Builder::new(&mut func, head);
2478 let shared = build.iconst(int, 3);
2479 let test = build.icmp(IntPred::Slt, left, right);
2480 build.br_if(test, arms[0], &[], arms[1], &[]);
2481 for (&arm, operand) in arms.iter().zip(operands) {
2482 let mut build = Builder::new(&mut func, arm);
2483 let it = build.binary(Opcode::Add, operand, shared, Flags::default());
2484 build.jump(join, &[it, it]);
2485 }
2486 let mut build = Builder::new(&mut func, join);
2487 build.ret(¶ms);
2488
2489 let stats = phiopt(&mut func);
2490 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 0);
2491 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
2492 assert_eq!(opcodes(&func, 0).iter().filter(|&&op| op == Opcode::Add).count(), 2);
2493 }
2494
2495 #[test]
2498 fn a_triangle_factors_nothing() {
2499 let mut names = Interner::new();
2500 let int = Type::int(32);
2501 let signature = Signature::new().with_params(&[int, int, int]);
2502 let mut func = Func::new(names.intern("f"), signature);
2503 let head = func.create_block();
2504 let left = func.append_param(head, int);
2505 let right = func.append_param(head, int);
2506 let operand = func.append_param(head, int);
2507 let arm = func.create_block();
2508 let join = func.create_block();
2509 let param = func.append_param(join, int);
2510
2511 let mut build = Builder::new(&mut func, head);
2512 let shared = build.iconst(int, 3);
2513 let test = build.icmp(IntPred::Slt, left, right);
2514 build.br_if(test, arm, &[], join, &[operand]);
2515 let mut build = Builder::new(&mut func, arm);
2516 let it = build.binary(Opcode::Add, operand, shared, Flags::default());
2517 build.jump(join, &[it]);
2518 let mut build = Builder::new(&mut func, join);
2519 build.ret(&[param]);
2520
2521 let stats = phiopt(&mut func);
2522 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 0);
2523 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
2524 }
2525}