1use rucc_cost::heuristics;
246use rucc_ir::{Block, Builder, Extra, Flags, Func, Inst, InstData, MemOrder, Opcode, Type, Value};
247
248use crate::cfg::Cfg;
249use crate::fold::constant;
250use crate::profile::Probability;
251use crate::simplify_cfg::{self, Bindings};
252use crate::{Analyses, Fuel, Pass, Preserved, Stats};
253
254const CONVERTED: &str =
256 "branch whose two arms only work out a value replaced by the value and no branch";
257
258const FACTORED: &str = "operation both arms did to different operands done once below the branch";
260
261const STORE_REPLACED: &str = "store both arms made to the same place made once below the branch";
263
264const ARM_HAS_EFFECTS: &str =
266 "branch kept, an arm does something that only happens on the path it is on";
267
268const STORE_ON_ONE_PATH: &str =
270 "branch kept, a store only one path makes would have to be made on the other path too";
271
272const STORES_DO_NOT_MATCH: &str =
274 "branch kept, both paths store but not to one address the two of them name the same way";
275
276const ARM_MAY_TRAP: &str = "branch kept, an arm divides and doing it on both paths could trap";
278
279const NO_SELECT_AT_THAT_WIDTH: &str =
281 "branch kept, the value the arms disagree about is not a width a select is lowered at";
282
283const ARMS_TOO_LONG: &str = "branch kept, its arms are more work than doing both of them is worth";
285
286const BRANCH_IS_PREDICTED: &str =
288 "branch kept, it goes one way often enough that the machine will predict it";
289
290const CONDITION_IS_DECIDED: &str =
292 "branch kept, its condition is already known and the arm that cannot run is better deleted";
293const NO_FUEL: &str = "branch kept, the pass ran out of fuel";
294
295#[derive(Debug, Clone, Copy, PartialEq, Eq)]
297pub struct PhiOpt;
298
299impl Pass for PhiOpt {
300 fn name(&self) -> &'static str {
301 "phiopt"
302 }
303
304 fn describe(&self) -> &'static str {
305 "a branch whose two arms only work out a value becomes a select, and the branch goes"
306 }
307
308 fn preserves(&self) -> Preserved {
309 Preserved::NONE
312 }
313
314 fn run(&self, func: &mut Func, an: &mut Analyses, fuel: &mut Fuel) -> Stats {
315 let mut stats = Stats::new();
316 if func.entry().is_none() {
317 return stats;
318 }
319 for head in func.blocks().collect::<Vec<Block>>() {
320 let cfg = an.cfg(func);
321 if !cfg.reaches(head) {
322 continue;
323 }
324 let Some(shape) = diamond(func, cfg, head) else { continue };
325 let store = storing(func, &shape);
326 if let Some(reason) = refused(func, &shape, store.as_ref()) {
327 stats.missed(reason);
328 continue;
329 }
330 let plan = factoring(func, &shape);
331 let replaced = plan.iter().flatten().count() + usize::from(store.is_some());
340 let saved = u32::try_from(replaced).unwrap_or(u32::MAX);
341 let work = shape
342 .arms
343 .map(|arm| arm.map_or(0, |block| length(func, block)).saturating_sub(saved));
344 if work.iter().any(|&count| count > 0) {
345 if work.iter().any(|&count| count > heuristics::PHIOPT_ARM_INSTRUCTIONS) {
346 stats.missed(ARMS_TOO_LONG);
347 continue;
348 }
349 if !unpredictable(an.frequencies(func).taken(head, 0)) {
354 stats.missed(BRANCH_IS_PREDICTED);
355 continue;
356 }
357 }
358 if !fuel.take() {
359 stats.missed(NO_FUEL);
363 break;
364 }
365 convert(func, &shape, &plan, store.as_ref());
366 an.clear();
369 for _ in plan.iter().flatten() {
370 stats.optimized(FACTORED);
371 }
372 if store.is_some() {
373 stats.optimized(STORE_REPLACED);
374 }
375 stats.optimized(CONVERTED);
376 }
377 stats
378 }
379}
380
381pub(crate) struct Diamond {
383 pub(crate) head: Block,
385 pub(crate) cond: Value,
387 pub(crate) join: Block,
389 pub(crate) arms: [Option<Block>; 2],
394 pub(crate) args: [Vec<Value>; 2],
396}
397
398pub(crate) fn diamond(func: &Func, cfg: &Cfg, head: Block) -> Option<Diamond> {
400 let entry = cfg.entry()?;
401 let term = func.terminator(head)?;
402 if func[term].opcode != Opcode::BrIf {
403 return None;
404 }
405 let cond = *func[func[term].args].first()?;
406 let mut targets = func.successors(term);
407 let sides = [targets.next()?, targets.next()?];
408 if sides[0].block == sides[1].block {
412 return None;
413 }
414 let through = [
415 passes_through(func, cfg, head, sides[0].block),
416 passes_through(func, cfg, head, sides[1].block),
417 ];
418 let join = match through {
421 [Some(left), Some(right)] if left == right => left,
422 [Some(left), _] if left == sides[1].block => left,
423 [_, Some(right)] if right == sides[0].block => right,
424 _ => return None,
425 };
426 if join == head || join == entry {
429 return None;
430 }
431 let arms = [
432 (sides[0].block != join).then_some(sides[0].block),
433 (sides[1].block != join).then_some(sides[1].block),
434 ];
435 let mut args = [Vec::new(), Vec::new()];
436 for (index, side) in sides.iter().enumerate() {
437 let carried = match arms[index] {
438 Some(arm) => func.successors(func.terminator(arm)?).next()?.args,
440 None => side.args,
441 };
442 args[index] = func[carried].to_vec();
443 }
444 Some(Diamond { head, cond, join, arms, args })
445}
446
447fn passes_through(func: &Func, cfg: &Cfg, head: Block, block: Block) -> Option<Block> {
455 if !func[block].params.is_empty() {
456 return None;
457 }
458 match cfg.predecessors(block) {
459 [only] if *only == head => {}
460 _ => return None,
461 }
462 let term = func.terminator(block)?;
463 if func[term].opcode != Opcode::Jump {
464 return None;
465 }
466 Some(func.successors(term).next()?.block)
467}
468
469fn refused(func: &Func, shape: &Diamond, store: Option<&Stored>) -> Option<&'static str> {
474 let term = func.terminator(shape.head).expect("the head of a diamond ends in its branch");
485 if simplify_cfg::taken(func, term, &Bindings::new()).is_some() {
486 return Some(CONDITION_IS_DECIDED);
487 }
488 let moving = store.map(|one| one.insts);
489 for &arm in shape.arms.iter().flatten() {
490 for inst in func.insts(arm) {
491 if func.is_terminator(inst) || moving.is_some_and(|two| two.contains(&inst)) {
492 continue;
493 }
494 if func[inst].opcode == Opcode::Store {
495 return Some(mismatch(func, shape));
503 }
504 if func[inst].opcode.has_effects() {
505 return Some(ARM_HAS_EFFECTS);
506 }
507 if !speculatable(func, inst) {
508 return Some(ARM_MAY_TRAP);
509 }
510 }
511 }
512 let params = func[shape.join].params.iter();
513 for ((¶m, &then), &other) in params.zip(&shape.args[0]).zip(&shape.args[1]) {
514 if agree(func, then, other) {
517 continue;
518 }
519 if !selectable(func[param].ty) {
520 return Some(NO_SELECT_AT_THAT_WIDTH);
521 }
522 }
523 None
524}
525
526fn agree(func: &Func, then: Value, other: Value) -> bool {
535 if then == other {
536 return true;
537 }
538 let (Some((left, lty)), Some((right, rty))) = (constant(func, then), constant(func, other))
539 else {
540 return false;
541 };
542 lty == rty && left == right
543}
544
545pub(crate) fn speculatable(func: &Func, inst: Inst) -> bool {
552 let opcode = func[inst].opcode;
553 if !matches!(opcode, Opcode::SDiv | Opcode::UDiv | Opcode::SRem | Opcode::URem) {
554 return true;
555 }
556 let Some(&divisor) = func[func[inst].args].get(1) else { return false };
557 let Some((imm, ty)) = constant(func, divisor) else { return false };
558 if imm.unsigned() == 0 {
559 return false;
560 }
561 imm.signed(ty) != -1
562}
563
564struct Stored {
570 insts: [Inst; 2],
572 values: [Value; 2],
574 addr: Value,
576 data: InstData,
578}
579
580fn mismatch(func: &Func, shape: &Diamond) -> &'static str {
588 let [Some(then), Some(other)] = shape.arms else { return STORE_ON_ONE_PATH };
589 match (stored_in(func, then), stored_in(func, other)) {
590 (Some(_), Some(_)) => STORES_DO_NOT_MATCH,
591 _ => STORE_ON_ONE_PATH,
592 }
593}
594
595fn storing(func: &Func, shape: &Diamond) -> Option<Stored> {
602 let [Some(then), Some(other)] = shape.arms else { return None };
603 let insts = [stored_in(func, then)?, stored_in(func, other)?];
604 let data = [func[insts[0]], func[insts[1]]];
605 if data[0].flags != data[1].flags || data[0].flags.contains(Flags::VOLATILE) {
612 return None;
613 }
614 let (Extra::Mem(one), Extra::Mem(two)) = (data[0].extra, data[1].extra) else { return None };
615 if func[one] != func[two] || func[one].order != MemOrder::NotAtomic {
618 return None;
619 }
620 let &[then, addr] = func[data[0].args].first_chunk::<2>()?;
622 let &[other, addr_two] = func[data[1].args].first_chunk::<2>()?;
623 if addr != addr_two || func[then].ty != func[other].ty {
628 return None;
629 }
630 if !agree(func, then, other) && !selectable(func[then].ty) {
631 return None;
632 }
633 Some(Stored { insts, values: [then, other], addr, data: data[0] })
634}
635
636fn stored_in(func: &Func, arm: Block) -> Option<Inst> {
643 let mut store = None;
644 for inst in func.insts(arm) {
645 if func.is_terminator(inst) || !func[inst].opcode.has_effects() {
646 continue;
647 }
648 if func[inst].opcode != Opcode::Store || store.is_some() {
649 return None;
650 }
651 store = Some(inst);
652 }
653 store
654}
655
656struct Factored {
662 insts: [Inst; 2],
664 operands: Vec<Value>,
666 differ: Option<(usize, [Value; 2])>,
671 data: InstData,
673 ty: Type,
675}
676
677fn factoring(func: &Func, shape: &Diamond) -> Vec<Option<Factored>> {
683 let count = shape.args[0].len();
684 let [Some(then), Some(other)] = shape.arms else {
685 return (0..count).map(|_| None).collect();
686 };
687 (0..count).map(|index| factored(func, shape, [then, other], index)).collect()
688}
689
690fn factored(func: &Func, shape: &Diamond, arms: [Block; 2], index: usize) -> Option<Factored> {
692 let sides = [shape.args[0][index], shape.args[1][index]];
693 if agree(func, sides[0], sides[1]) {
695 return None;
696 }
697 let insts = [written_in(func, arms[0], sides[0])?, written_in(func, arms[1], sides[1])?];
698 let data = [func[insts[0]], func[insts[1]]];
699 if data[0].opcode != data[1].opcode || data[0].flags != data[1].flags {
705 return None;
706 }
707 if data[0].extra != data[1].extra || func[sides[0]].ty != func[sides[1]].ty {
708 return None;
709 }
710 let operands = [func[data[0].args].to_vec(), func[data[1].args].to_vec()];
711 if operands[0].len() != operands[1].len() {
712 return None;
713 }
714 let mut apart =
715 operands[0].iter().zip(&operands[1]).enumerate().filter(|(_, (one, two))| one != two);
716 let differ = match (apart.next(), apart.next()) {
717 (_, Some(_)) => return None,
720 (Some((at, (&one, &two))), None) => {
721 if func[one].ty != func[two].ty || !selectable(func[one].ty) {
722 return None;
723 }
724 Some((at, [one, two]))
725 }
726 (None, None) => None,
727 };
728 let ty = func[sides[0]].ty;
729 Some(Factored { insts, operands: operands[0].clone(), differ, data: data[0], ty })
730}
731
732fn written_in(func: &Func, arm: Block, value: Value) -> Option<Inst> {
741 let inst = func
742 .insts(arm)
743 .find(|&inst| func[inst].results == 1 && func[inst].first_result == Some(value))?;
744 let mut seen = 0;
745 for inst in func.insts(arm) {
746 seen += func[func[inst].args].iter().filter(|&&arg| arg == value).count();
747 for call in func.successors(inst) {
748 seen += func[call.args].iter().filter(|&&arg| arg == value).count();
749 }
750 }
751 (seen == 1).then_some(inst)
752}
753
754fn selectable(ty: Type) -> bool {
760 ty.is_scalar() && ty.is_int() && matches!(ty.bits(), 8 | 16 | 32 | 64)
761}
762
763pub(crate) fn length(func: &Func, block: Block) -> u32 {
765 let count = func.insts(block).filter(|&inst| !func.is_terminator(inst)).count();
766 u32::try_from(count).unwrap_or(u32::MAX)
767}
768
769pub(crate) fn unpredictable(taken: Probability) -> bool {
771 let margin = heuristics::PHIOPT_UNPREDICTABLE_MARGIN_PERCENT * (Probability::SCALE / 100);
772 taken.parts() >= margin && taken.parts() <= Probability::SCALE - margin
773}
774
775fn convert(func: &mut Func, shape: &Diamond, plan: &[Option<Factored>], store: Option<&Stored>) {
782 let term = func.terminator(shape.head).expect("the head of a diamond ends in its branch");
783 let span = func.span(term);
784 func.remove_inst(term);
785 let mut dropped: Vec<Inst> = plan.iter().flatten().flat_map(|one| one.insts).collect();
786 dropped.extend(store.iter().flat_map(|one| one.insts));
787 for &arm in shape.arms.iter().flatten() {
788 for inst in func.insts(arm).collect::<Vec<Inst>>() {
789 if func.is_terminator(inst) {
790 continue;
791 }
792 func.remove_inst(inst);
793 if !dropped.contains(&inst) {
796 func.append_inst(shape.head, inst);
797 }
798 }
799 }
800 let mut build = Builder::new(func, shape.head).at(span);
801 let mut args = Vec::with_capacity(shape.args[0].len());
802 for (index, (&then, &other)) in shape.args[0].iter().zip(&shape.args[1]).enumerate() {
803 if let Some(one) = &plan[index] {
804 let mut operands = one.operands.clone();
805 if let Some((at, sides)) = one.differ {
806 operands[at] = build.select(shape.cond, sides[0], sides[1]);
807 }
808 let list = build.func().push_values(&operands);
809 args.push(build.value(InstData { args: list, ..one.data }, one.ty));
810 continue;
811 }
812 let same = agree(build.func(), then, other);
815 args.push(if same { then } else { build.select(shape.cond, then, other) });
816 }
817 if let Some(one) = store {
820 let [then, other] = one.values;
821 let same = agree(build.func(), then, other);
822 let what = if same { then } else { build.select(shape.cond, then, other) };
823 let list = build.func().push_values(&[what, one.addr]);
824 build.inst(InstData { args: list, ..one.data }, &[]);
825 }
826 build.jump(shape.join, &args);
827 for &arm in shape.arms.iter().flatten() {
831 func.remove_block(arm);
832 }
833}
834
835#[cfg(test)]
836mod tests {
837 use rucc_base::Interner;
838 use rucc_ir::{
839 Block, Builder, Flags, Func, IntPred, MemInfo, MemOrder, Opcode, Restrict, Signature, Type,
840 Value,
841 };
842
843 use super::PhiOpt;
844 use crate::profile::{Probability, Quality};
845 use crate::stats::Kind;
846 use crate::{Analyses, Fuel, Pass, Stats};
847
848 fn phiopt(func: &mut Func) -> Stats {
850 PhiOpt.run(func, &mut Analyses::new(), &mut Fuel::unlimited())
851 }
852
853 fn blocks(func: &Func) -> Vec<usize> {
855 func.blocks().map(Block::index).collect()
856 }
857
858 fn goes_to(func: &Func, block: usize) -> Vec<usize> {
860 let block = Block::from_usize(block);
861 let term = func.terminator(block).expect("every block here has one");
862 func.successors(term).map(|call| call.block.index()).collect()
863 }
864
865 fn opcodes(func: &Func, block: usize) -> Vec<Opcode> {
867 let block = Block::from_usize(block);
868 func.insts(block).map(|inst| func[inst].opcode).collect()
869 }
870
871 fn carries(func: &Func, block: usize) -> Vec<Value> {
873 let block = Block::from_usize(block);
874 let term = func.terminator(block).expect("every block here has one");
875 let call = func.successors(term).next().expect("a terminator here has an edge");
876 func[call.args].to_vec()
877 }
878
879 fn plain() -> MemInfo {
881 MemInfo {
882 size: 4,
883 align: 4,
884 order: MemOrder::NotAtomic,
885 tbaa: None,
886 restrict: Restrict::NONE,
887 }
888 }
889
890 fn store_something(build: &mut Builder<'_>) {
892 let what = build.iconst(Type::int(32), 7);
893 let address = build.iconst(Type::int(64), 16);
894 let address = build.unary(Opcode::IntToPtr, address, Type::PTR);
895 build.store(what, address, plain(), Flags::NONE);
896 }
897
898 fn both_arms_store(info: MemInfo, flags: [Flags; 2], addresses: bool) -> Func {
904 let mut names = Interner::new();
905 let ints = [Type::PTR, Type::int(32), Type::int(32), Type::PTR];
906 let signature = Signature::new().with_params(&ints);
907 let mut func = Func::new(names.intern("f"), signature);
908 let head = func.create_block();
909 let address = func.append_param(head, Type::PTR);
910 let written =
911 [func.append_param(head, Type::int(32)), func.append_param(head, Type::int(32))];
912 let elsewhere = func.append_param(head, Type::PTR);
913 let arms = [func.create_block(), func.create_block()];
914 let join = func.create_block();
915
916 let mut build = Builder::new(&mut func, head);
917 let zero = build.iconst(Type::int(32), 0);
918 let test = build.icmp(IntPred::Slt, written[0], zero);
919 build.br_if(test, arms[0], &[], arms[1], &[]);
920 for (index, arm) in arms.iter().enumerate() {
921 let mut build = Builder::new(&mut func, *arm);
922 let where_to = if addresses && index == 1 { elsewhere } else { address };
923 build.store(written[index], where_to, info, flags[index]);
924 build.jump(join, &[]);
925 }
926 let mut build = Builder::new(&mut func, join);
927 build.ret(&[]);
928 func
929 }
930
931 fn empty_arms() -> Func {
937 let mut names = Interner::new();
938 let signature = Signature::new().with_params(&[Type::int(32), Type::int(32)]);
939 let mut func = Func::new(names.intern("f"), signature);
940 let head = func.create_block();
941 let left = func.append_param(head, Type::int(32));
942 let right = func.append_param(head, Type::int(32));
943 let arms = [func.create_block(), func.create_block()];
944 let join = func.create_block();
945 let param = func.append_param(join, Type::int(32));
946
947 let mut build = Builder::new(&mut func, head);
948 let test = build.icmp(IntPred::Slt, left, right);
949 build.br_if(test, arms[0], &[], arms[1], &[]);
950 for (arm, value) in arms.iter().zip([1, 2]) {
951 let mut build = Builder::new(&mut func, *arm);
952 let it = build.iconst(Type::int(32), value);
953 build.jump(join, &[it]);
954 }
955 let mut build = Builder::new(&mut func, join);
956 build.ret(&[param]);
957 func
958 }
959
960 #[test]
961 fn a_branch_that_is_already_decided_is_left_for_simplify_cfg() {
962 let mut names = Interner::new();
966 let mut func = Func::new(names.intern("f"), Signature::new());
967 let head = func.create_block();
968 let arms = [func.create_block(), func.create_block()];
969 let join = func.create_block();
970 let param = func.append_param(join, Type::int(32));
971
972 let mut build = Builder::new(&mut func, head);
973 let one = build.iconst(Type::int(32), 1);
976 let zero = build.iconst(Type::int(32), 0);
977 let test = build.icmp(IntPred::Ne, one, zero);
978 build.br_if(test, arms[0], &[], arms[1], &[]);
979 for (arm, value) in arms.iter().zip([1, 2]) {
980 let mut build = Builder::new(&mut func, *arm);
981 let it = build.iconst(Type::int(32), value);
982 build.jump(join, &[it]);
983 }
984 let mut build = Builder::new(&mut func, join);
985 build.ret(&[param]);
986
987 let stats = phiopt(&mut func);
988 assert_eq!(stats.count(Kind::Missed, super::CONDITION_IS_DECIDED), 1);
989 assert_eq!(blocks(&func), vec![0, 1, 2, 3]);
990 }
991
992 #[test]
993 fn a_diamond_whose_arms_are_empty_becomes_a_select() {
994 let mut func = empty_arms();
995 let stats = phiopt(&mut func);
996 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
997 assert_eq!(
999 opcodes(&func, 0),
1000 vec![Opcode::ICmp, Opcode::IConst, Opcode::IConst, Opcode::Select, Opcode::Jump]
1001 );
1002 assert_eq!(goes_to(&func, 0), vec![3]);
1003 assert_eq!(blocks(&func), vec![0, 3]);
1004 }
1005
1006 #[test]
1007 fn the_side_the_condition_holds_on_is_the_side_the_select_takes_first() {
1008 let mut func = empty_arms();
1009 phiopt(&mut func);
1010 let select = func
1011 .insts(Block::from_usize(0))
1012 .find(|&inst| func[inst].opcode == Opcode::Select)
1013 .expect("the select the pass just built");
1014 let args = func[func[select].args].to_vec();
1015 let one = crate::fold::constant(&func, args[1]).expect("the true arm carried a constant");
1016 let two = crate::fold::constant(&func, args[2]).expect("the false arm carried a constant");
1017 assert_eq!(one.0.unsigned(), 1, "the arm the branch named first");
1018 assert_eq!(two.0.unsigned(), 2, "the arm the branch named second");
1019 }
1020
1021 #[test]
1023 fn a_triangle_whose_empty_side_goes_straight_to_the_join_is_converted() {
1024 let mut names = Interner::new();
1025 let signature = Signature::new().with_params(&[Type::int(32)]);
1026 let mut func = Func::new(names.intern("f"), signature);
1027 let head = func.create_block();
1028 let outside = func.append_param(head, Type::int(32));
1029 let arm = func.create_block();
1030 let join = func.create_block();
1031 let param = func.append_param(join, Type::int(32));
1032
1033 let mut build = Builder::new(&mut func, head);
1034 let zero = build.iconst(Type::int(32), 0);
1035 let test = build.icmp(IntPred::Slt, outside, zero);
1036 build.br_if(test, arm, &[], join, &[outside]);
1037 let mut build = Builder::new(&mut func, arm);
1038 let it = build.iconst(Type::int(32), 0);
1039 build.jump(join, &[it]);
1040 let mut build = Builder::new(&mut func, join);
1041 build.ret(&[param]);
1042
1043 let stats = phiopt(&mut func);
1044 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1045 assert_eq!(blocks(&func), vec![0, 2]);
1046 assert_eq!(goes_to(&func, 0), vec![2]);
1047 assert_eq!(opcodes(&func, 0).last(), Some(&Opcode::Jump));
1048 }
1049
1050 #[test]
1051 fn a_parameter_both_sides_agree_about_needs_no_select() {
1052 let mut names = Interner::new();
1053 let signature = Signature::new().with_params(&[Type::int(32)]);
1054 let mut func = Func::new(names.intern("f"), signature);
1055 let head = func.create_block();
1056 let outside = func.append_param(head, Type::int(32));
1057 let arms = [func.create_block(), func.create_block()];
1058 let join = func.create_block();
1059 let param = func.append_param(join, Type::int(32));
1060
1061 let mut build = Builder::new(&mut func, head);
1062 let zero = build.iconst(Type::int(32), 0);
1063 let test = build.icmp(IntPred::Slt, outside, zero);
1064 build.br_if(test, arms[0], &[], arms[1], &[]);
1065 for arm in arms {
1066 let mut build = Builder::new(&mut func, arm);
1067 build.jump(join, &[outside]);
1068 }
1069 let mut build = Builder::new(&mut func, join);
1070 build.ret(&[param]);
1071
1072 let stats = phiopt(&mut func);
1073 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1074 assert!(!opcodes(&func, 0).contains(&Opcode::Select), "both sides carried the same value");
1075 assert_eq!(carries(&func, 0), vec![outside]);
1076 }
1077
1078 #[test]
1079 fn two_sides_carrying_the_same_number_need_no_select_either() {
1080 let mut names = Interner::new();
1084 let signature = Signature::new().with_params(&[Type::int(32)]);
1085 let mut func = Func::new(names.intern("f"), signature);
1086 let head = func.create_block();
1087 let outside = func.append_param(head, Type::int(32));
1088 let arms = [func.create_block(), func.create_block()];
1089 let join = func.create_block();
1090 let param = func.append_param(join, Type::int(32));
1091
1092 let mut build = Builder::new(&mut func, head);
1093 let zero = build.iconst(Type::int(32), 0);
1094 let test = build.icmp(IntPred::Slt, outside, zero);
1095 build.br_if(test, arms[0], &[], arms[1], &[]);
1096 for arm in arms {
1097 let mut build = Builder::new(&mut func, arm);
1098 let seven = build.iconst(Type::int(32), 7);
1099 build.jump(join, &[seven]);
1100 }
1101 let mut build = Builder::new(&mut func, join);
1102 build.ret(&[param]);
1103
1104 let stats = phiopt(&mut func);
1105 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1106 assert!(!opcodes(&func, 0).contains(&Opcode::Select), "both sides carried a seven");
1107 }
1108
1109 #[test]
1110 fn two_sides_carrying_different_numbers_still_get_a_select() {
1111 let mut func = empty_arms();
1112 let stats = phiopt(&mut func);
1113 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1114 assert!(opcodes(&func, 0).contains(&Opcode::Select), "one and two are not the same number");
1115 }
1116
1117 #[test]
1118 fn a_store_both_arms_make_to_one_place_is_made_once_below_the_branch() {
1119 let mut func = both_arms_store(plain(), [Flags::NONE; 2], false);
1120 let stats = phiopt(&mut func);
1121 assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 1);
1122 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1123 assert_eq!(
1125 opcodes(&func, 0),
1126 vec![Opcode::IConst, Opcode::ICmp, Opcode::Select, Opcode::Store, Opcode::Jump]
1127 );
1128 assert_eq!(blocks(&func), vec![0, 3]);
1129 assert_eq!(goes_to(&func, 0), vec![3]);
1130 }
1131
1132 #[test]
1133 fn the_one_store_writes_what_the_side_the_condition_holds_on_was_writing() {
1134 let mut func = both_arms_store(plain(), [Flags::NONE; 2], false);
1135 phiopt(&mut func);
1136 let head = Block::from_usize(0);
1137 let select = func
1138 .insts(head)
1139 .find(|&inst| func[inst].opcode == Opcode::Select)
1140 .expect("the select the pass just built");
1141 let store = func
1142 .insts(head)
1143 .find(|&inst| func[inst].opcode == Opcode::Store)
1144 .expect("the one store that is left");
1145 let chosen = func[func[select].args].to_vec();
1146 let written = func[func[store].args].to_vec();
1147 let params = func[head].params.to_vec();
1149 assert_eq!(chosen[1], params[1], "the arm the branch named first");
1150 assert_eq!(chosen[2], params[2], "the arm the branch named second");
1151 assert_eq!(written[0], func[select].first_result.expect("a select produces one value"));
1152 assert_eq!(written[1], params[0], "the address both arms named");
1153 }
1154
1155 #[test]
1157 fn two_arms_that_write_the_same_thing_get_a_store_and_no_select() {
1158 let mut func = both_arms_store(plain(), [Flags::NONE; 2], false);
1159 let head = Block::from_usize(0);
1162 let params = func[head].params.to_vec();
1163 let store = func
1164 .insts(Block::from_usize(2))
1165 .find(|&inst| func[inst].opcode == Opcode::Store)
1166 .expect("the second arm's store");
1167 let args = func.push_values(&[params[1], params[0]]);
1168 func[store].args = args;
1169
1170 let stats = phiopt(&mut func);
1171 assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 1);
1172 assert_eq!(
1173 opcodes(&func, 0),
1174 vec![Opcode::IConst, Opcode::ICmp, Opcode::Store, Opcode::Jump]
1175 );
1176 }
1177
1178 #[test]
1180 fn two_arms_that_store_to_different_addresses_keep_their_branch() {
1181 let mut func = both_arms_store(plain(), [Flags::NONE; 2], true);
1182 let stats = phiopt(&mut func);
1183 assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 0);
1184 assert_eq!(stats.count(Kind::Missed, super::STORES_DO_NOT_MATCH), 1);
1185 assert_eq!(goes_to(&func, 0), vec![1, 2]);
1186 }
1187
1188 #[test]
1189 fn a_volatile_store_keeps_its_branch_even_when_both_arms_make_it() {
1190 let mut func = both_arms_store(plain(), [Flags::VOLATILE; 2], false);
1191 let stats = phiopt(&mut func);
1192 assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 0);
1193 assert_eq!(stats.count(Kind::Missed, super::STORES_DO_NOT_MATCH), 1);
1194 assert_eq!(goes_to(&func, 0), vec![1, 2]);
1195 }
1196
1197 #[test]
1198 fn an_atomic_store_keeps_its_branch_even_when_both_arms_make_it() {
1199 let mut func = both_arms_store(
1200 MemInfo { order: MemOrder::SeqCst, ..plain() },
1201 [Flags::NONE; 2],
1202 false,
1203 );
1204 let stats = phiopt(&mut func);
1205 assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 0);
1206 assert_eq!(stats.count(Kind::Missed, super::STORES_DO_NOT_MATCH), 1);
1207 }
1208
1209 #[test]
1211 fn two_stores_that_disagree_about_the_access_keep_their_branch() {
1212 let mut func = both_arms_store(plain(), [Flags::NONE; 2], false);
1213 let store = func
1214 .insts(Block::from_usize(2))
1215 .find(|&inst| func[inst].opcode == Opcode::Store)
1216 .expect("the second arm's store");
1217 let mem = func.add_mem(MemInfo { align: 1, ..plain() });
1218 func[store].extra = rucc_ir::Extra::Mem(mem);
1219
1220 let stats = phiopt(&mut func);
1221 assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 0);
1222 assert_eq!(stats.count(Kind::Missed, super::STORES_DO_NOT_MATCH), 1);
1223 }
1224
1225 #[test]
1230 fn a_store_each_way_does_not_count_against_how_long_the_arms_may_be() {
1231 let mut func = both_arms_store(plain(), [Flags::NONE; 2], false);
1232 let params = func[Block::from_usize(0)].params.to_vec();
1233 for arm in [1, 2] {
1234 let block = Block::from_usize(arm);
1235 let term = func.terminator(block).expect("an arm ends in its jump");
1236 func.remove_inst(term);
1237 let mut build = Builder::new(&mut func, block);
1238 let mut value = params[1];
1239 for _ in 0..rucc_cost::heuristics::PHIOPT_ARM_INSTRUCTIONS {
1240 value = build.binary(Opcode::Add, value, params[2], Flags::NONE);
1241 }
1242 func.append_inst(block, term);
1243 }
1244
1245 let stats = phiopt(&mut func);
1246 assert_eq!(stats.count(Kind::Missed, super::ARMS_TOO_LONG), 0);
1247 assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 1);
1248 }
1249
1250 #[test]
1252 fn an_arm_that_stores_where_the_other_does_not_keeps_its_branch() {
1253 let mut names = Interner::new();
1254 let signature = Signature::new().with_params(&[Type::int(32)]);
1255 let mut func = Func::new(names.intern("f"), signature);
1256 let head = func.create_block();
1257 let outside = func.append_param(head, Type::int(32));
1258 let arms = [func.create_block(), func.create_block()];
1259 let join = func.create_block();
1260 let param = func.append_param(join, Type::int(32));
1261
1262 let mut build = Builder::new(&mut func, head);
1263 let zero = build.iconst(Type::int(32), 0);
1264 let test = build.icmp(IntPred::Slt, outside, zero);
1265 build.br_if(test, arms[0], &[], arms[1], &[]);
1266 let mut build = Builder::new(&mut func, arms[0]);
1267 store_something(&mut build);
1268 let it = build.iconst(Type::int(32), 1);
1269 build.jump(join, &[it]);
1270 let mut build = Builder::new(&mut func, arms[1]);
1271 let it = build.iconst(Type::int(32), 2);
1272 build.jump(join, &[it]);
1273 let mut build = Builder::new(&mut func, join);
1274 build.ret(&[param]);
1275
1276 let stats = phiopt(&mut func);
1277 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
1278 assert_eq!(stats.count(Kind::Missed, super::STORE_ON_ONE_PATH), 1);
1279 assert_eq!(goes_to(&func, 0), vec![1, 2]);
1280 }
1281
1282 #[test]
1284 fn an_arm_that_does_something_else_keeps_its_branch() {
1285 let mut names = Interner::new();
1286 let signature = Signature::new().with_params(&[Type::int(32)]);
1287 let mut func = Func::new(names.intern("f"), signature);
1288 let head = func.create_block();
1289 let outside = func.append_param(head, Type::int(32));
1290 let arms = [func.create_block(), func.create_block()];
1291 let join = func.create_block();
1292 let param = func.append_param(join, Type::int(32));
1293
1294 let mut build = Builder::new(&mut func, head);
1295 let zero = build.iconst(Type::int(32), 0);
1296 let test = build.icmp(IntPred::Slt, outside, zero);
1297 build.br_if(test, arms[0], &[], arms[1], &[]);
1298 let mut build = Builder::new(&mut func, arms[0]);
1299 let address = build.iconst(Type::int(64), 16);
1300 let address = build.unary(Opcode::IntToPtr, address, Type::PTR);
1301 let it = build.load(Type::int(32), address, plain(), Flags::NONE);
1302 build.jump(join, &[it]);
1303 let mut build = Builder::new(&mut func, arms[1]);
1304 let it = build.iconst(Type::int(32), 2);
1305 build.jump(join, &[it]);
1306 let mut build = Builder::new(&mut func, join);
1307 build.ret(&[param]);
1308
1309 let stats = phiopt(&mut func);
1310 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
1311 assert_eq!(stats.count(Kind::Missed, super::ARM_HAS_EFFECTS), 1);
1312 assert_eq!(goes_to(&func, 0), vec![1, 2]);
1313 }
1314
1315 #[test]
1317 fn an_arm_that_divides_by_something_unknown_keeps_its_branch() {
1318 let mut names = Interner::new();
1319 let signature = Signature::new().with_params(&[Type::int(32), Type::int(32)]);
1320 let mut func = Func::new(names.intern("f"), signature);
1321 let head = func.create_block();
1322 let left = func.append_param(head, Type::int(32));
1323 let right = func.append_param(head, Type::int(32));
1324 let arms = [func.create_block(), func.create_block()];
1325 let join = func.create_block();
1326 let param = func.append_param(join, Type::int(32));
1327
1328 let mut build = Builder::new(&mut func, head);
1329 let zero = build.iconst(Type::int(32), 0);
1330 let test = build.icmp(IntPred::Ne, right, zero);
1331 build.br_if(test, arms[0], &[], arms[1], &[]);
1332 let mut build = Builder::new(&mut func, arms[0]);
1333 let it = build.binary(Opcode::SDiv, left, right, Flags::NONE);
1334 build.jump(join, &[it]);
1335 let mut build = Builder::new(&mut func, arms[1]);
1336 let it = build.iconst(Type::int(32), 0);
1337 build.jump(join, &[it]);
1338 let mut build = Builder::new(&mut func, join);
1339 build.ret(&[param]);
1340
1341 let stats = phiopt(&mut func);
1342 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
1343 assert_eq!(stats.count(Kind::Missed, super::ARM_MAY_TRAP), 1);
1344 assert_eq!(goes_to(&func, 0), vec![1, 2]);
1345 }
1346
1347 #[test]
1348 fn a_division_by_a_constant_that_is_not_zero_or_minus_one_is_moved() {
1349 let mut names = Interner::new();
1350 let signature = Signature::new().with_params(&[Type::int(32)]);
1351 let mut func = Func::new(names.intern("f"), signature);
1352 let head = func.create_block();
1353 let outside = func.append_param(head, Type::int(32));
1354 let arms = [func.create_block(), func.create_block()];
1355 let join = func.create_block();
1356 let param = func.append_param(join, Type::int(32));
1357
1358 let mut build = Builder::new(&mut func, head);
1359 let zero = build.iconst(Type::int(32), 0);
1360 let test = build.icmp(IntPred::Slt, outside, zero);
1361 build.br_if(test, arms[0], &[], arms[1], &[]);
1362 let mut build = Builder::new(&mut func, arms[0]);
1363 let three = build.iconst(Type::int(32), 3);
1364 let it = build.binary(Opcode::SDiv, outside, three, Flags::NONE);
1365 build.jump(join, &[it]);
1366 let mut build = Builder::new(&mut func, arms[1]);
1367 let it = build.iconst(Type::int(32), 0);
1368 build.jump(join, &[it]);
1369 let mut build = Builder::new(&mut func, join);
1370 build.ret(&[param]);
1371
1372 let stats = phiopt(&mut func);
1373 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1374 assert!(opcodes(&func, 0).contains(&Opcode::SDiv));
1375 }
1376
1377 #[test]
1379 fn a_value_no_select_is_lowered_for_keeps_its_branch() {
1380 let mut names = Interner::new();
1381 let signature = Signature::new().with_params(&[Type::int(32)]);
1382 let mut func = Func::new(names.intern("f"), signature);
1383 let head = func.create_block();
1384 let outside = func.append_param(head, Type::int(32));
1385 let arms = [func.create_block(), func.create_block()];
1386 let join = func.create_block();
1387 func.append_param(join, Type::PTR);
1388
1389 let mut build = Builder::new(&mut func, head);
1390 let zero = build.iconst(Type::int(32), 0);
1391 let test = build.icmp(IntPred::Slt, outside, zero);
1392 build.br_if(test, arms[0], &[], arms[1], &[]);
1393 for (arm, value) in arms.iter().zip([16, 32]) {
1394 let mut build = Builder::new(&mut func, *arm);
1395 let it = build.iconst(Type::int(64), value);
1396 let it = build.unary(Opcode::IntToPtr, it, Type::PTR);
1397 build.jump(join, &[it]);
1398 }
1399 let mut build = Builder::new(&mut func, join);
1400 build.ret(&[]);
1401
1402 let stats = phiopt(&mut func);
1403 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
1404 assert_eq!(stats.count(Kind::Missed, super::NO_SELECT_AT_THAT_WIDTH), 1);
1405 }
1406
1407 #[test]
1408 fn arms_with_more_work_in_them_than_the_budget_keep_their_branch() {
1409 let mut names = Interner::new();
1410 let signature = Signature::new().with_params(&[Type::int(32)]);
1411 let mut func = Func::new(names.intern("f"), signature);
1412 let head = func.create_block();
1413 let outside = func.append_param(head, Type::int(32));
1414 let arms = [func.create_block(), func.create_block()];
1415 let join = func.create_block();
1416 let param = func.append_param(join, Type::int(32));
1417
1418 let mut build = Builder::new(&mut func, head);
1419 let zero = build.iconst(Type::int(32), 0);
1420 let test = build.icmp(IntPred::Slt, outside, zero);
1421 build.br_if(test, arms[0], &[], arms[1], &[]);
1422 let mut build = Builder::new(&mut func, arms[0]);
1423 let mut it = outside;
1425 for _ in 0..4 {
1426 it = build.binary(Opcode::Add, it, outside, Flags::NONE);
1427 }
1428 build.jump(join, &[it]);
1429 let mut build = Builder::new(&mut func, arms[1]);
1430 let it = build.iconst(Type::int(32), 0);
1431 build.jump(join, &[it]);
1432 let mut build = Builder::new(&mut func, join);
1433 build.ret(&[param]);
1434
1435 let stats = phiopt(&mut func);
1436 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
1437 assert_eq!(stats.count(Kind::Missed, super::ARMS_TOO_LONG), 1);
1438 }
1439
1440 #[test]
1447 fn the_margin_is_a_quarter_in_from_each_end() {
1448 let guessed = |percent: u32| Probability::percent(percent, Quality::Guessed);
1449 assert!(super::unpredictable(Probability::even()));
1450 assert!(super::unpredictable(guessed(25)));
1451 assert!(super::unpredictable(guessed(75)));
1452 assert!(!super::unpredictable(guessed(24)));
1453 assert!(!super::unpredictable(guessed(76)));
1454 assert!(!super::unpredictable(Probability::always()));
1455 assert!(!super::unpredictable(Probability::never()));
1456 }
1457
1458 #[test]
1459 fn an_arm_that_two_edges_reach_is_not_an_arm() {
1460 let mut names = Interner::new();
1461 let signature = Signature::new().with_params(&[Type::int(32)]);
1462 let mut func = Func::new(names.intern("f"), signature);
1463 let head = func.create_block();
1464 let outside = func.append_param(head, Type::int(32));
1465 let above = func.create_block();
1466 let arms = [func.create_block(), func.create_block()];
1467 let join = func.create_block();
1468 let param = func.append_param(join, Type::int(32));
1469
1470 let mut build = Builder::new(&mut func, head);
1473 let zero = build.iconst(Type::int(32), 0);
1474 let first = build.icmp(IntPred::Slt, outside, zero);
1475 build.br_if(first, above, &[], arms[0], &[]);
1476 let mut build = Builder::new(&mut func, above);
1477 let one = build.iconst(Type::int(32), 1);
1478 let second = build.icmp(IntPred::Slt, outside, one);
1479 build.br_if(second, arms[0], &[], arms[1], &[]);
1480 for (arm, value) in arms.iter().zip([1, 2]) {
1481 let mut build = Builder::new(&mut func, *arm);
1482 let it = build.iconst(Type::int(32), value);
1483 build.jump(join, &[it]);
1484 }
1485 let mut build = Builder::new(&mut func, join);
1486 build.ret(&[param]);
1487
1488 let stats = phiopt(&mut func);
1489 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
1493 assert_eq!(goes_to(&func, 0), vec![1, 2]);
1494 assert_eq!(goes_to(&func, 1), vec![2, 3]);
1495 }
1496
1497 #[test]
1498 fn fuel_stops_the_conversion_where_it_stands() {
1499 let mut func = empty_arms();
1500 let mut fuel = Fuel::of(0);
1501 let stats = PhiOpt.run(&mut func, &mut Analyses::new(), &mut fuel);
1502 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
1503 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
1504 assert_eq!(goes_to(&func, 0), vec![1, 2]);
1505 }
1506
1507 fn same_operation(steps: &[Opcode]) -> Func {
1515 let mut names = Interner::new();
1516 let int = Type::int(32);
1517 let signature = Signature::new().with_params(&[int, int, int, int]);
1518 let mut func = Func::new(names.intern("f"), signature);
1519 let head = func.create_block();
1520 let left = func.append_param(head, int);
1521 let right = func.append_param(head, int);
1522 let operands = [func.append_param(head, int), func.append_param(head, int)];
1523 let arms = [func.create_block(), func.create_block()];
1524 let join = func.create_block();
1525 let params: Vec<Value> = steps.iter().map(|_| func.append_param(join, int)).collect();
1526
1527 let mut build = Builder::new(&mut func, head);
1528 let shared = build.iconst(int, 3);
1531 let test = build.icmp(IntPred::Slt, left, right);
1532 build.br_if(test, arms[0], &[], arms[1], &[]);
1533 for (&arm, operand) in arms.iter().zip(operands) {
1534 let mut build = Builder::new(&mut func, arm);
1535 let carried: Vec<Value> = steps
1536 .iter()
1537 .map(|&opcode| build.binary(opcode, operand, shared, Flags::default()))
1538 .collect();
1539 build.jump(join, &carried);
1540 }
1541 let mut build = Builder::new(&mut func, join);
1542 build.ret(¶ms);
1543 func
1544 }
1545
1546 #[test]
1548 fn an_operation_both_arms_did_is_done_once_below_the_branch() {
1549 let mut func = same_operation(&[Opcode::Add]);
1550 let stats = phiopt(&mut func);
1551 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 1);
1552 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1553 assert_eq!(
1554 opcodes(&func, 0),
1555 vec![Opcode::IConst, Opcode::ICmp, Opcode::Select, Opcode::Add, Opcode::Jump],
1556 "the select chooses the operand and the add happens once"
1557 );
1558 assert_eq!(blocks(&func), vec![0, 3]);
1559 }
1560
1561 #[test]
1565 fn the_select_chooses_the_operands_and_not_the_answers() {
1566 let mut func = same_operation(&[Opcode::Add]);
1567 phiopt(&mut func);
1568 let head = Block::from_usize(0);
1569 let select = func
1570 .insts(head)
1571 .find(|&inst| func[inst].opcode == Opcode::Select)
1572 .expect("the select the pass just built");
1573 let add = func
1574 .insts(head)
1575 .find(|&inst| func[inst].opcode == Opcode::Add)
1576 .expect("the add the pass just wrote");
1577 let chosen = func[func[select].args].to_vec();
1578 let params = func[head].params.to_vec();
1579 assert_eq!(&chosen[1..], ¶ms[2..], "the two operands the arms differed in");
1580 let added = func[func[add].args].to_vec();
1581 assert_eq!(added[0], func[select].first_result.expect("a select has a result"));
1582 assert_eq!(carries(&func, 0), vec![func[add].first_result.expect("an add has a result")]);
1583 }
1584
1585 #[test]
1589 fn arms_that_factor_away_entirely_are_not_too_long() {
1590 let steps = [Opcode::Add, Opcode::Sub, Opcode::Mul];
1591 let mut func = same_operation(&steps);
1592 let stats = phiopt(&mut func);
1593 assert_eq!(stats.count(Kind::Missed, super::ARMS_TOO_LONG), 0);
1594 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 3);
1595 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1596 let written = opcodes(&func, 0);
1597 assert_eq!(written.iter().filter(|&&op| op == Opcode::Select).count(), 3);
1598 for step in steps {
1599 assert_eq!(written.iter().filter(|&&op| op == step).count(), 1, "{step:?} once");
1600 }
1601 }
1602
1603 #[test]
1606 fn arms_that_agree_in_every_operand_need_no_select() {
1607 let mut names = Interner::new();
1608 let int = Type::int(32);
1609 let signature = Signature::new().with_params(&[int, int, int]);
1610 let mut func = Func::new(names.intern("f"), signature);
1611 let head = func.create_block();
1612 let left = func.append_param(head, int);
1613 let right = func.append_param(head, int);
1614 let operand = func.append_param(head, int);
1615 let arms = [func.create_block(), func.create_block()];
1616 let join = func.create_block();
1617 let param = func.append_param(join, int);
1618
1619 let mut build = Builder::new(&mut func, head);
1620 let shared = build.iconst(int, 3);
1621 let test = build.icmp(IntPred::Slt, left, right);
1622 build.br_if(test, arms[0], &[], arms[1], &[]);
1623 for &arm in &arms {
1624 let mut build = Builder::new(&mut func, arm);
1625 let it = build.binary(Opcode::Add, operand, shared, Flags::default());
1626 build.jump(join, &[it]);
1627 }
1628 let mut build = Builder::new(&mut func, join);
1629 build.ret(&[param]);
1630
1631 let stats = phiopt(&mut func);
1632 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 1);
1633 assert_eq!(
1634 opcodes(&func, 0),
1635 vec![Opcode::IConst, Opcode::ICmp, Opcode::Add, Opcode::Jump],
1636 "one add and nothing to choose between"
1637 );
1638 }
1639
1640 #[test]
1643 fn arms_that_do_different_things_are_not_factored() {
1644 let mut names = Interner::new();
1645 let int = Type::int(32);
1646 let signature = Signature::new().with_params(&[int, int, int, int]);
1647 let mut func = Func::new(names.intern("f"), signature);
1648 let head = func.create_block();
1649 let left = func.append_param(head, int);
1650 let right = func.append_param(head, int);
1651 let operands = [func.append_param(head, int), func.append_param(head, int)];
1652 let arms = [func.create_block(), func.create_block()];
1653 let join = func.create_block();
1654 let param = func.append_param(join, int);
1655
1656 let mut build = Builder::new(&mut func, head);
1657 let shared = build.iconst(int, 3);
1658 let test = build.icmp(IntPred::Slt, left, right);
1659 build.br_if(test, arms[0], &[], arms[1], &[]);
1660 for ((&arm, operand), opcode) in arms.iter().zip(operands).zip([Opcode::Add, Opcode::Sub]) {
1661 let mut build = Builder::new(&mut func, arm);
1662 let it = build.binary(opcode, operand, shared, Flags::default());
1663 build.jump(join, &[it]);
1664 }
1665 let mut build = Builder::new(&mut func, join);
1666 build.ret(&[param]);
1667
1668 let stats = phiopt(&mut func);
1669 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 0);
1670 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1671 assert_eq!(
1672 opcodes(&func, 0),
1673 vec![
1674 Opcode::IConst,
1675 Opcode::ICmp,
1676 Opcode::Add,
1677 Opcode::Sub,
1678 Opcode::Select,
1679 Opcode::Jump
1680 ],
1681 "both operations hoisted and a select between their answers"
1682 );
1683 }
1684
1685 #[test]
1688 fn arms_that_differ_in_two_operands_are_not_factored() {
1689 let mut names = Interner::new();
1690 let int = Type::int(32);
1691 let signature = Signature::new().with_params(&[int, int, int, int, int, int]);
1692 let mut func = Func::new(names.intern("f"), signature);
1693 let head = func.create_block();
1694 let left = func.append_param(head, int);
1695 let right = func.append_param(head, int);
1696 let first = [func.append_param(head, int), func.append_param(head, int)];
1697 let second = [func.append_param(head, int), func.append_param(head, int)];
1698 let arms = [func.create_block(), func.create_block()];
1699 let join = func.create_block();
1700 let param = func.append_param(join, int);
1701
1702 let mut build = Builder::new(&mut func, head);
1703 let test = build.icmp(IntPred::Slt, left, right);
1704 build.br_if(test, arms[0], &[], arms[1], &[]);
1705 for ((&arm, one), two) in arms.iter().zip(first).zip(second) {
1706 let mut build = Builder::new(&mut func, arm);
1707 let it = build.binary(Opcode::Add, one, two, Flags::default());
1708 build.jump(join, &[it]);
1709 }
1710 let mut build = Builder::new(&mut func, join);
1711 build.ret(&[param]);
1712
1713 let stats = phiopt(&mut func);
1714 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 0);
1715 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1716 assert_eq!(opcodes(&func, 0).iter().filter(|&&op| op == Opcode::Add).count(), 2);
1717 }
1718
1719 #[test]
1723 fn an_operation_read_more_than_once_is_not_factored() {
1724 let mut names = Interner::new();
1725 let int = Type::int(32);
1726 let signature = Signature::new().with_params(&[int, int, int, int]);
1727 let mut func = Func::new(names.intern("f"), signature);
1728 let head = func.create_block();
1729 let left = func.append_param(head, int);
1730 let right = func.append_param(head, int);
1731 let operands = [func.append_param(head, int), func.append_param(head, int)];
1732 let arms = [func.create_block(), func.create_block()];
1733 let join = func.create_block();
1734 let params = [func.append_param(join, int), func.append_param(join, int)];
1735
1736 let mut build = Builder::new(&mut func, head);
1737 let shared = build.iconst(int, 3);
1738 let test = build.icmp(IntPred::Slt, left, right);
1739 build.br_if(test, arms[0], &[], arms[1], &[]);
1740 for (&arm, operand) in arms.iter().zip(operands) {
1741 let mut build = Builder::new(&mut func, arm);
1742 let it = build.binary(Opcode::Add, operand, shared, Flags::default());
1743 build.jump(join, &[it, it]);
1744 }
1745 let mut build = Builder::new(&mut func, join);
1746 build.ret(¶ms);
1747
1748 let stats = phiopt(&mut func);
1749 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 0);
1750 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1751 assert_eq!(opcodes(&func, 0).iter().filter(|&&op| op == Opcode::Add).count(), 2);
1752 }
1753
1754 #[test]
1757 fn a_triangle_factors_nothing() {
1758 let mut names = Interner::new();
1759 let int = Type::int(32);
1760 let signature = Signature::new().with_params(&[int, int, int]);
1761 let mut func = Func::new(names.intern("f"), signature);
1762 let head = func.create_block();
1763 let left = func.append_param(head, int);
1764 let right = func.append_param(head, int);
1765 let operand = func.append_param(head, int);
1766 let arm = func.create_block();
1767 let join = func.create_block();
1768 let param = func.append_param(join, int);
1769
1770 let mut build = Builder::new(&mut func, head);
1771 let shared = build.iconst(int, 3);
1772 let test = build.icmp(IntPred::Slt, left, right);
1773 build.br_if(test, arm, &[], join, &[operand]);
1774 let mut build = Builder::new(&mut func, arm);
1775 let it = build.binary(Opcode::Add, operand, shared, Flags::default());
1776 build.jump(join, &[it]);
1777 let mut build = Builder::new(&mut func, join);
1778 build.ret(&[param]);
1779
1780 let stats = phiopt(&mut func);
1781 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 0);
1782 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1783 }
1784}