1use rucc_cost::heuristics;
145use rucc_ir::{Block, Builder, Func, Inst, Opcode, Type, Value};
146
147use crate::cfg::Cfg;
148use crate::fold::constant;
149use crate::profile::Probability;
150use crate::simplify_cfg::{self, Bindings};
151use crate::{Analyses, Fuel, Pass, Preserved, Stats};
152
153const CONVERTED: &str =
155 "branch whose two arms only work out a value replaced by the value and no branch";
156
157const ARM_HAS_EFFECTS: &str =
159 "branch kept, an arm does something that only happens on the path it is on";
160
161const ARM_MAY_TRAP: &str = "branch kept, an arm divides and doing it on both paths could trap";
163
164const NO_SELECT_AT_THAT_WIDTH: &str =
166 "branch kept, the value the arms disagree about is not a width a select is lowered at";
167
168const ARMS_TOO_LONG: &str = "branch kept, its arms are more work than doing both of them is worth";
170
171const BRANCH_IS_PREDICTED: &str =
173 "branch kept, it goes one way often enough that the machine will predict it";
174
175const CONDITION_IS_DECIDED: &str =
177 "branch kept, its condition is already known and the arm that cannot run is better deleted";
178const NO_FUEL: &str = "branch kept, the pass ran out of fuel";
179
180#[derive(Debug, Clone, Copy, PartialEq, Eq)]
182pub struct PhiOpt;
183
184impl Pass for PhiOpt {
185 fn name(&self) -> &'static str {
186 "phiopt"
187 }
188
189 fn describe(&self) -> &'static str {
190 "a branch whose two arms only work out a value becomes a select, and the branch goes"
191 }
192
193 fn preserves(&self) -> Preserved {
194 Preserved::NONE
197 }
198
199 fn run(&self, func: &mut Func, an: &mut Analyses, fuel: &mut Fuel) -> Stats {
200 let mut stats = Stats::new();
201 if func.entry().is_none() {
202 return stats;
203 }
204 for head in func.blocks().collect::<Vec<Block>>() {
205 let cfg = an.cfg(func);
206 if !cfg.reaches(head) {
207 continue;
208 }
209 let Some(shape) = diamond(func, cfg, head) else { continue };
210 if let Some(reason) = refused(func, &shape) {
211 stats.missed(reason);
212 continue;
213 }
214 let work = shape.arms.map(|arm| arm.map_or(0, |block| length(func, block)));
215 if work.iter().any(|&count| count > 0) {
216 if work.iter().any(|&count| count > heuristics::PHIOPT_ARM_INSTRUCTIONS) {
217 stats.missed(ARMS_TOO_LONG);
218 continue;
219 }
220 if !unpredictable(an.frequencies(func).taken(head, 0)) {
225 stats.missed(BRANCH_IS_PREDICTED);
226 continue;
227 }
228 }
229 if !fuel.take() {
230 stats.missed(NO_FUEL);
234 break;
235 }
236 convert(func, &shape);
237 an.clear();
240 stats.optimized(CONVERTED);
241 }
242 stats
243 }
244}
245
246struct Diamond {
248 head: Block,
250 cond: Value,
252 join: Block,
254 arms: [Option<Block>; 2],
259 args: [Vec<Value>; 2],
261}
262
263fn diamond(func: &Func, cfg: &Cfg, head: Block) -> Option<Diamond> {
265 let entry = cfg.entry()?;
266 let term = func.terminator(head)?;
267 if func[term].opcode != Opcode::BrIf {
268 return None;
269 }
270 let cond = *func[func[term].args].first()?;
271 let mut targets = func.successors(term);
272 let sides = [targets.next()?, targets.next()?];
273 if sides[0].block == sides[1].block {
277 return None;
278 }
279 let through = [
280 passes_through(func, cfg, head, sides[0].block),
281 passes_through(func, cfg, head, sides[1].block),
282 ];
283 let join = match through {
286 [Some(left), Some(right)] if left == right => left,
287 [Some(left), _] if left == sides[1].block => left,
288 [_, Some(right)] if right == sides[0].block => right,
289 _ => return None,
290 };
291 if join == head || join == entry {
294 return None;
295 }
296 let arms = [
297 (sides[0].block != join).then_some(sides[0].block),
298 (sides[1].block != join).then_some(sides[1].block),
299 ];
300 let mut args = [Vec::new(), Vec::new()];
301 for (index, side) in sides.iter().enumerate() {
302 let carried = match arms[index] {
303 Some(arm) => func.successors(func.terminator(arm)?).next()?.args,
305 None => side.args,
306 };
307 args[index] = func[carried].to_vec();
308 }
309 Some(Diamond { head, cond, join, arms, args })
310}
311
312fn passes_through(func: &Func, cfg: &Cfg, head: Block, block: Block) -> Option<Block> {
320 if !func[block].params.is_empty() {
321 return None;
322 }
323 match cfg.predecessors(block) {
324 [only] if *only == head => {}
325 _ => return None,
326 }
327 let term = func.terminator(block)?;
328 if func[term].opcode != Opcode::Jump {
329 return None;
330 }
331 Some(func.successors(term).next()?.block)
332}
333
334fn refused(func: &Func, shape: &Diamond) -> Option<&'static str> {
336 let term = func.terminator(shape.head).expect("the head of a diamond ends in its branch");
347 if simplify_cfg::taken(func, term, &Bindings::new()).is_some() {
348 return Some(CONDITION_IS_DECIDED);
349 }
350 for &arm in shape.arms.iter().flatten() {
351 for inst in func.insts(arm) {
352 if func.is_terminator(inst) {
353 continue;
354 }
355 if func[inst].opcode.has_effects() {
356 return Some(ARM_HAS_EFFECTS);
357 }
358 if !speculatable(func, inst) {
359 return Some(ARM_MAY_TRAP);
360 }
361 }
362 }
363 let params = func[shape.join].params.iter();
364 for ((¶m, &then), &other) in params.zip(&shape.args[0]).zip(&shape.args[1]) {
365 if agree(func, then, other) {
368 continue;
369 }
370 if !selectable(func[param].ty) {
371 return Some(NO_SELECT_AT_THAT_WIDTH);
372 }
373 }
374 None
375}
376
377fn agree(func: &Func, then: Value, other: Value) -> bool {
386 if then == other {
387 return true;
388 }
389 let (Some((left, lty)), Some((right, rty))) = (constant(func, then), constant(func, other))
390 else {
391 return false;
392 };
393 lty == rty && left == right
394}
395
396fn speculatable(func: &Func, inst: Inst) -> bool {
403 let opcode = func[inst].opcode;
404 if !matches!(opcode, Opcode::SDiv | Opcode::UDiv | Opcode::SRem | Opcode::URem) {
405 return true;
406 }
407 let Some(&divisor) = func[func[inst].args].get(1) else { return false };
408 let Some((imm, ty)) = constant(func, divisor) else { return false };
409 if imm.unsigned() == 0 {
410 return false;
411 }
412 imm.signed(ty) != -1
413}
414
415fn selectable(ty: Type) -> bool {
421 ty.is_scalar() && ty.is_int() && matches!(ty.bits(), 8 | 16 | 32 | 64)
422}
423
424fn length(func: &Func, block: Block) -> u32 {
426 let count = func.insts(block).filter(|&inst| !func.is_terminator(inst)).count();
427 u32::try_from(count).unwrap_or(u32::MAX)
428}
429
430fn unpredictable(taken: Probability) -> bool {
432 let margin = heuristics::PHIOPT_UNPREDICTABLE_MARGIN_PERCENT * (Probability::SCALE / 100);
433 taken.parts() >= margin && taken.parts() <= Probability::SCALE - margin
434}
435
436fn convert(func: &mut Func, shape: &Diamond) {
443 let term = func.terminator(shape.head).expect("the head of a diamond ends in its branch");
444 let span = func.span(term);
445 func.remove_inst(term);
446 for &arm in shape.arms.iter().flatten() {
447 for inst in func.insts(arm).collect::<Vec<Inst>>() {
448 if func.is_terminator(inst) {
449 continue;
450 }
451 func.remove_inst(inst);
452 func.append_inst(shape.head, inst);
453 }
454 }
455 let mut build = Builder::new(func, shape.head).at(span);
456 let mut args = Vec::with_capacity(shape.args[0].len());
457 for (&then, &other) in shape.args[0].iter().zip(&shape.args[1]) {
458 let same = agree(build.func(), then, other);
461 args.push(if same { then } else { build.select(shape.cond, then, other) });
462 }
463 build.jump(shape.join, &args);
464 for &arm in shape.arms.iter().flatten() {
468 func.remove_block(arm);
469 }
470}
471
472#[cfg(test)]
473mod tests {
474 use rucc_base::Interner;
475 use rucc_ir::{
476 Block, Builder, Flags, Func, IntPred, MemInfo, MemOrder, Opcode, Restrict, Signature, Type,
477 Value,
478 };
479
480 use super::PhiOpt;
481 use crate::profile::{Probability, Quality};
482 use crate::stats::Kind;
483 use crate::{Analyses, Fuel, Pass, Stats};
484
485 fn phiopt(func: &mut Func) -> Stats {
487 PhiOpt.run(func, &mut Analyses::new(), &mut Fuel::unlimited())
488 }
489
490 fn blocks(func: &Func) -> Vec<usize> {
492 func.blocks().map(Block::index).collect()
493 }
494
495 fn goes_to(func: &Func, block: usize) -> Vec<usize> {
497 let block = Block::from_usize(block);
498 let term = func.terminator(block).expect("every block here has one");
499 func.successors(term).map(|call| call.block.index()).collect()
500 }
501
502 fn opcodes(func: &Func, block: usize) -> Vec<Opcode> {
504 let block = Block::from_usize(block);
505 func.insts(block).map(|inst| func[inst].opcode).collect()
506 }
507
508 fn carries(func: &Func, block: usize) -> Vec<Value> {
510 let block = Block::from_usize(block);
511 let term = func.terminator(block).expect("every block here has one");
512 let call = func.successors(term).next().expect("a terminator here has an edge");
513 func[call.args].to_vec()
514 }
515
516 fn store_something(build: &mut Builder<'_>) {
518 let what = build.iconst(Type::int(32), 7);
519 let address = build.iconst(Type::int(64), 16);
520 let address = build.unary(Opcode::IntToPtr, address, Type::PTR);
521 let info = MemInfo {
522 size: 4,
523 align: 4,
524 order: MemOrder::NotAtomic,
525 tbaa: None,
526 restrict: Restrict::NONE,
527 };
528 build.store(what, address, info, Flags::NONE);
529 }
530
531 fn empty_arms() -> Func {
537 let mut names = Interner::new();
538 let signature = Signature::new().with_params(&[Type::int(32), Type::int(32)]);
539 let mut func = Func::new(names.intern("f"), signature);
540 let head = func.create_block();
541 let left = func.append_param(head, Type::int(32));
542 let right = func.append_param(head, Type::int(32));
543 let arms = [func.create_block(), func.create_block()];
544 let join = func.create_block();
545 let param = func.append_param(join, Type::int(32));
546
547 let mut build = Builder::new(&mut func, head);
548 let test = build.icmp(IntPred::Slt, left, right);
549 build.br_if(test, arms[0], &[], arms[1], &[]);
550 for (arm, value) in arms.iter().zip([1, 2]) {
551 let mut build = Builder::new(&mut func, *arm);
552 let it = build.iconst(Type::int(32), value);
553 build.jump(join, &[it]);
554 }
555 let mut build = Builder::new(&mut func, join);
556 build.ret(&[param]);
557 func
558 }
559
560 #[test]
561 fn a_branch_that_is_already_decided_is_left_for_simplify_cfg() {
562 let mut names = Interner::new();
566 let mut func = Func::new(names.intern("f"), Signature::new());
567 let head = func.create_block();
568 let arms = [func.create_block(), func.create_block()];
569 let join = func.create_block();
570 let param = func.append_param(join, Type::int(32));
571
572 let mut build = Builder::new(&mut func, head);
573 let one = build.iconst(Type::int(32), 1);
576 let zero = build.iconst(Type::int(32), 0);
577 let test = build.icmp(IntPred::Ne, one, zero);
578 build.br_if(test, arms[0], &[], arms[1], &[]);
579 for (arm, value) in arms.iter().zip([1, 2]) {
580 let mut build = Builder::new(&mut func, *arm);
581 let it = build.iconst(Type::int(32), value);
582 build.jump(join, &[it]);
583 }
584 let mut build = Builder::new(&mut func, join);
585 build.ret(&[param]);
586
587 let stats = phiopt(&mut func);
588 assert_eq!(stats.count(Kind::Missed, super::CONDITION_IS_DECIDED), 1);
589 assert_eq!(blocks(&func), vec![0, 1, 2, 3]);
590 }
591
592 #[test]
593 fn a_diamond_whose_arms_are_empty_becomes_a_select() {
594 let mut func = empty_arms();
595 let stats = phiopt(&mut func);
596 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
597 assert_eq!(
599 opcodes(&func, 0),
600 vec![Opcode::ICmp, Opcode::IConst, Opcode::IConst, Opcode::Select, Opcode::Jump]
601 );
602 assert_eq!(goes_to(&func, 0), vec![3]);
603 assert_eq!(blocks(&func), vec![0, 3]);
604 }
605
606 #[test]
607 fn the_side_the_condition_holds_on_is_the_side_the_select_takes_first() {
608 let mut func = empty_arms();
609 phiopt(&mut func);
610 let select = func
611 .insts(Block::from_usize(0))
612 .find(|&inst| func[inst].opcode == Opcode::Select)
613 .expect("the select the pass just built");
614 let args = func[func[select].args].to_vec();
615 let one = crate::fold::constant(&func, args[1]).expect("the true arm carried a constant");
616 let two = crate::fold::constant(&func, args[2]).expect("the false arm carried a constant");
617 assert_eq!(one.0.unsigned(), 1, "the arm the branch named first");
618 assert_eq!(two.0.unsigned(), 2, "the arm the branch named second");
619 }
620
621 #[test]
623 fn a_triangle_whose_empty_side_goes_straight_to_the_join_is_converted() {
624 let mut names = Interner::new();
625 let signature = Signature::new().with_params(&[Type::int(32)]);
626 let mut func = Func::new(names.intern("f"), signature);
627 let head = func.create_block();
628 let outside = func.append_param(head, Type::int(32));
629 let arm = func.create_block();
630 let join = func.create_block();
631 let param = func.append_param(join, Type::int(32));
632
633 let mut build = Builder::new(&mut func, head);
634 let zero = build.iconst(Type::int(32), 0);
635 let test = build.icmp(IntPred::Slt, outside, zero);
636 build.br_if(test, arm, &[], join, &[outside]);
637 let mut build = Builder::new(&mut func, arm);
638 let it = build.iconst(Type::int(32), 0);
639 build.jump(join, &[it]);
640 let mut build = Builder::new(&mut func, join);
641 build.ret(&[param]);
642
643 let stats = phiopt(&mut func);
644 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
645 assert_eq!(blocks(&func), vec![0, 2]);
646 assert_eq!(goes_to(&func, 0), vec![2]);
647 assert_eq!(opcodes(&func, 0).last(), Some(&Opcode::Jump));
648 }
649
650 #[test]
651 fn a_parameter_both_sides_agree_about_needs_no_select() {
652 let mut names = Interner::new();
653 let signature = Signature::new().with_params(&[Type::int(32)]);
654 let mut func = Func::new(names.intern("f"), signature);
655 let head = func.create_block();
656 let outside = func.append_param(head, Type::int(32));
657 let arms = [func.create_block(), func.create_block()];
658 let join = func.create_block();
659 let param = func.append_param(join, Type::int(32));
660
661 let mut build = Builder::new(&mut func, head);
662 let zero = build.iconst(Type::int(32), 0);
663 let test = build.icmp(IntPred::Slt, outside, zero);
664 build.br_if(test, arms[0], &[], arms[1], &[]);
665 for arm in arms {
666 let mut build = Builder::new(&mut func, arm);
667 build.jump(join, &[outside]);
668 }
669 let mut build = Builder::new(&mut func, join);
670 build.ret(&[param]);
671
672 let stats = phiopt(&mut func);
673 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
674 assert!(!opcodes(&func, 0).contains(&Opcode::Select), "both sides carried the same value");
675 assert_eq!(carries(&func, 0), vec![outside]);
676 }
677
678 #[test]
679 fn two_sides_carrying_the_same_number_need_no_select_either() {
680 let mut names = Interner::new();
684 let signature = Signature::new().with_params(&[Type::int(32)]);
685 let mut func = Func::new(names.intern("f"), signature);
686 let head = func.create_block();
687 let outside = func.append_param(head, Type::int(32));
688 let arms = [func.create_block(), func.create_block()];
689 let join = func.create_block();
690 let param = func.append_param(join, Type::int(32));
691
692 let mut build = Builder::new(&mut func, head);
693 let zero = build.iconst(Type::int(32), 0);
694 let test = build.icmp(IntPred::Slt, outside, zero);
695 build.br_if(test, arms[0], &[], arms[1], &[]);
696 for arm in arms {
697 let mut build = Builder::new(&mut func, arm);
698 let seven = build.iconst(Type::int(32), 7);
699 build.jump(join, &[seven]);
700 }
701 let mut build = Builder::new(&mut func, join);
702 build.ret(&[param]);
703
704 let stats = phiopt(&mut func);
705 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
706 assert!(!opcodes(&func, 0).contains(&Opcode::Select), "both sides carried a seven");
707 }
708
709 #[test]
710 fn two_sides_carrying_different_numbers_still_get_a_select() {
711 let mut func = empty_arms();
712 let stats = phiopt(&mut func);
713 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
714 assert!(opcodes(&func, 0).contains(&Opcode::Select), "one and two are not the same number");
715 }
716
717 #[test]
718 fn an_arm_that_does_something_keeps_its_branch() {
719 let mut names = Interner::new();
720 let signature = Signature::new().with_params(&[Type::int(32)]);
721 let mut func = Func::new(names.intern("f"), signature);
722 let head = func.create_block();
723 let outside = func.append_param(head, Type::int(32));
724 let arms = [func.create_block(), func.create_block()];
725 let join = func.create_block();
726 let param = func.append_param(join, Type::int(32));
727
728 let mut build = Builder::new(&mut func, head);
729 let zero = build.iconst(Type::int(32), 0);
730 let test = build.icmp(IntPred::Slt, outside, zero);
731 build.br_if(test, arms[0], &[], arms[1], &[]);
732 let mut build = Builder::new(&mut func, arms[0]);
733 store_something(&mut build);
734 let it = build.iconst(Type::int(32), 1);
735 build.jump(join, &[it]);
736 let mut build = Builder::new(&mut func, arms[1]);
737 let it = build.iconst(Type::int(32), 2);
738 build.jump(join, &[it]);
739 let mut build = Builder::new(&mut func, join);
740 build.ret(&[param]);
741
742 let stats = phiopt(&mut func);
743 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
744 assert_eq!(stats.count(Kind::Missed, super::ARM_HAS_EFFECTS), 1);
745 assert_eq!(goes_to(&func, 0), vec![1, 2]);
746 }
747
748 #[test]
750 fn an_arm_that_divides_by_something_unknown_keeps_its_branch() {
751 let mut names = Interner::new();
752 let signature = Signature::new().with_params(&[Type::int(32), Type::int(32)]);
753 let mut func = Func::new(names.intern("f"), signature);
754 let head = func.create_block();
755 let left = func.append_param(head, Type::int(32));
756 let right = func.append_param(head, Type::int(32));
757 let arms = [func.create_block(), func.create_block()];
758 let join = func.create_block();
759 let param = func.append_param(join, Type::int(32));
760
761 let mut build = Builder::new(&mut func, head);
762 let zero = build.iconst(Type::int(32), 0);
763 let test = build.icmp(IntPred::Ne, right, zero);
764 build.br_if(test, arms[0], &[], arms[1], &[]);
765 let mut build = Builder::new(&mut func, arms[0]);
766 let it = build.binary(Opcode::SDiv, left, right, Flags::NONE);
767 build.jump(join, &[it]);
768 let mut build = Builder::new(&mut func, arms[1]);
769 let it = build.iconst(Type::int(32), 0);
770 build.jump(join, &[it]);
771 let mut build = Builder::new(&mut func, join);
772 build.ret(&[param]);
773
774 let stats = phiopt(&mut func);
775 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
776 assert_eq!(stats.count(Kind::Missed, super::ARM_MAY_TRAP), 1);
777 assert_eq!(goes_to(&func, 0), vec![1, 2]);
778 }
779
780 #[test]
781 fn a_division_by_a_constant_that_is_not_zero_or_minus_one_is_moved() {
782 let mut names = Interner::new();
783 let signature = Signature::new().with_params(&[Type::int(32)]);
784 let mut func = Func::new(names.intern("f"), signature);
785 let head = func.create_block();
786 let outside = func.append_param(head, Type::int(32));
787 let arms = [func.create_block(), func.create_block()];
788 let join = func.create_block();
789 let param = func.append_param(join, Type::int(32));
790
791 let mut build = Builder::new(&mut func, head);
792 let zero = build.iconst(Type::int(32), 0);
793 let test = build.icmp(IntPred::Slt, outside, zero);
794 build.br_if(test, arms[0], &[], arms[1], &[]);
795 let mut build = Builder::new(&mut func, arms[0]);
796 let three = build.iconst(Type::int(32), 3);
797 let it = build.binary(Opcode::SDiv, outside, three, Flags::NONE);
798 build.jump(join, &[it]);
799 let mut build = Builder::new(&mut func, arms[1]);
800 let it = build.iconst(Type::int(32), 0);
801 build.jump(join, &[it]);
802 let mut build = Builder::new(&mut func, join);
803 build.ret(&[param]);
804
805 let stats = phiopt(&mut func);
806 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
807 assert!(opcodes(&func, 0).contains(&Opcode::SDiv));
808 }
809
810 #[test]
812 fn a_value_no_select_is_lowered_for_keeps_its_branch() {
813 let mut names = Interner::new();
814 let signature = Signature::new().with_params(&[Type::int(32)]);
815 let mut func = Func::new(names.intern("f"), signature);
816 let head = func.create_block();
817 let outside = func.append_param(head, Type::int(32));
818 let arms = [func.create_block(), func.create_block()];
819 let join = func.create_block();
820 func.append_param(join, Type::PTR);
821
822 let mut build = Builder::new(&mut func, head);
823 let zero = build.iconst(Type::int(32), 0);
824 let test = build.icmp(IntPred::Slt, outside, zero);
825 build.br_if(test, arms[0], &[], arms[1], &[]);
826 for (arm, value) in arms.iter().zip([16, 32]) {
827 let mut build = Builder::new(&mut func, *arm);
828 let it = build.iconst(Type::int(64), value);
829 let it = build.unary(Opcode::IntToPtr, it, Type::PTR);
830 build.jump(join, &[it]);
831 }
832 let mut build = Builder::new(&mut func, join);
833 build.ret(&[]);
834
835 let stats = phiopt(&mut func);
836 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
837 assert_eq!(stats.count(Kind::Missed, super::NO_SELECT_AT_THAT_WIDTH), 1);
838 }
839
840 #[test]
841 fn arms_with_more_work_in_them_than_the_budget_keep_their_branch() {
842 let mut names = Interner::new();
843 let signature = Signature::new().with_params(&[Type::int(32)]);
844 let mut func = Func::new(names.intern("f"), signature);
845 let head = func.create_block();
846 let outside = func.append_param(head, Type::int(32));
847 let arms = [func.create_block(), func.create_block()];
848 let join = func.create_block();
849 let param = func.append_param(join, Type::int(32));
850
851 let mut build = Builder::new(&mut func, head);
852 let zero = build.iconst(Type::int(32), 0);
853 let test = build.icmp(IntPred::Slt, outside, zero);
854 build.br_if(test, arms[0], &[], arms[1], &[]);
855 let mut build = Builder::new(&mut func, arms[0]);
856 let mut it = outside;
858 for _ in 0..4 {
859 it = build.binary(Opcode::Add, it, outside, Flags::NONE);
860 }
861 build.jump(join, &[it]);
862 let mut build = Builder::new(&mut func, arms[1]);
863 let it = build.iconst(Type::int(32), 0);
864 build.jump(join, &[it]);
865 let mut build = Builder::new(&mut func, join);
866 build.ret(&[param]);
867
868 let stats = phiopt(&mut func);
869 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
870 assert_eq!(stats.count(Kind::Missed, super::ARMS_TOO_LONG), 1);
871 }
872
873 #[test]
880 fn the_margin_is_a_quarter_in_from_each_end() {
881 let guessed = |percent: u32| Probability::percent(percent, Quality::Guessed);
882 assert!(super::unpredictable(Probability::even()));
883 assert!(super::unpredictable(guessed(25)));
884 assert!(super::unpredictable(guessed(75)));
885 assert!(!super::unpredictable(guessed(24)));
886 assert!(!super::unpredictable(guessed(76)));
887 assert!(!super::unpredictable(Probability::always()));
888 assert!(!super::unpredictable(Probability::never()));
889 }
890
891 #[test]
892 fn an_arm_that_two_edges_reach_is_not_an_arm() {
893 let mut names = Interner::new();
894 let signature = Signature::new().with_params(&[Type::int(32)]);
895 let mut func = Func::new(names.intern("f"), signature);
896 let head = func.create_block();
897 let outside = func.append_param(head, Type::int(32));
898 let above = func.create_block();
899 let arms = [func.create_block(), func.create_block()];
900 let join = func.create_block();
901 let param = func.append_param(join, Type::int(32));
902
903 let mut build = Builder::new(&mut func, head);
906 let zero = build.iconst(Type::int(32), 0);
907 let first = build.icmp(IntPred::Slt, outside, zero);
908 build.br_if(first, above, &[], arms[0], &[]);
909 let mut build = Builder::new(&mut func, above);
910 let one = build.iconst(Type::int(32), 1);
911 let second = build.icmp(IntPred::Slt, outside, one);
912 build.br_if(second, arms[0], &[], arms[1], &[]);
913 for (arm, value) in arms.iter().zip([1, 2]) {
914 let mut build = Builder::new(&mut func, *arm);
915 let it = build.iconst(Type::int(32), value);
916 build.jump(join, &[it]);
917 }
918 let mut build = Builder::new(&mut func, join);
919 build.ret(&[param]);
920
921 let stats = phiopt(&mut func);
922 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
926 assert_eq!(goes_to(&func, 0), vec![1, 2]);
927 assert_eq!(goes_to(&func, 1), vec![2, 3]);
928 }
929
930 #[test]
931 fn fuel_stops_the_conversion_where_it_stands() {
932 let mut func = empty_arms();
933 let mut fuel = Fuel::of(0);
934 let stats = PhiOpt.run(&mut func, &mut Analyses::new(), &mut fuel);
935 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
936 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
937 assert_eq!(goes_to(&func, 0), vec![1, 2]);
938 }
939}