1use rucc_ir::{
85 Block, Def, Extra, Flags, Func, Imm, Inst, InstData, IntPred, Opcode, Type, Value, ValueList,
86};
87
88use crate::uses::count;
89use crate::{Analyses, Analysis, Fuel, Pass, Preserved, Stats};
90
91const NARROWED: &str = "arithmetic redone at the width the program truncates it to";
93
94const NO_FUEL: &str = "arithmetic left wide, the pass ran out of fuel";
96
97const DEPTH: u32 = 6;
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107pub struct Narrow;
108
109impl Pass for Narrow {
110 fn name(&self) -> &'static str {
111 "narrow"
112 }
113
114 fn describe(&self) -> &'static str {
115 "arithmetic the program truncates is redone at the width it truncates to"
116 }
117
118 fn preserves(&self) -> Preserved {
119 Preserved::ALL.without(Analysis::Liveness)
124 }
125
126 fn run(&self, func: &mut Func, _an: &mut Analyses, fuel: &mut Fuel) -> Stats {
127 let mut stats = Stats::new();
128 let mut uses = count(func);
129 for block in func.blocks().collect::<Vec<Block>>() {
130 for inst in func.insts(block).collect::<Vec<Inst>>() {
131 let Some(redo) = truncated_arithmetic(func, inst, &uses)
132 .or_else(|| extended_comparison(func, inst))
133 .or_else(|| widened_bits(func, inst, &uses))
134 else {
135 continue;
136 };
137 if !fuel.take() {
138 stats.missed(NO_FUEL);
142 continue;
143 }
144 apply(func, inst, &redo, &mut uses);
145 stats.optimized(NARROWED);
146 }
147 }
148 stats
149 }
150}
151
152struct Redo {
154 opcode: Opcode,
156 extra: Extra,
158 ty: Type,
160 lhs: Plan,
162 rhs: Option<Plan>,
164}
165
166enum Plan {
168 Already(Value),
170 Constant(i128),
172 Nested(Box<Redo>),
174}
175
176fn truncated_arithmetic(func: &Func, inst: Inst, uses: &[u32]) -> Option<Redo> {
182 let data = &func[inst];
183 if data.opcode != Opcode::Trunc {
184 return None;
185 }
186 let ty = func[data.results().next()?].ty;
187 if !narrowable(ty) {
188 return None;
189 }
190 redo(func, *func[data.args].first()?, ty, uses, DEPTH)
191}
192
193const fn narrowable(ty: Type) -> bool {
206 ty.is_int() && ty.is_scalar() && ty.bits() >= 8
207}
208
209fn redo(func: &Func, value: Value, ty: Type, uses: &[u32], depth: u32) -> Option<Redo> {
211 if depth == 0 || uses[value.index()] != 1 {
212 return None;
213 }
214 let Def::Result { inst, .. } = func[value].def else { return None };
215 let data = &func[inst];
216 if !low_bits_only(data.opcode) {
217 return None;
218 }
219 let args = &func[data.args];
220 let (&left, &right) = (args.first()?, args.get(1)?);
221 let lhs = plan(func, left, ty, uses, depth)?;
222 let rhs = match data.opcode {
225 Opcode::Shl => Plan::Constant(count_below(func, right, ty)?),
226 _ => plan(func, right, ty, uses, depth)?,
227 };
228 Some(Redo { opcode: data.opcode, extra: Extra::None, ty, lhs, rhs: Some(rhs) })
229}
230
231fn plan(func: &Func, value: Value, ty: Type, uses: &[u32], depth: u32) -> Option<Plan> {
233 if let Some(narrow) = extended(func, value, ty) {
234 return Some(Plan::Already(narrow));
235 }
236 if let Some((imm, wide)) = constant(func, value) {
237 return Some(Plan::Constant(imm.signed(wide)));
238 }
239 redo(func, value, ty, uses, depth - 1).map(|redo| Plan::Nested(Box::new(redo)))
240}
241
242const fn low_bits_only(opcode: Opcode) -> bool {
247 matches!(
248 opcode,
249 Opcode::Add
250 | Opcode::Sub
251 | Opcode::Mul
252 | Opcode::And
253 | Opcode::Or
254 | Opcode::Xor
255 | Opcode::Shl
256 )
257}
258
259fn extended_comparison(func: &Func, inst: Inst) -> Option<Redo> {
281 let data = &func[inst];
282 if data.opcode != Opcode::ICmp {
283 return None;
284 }
285 let Extra::IntPred(pred) = data.extra else { return None };
286 let args = &func[data.args];
287 let (&left, &right) = (args.first()?, args.get(1)?);
288 let (kind, ty, narrow) = widening(func, left)?;
289 if !narrowable(ty) {
290 return None;
291 }
292 let widens = ty.bits() < func[left].ty.bits();
293 let pred = if kind == Opcode::ZExt && widens { pred.unsigned() } else { pred };
294 let rhs = match widening(func, right) {
295 Some((same, from, other)) if same == kind && from == ty => Plan::Already(other),
296 _ => Plan::Constant(survives(func, right, kind, ty)?),
297 };
298 let extra = Extra::IntPred(pred);
299 Some(Redo { opcode: Opcode::ICmp, extra, ty, lhs: Plan::Already(narrow), rhs: Some(rhs) })
300}
301
302fn widened_bits(func: &Func, inst: Inst, uses: &[u32]) -> Option<Redo> {
318 let (wide, back) = asked(func, inst, uses)?;
319 let data = &func[wide];
320 if !bit_at_a_time(data.opcode) {
321 return None;
322 }
323 if func[data.results().next()?].ty.bits() <= 1 {
329 return None;
330 }
331 let args = &func[data.args];
332 let (&left, &right) = (args.first()?, args.get(1)?);
333 let readers = if left == right { 2 } else { 1 };
337 let lhs = side(func, left, uses, readers)?;
338 let rhs = side(func, right, uses, readers)?;
339 if matches!((&lhs, &rhs), (Plan::Constant(_), Plan::Constant(_))) {
345 return None;
346 }
347 let extra = Extra::None;
348 let bit = Redo { opcode: data.opcode, extra, ty: Type::int(1), lhs, rhs: Some(rhs) };
349 let Some(ty) = back else { return Some(bit) };
350 let lhs = Plan::Nested(Box::new(bit));
351 Some(Redo { opcode: Opcode::ZExt, extra, ty, lhs, rhs: None })
352}
353
354fn asked(func: &Func, inst: Inst, uses: &[u32]) -> Option<(Inst, Option<Type>)> {
374 let data = &func[inst];
375 let args = &func[data.args];
376 match data.opcode {
377 Opcode::ICmp if data.extra == Extra::IntPred(IntPred::Ne) => {
378 let (&left, &right) = (args.first()?, args.get(1)?);
379 let (zero, wide) = constant(func, right)?;
380 (zero.signed(wide) == 0).then_some((read_by(func, left, uses, 1)?, None))
381 }
382 Opcode::ZExt | Opcode::SExt => {
383 let ty = func[data.results().next()?].ty;
384 Some((read_by(func, *args.first()?, uses, 1)?, Some(ty)))
385 }
386 _ => None,
387 }
388}
389
390fn side(func: &Func, value: Value, uses: &[u32], readers: u32) -> Option<Plan> {
398 if let Some((imm, wide)) = constant(func, value) {
399 let k = imm.signed(wide);
400 return (k == 0 || k == 1).then_some(Plan::Constant(k));
401 }
402 Some(Plan::Already(widened_bit(func, value, uses, readers)?))
403}
404
405fn read_by(func: &Func, value: Value, uses: &[u32], readers: u32) -> Option<Inst> {
411 if uses[value.index()] != readers {
412 return None;
413 }
414 let Def::Result { inst, .. } = func[value].def else { return None };
415 Some(inst)
416}
417
418const fn bit_at_a_time(opcode: Opcode) -> bool {
425 matches!(opcode, Opcode::And | Opcode::Or | Opcode::Xor)
426}
427
428fn widened_bit(func: &Func, value: Value, uses: &[u32], readers: u32) -> Option<Value> {
437 let inst = read_by(func, value, uses, readers)?;
438 let data = &func[inst];
439 if data.opcode != Opcode::ZExt {
440 return None;
441 }
442 let narrow = *func[data.args].first()?;
443 (func[narrow].ty == Type::int(1)).then_some(narrow)
444}
445
446fn widening(func: &Func, value: Value) -> Option<(Opcode, Type, Value)> {
448 let Def::Result { inst, .. } = func[value].def else { return None };
449 let data = &func[inst];
450 if data.opcode != Opcode::SExt && data.opcode != Opcode::ZExt {
451 return None;
452 }
453 let narrow = *func[data.args].first()?;
454 Some((data.opcode, func[narrow].ty, narrow))
455}
456
457fn extended(func: &Func, value: Value, ty: Type) -> Option<Value> {
462 let (_, from, narrow) = widening(func, value)?;
463 (from == ty).then_some(narrow)
464}
465
466fn constant(func: &Func, value: Value) -> Option<(Imm, Type)> {
468 let Def::Result { inst, .. } = func[value].def else { return None };
469 let data = &func[inst];
470 let Extra::Imm(at) = data.extra else { return None };
471 if data.opcode != Opcode::IConst {
472 return None;
473 }
474 let ty = func[value].ty;
475 ty.is_int().then(|| (func[at], ty))
476}
477
478fn count_below(func: &Func, value: Value, ty: Type) -> Option<i128> {
484 let (imm, wide) = constant(func, value)?;
485 let by = imm.signed(wide);
486 (by >= 0 && by < i128::from(ty.bits())).then_some(by)
487}
488
489fn survives(func: &Func, value: Value, kind: Opcode, ty: Type) -> Option<i128> {
495 let (imm, wide) = constant(func, value)?;
496 let k = imm.signed(wide);
497 let back = Imm::int(k, ty).signed(ty);
498 let same = if kind == Opcode::SExt { back } else { Imm::int(k, ty).unsigned() as i128 };
499 (same == k).then_some(k)
500}
501
502fn apply(func: &mut Func, inst: Inst, redo: &Redo, uses: &mut Vec<u32>) {
508 let operands = operands(func, inst, redo, uses);
509 for value in func[func[inst].args].iter().copied() {
510 uses[value.index()] -= 1;
511 }
512 let args = listed(func, operands, uses);
513 let data = &mut func[inst];
514 data.opcode = redo.opcode;
515 data.flags = Flags::NONE;
519 data.args = args;
520 data.extra = redo.extra;
521}
522
523fn build(func: &mut Func, before: Inst, ty: Type, plan: &Plan, uses: &mut Vec<u32>) -> Value {
525 match plan {
526 Plan::Already(value) => *value,
527 Plan::Constant(value) => {
528 let at = func.add_imm(Imm::int(*value, ty.lane()));
529 let data = InstData { extra: Extra::Imm(at), ..InstData::new(Opcode::IConst) };
530 written(func, before, data, ty, uses)
531 }
532 Plan::Nested(redo) => {
533 let operands = operands(func, before, redo, uses);
534 let args = listed(func, operands, uses);
535 let data = InstData { args, extra: redo.extra, ..InstData::new(redo.opcode) };
536 written(func, before, data, redo.ty, uses)
537 }
538 }
539}
540
541fn operands(
543 func: &mut Func,
544 before: Inst,
545 redo: &Redo,
546 uses: &mut Vec<u32>,
547) -> (Value, Option<Value>) {
548 let lhs = build(func, before, redo.ty, &redo.lhs, uses);
549 let rhs = redo.rhs.as_ref().map(|plan| build(func, before, redo.ty, plan, uses));
550 (lhs, rhs)
551}
552
553fn listed(func: &mut Func, (lhs, rhs): (Value, Option<Value>), uses: &mut [u32]) -> ValueList {
555 uses[lhs.index()] += 1;
556 let Some(rhs) = rhs else { return func.push_values(&[lhs]) };
557 uses[rhs.index()] += 1;
558 func.push_values(&[lhs, rhs])
559}
560
561fn written(func: &mut Func, before: Inst, data: InstData, ty: Type, uses: &mut Vec<u32>) -> Value {
563 let span = func.span(before);
564 let inst = func.create_inst(data, &[ty], span);
565 func.insert_before(inst, before);
566 uses.resize(func.counts().values, 0);
567 func[inst].first_result.expect("one result was asked for")
568}
569
570#[cfg(test)]
571mod tests {
572 use rucc_base::Interner;
573 use rucc_ir::{Block, Builder, Flags, Func, Inst, IntPred, Opcode, Signature, Type, Value};
574
575 use crate::narrow::Narrow;
576 use crate::{Fuel, Pass};
577
578 fn blank() -> (Func, Block) {
580 let mut names = Interner::new();
581 let name = names.intern("f");
582 let mut func = Func::new(name, Signature::new().with_returns(&[Type::int(32)]));
583 let block = func.create_block();
584 (func, block)
585 }
586
587 fn shape(func: &Func, value: Value) -> (Opcode, Vec<Type>) {
589 let rucc_ir::Def::Result { inst, .. } = func[value].def else { panic!("a result") };
590 let data = &func[inst];
591 (data.opcode, func[data.args].iter().map(|&arg| func[arg].ty).collect())
592 }
593
594 fn under(func: &Func, value: Value) -> Value {
596 let rucc_ir::Def::Result { inst, .. } = func[value].def else { panic!("a result") };
597 *func[func[inst].args].first().expect("an operand")
598 }
599
600 fn predicate(func: &Func, value: Value) -> IntPred {
602 let rucc_ir::Def::Result { inst, .. } = func[value].def else { panic!("a result") };
603 let rucc_ir::Extra::IntPred(pred) = func[inst].extra else { panic!("a comparison") };
604 pred
605 }
606
607 fn left(func: &Func, block: Block) -> usize {
609 func.insts(block).count()
610 }
611
612 fn last(func: &Func, block: Block) -> Inst {
614 func.insts(block).last().expect("a block with something in it")
615 }
616
617 #[test]
618 fn a_truncated_sum_of_two_extensions_is_the_sum_at_the_narrow_width() {
619 let (mut func, block) = blank();
620 let a = func.append_param(block, Type::int(8));
621 let b = func.append_param(block, Type::int(8));
622 let mut build = Builder::new(&mut func, block);
623 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
624 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
625 let sum = build.binary(Opcode::Add, wide_a, wide_b, Flags::NONE);
626 let narrow = build.unary(Opcode::Trunc, sum, Type::int(8));
627 build.ret(&[narrow]);
628 assert!(
629 Narrow
630 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
631 .changed()
632 );
633 assert_eq!(shape(&func, narrow), (Opcode::Add, vec![Type::int(8), Type::int(8)]));
634 assert_eq!(left(&func, block), 5);
637 }
638
639 #[test]
640 fn a_constant_operand_is_written_down_again_at_the_narrow_width() {
641 let (mut func, block) = blank();
642 let a = func.append_param(block, Type::int(8));
643 let mut build = Builder::new(&mut func, block);
644 let wide = build.unary(Opcode::SExt, a, Type::int(32));
645 let one = build.iconst(Type::int(32), 1);
646 let sum = build.binary(Opcode::Add, wide, one, Flags::NONE);
647 let narrow = build.unary(Opcode::Trunc, sum, Type::int(8));
648 build.ret(&[narrow]);
649 assert!(
650 Narrow
651 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
652 .changed()
653 );
654 assert_eq!(shape(&func, narrow), (Opcode::Add, vec![Type::int(8), Type::int(8)]));
655 }
656
657 #[test]
658 fn a_chain_of_arithmetic_narrows_the_whole_way_down() {
659 let (mut func, block) = blank();
660 let a = func.append_param(block, Type::int(8));
661 let b = func.append_param(block, Type::int(8));
662 let c = func.append_param(block, Type::int(8));
663 let mut build = Builder::new(&mut func, block);
664 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
665 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
666 let wide_c = build.unary(Opcode::SExt, c, Type::int(32));
667 let inner = build.binary(Opcode::Add, wide_a, wide_b, Flags::NONE);
668 let outer = build.binary(Opcode::Mul, inner, wide_c, Flags::NONE);
669 let narrow = build.unary(Opcode::Trunc, outer, Type::int(8));
670 build.ret(&[narrow]);
671 assert!(
672 Narrow
673 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
674 .changed()
675 );
676 assert_eq!(shape(&func, narrow), (Opcode::Mul, vec![Type::int(8), Type::int(8)]));
679 assert_eq!(left(&func, block), 8);
680 }
681
682 #[test]
683 fn an_operation_something_else_reads_stays_wide() {
684 let (mut func, block) = blank();
685 let a = func.append_param(block, Type::int(8));
686 let b = func.append_param(block, Type::int(8));
687 let mut build = Builder::new(&mut func, block);
688 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
689 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
690 let sum = build.binary(Opcode::Add, wide_a, wide_b, Flags::NONE);
691 let narrow = build.unary(Opcode::Trunc, sum, Type::int(8));
692 let kept = build.unary(Opcode::SExt, narrow, Type::int(32));
693 build.ret(&[sum, kept]);
694 assert!(
695 !Narrow
696 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
697 .changed()
698 );
699 assert_eq!(shape(&func, narrow), (Opcode::Trunc, vec![Type::int(32)]));
702 }
703
704 #[test]
705 fn a_divide_stays_wide_because_the_narrow_one_can_raise() {
706 let (mut func, block) = blank();
707 let a = func.append_param(block, Type::int(8));
708 let b = func.append_param(block, Type::int(8));
709 let mut build = Builder::new(&mut func, block);
710 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
711 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
712 let quotient = build.binary(Opcode::SDiv, wide_a, wide_b, Flags::NONE);
713 let narrow = build.unary(Opcode::Trunc, quotient, Type::int(8));
714 build.ret(&[narrow]);
715 assert!(
716 !Narrow
717 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
718 .changed()
719 );
720 assert_eq!(shape(&func, narrow), (Opcode::Trunc, vec![Type::int(32)]));
724 }
725
726 #[test]
727 fn a_shift_by_a_constant_below_the_width_narrows_and_one_at_it_does_not() {
728 for (by, narrows) in [(3, true), (20, false)] {
729 let (mut func, block) = blank();
730 let a = func.append_param(block, Type::int(8));
731 let mut build = Builder::new(&mut func, block);
732 let wide = build.unary(Opcode::SExt, a, Type::int(32));
733 let count = build.iconst(Type::int(32), by);
734 let shifted = build.binary(Opcode::Shl, wide, count, Flags::NONE);
735 let narrow = build.unary(Opcode::Trunc, shifted, Type::int(8));
736 build.ret(&[narrow]);
737 assert_eq!(
738 Narrow
739 .run(
740 &mut func,
741 &mut crate::machine::fixtures::analyses(),
742 &mut Fuel::unlimited()
743 )
744 .changed(),
745 narrows,
746 "shift by {by}"
747 );
748 let want = if narrows { Opcode::Shl } else { Opcode::Trunc };
751 assert_eq!(shape(&func, narrow).0, want, "shift by {by}");
752 }
753 }
754
755 #[test]
756 fn a_shift_by_a_value_stays_wide() {
757 let (mut func, block) = blank();
758 let a = func.append_param(block, Type::int(8));
759 let n = func.append_param(block, Type::int(8));
760 let mut build = Builder::new(&mut func, block);
761 let wide = build.unary(Opcode::SExt, a, Type::int(32));
762 let by = build.unary(Opcode::SExt, n, Type::int(32));
763 let shifted = build.binary(Opcode::Shl, wide, by, Flags::NONE);
764 let narrow = build.unary(Opcode::Trunc, shifted, Type::int(8));
765 build.ret(&[narrow]);
766 assert!(
767 !Narrow
768 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
769 .changed()
770 );
771 assert_eq!(shape(&func, narrow).0, Opcode::Trunc);
772 }
773
774 #[test]
775 fn a_comparison_of_two_sign_extensions_is_the_comparison_of_what_they_extended() {
776 for pred in IntPred::all() {
777 let (mut func, block) = blank();
778 let a = func.append_param(block, Type::int(8));
779 let b = func.append_param(block, Type::int(8));
780 let mut build = Builder::new(&mut func, block);
781 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
782 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
783 let answer = build.icmp(pred, wide_a, wide_b);
784 build.ret(&[answer]);
785 assert!(
786 Narrow
787 .run(
788 &mut func,
789 &mut crate::machine::fixtures::analyses(),
790 &mut Fuel::unlimited()
791 )
792 .changed(),
793 "{pred}"
794 );
795 assert_eq!(shape(&func, answer).1, vec![Type::int(8), Type::int(8)], "{pred}");
798 }
799 }
800
801 #[test]
802 fn a_comparison_of_two_zero_extensions_narrows_at_every_predicate() {
803 for pred in IntPred::all() {
804 let (mut func, block) = blank();
805 let a = func.append_param(block, Type::int(8));
806 let b = func.append_param(block, Type::int(8));
807 let mut build = Builder::new(&mut func, block);
808 let wide_a = build.unary(Opcode::ZExt, a, Type::int(32));
809 let wide_b = build.unary(Opcode::ZExt, b, Type::int(32));
810 let answer = build.icmp(pred, wide_a, wide_b);
811 build.ret(&[answer]);
812 assert!(
813 Narrow
814 .run(
815 &mut func,
816 &mut crate::machine::fixtures::analyses(),
817 &mut Fuel::unlimited()
818 )
819 .changed(),
820 "{pred}"
821 );
822 assert_eq!(shape(&func, answer).1, vec![Type::int(8), Type::int(8)], "{pred}");
823 }
824 }
825
826 #[test]
827 fn a_signed_comparison_of_two_zero_extensions_narrows_to_the_unsigned_one() {
828 for pred in IntPred::all() {
833 let (mut func, block) = blank();
834 let a = func.append_param(block, Type::int(8));
835 let b = func.append_param(block, Type::int(8));
836 let mut build = Builder::new(&mut func, block);
837 let wide_a = build.unary(Opcode::ZExt, a, Type::int(32));
838 let wide_b = build.unary(Opcode::ZExt, b, Type::int(32));
839 let answer = build.icmp(pred, wide_a, wide_b);
840 build.ret(&[answer]);
841 Narrow.run(
842 &mut func,
843 &mut crate::machine::fixtures::analyses(),
844 &mut Fuel::unlimited(),
845 );
846 assert_eq!(predicate(&func, answer), pred.unsigned(), "{pred}");
847 }
848 }
849
850 #[test]
851 fn a_signed_comparison_of_two_sign_extensions_keeps_the_predicate_it_was_written_with() {
852 for pred in IntPred::all() {
853 let (mut func, block) = blank();
854 let a = func.append_param(block, Type::int(8));
855 let b = func.append_param(block, Type::int(8));
856 let mut build = Builder::new(&mut func, block);
857 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
858 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
859 let answer = build.icmp(pred, wide_a, wide_b);
860 build.ret(&[answer]);
861 Narrow.run(
862 &mut func,
863 &mut crate::machine::fixtures::analyses(),
864 &mut Fuel::unlimited(),
865 );
866 assert_eq!(predicate(&func, answer), pred, "{pred}");
867 }
868 }
869
870 #[test]
871 fn a_signed_comparison_of_a_zero_extension_against_a_constant_narrows_to_the_unsigned_one() {
872 let (mut func, block) = blank();
876 let a = func.append_param(block, Type::int(8));
877 let mut build = Builder::new(&mut func, block);
878 let wide = build.unary(Opcode::ZExt, a, Type::int(32));
879 let k = build.iconst(Type::int(32), 200);
880 let answer = build.icmp(IntPred::Slt, wide, k);
881 build.ret(&[answer]);
882 assert!(
883 Narrow
884 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
885 .changed()
886 );
887 assert_eq!(shape(&func, answer).1, vec![Type::int(8), Type::int(8)]);
888 assert_eq!(predicate(&func, answer), IntPred::Ult);
889 }
890
891 #[test]
892 fn a_signed_comparison_of_a_zero_extension_against_a_negative_constant_is_left_alone() {
893 let (mut func, block) = blank();
897 let a = func.append_param(block, Type::int(8));
898 let mut build = Builder::new(&mut func, block);
899 let wide = build.unary(Opcode::ZExt, a, Type::int(32));
900 let k = build.iconst(Type::int(32), -1);
901 let answer = build.icmp(IntPred::Sgt, wide, k);
902 build.ret(&[answer]);
903 assert!(
904 !Narrow
905 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
906 .changed()
907 );
908 }
909
910 #[test]
911 fn a_comparison_against_a_constant_narrows_when_the_constant_is_one_of_the_narrow_ones() {
912 for (k, narrows) in [(120, true), (-1, true), (200, false)] {
913 let (mut func, block) = blank();
914 let a = func.append_param(block, Type::int(8));
915 let mut build = Builder::new(&mut func, block);
916 let wide = build.unary(Opcode::SExt, a, Type::int(32));
917 let k = build.iconst(Type::int(32), k);
918 let answer = build.icmp(IntPred::Eq, wide, k);
919 build.ret(&[answer]);
920 assert_eq!(
923 Narrow
924 .run(
925 &mut func,
926 &mut crate::machine::fixtures::analyses(),
927 &mut Fuel::unlimited()
928 )
929 .changed(),
930 narrows
931 );
932 }
933 }
934
935 #[test]
936 fn one_extension_against_the_other_kind_is_not_a_comparison_at_the_narrow_width() {
937 for pred in IntPred::all() {
942 let (mut func, block) = blank();
943 let a = func.append_param(block, Type::int(8));
944 let b = func.append_param(block, Type::int(8));
945 let mut build = Builder::new(&mut func, block);
946 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
947 let wide_b = build.unary(Opcode::ZExt, b, Type::int(32));
948 let answer = build.icmp(pred, wide_a, wide_b);
949 build.ret(&[answer]);
950 assert!(
951 !Narrow
952 .run(
953 &mut func,
954 &mut crate::machine::fixtures::analyses(),
955 &mut Fuel::unlimited()
956 )
957 .changed(),
958 "{pred}"
959 );
960 }
961 }
962
963 #[test]
964 fn a_truth_is_not_a_width_to_narrow_to() {
965 let (mut func, block) = blank();
969 let a = func.append_param(block, Type::int(1));
970 let mut build = Builder::new(&mut func, block);
971 let wide = build.unary(Opcode::ZExt, a, Type::int(32));
972 let zero = build.iconst(Type::int(32), 0);
973 let answer = build.icmp(IntPred::Ne, wide, zero);
974 build.ret(&[answer]);
975 assert!(
976 !Narrow
977 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
978 .changed()
979 );
980 assert_eq!(shape(&func, answer).1, vec![Type::int(32), Type::int(32)]);
981 }
982
983 #[test]
984 fn extensions_from_different_widths_are_not_a_comparison_at_either_of_them() {
985 let (mut func, block) = blank();
986 let a = func.append_param(block, Type::int(8));
987 let b = func.append_param(block, Type::int(16));
988 let mut build = Builder::new(&mut func, block);
989 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
990 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
991 let answer = build.icmp(IntPred::Slt, wide_a, wide_b);
992 build.ret(&[answer]);
993 assert!(
994 !Narrow
995 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
996 .changed()
997 );
998 }
999
1000 #[test]
1001 fn the_overflow_flags_do_not_come_along() {
1002 let (mut func, block) = blank();
1003 let a = func.append_param(block, Type::int(8));
1004 let b = func.append_param(block, Type::int(8));
1005 let mut build = Builder::new(&mut func, block);
1006 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
1007 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
1008 let sum = build.binary(Opcode::Add, wide_a, wide_b, Flags::NSW);
1009 let narrow = build.unary(Opcode::Trunc, sum, Type::int(8));
1010 build.ret(&[narrow]);
1011 assert!(
1012 Narrow
1013 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1014 .changed()
1015 );
1016 let rucc_ir::Def::Result { inst, .. } = func[narrow].def else { panic!("a result") };
1019 assert_eq!(func[inst].flags, Flags::NONE);
1020 }
1021
1022 #[test]
1028 fn a_bitwise_operation_on_two_widened_bits_is_done_at_one_bit() {
1029 for opcode in [Opcode::And, Opcode::Or, Opcode::Xor] {
1030 let (mut func, block) = blank();
1031 let p = func.append_param(block, Type::int(1));
1032 let q = func.append_param(block, Type::int(1));
1033 let mut build = Builder::new(&mut func, block);
1034 let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1035 let wide_q = build.unary(Opcode::ZExt, q, Type::int(32));
1036 let both = build.binary(opcode, wide_p, wide_q, Flags::NONE);
1037 let zero = build.iconst(Type::int(32), 0);
1038 let answer = build.icmp(IntPred::Ne, both, zero);
1039 build.ret(&[answer]);
1040 assert!(
1041 Narrow
1042 .run(
1043 &mut func,
1044 &mut crate::machine::fixtures::analyses(),
1045 &mut Fuel::unlimited()
1046 )
1047 .changed(),
1048 "{opcode:?}"
1049 );
1050 assert_eq!(shape(&func, answer), (opcode, vec![Type::int(1), Type::int(1)]));
1051 }
1052 }
1053
1054 #[test]
1057 fn asking_whether_a_bitwise_operation_on_widened_bits_is_zero_is_left_alone() {
1058 let (mut func, block) = blank();
1059 let p = func.append_param(block, Type::int(1));
1060 let q = func.append_param(block, Type::int(1));
1061 let mut build = Builder::new(&mut func, block);
1062 let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1063 let wide_q = build.unary(Opcode::ZExt, q, Type::int(32));
1064 let both = build.binary(Opcode::And, wide_p, wide_q, Flags::NONE);
1065 let zero = build.iconst(Type::int(32), 0);
1066 let answer = build.icmp(IntPred::Eq, both, zero);
1067 build.ret(&[answer]);
1068 assert!(
1069 !Narrow
1070 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1071 .changed()
1072 );
1073 assert_eq!(shape(&func, answer).1, vec![Type::int(32), Type::int(32)]);
1074 }
1075
1076 #[test]
1079 fn a_sum_of_two_widened_bits_is_left_alone() {
1080 let (mut func, block) = blank();
1081 let p = func.append_param(block, Type::int(1));
1082 let q = func.append_param(block, Type::int(1));
1083 let mut build = Builder::new(&mut func, block);
1084 let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1085 let wide_q = build.unary(Opcode::ZExt, q, Type::int(32));
1086 let both = build.binary(Opcode::Add, wide_p, wide_q, Flags::NONE);
1087 let zero = build.iconst(Type::int(32), 0);
1088 let answer = build.icmp(IntPred::Ne, both, zero);
1089 build.ret(&[answer]);
1090 assert!(
1091 !Narrow
1092 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1093 .changed()
1094 );
1095 }
1096
1097 #[test]
1100 fn a_bitwise_operation_on_something_wider_than_a_bit_is_not_this_shape() {
1101 let (mut func, block) = blank();
1102 let a = func.append_param(block, Type::int(8));
1103 let b = func.append_param(block, Type::int(8));
1104 let mut build = Builder::new(&mut func, block);
1105 let wide_a = build.unary(Opcode::ZExt, a, Type::int(32));
1106 let wide_b = build.unary(Opcode::ZExt, b, Type::int(32));
1107 let both = build.binary(Opcode::And, wide_a, wide_b, Flags::NONE);
1108 let zero = build.iconst(Type::int(32), 0);
1109 let answer = build.icmp(IntPred::Ne, both, zero);
1110 build.ret(&[answer]);
1111 Narrow.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
1112 assert_eq!(shape(&func, answer).0, Opcode::ICmp);
1115 }
1116
1117 #[test]
1122 fn a_bitwise_operation_on_a_widened_bit_and_a_bit_constant_is_done_at_one_bit() {
1123 for (opcode, k) in
1124 [(Opcode::And, 0), (Opcode::And, 1), (Opcode::Or, 0), (Opcode::Or, 1), (Opcode::Xor, 0)]
1125 {
1126 let (mut func, block) = blank();
1127 let p = func.append_param(block, Type::int(1));
1128 let mut build = Builder::new(&mut func, block);
1129 let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1130 let bit = build.iconst(Type::int(32), k);
1131 let both = build.binary(opcode, wide_p, bit, Flags::NONE);
1132 let zero = build.iconst(Type::int(32), 0);
1133 let answer = build.icmp(IntPred::Ne, both, zero);
1134 build.ret(&[answer]);
1135 assert!(
1136 Narrow
1137 .run(
1138 &mut func,
1139 &mut crate::machine::fixtures::analyses(),
1140 &mut Fuel::unlimited()
1141 )
1142 .changed(),
1143 "{opcode:?} {k}"
1144 );
1145 assert_eq!(shape(&func, answer), (opcode, vec![Type::int(1), Type::int(1)]));
1146 }
1147 }
1148
1149 #[test]
1152 fn a_bitwise_operation_against_a_constant_wider_than_a_bit_is_left_alone() {
1153 let (mut func, block) = blank();
1154 let p = func.append_param(block, Type::int(1));
1155 let mut build = Builder::new(&mut func, block);
1156 let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1157 let two = build.iconst(Type::int(32), 2);
1158 let both = build.binary(Opcode::Or, wide_p, two, Flags::NONE);
1159 let zero = build.iconst(Type::int(32), 0);
1160 let answer = build.icmp(IntPred::Ne, both, zero);
1161 build.ret(&[answer]);
1162 assert!(
1163 !Narrow
1164 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1165 .changed()
1166 );
1167 }
1168
1169 #[test]
1172 fn a_widened_bit_the_operation_reads_twice_is_still_only_read_by_it() {
1173 let (mut func, block) = blank();
1174 let p = func.append_param(block, Type::int(1));
1175 let mut build = Builder::new(&mut func, block);
1176 let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1177 let both = build.binary(Opcode::And, wide_p, wide_p, Flags::NONE);
1178 let zero = build.iconst(Type::int(32), 0);
1179 let answer = build.icmp(IntPred::Ne, both, zero);
1180 build.ret(&[answer]);
1181 assert!(
1182 Narrow
1183 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1184 .changed()
1185 );
1186 assert_eq!(shape(&func, answer), (Opcode::And, vec![Type::int(1), Type::int(1)]));
1187 }
1188
1189 #[test]
1192 fn a_widened_bit_that_something_else_reads_is_left_alone() {
1193 let (mut func, block) = blank();
1194 let p = func.append_param(block, Type::int(1));
1195 let q = func.append_param(block, Type::int(1));
1196 let mut build = Builder::new(&mut func, block);
1197 let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1198 let wide_q = build.unary(Opcode::ZExt, q, Type::int(32));
1199 let both = build.binary(Opcode::And, wide_p, wide_q, Flags::NONE);
1200 let zero = build.iconst(Type::int(32), 0);
1201 let answer = build.icmp(IntPred::Ne, both, zero);
1202 build.ret(&[answer, wide_p]);
1203 assert!(
1204 !Narrow
1205 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1206 .changed()
1207 );
1208 }
1209
1210 #[test]
1212 fn a_bitwise_operation_on_widened_bits_compared_against_one_is_left_alone() {
1213 let (mut func, block) = blank();
1214 let p = func.append_param(block, Type::int(1));
1215 let q = func.append_param(block, Type::int(1));
1216 let mut build = Builder::new(&mut func, block);
1217 let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1218 let wide_q = build.unary(Opcode::ZExt, q, Type::int(32));
1219 let both = build.binary(Opcode::Or, wide_p, wide_q, Flags::NONE);
1220 let one = build.iconst(Type::int(32), 1);
1221 let answer = build.icmp(IntPred::Ne, both, one);
1222 build.ret(&[answer]);
1223 assert!(
1224 !Narrow
1225 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1226 .changed()
1227 );
1228 }
1229
1230 #[test]
1235 fn a_bitwise_operation_on_widened_bits_taken_wider_is_done_at_one_bit() {
1236 for kind in [Opcode::ZExt, Opcode::SExt] {
1237 let (mut func, block) = blank();
1238 let p = func.append_param(block, Type::int(1));
1239 let q = func.append_param(block, Type::int(1));
1240 let mut build = Builder::new(&mut func, block);
1241 let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1242 let wide_q = build.unary(Opcode::ZExt, q, Type::int(32));
1243 let both = build.binary(Opcode::And, wide_p, wide_q, Flags::NONE);
1244 let wider = build.unary(kind, both, Type::int(64));
1245 build.ret(&[wider]);
1246 assert!(
1247 Narrow
1248 .run(
1249 &mut func,
1250 &mut crate::machine::fixtures::analyses(),
1251 &mut Fuel::unlimited()
1252 )
1253 .changed(),
1254 "{kind:?}"
1255 );
1256 assert_eq!(shape(&func, wider), (Opcode::ZExt, vec![Type::int(1)]), "{kind:?}");
1257 let bit = under(&func, wider);
1258 let want = (Opcode::And, vec![Type::int(1), Type::int(1)]);
1259 assert_eq!(shape(&func, bit), want, "{kind:?}");
1260 }
1261 }
1262
1263 #[test]
1266 fn a_bitwise_operation_on_two_bit_constants_is_left_to_the_folder() {
1267 let (mut func, block) = blank();
1268 let mut build = Builder::new(&mut func, block);
1269 let zero = build.iconst(Type::int(32), 0);
1270 let one = build.iconst(Type::int(32), 1);
1271 let both = build.binary(Opcode::And, zero, one, Flags::NONE);
1272 let wider = build.unary(Opcode::ZExt, both, Type::int(64));
1273 build.ret(&[wider]);
1274 assert!(
1275 !Narrow
1276 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1277 .changed()
1278 );
1279 }
1280
1281 #[test]
1285 fn a_bitwise_operation_already_at_one_bit_is_not_done_again() {
1286 let (mut func, block) = blank();
1287 let p = func.append_param(block, Type::int(1));
1288 let q = func.append_param(block, Type::int(1));
1289 let mut build = Builder::new(&mut func, block);
1290 let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1291 let wide_q = build.unary(Opcode::ZExt, q, Type::int(32));
1292 let both = build.binary(Opcode::Xor, wide_p, wide_q, Flags::NONE);
1293 let wider = build.unary(Opcode::SExt, both, Type::int(64));
1294 build.ret(&[wider]);
1295 let mut an = crate::machine::fixtures::analyses();
1296 assert!(Narrow.run(&mut func, &mut an, &mut Fuel::unlimited()).changed());
1297 assert!(!Narrow.run(&mut func, &mut an, &mut Fuel::unlimited()).changed());
1298 }
1299
1300 #[test]
1303 fn a_bit_constant_comes_over_under_an_extension_too() {
1304 let (mut func, block) = blank();
1305 let p = func.append_param(block, Type::int(1));
1306 let mut build = Builder::new(&mut func, block);
1307 let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1308 let one = build.iconst(Type::int(32), 1);
1309 let both = build.binary(Opcode::And, wide_p, one, Flags::NONE);
1310 let wider = build.unary(Opcode::SExt, both, Type::int(64));
1311 build.ret(&[wider]);
1312 assert!(
1313 Narrow
1314 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1315 .changed()
1316 );
1317 assert_eq!(shape(&func, wider), (Opcode::ZExt, vec![Type::int(1)]));
1318 assert_eq!(shape(&func, under(&func, wider)), (Opcode::And, vec![Type::int(1); 2]));
1319 }
1320
1321 #[test]
1324 fn an_extension_of_a_bitwise_operation_on_bytes_is_left_alone() {
1325 let (mut func, block) = blank();
1326 let a = func.append_param(block, Type::int(8));
1327 let b = func.append_param(block, Type::int(8));
1328 let mut build = Builder::new(&mut func, block);
1329 let wide_a = build.unary(Opcode::ZExt, a, Type::int(32));
1330 let wide_b = build.unary(Opcode::ZExt, b, Type::int(32));
1331 let both = build.binary(Opcode::And, wide_a, wide_b, Flags::NONE);
1332 let wider = build.unary(Opcode::SExt, both, Type::int(64));
1333 build.ret(&[wider]);
1334 assert!(
1335 !Narrow
1336 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1337 .changed()
1338 );
1339 }
1340
1341 #[test]
1344 fn an_extension_of_a_sum_of_two_widened_bits_is_left_alone() {
1345 let (mut func, block) = blank();
1346 let p = func.append_param(block, Type::int(1));
1347 let q = func.append_param(block, Type::int(1));
1348 let mut build = Builder::new(&mut func, block);
1349 let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1350 let wide_q = build.unary(Opcode::ZExt, q, Type::int(32));
1351 let both = build.binary(Opcode::Add, wide_p, wide_q, Flags::NONE);
1352 let wider = build.unary(Opcode::SExt, both, Type::int(64));
1353 build.ret(&[wider]);
1354 assert!(
1355 !Narrow
1356 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1357 .changed()
1358 );
1359 }
1360
1361 #[test]
1362 fn fuel_stops_the_narrowing_and_not_the_looking() {
1363 let (mut func, block) = blank();
1364 let a = func.append_param(block, Type::int(8));
1365 let b = func.append_param(block, Type::int(8));
1366 let mut build = Builder::new(&mut func, block);
1367 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
1368 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
1369 let first = build.icmp(IntPred::Slt, wide_a, wide_b);
1370 let second = build.icmp(IntPred::Sgt, wide_a, wide_b);
1371 build.ret(&[first, second]);
1372 let mut fuel = Fuel::of(1);
1373 assert!(
1374 Narrow.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut fuel).changed()
1375 );
1376 assert_eq!(shape(&func, first).1, vec![Type::int(8), Type::int(8)]);
1377 assert_eq!(shape(&func, second).1, vec![Type::int(32), Type::int(32)]);
1378 }
1379
1380 #[test]
1381 fn a_block_that_narrows_nothing_is_left_exactly_as_it_was() {
1382 let (mut func, block) = blank();
1383 let a = func.append_param(block, Type::int(32));
1384 let mut build = Builder::new(&mut func, block);
1385 let sum = build.binary(Opcode::Add, a, a, Flags::NONE);
1386 build.ret(&[sum]);
1387 assert!(
1388 !Narrow
1389 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1390 .changed()
1391 );
1392 assert_eq!(left(&func, block), 2);
1393 assert_eq!(func[last(&func, block)].opcode, Opcode::Return);
1394 }
1395}