1use rucc_ir::{
99 Block, Def, Extra, Flags, Func, Imm, Inst, InstData, IntPred, Opcode, Type, Value, ValueList,
100};
101
102use crate::range::query::Ranges;
103use crate::uses::count;
104use crate::{Analyses, Analysis, Fuel, Pass, Preserved, Stats};
105
106const NARROWED: &str = "arithmetic redone at the width the program truncates it to";
108
109const NO_FUEL: &str = "arithmetic left wide, the pass ran out of fuel";
111
112const DEPTH: u32 = 6;
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub struct Narrow;
123
124impl Pass for Narrow {
125 fn name(&self) -> &'static str {
126 "narrow"
127 }
128
129 fn describe(&self) -> &'static str {
130 "arithmetic the program truncates is redone at the width it truncates to"
131 }
132
133 fn preserves(&self) -> Preserved {
134 Preserved::ALL.without(Analysis::Liveness)
139 }
140
141 fn run(&self, func: &mut Func, an: &mut Analyses, fuel: &mut Fuel) -> Stats {
142 let mut stats = Stats::new();
143 let mut uses = count(func);
144 let cleared = cleared(func, an);
145 let seen = Seen { uses: &[], cleared: &cleared };
146 for block in func.blocks().collect::<Vec<Block>>() {
147 for inst in func.insts(block).collect::<Vec<Inst>>() {
148 let seen = Seen { uses: &uses, ..seen };
149 let Some(redo) = truncated_arithmetic(func, inst, seen)
150 .or_else(|| extended_comparison(func, inst))
151 .or_else(|| widened_bits(func, inst, &uses))
152 else {
153 continue;
154 };
155 if !fuel.take() {
156 stats.missed(NO_FUEL);
160 continue;
161 }
162 apply(func, inst, &redo, &mut uses);
163 stats.optimized(NARROWED);
164 }
165 }
166 stats
167 }
168}
169
170#[derive(Clone, Copy)]
172struct Seen<'a> {
173 uses: &'a [u32],
175 cleared: &'a [Inst],
177}
178
179fn cleared(func: &Func, an: &Analyses) -> Vec<Inst> {
187 let asked: Vec<(Inst, Value, Value, Type)> = func
188 .blocks()
189 .flat_map(|block| func.insts(block))
190 .filter_map(|inst| signed_division(func, inst))
191 .collect();
192 if asked.is_empty() {
193 return Vec::new();
194 }
195 let mut ranges = Ranges::new(func, an.cfg(func), an.dominators(func));
196 let mut cleared = Vec::new();
197 for (inst, left, right, ty) in asked {
198 let least = (1u128 << (ty.bits() - 1)).wrapping_neg();
202 if !ranges.at_inst(left, inst).contains(least) || !ranges.at_inst(right, inst).contains(!0)
203 {
204 cleared.push(inst);
205 }
206 }
207 cleared
208}
209
210fn signed_division(func: &Func, inst: Inst) -> Option<(Inst, Value, Value, Type)> {
213 let data = &func[inst];
214 if !matches!(data.opcode, Opcode::SDiv | Opcode::SRem) {
215 return None;
216 }
217 let args = &func[data.args];
218 let (&left, &right) = (args.first()?, args.get(1)?);
219 let (Opcode::SExt, ty, _) = widening(func, left)? else { return None };
220 let (Opcode::SExt, from, _) = widening(func, right)? else { return None };
221 (from == ty && narrowable(ty)).then_some((inst, left, right, ty))
222}
223
224struct Redo {
226 opcode: Opcode,
228 extra: Extra,
230 ty: Type,
232 lhs: Plan,
234 rhs: Option<Plan>,
236}
237
238enum Plan {
240 Already(Value),
242 Constant(i128),
244 Nested(Box<Redo>),
246}
247
248fn truncated_arithmetic(func: &Func, inst: Inst, seen: Seen<'_>) -> Option<Redo> {
254 let data = &func[inst];
255 if data.opcode != Opcode::Trunc {
256 return None;
257 }
258 let ty = func[data.results().next()?].ty;
259 if !narrowable(ty) {
260 return None;
261 }
262 redo(func, *func[data.args].first()?, ty, seen, DEPTH)
263}
264
265const fn narrowable(ty: Type) -> bool {
278 ty.is_int() && ty.is_scalar() && ty.bits() >= 8
279}
280
281fn redo(func: &Func, value: Value, ty: Type, seen: Seen<'_>, depth: u32) -> Option<Redo> {
283 if depth == 0 || seen.uses[value.index()] != 1 {
284 return None;
285 }
286 let Def::Result { inst, .. } = func[value].def else { return None };
287 let data = &func[inst];
288 let args = &func[data.args];
289 let (&left, &right) = (args.first()?, args.get(1)?);
290 if let Some(opcode) = unsigned_division(data.opcode) {
291 let unsigned = zero_extended(func, left, ty).zip(zero_extended(func, right, ty));
295 let signed = seen.cleared.contains(&inst).then(|| sign_extended(func, left, right, ty));
296 let (opcode, (lhs, rhs)) = match (unsigned, signed.flatten()) {
297 (Some(pair), _) => (opcode, pair),
298 (None, Some(pair)) => (data.opcode, pair),
299 (None, None) => return None,
300 };
301 let (lhs, rhs) = (Plan::Already(lhs), Some(Plan::Already(rhs)));
302 return Some(Redo { opcode, extra: Extra::None, ty, lhs, rhs });
303 }
304 if !low_bits_only(data.opcode) {
305 return None;
306 }
307 let lhs = plan(func, left, ty, seen, depth)?;
308 let rhs = match data.opcode {
311 Opcode::Shl => Plan::Constant(count_below(func, right, ty)?),
312 _ => plan(func, right, ty, seen, depth)?,
313 };
314 Some(Redo { opcode: data.opcode, extra: Extra::None, ty, lhs, rhs: Some(rhs) })
315}
316
317fn plan(func: &Func, value: Value, ty: Type, seen: Seen<'_>, depth: u32) -> Option<Plan> {
319 if let Some(narrow) = extended(func, value, ty) {
320 return Some(Plan::Already(narrow));
321 }
322 if let Some((imm, wide)) = constant(func, value) {
323 return Some(Plan::Constant(imm.signed(wide)));
324 }
325 redo(func, value, ty, seen, depth - 1).map(|redo| Plan::Nested(Box::new(redo)))
326}
327
328const fn low_bits_only(opcode: Opcode) -> bool {
333 matches!(
334 opcode,
335 Opcode::Add
336 | Opcode::Sub
337 | Opcode::Mul
338 | Opcode::And
339 | Opcode::Or
340 | Opcode::Xor
341 | Opcode::Shl
342 )
343}
344
345const fn unsigned_division(opcode: Opcode) -> Option<Opcode> {
353 match opcode {
354 Opcode::UDiv | Opcode::SDiv => Some(Opcode::UDiv),
355 Opcode::URem | Opcode::SRem => Some(Opcode::URem),
356 _ => None,
357 }
358}
359
360fn zero_extended(func: &Func, value: Value, ty: Type) -> Option<Value> {
367 match widening(func, value)? {
368 (Opcode::ZExt, from, narrow) if from == ty => Some(narrow),
369 _ => None,
370 }
371}
372
373fn sign_extended(func: &Func, left: Value, right: Value, ty: Type) -> Option<(Value, Value)> {
375 let (Opcode::SExt, from, left) = widening(func, left)? else { return None };
376 let (Opcode::SExt, other, right) = widening(func, right)? else { return None };
377 (from == ty && other == ty).then_some((left, right))
378}
379
380fn extended_comparison(func: &Func, inst: Inst) -> Option<Redo> {
402 let data = &func[inst];
403 if data.opcode != Opcode::ICmp {
404 return None;
405 }
406 let Extra::IntPred(pred) = data.extra else { return None };
407 let args = &func[data.args];
408 let (&left, &right) = (args.first()?, args.get(1)?);
409 let (kind, ty, narrow) = widening(func, left)?;
410 if !narrowable(ty) {
411 return None;
412 }
413 let widens = ty.bits() < func[left].ty.bits();
414 let pred = if kind == Opcode::ZExt && widens { pred.unsigned() } else { pred };
415 let rhs = match widening(func, right) {
416 Some((same, from, other)) if same == kind && from == ty => Plan::Already(other),
417 _ => Plan::Constant(survives(func, right, kind, ty)?),
418 };
419 let extra = Extra::IntPred(pred);
420 Some(Redo { opcode: Opcode::ICmp, extra, ty, lhs: Plan::Already(narrow), rhs: Some(rhs) })
421}
422
423fn widened_bits(func: &Func, inst: Inst, uses: &[u32]) -> Option<Redo> {
439 let (wide, back) = asked(func, inst, uses)?;
440 let data = &func[wide];
441 if !bit_at_a_time(data.opcode) {
442 return None;
443 }
444 if func[data.results().next()?].ty.bits() <= 1 {
450 return None;
451 }
452 let args = &func[data.args];
453 let (&left, &right) = (args.first()?, args.get(1)?);
454 let readers = if left == right { 2 } else { 1 };
458 let lhs = side(func, left, uses, readers)?;
459 let rhs = side(func, right, uses, readers)?;
460 if matches!((&lhs, &rhs), (Plan::Constant(_), Plan::Constant(_))) {
466 return None;
467 }
468 let extra = Extra::None;
469 let bit = Redo { opcode: data.opcode, extra, ty: Type::int(1), lhs, rhs: Some(rhs) };
470 let Some(ty) = back else { return Some(bit) };
471 let lhs = Plan::Nested(Box::new(bit));
472 Some(Redo { opcode: Opcode::ZExt, extra, ty, lhs, rhs: None })
473}
474
475fn asked(func: &Func, inst: Inst, uses: &[u32]) -> Option<(Inst, Option<Type>)> {
495 let data = &func[inst];
496 let args = &func[data.args];
497 match data.opcode {
498 Opcode::ICmp if data.extra == Extra::IntPred(IntPred::Ne) => {
499 let (&left, &right) = (args.first()?, args.get(1)?);
500 let (zero, wide) = constant(func, right)?;
501 (zero.signed(wide) == 0).then_some((read_by(func, left, uses, 1)?, None))
502 }
503 Opcode::ZExt | Opcode::SExt => {
504 let ty = func[data.results().next()?].ty;
505 Some((read_by(func, *args.first()?, uses, 1)?, Some(ty)))
506 }
507 _ => None,
508 }
509}
510
511fn side(func: &Func, value: Value, uses: &[u32], readers: u32) -> Option<Plan> {
519 if let Some((imm, wide)) = constant(func, value) {
520 let k = imm.signed(wide);
521 return (k == 0 || k == 1).then_some(Plan::Constant(k));
522 }
523 Some(Plan::Already(widened_bit(func, value, uses, readers)?))
524}
525
526fn read_by(func: &Func, value: Value, uses: &[u32], readers: u32) -> Option<Inst> {
532 if uses[value.index()] != readers {
533 return None;
534 }
535 let Def::Result { inst, .. } = func[value].def else { return None };
536 Some(inst)
537}
538
539const fn bit_at_a_time(opcode: Opcode) -> bool {
546 matches!(opcode, Opcode::And | Opcode::Or | Opcode::Xor)
547}
548
549fn widened_bit(func: &Func, value: Value, uses: &[u32], readers: u32) -> Option<Value> {
558 let inst = read_by(func, value, uses, readers)?;
559 let data = &func[inst];
560 if data.opcode != Opcode::ZExt {
561 return None;
562 }
563 let narrow = *func[data.args].first()?;
564 (func[narrow].ty == Type::int(1)).then_some(narrow)
565}
566
567fn widening(func: &Func, value: Value) -> Option<(Opcode, Type, Value)> {
569 let Def::Result { inst, .. } = func[value].def else { return None };
570 let data = &func[inst];
571 if data.opcode != Opcode::SExt && data.opcode != Opcode::ZExt {
572 return None;
573 }
574 let narrow = *func[data.args].first()?;
575 Some((data.opcode, func[narrow].ty, narrow))
576}
577
578fn extended(func: &Func, value: Value, ty: Type) -> Option<Value> {
583 let (_, from, narrow) = widening(func, value)?;
584 (from == ty).then_some(narrow)
585}
586
587fn constant(func: &Func, value: Value) -> Option<(Imm, Type)> {
589 let Def::Result { inst, .. } = func[value].def else { return None };
590 let data = &func[inst];
591 let Extra::Imm(at) = data.extra else { return None };
592 if data.opcode != Opcode::IConst {
593 return None;
594 }
595 let ty = func[value].ty;
596 ty.is_int().then(|| (func[at], ty))
597}
598
599fn count_below(func: &Func, value: Value, ty: Type) -> Option<i128> {
605 let (imm, wide) = constant(func, value)?;
606 let by = imm.signed(wide);
607 (by >= 0 && by < i128::from(ty.bits())).then_some(by)
608}
609
610fn survives(func: &Func, value: Value, kind: Opcode, ty: Type) -> Option<i128> {
616 let (imm, wide) = constant(func, value)?;
617 let k = imm.signed(wide);
618 let back = Imm::int(k, ty).signed(ty);
619 let same = if kind == Opcode::SExt { back } else { Imm::int(k, ty).unsigned() as i128 };
620 (same == k).then_some(k)
621}
622
623fn apply(func: &mut Func, inst: Inst, redo: &Redo, uses: &mut Vec<u32>) {
629 let operands = operands(func, inst, redo, uses);
630 for value in func[func[inst].args].iter().copied() {
631 uses[value.index()] -= 1;
632 }
633 let args = listed(func, operands, uses);
634 let data = &mut func[inst];
635 data.opcode = redo.opcode;
636 data.flags = Flags::NONE;
640 data.args = args;
641 data.extra = redo.extra;
642}
643
644fn build(func: &mut Func, before: Inst, ty: Type, plan: &Plan, uses: &mut Vec<u32>) -> Value {
646 match plan {
647 Plan::Already(value) => *value,
648 Plan::Constant(value) => {
649 let at = func.add_imm(Imm::int(*value, ty.lane()));
650 let data = InstData { extra: Extra::Imm(at), ..InstData::new(Opcode::IConst) };
651 written(func, before, data, ty, uses)
652 }
653 Plan::Nested(redo) => {
654 let operands = operands(func, before, redo, uses);
655 let args = listed(func, operands, uses);
656 let data = InstData { args, extra: redo.extra, ..InstData::new(redo.opcode) };
657 written(func, before, data, redo.ty, uses)
658 }
659 }
660}
661
662fn operands(
664 func: &mut Func,
665 before: Inst,
666 redo: &Redo,
667 uses: &mut Vec<u32>,
668) -> (Value, Option<Value>) {
669 let lhs = build(func, before, redo.ty, &redo.lhs, uses);
670 let rhs = redo.rhs.as_ref().map(|plan| build(func, before, redo.ty, plan, uses));
671 (lhs, rhs)
672}
673
674fn listed(func: &mut Func, (lhs, rhs): (Value, Option<Value>), uses: &mut [u32]) -> ValueList {
676 uses[lhs.index()] += 1;
677 let Some(rhs) = rhs else { return func.push_values(&[lhs]) };
678 uses[rhs.index()] += 1;
679 func.push_values(&[lhs, rhs])
680}
681
682fn written(func: &mut Func, before: Inst, data: InstData, ty: Type, uses: &mut Vec<u32>) -> Value {
684 let span = func.span(before);
685 let inst = func.create_inst(data, &[ty], span);
686 func.insert_before(inst, before);
687 uses.resize(func.counts().values, 0);
688 func[inst].first_result.expect("one result was asked for")
689}
690
691#[cfg(test)]
692mod tests {
693 use rucc_base::Interner;
694 use rucc_ir::{Block, Builder, Flags, Func, Inst, IntPred, Opcode, Signature, Type, Value};
695
696 use crate::narrow::Narrow;
697 use crate::{Fuel, Pass};
698
699 fn blank() -> (Func, Block) {
701 let mut names = Interner::new();
702 let name = names.intern("f");
703 let mut func = Func::new(name, Signature::new().with_returns(&[Type::int(32)]));
704 let block = func.create_block();
705 (func, block)
706 }
707
708 fn shape(func: &Func, value: Value) -> (Opcode, Vec<Type>) {
710 let rucc_ir::Def::Result { inst, .. } = func[value].def else { panic!("a result") };
711 let data = &func[inst];
712 (data.opcode, func[data.args].iter().map(|&arg| func[arg].ty).collect())
713 }
714
715 fn under(func: &Func, value: Value) -> Value {
717 let rucc_ir::Def::Result { inst, .. } = func[value].def else { panic!("a result") };
718 *func[func[inst].args].first().expect("an operand")
719 }
720
721 fn predicate(func: &Func, value: Value) -> IntPred {
723 let rucc_ir::Def::Result { inst, .. } = func[value].def else { panic!("a result") };
724 let rucc_ir::Extra::IntPred(pred) = func[inst].extra else { panic!("a comparison") };
725 pred
726 }
727
728 fn left(func: &Func, block: Block) -> usize {
730 func.insts(block).count()
731 }
732
733 fn last(func: &Func, block: Block) -> Inst {
735 func.insts(block).last().expect("a block with something in it")
736 }
737
738 #[test]
739 fn a_truncated_sum_of_two_extensions_is_the_sum_at_the_narrow_width() {
740 let (mut func, block) = blank();
741 let a = func.append_param(block, Type::int(8));
742 let b = func.append_param(block, Type::int(8));
743 let mut build = Builder::new(&mut func, block);
744 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
745 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
746 let sum = build.binary(Opcode::Add, wide_a, wide_b, Flags::NONE);
747 let narrow = build.unary(Opcode::Trunc, sum, Type::int(8));
748 build.ret(&[narrow]);
749 assert!(
750 Narrow
751 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
752 .changed()
753 );
754 assert_eq!(shape(&func, narrow), (Opcode::Add, vec![Type::int(8), Type::int(8)]));
755 assert_eq!(left(&func, block), 5);
758 }
759
760 #[test]
761 fn a_constant_operand_is_written_down_again_at_the_narrow_width() {
762 let (mut func, block) = blank();
763 let a = func.append_param(block, Type::int(8));
764 let mut build = Builder::new(&mut func, block);
765 let wide = build.unary(Opcode::SExt, a, Type::int(32));
766 let one = build.iconst(Type::int(32), 1);
767 let sum = build.binary(Opcode::Add, wide, one, Flags::NONE);
768 let narrow = build.unary(Opcode::Trunc, sum, Type::int(8));
769 build.ret(&[narrow]);
770 assert!(
771 Narrow
772 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
773 .changed()
774 );
775 assert_eq!(shape(&func, narrow), (Opcode::Add, vec![Type::int(8), Type::int(8)]));
776 }
777
778 #[test]
779 fn a_chain_of_arithmetic_narrows_the_whole_way_down() {
780 let (mut func, block) = blank();
781 let a = func.append_param(block, Type::int(8));
782 let b = func.append_param(block, Type::int(8));
783 let c = func.append_param(block, Type::int(8));
784 let mut build = Builder::new(&mut func, block);
785 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
786 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
787 let wide_c = build.unary(Opcode::SExt, c, Type::int(32));
788 let inner = build.binary(Opcode::Add, wide_a, wide_b, Flags::NONE);
789 let outer = build.binary(Opcode::Mul, inner, wide_c, Flags::NONE);
790 let narrow = build.unary(Opcode::Trunc, outer, Type::int(8));
791 build.ret(&[narrow]);
792 assert!(
793 Narrow
794 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
795 .changed()
796 );
797 assert_eq!(shape(&func, narrow), (Opcode::Mul, vec![Type::int(8), Type::int(8)]));
800 assert_eq!(left(&func, block), 8);
801 }
802
803 #[test]
804 fn an_operation_something_else_reads_stays_wide() {
805 let (mut func, block) = blank();
806 let a = func.append_param(block, Type::int(8));
807 let b = func.append_param(block, Type::int(8));
808 let mut build = Builder::new(&mut func, block);
809 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
810 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
811 let sum = build.binary(Opcode::Add, wide_a, wide_b, Flags::NONE);
812 let narrow = build.unary(Opcode::Trunc, sum, Type::int(8));
813 let kept = build.unary(Opcode::SExt, narrow, Type::int(32));
814 build.ret(&[sum, kept]);
815 assert!(
816 !Narrow
817 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
818 .changed()
819 );
820 assert_eq!(shape(&func, narrow), (Opcode::Trunc, vec![Type::int(32)]));
823 }
824
825 #[test]
826 fn a_divide_stays_wide_because_the_narrow_one_can_raise() {
827 let (mut func, block) = blank();
828 let a = func.append_param(block, Type::int(8));
829 let b = func.append_param(block, Type::int(8));
830 let mut build = Builder::new(&mut func, block);
831 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
832 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
833 let quotient = build.binary(Opcode::SDiv, wide_a, wide_b, Flags::NONE);
834 let narrow = build.unary(Opcode::Trunc, quotient, Type::int(8));
835 build.ret(&[narrow]);
836 assert!(
837 !Narrow
838 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
839 .changed()
840 );
841 assert_eq!(shape(&func, narrow), (Opcode::Trunc, vec![Type::int(32)]));
845 }
846
847 #[test]
851 fn a_division_of_two_zero_extensions_is_the_unsigned_division_at_the_narrow_width() {
852 let cases = [
853 (Opcode::SDiv, Opcode::UDiv),
854 (Opcode::UDiv, Opcode::UDiv),
855 (Opcode::SRem, Opcode::URem),
856 (Opcode::URem, Opcode::URem),
857 ];
858 for (width, (wide, want)) in [8, 16].into_iter().flat_map(|w| cases.map(|c| (w, c))) {
859 let (mut func, block) = blank();
860 let a = func.append_param(block, Type::int(width));
861 let b = func.append_param(block, Type::int(width));
862 let mut build = Builder::new(&mut func, block);
863 let wide_a = build.unary(Opcode::ZExt, a, Type::int(32));
864 let wide_b = build.unary(Opcode::ZExt, b, Type::int(32));
865 let divided = build.binary(wide, wide_a, wide_b, Flags::NONE);
866 let narrow = build.unary(Opcode::Trunc, divided, Type::int(width));
867 build.ret(&[narrow]);
868 assert!(
869 Narrow
870 .run(
871 &mut func,
872 &mut crate::machine::fixtures::analyses(),
873 &mut Fuel::unlimited()
874 )
875 .changed()
876 );
877 let operands = vec![Type::int(width), Type::int(width)];
878 assert_eq!(shape(&func, narrow), (want, operands));
879 assert_eq!(left(&func, block), 5);
880 }
881 }
882
883 #[test]
884 fn a_division_inside_narrow_arithmetic_narrows_with_it() {
885 let (mut func, block) = blank();
886 let a = func.append_param(block, Type::int(8));
887 let b = func.append_param(block, Type::int(8));
888 let mut build = Builder::new(&mut func, block);
889 let wide_a = build.unary(Opcode::ZExt, a, Type::int(32));
890 let wide_b = build.unary(Opcode::ZExt, b, Type::int(32));
891 let quotient = build.binary(Opcode::SDiv, wide_a, wide_b, Flags::NONE);
892 let one = build.iconst(Type::int(32), 1);
893 let sum = build.binary(Opcode::Add, quotient, one, Flags::NONE);
894 let narrow = build.unary(Opcode::Trunc, sum, Type::int(8));
895 build.ret(&[narrow]);
896 assert!(
897 Narrow
898 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
899 .changed()
900 );
901 assert_eq!(shape(&func, narrow), (Opcode::Add, vec![Type::int(8), Type::int(8)]));
902 let inner = under(&func, narrow);
903 assert_eq!(shape(&func, inner), (Opcode::UDiv, vec![Type::int(8), Type::int(8)]));
904 }
905
906 #[test]
910 fn a_signed_division_the_ranges_clear_is_the_signed_division_at_the_narrow_width() {
911 for (opcode, masked_left) in [
912 (Opcode::SDiv, true),
913 (Opcode::SDiv, false),
914 (Opcode::SRem, true),
915 (Opcode::SRem, false),
916 ] {
917 let (mut func, block) = blank();
918 let a = func.append_param(block, Type::int(8));
919 let b = func.append_param(block, Type::int(8));
920 let mut build = Builder::new(&mut func, block);
921 let mask = build.iconst(Type::int(8), 0x7f);
923 let (a, b) = if masked_left {
924 (build.binary(Opcode::And, a, mask, Flags::NONE), b)
925 } else {
926 (a, build.binary(Opcode::And, b, mask, Flags::NONE))
927 };
928 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
929 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
930 let divided = build.binary(opcode, wide_a, wide_b, Flags::NONE);
931 let narrow = build.unary(Opcode::Trunc, divided, Type::int(8));
932 build.ret(&[narrow]);
933 assert!(
934 Narrow
935 .run(
936 &mut func,
937 &mut crate::machine::fixtures::analyses(),
938 &mut Fuel::unlimited()
939 )
940 .changed()
941 );
942 assert_eq!(shape(&func, narrow), (opcode, vec![Type::int(8), Type::int(8)]));
943 }
944 }
945
946 #[test]
947 fn a_signed_division_whose_divisor_can_only_be_minus_one_when_the_dividend_is_not_the_least() {
948 let (mut func, block) = blank();
951 let a = func.append_param(block, Type::int(8));
952 let b = func.append_param(block, Type::int(8));
953 let mut build = Builder::new(&mut func, block);
954 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
955 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
956 let divided = build.binary(Opcode::SRem, wide_a, wide_b, Flags::NONE);
957 let narrow = build.unary(Opcode::Trunc, divided, Type::int(8));
958 build.ret(&[narrow]);
959 assert!(
960 !Narrow
961 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
962 .changed()
963 );
964 assert_eq!(shape(&func, narrow), (Opcode::Trunc, vec![Type::int(32)]));
965 }
966
967 #[test]
968 fn a_division_of_a_zero_extension_by_a_sign_extension_stays_wide() {
969 let (mut func, block) = blank();
970 let a = func.append_param(block, Type::int(8));
971 let b = func.append_param(block, Type::int(8));
972 let mut build = Builder::new(&mut func, block);
973 let wide_a = build.unary(Opcode::ZExt, a, Type::int(32));
974 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
975 let quotient = build.binary(Opcode::SDiv, wide_a, wide_b, Flags::NONE);
976 let narrow = build.unary(Opcode::Trunc, quotient, Type::int(8));
977 build.ret(&[narrow]);
978 assert!(
979 !Narrow
980 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
981 .changed()
982 );
983 assert_eq!(shape(&func, narrow), (Opcode::Trunc, vec![Type::int(32)]));
986 }
987
988 #[test]
989 fn a_division_of_zero_extensions_from_a_narrower_width_stays_wide() {
990 let (mut func, block) = blank();
991 let a = func.append_param(block, Type::int(8));
992 let b = func.append_param(block, Type::int(8));
993 let mut build = Builder::new(&mut func, block);
994 let wide_a = build.unary(Opcode::ZExt, a, Type::int(32));
995 let wide_b = build.unary(Opcode::ZExt, b, Type::int(32));
996 let quotient = build.binary(Opcode::UDiv, wide_a, wide_b, Flags::NONE);
997 let narrow = build.unary(Opcode::Trunc, quotient, Type::int(16));
998 build.ret(&[narrow]);
999 assert!(
1000 !Narrow
1001 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1002 .changed()
1003 );
1004 assert_eq!(shape(&func, narrow), (Opcode::Trunc, vec![Type::int(32)]));
1005 }
1006
1007 #[test]
1008 fn a_division_by_a_constant_stays_wide() {
1009 let (mut func, block) = blank();
1010 let a = func.append_param(block, Type::int(8));
1011 let mut build = Builder::new(&mut func, block);
1012 let wide = build.unary(Opcode::ZExt, a, Type::int(32));
1013 let ten = build.iconst(Type::int(32), 10);
1014 let quotient = build.binary(Opcode::SDiv, wide, ten, Flags::NONE);
1015 let narrow = build.unary(Opcode::Trunc, quotient, Type::int(8));
1016 build.ret(&[narrow]);
1017 assert!(
1018 !Narrow
1019 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1020 .changed()
1021 );
1022 assert_eq!(shape(&func, narrow), (Opcode::Trunc, vec![Type::int(32)]));
1023 }
1024
1025 #[test]
1026 fn a_shift_by_a_constant_below_the_width_narrows_and_one_at_it_does_not() {
1027 for (by, narrows) in [(3, true), (20, false)] {
1028 let (mut func, block) = blank();
1029 let a = func.append_param(block, Type::int(8));
1030 let mut build = Builder::new(&mut func, block);
1031 let wide = build.unary(Opcode::SExt, a, Type::int(32));
1032 let count = build.iconst(Type::int(32), by);
1033 let shifted = build.binary(Opcode::Shl, wide, count, Flags::NONE);
1034 let narrow = build.unary(Opcode::Trunc, shifted, Type::int(8));
1035 build.ret(&[narrow]);
1036 assert_eq!(
1037 Narrow
1038 .run(
1039 &mut func,
1040 &mut crate::machine::fixtures::analyses(),
1041 &mut Fuel::unlimited()
1042 )
1043 .changed(),
1044 narrows,
1045 "shift by {by}"
1046 );
1047 let want = if narrows { Opcode::Shl } else { Opcode::Trunc };
1050 assert_eq!(shape(&func, narrow).0, want, "shift by {by}");
1051 }
1052 }
1053
1054 #[test]
1055 fn a_shift_by_a_value_stays_wide() {
1056 let (mut func, block) = blank();
1057 let a = func.append_param(block, Type::int(8));
1058 let n = func.append_param(block, Type::int(8));
1059 let mut build = Builder::new(&mut func, block);
1060 let wide = build.unary(Opcode::SExt, a, Type::int(32));
1061 let by = build.unary(Opcode::SExt, n, Type::int(32));
1062 let shifted = build.binary(Opcode::Shl, wide, by, Flags::NONE);
1063 let narrow = build.unary(Opcode::Trunc, shifted, Type::int(8));
1064 build.ret(&[narrow]);
1065 assert!(
1066 !Narrow
1067 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1068 .changed()
1069 );
1070 assert_eq!(shape(&func, narrow).0, Opcode::Trunc);
1071 }
1072
1073 #[test]
1074 fn a_comparison_of_two_sign_extensions_is_the_comparison_of_what_they_extended() {
1075 for pred in IntPred::all() {
1076 let (mut func, block) = blank();
1077 let a = func.append_param(block, Type::int(8));
1078 let b = func.append_param(block, Type::int(8));
1079 let mut build = Builder::new(&mut func, block);
1080 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
1081 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
1082 let answer = build.icmp(pred, wide_a, wide_b);
1083 build.ret(&[answer]);
1084 assert!(
1085 Narrow
1086 .run(
1087 &mut func,
1088 &mut crate::machine::fixtures::analyses(),
1089 &mut Fuel::unlimited()
1090 )
1091 .changed(),
1092 "{pred}"
1093 );
1094 assert_eq!(shape(&func, answer).1, vec![Type::int(8), Type::int(8)], "{pred}");
1097 }
1098 }
1099
1100 #[test]
1101 fn a_comparison_of_two_zero_extensions_narrows_at_every_predicate() {
1102 for pred in IntPred::all() {
1103 let (mut func, block) = blank();
1104 let a = func.append_param(block, Type::int(8));
1105 let b = func.append_param(block, Type::int(8));
1106 let mut build = Builder::new(&mut func, block);
1107 let wide_a = build.unary(Opcode::ZExt, a, Type::int(32));
1108 let wide_b = build.unary(Opcode::ZExt, b, Type::int(32));
1109 let answer = build.icmp(pred, wide_a, wide_b);
1110 build.ret(&[answer]);
1111 assert!(
1112 Narrow
1113 .run(
1114 &mut func,
1115 &mut crate::machine::fixtures::analyses(),
1116 &mut Fuel::unlimited()
1117 )
1118 .changed(),
1119 "{pred}"
1120 );
1121 assert_eq!(shape(&func, answer).1, vec![Type::int(8), Type::int(8)], "{pred}");
1122 }
1123 }
1124
1125 #[test]
1126 fn a_signed_comparison_of_two_zero_extensions_narrows_to_the_unsigned_one() {
1127 for pred in IntPred::all() {
1132 let (mut func, block) = blank();
1133 let a = func.append_param(block, Type::int(8));
1134 let b = func.append_param(block, Type::int(8));
1135 let mut build = Builder::new(&mut func, block);
1136 let wide_a = build.unary(Opcode::ZExt, a, Type::int(32));
1137 let wide_b = build.unary(Opcode::ZExt, b, Type::int(32));
1138 let answer = build.icmp(pred, wide_a, wide_b);
1139 build.ret(&[answer]);
1140 Narrow.run(
1141 &mut func,
1142 &mut crate::machine::fixtures::analyses(),
1143 &mut Fuel::unlimited(),
1144 );
1145 assert_eq!(predicate(&func, answer), pred.unsigned(), "{pred}");
1146 }
1147 }
1148
1149 #[test]
1150 fn a_signed_comparison_of_two_sign_extensions_keeps_the_predicate_it_was_written_with() {
1151 for pred in IntPred::all() {
1152 let (mut func, block) = blank();
1153 let a = func.append_param(block, Type::int(8));
1154 let b = func.append_param(block, Type::int(8));
1155 let mut build = Builder::new(&mut func, block);
1156 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
1157 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
1158 let answer = build.icmp(pred, wide_a, wide_b);
1159 build.ret(&[answer]);
1160 Narrow.run(
1161 &mut func,
1162 &mut crate::machine::fixtures::analyses(),
1163 &mut Fuel::unlimited(),
1164 );
1165 assert_eq!(predicate(&func, answer), pred, "{pred}");
1166 }
1167 }
1168
1169 #[test]
1170 fn a_signed_comparison_of_a_zero_extension_against_a_constant_narrows_to_the_unsigned_one() {
1171 let (mut func, block) = blank();
1175 let a = func.append_param(block, Type::int(8));
1176 let mut build = Builder::new(&mut func, block);
1177 let wide = build.unary(Opcode::ZExt, a, Type::int(32));
1178 let k = build.iconst(Type::int(32), 200);
1179 let answer = build.icmp(IntPred::Slt, wide, k);
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).1, vec![Type::int(8), Type::int(8)]);
1187 assert_eq!(predicate(&func, answer), IntPred::Ult);
1188 }
1189
1190 #[test]
1191 fn a_signed_comparison_of_a_zero_extension_against_a_negative_constant_is_left_alone() {
1192 let (mut func, block) = blank();
1196 let a = func.append_param(block, Type::int(8));
1197 let mut build = Builder::new(&mut func, block);
1198 let wide = build.unary(Opcode::ZExt, a, Type::int(32));
1199 let k = build.iconst(Type::int(32), -1);
1200 let answer = build.icmp(IntPred::Sgt, wide, k);
1201 build.ret(&[answer]);
1202 assert!(
1203 !Narrow
1204 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1205 .changed()
1206 );
1207 }
1208
1209 #[test]
1210 fn a_comparison_against_a_constant_narrows_when_the_constant_is_one_of_the_narrow_ones() {
1211 for (k, narrows) in [(120, true), (-1, true), (200, false)] {
1212 let (mut func, block) = blank();
1213 let a = func.append_param(block, Type::int(8));
1214 let mut build = Builder::new(&mut func, block);
1215 let wide = build.unary(Opcode::SExt, a, Type::int(32));
1216 let k = build.iconst(Type::int(32), k);
1217 let answer = build.icmp(IntPred::Eq, wide, k);
1218 build.ret(&[answer]);
1219 assert_eq!(
1222 Narrow
1223 .run(
1224 &mut func,
1225 &mut crate::machine::fixtures::analyses(),
1226 &mut Fuel::unlimited()
1227 )
1228 .changed(),
1229 narrows
1230 );
1231 }
1232 }
1233
1234 #[test]
1235 fn one_extension_against_the_other_kind_is_not_a_comparison_at_the_narrow_width() {
1236 for pred in IntPred::all() {
1241 let (mut func, block) = blank();
1242 let a = func.append_param(block, Type::int(8));
1243 let b = func.append_param(block, Type::int(8));
1244 let mut build = Builder::new(&mut func, block);
1245 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
1246 let wide_b = build.unary(Opcode::ZExt, b, Type::int(32));
1247 let answer = build.icmp(pred, wide_a, wide_b);
1248 build.ret(&[answer]);
1249 assert!(
1250 !Narrow
1251 .run(
1252 &mut func,
1253 &mut crate::machine::fixtures::analyses(),
1254 &mut Fuel::unlimited()
1255 )
1256 .changed(),
1257 "{pred}"
1258 );
1259 }
1260 }
1261
1262 #[test]
1263 fn a_truth_is_not_a_width_to_narrow_to() {
1264 let (mut func, block) = blank();
1268 let a = func.append_param(block, Type::int(1));
1269 let mut build = Builder::new(&mut func, block);
1270 let wide = build.unary(Opcode::ZExt, a, Type::int(32));
1271 let zero = build.iconst(Type::int(32), 0);
1272 let answer = build.icmp(IntPred::Ne, wide, zero);
1273 build.ret(&[answer]);
1274 assert!(
1275 !Narrow
1276 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1277 .changed()
1278 );
1279 assert_eq!(shape(&func, answer).1, vec![Type::int(32), Type::int(32)]);
1280 }
1281
1282 #[test]
1283 fn extensions_from_different_widths_are_not_a_comparison_at_either_of_them() {
1284 let (mut func, block) = blank();
1285 let a = func.append_param(block, Type::int(8));
1286 let b = func.append_param(block, Type::int(16));
1287 let mut build = Builder::new(&mut func, block);
1288 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
1289 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
1290 let answer = build.icmp(IntPred::Slt, wide_a, wide_b);
1291 build.ret(&[answer]);
1292 assert!(
1293 !Narrow
1294 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1295 .changed()
1296 );
1297 }
1298
1299 #[test]
1300 fn the_overflow_flags_do_not_come_along() {
1301 let (mut func, block) = blank();
1302 let a = func.append_param(block, Type::int(8));
1303 let b = func.append_param(block, Type::int(8));
1304 let mut build = Builder::new(&mut func, block);
1305 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
1306 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
1307 let sum = build.binary(Opcode::Add, wide_a, wide_b, Flags::NSW);
1308 let narrow = build.unary(Opcode::Trunc, sum, Type::int(8));
1309 build.ret(&[narrow]);
1310 assert!(
1311 Narrow
1312 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1313 .changed()
1314 );
1315 let rucc_ir::Def::Result { inst, .. } = func[narrow].def else { panic!("a result") };
1318 assert_eq!(func[inst].flags, Flags::NONE);
1319 }
1320
1321 #[test]
1327 fn a_bitwise_operation_on_two_widened_bits_is_done_at_one_bit() {
1328 for opcode in [Opcode::And, Opcode::Or, Opcode::Xor] {
1329 let (mut func, block) = blank();
1330 let p = func.append_param(block, Type::int(1));
1331 let q = func.append_param(block, Type::int(1));
1332 let mut build = Builder::new(&mut func, block);
1333 let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1334 let wide_q = build.unary(Opcode::ZExt, q, Type::int(32));
1335 let both = build.binary(opcode, wide_p, wide_q, Flags::NONE);
1336 let zero = build.iconst(Type::int(32), 0);
1337 let answer = build.icmp(IntPred::Ne, both, zero);
1338 build.ret(&[answer]);
1339 assert!(
1340 Narrow
1341 .run(
1342 &mut func,
1343 &mut crate::machine::fixtures::analyses(),
1344 &mut Fuel::unlimited()
1345 )
1346 .changed(),
1347 "{opcode:?}"
1348 );
1349 assert_eq!(shape(&func, answer), (opcode, vec![Type::int(1), Type::int(1)]));
1350 }
1351 }
1352
1353 #[test]
1356 fn asking_whether_a_bitwise_operation_on_widened_bits_is_zero_is_left_alone() {
1357 let (mut func, block) = blank();
1358 let p = func.append_param(block, Type::int(1));
1359 let q = func.append_param(block, Type::int(1));
1360 let mut build = Builder::new(&mut func, block);
1361 let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1362 let wide_q = build.unary(Opcode::ZExt, q, Type::int(32));
1363 let both = build.binary(Opcode::And, wide_p, wide_q, Flags::NONE);
1364 let zero = build.iconst(Type::int(32), 0);
1365 let answer = build.icmp(IntPred::Eq, both, zero);
1366 build.ret(&[answer]);
1367 assert!(
1368 !Narrow
1369 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1370 .changed()
1371 );
1372 assert_eq!(shape(&func, answer).1, vec![Type::int(32), Type::int(32)]);
1373 }
1374
1375 #[test]
1378 fn a_sum_of_two_widened_bits_is_left_alone() {
1379 let (mut func, block) = blank();
1380 let p = func.append_param(block, Type::int(1));
1381 let q = func.append_param(block, Type::int(1));
1382 let mut build = Builder::new(&mut func, block);
1383 let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1384 let wide_q = build.unary(Opcode::ZExt, q, Type::int(32));
1385 let both = build.binary(Opcode::Add, wide_p, wide_q, Flags::NONE);
1386 let zero = build.iconst(Type::int(32), 0);
1387 let answer = build.icmp(IntPred::Ne, both, zero);
1388 build.ret(&[answer]);
1389 assert!(
1390 !Narrow
1391 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1392 .changed()
1393 );
1394 }
1395
1396 #[test]
1399 fn a_bitwise_operation_on_something_wider_than_a_bit_is_not_this_shape() {
1400 let (mut func, block) = blank();
1401 let a = func.append_param(block, Type::int(8));
1402 let b = func.append_param(block, Type::int(8));
1403 let mut build = Builder::new(&mut func, block);
1404 let wide_a = build.unary(Opcode::ZExt, a, Type::int(32));
1405 let wide_b = build.unary(Opcode::ZExt, b, Type::int(32));
1406 let both = build.binary(Opcode::And, wide_a, wide_b, Flags::NONE);
1407 let zero = build.iconst(Type::int(32), 0);
1408 let answer = build.icmp(IntPred::Ne, both, zero);
1409 build.ret(&[answer]);
1410 Narrow.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
1411 assert_eq!(shape(&func, answer).0, Opcode::ICmp);
1414 }
1415
1416 #[test]
1421 fn a_bitwise_operation_on_a_widened_bit_and_a_bit_constant_is_done_at_one_bit() {
1422 for (opcode, k) in
1423 [(Opcode::And, 0), (Opcode::And, 1), (Opcode::Or, 0), (Opcode::Or, 1), (Opcode::Xor, 0)]
1424 {
1425 let (mut func, block) = blank();
1426 let p = func.append_param(block, Type::int(1));
1427 let mut build = Builder::new(&mut func, block);
1428 let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1429 let bit = build.iconst(Type::int(32), k);
1430 let both = build.binary(opcode, wide_p, bit, Flags::NONE);
1431 let zero = build.iconst(Type::int(32), 0);
1432 let answer = build.icmp(IntPred::Ne, both, zero);
1433 build.ret(&[answer]);
1434 assert!(
1435 Narrow
1436 .run(
1437 &mut func,
1438 &mut crate::machine::fixtures::analyses(),
1439 &mut Fuel::unlimited()
1440 )
1441 .changed(),
1442 "{opcode:?} {k}"
1443 );
1444 assert_eq!(shape(&func, answer), (opcode, vec![Type::int(1), Type::int(1)]));
1445 }
1446 }
1447
1448 #[test]
1451 fn a_bitwise_operation_against_a_constant_wider_than_a_bit_is_left_alone() {
1452 let (mut func, block) = blank();
1453 let p = func.append_param(block, Type::int(1));
1454 let mut build = Builder::new(&mut func, block);
1455 let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1456 let two = build.iconst(Type::int(32), 2);
1457 let both = build.binary(Opcode::Or, wide_p, two, Flags::NONE);
1458 let zero = build.iconst(Type::int(32), 0);
1459 let answer = build.icmp(IntPred::Ne, both, zero);
1460 build.ret(&[answer]);
1461 assert!(
1462 !Narrow
1463 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1464 .changed()
1465 );
1466 }
1467
1468 #[test]
1471 fn a_widened_bit_the_operation_reads_twice_is_still_only_read_by_it() {
1472 let (mut func, block) = blank();
1473 let p = func.append_param(block, Type::int(1));
1474 let mut build = Builder::new(&mut func, block);
1475 let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1476 let both = build.binary(Opcode::And, wide_p, wide_p, Flags::NONE);
1477 let zero = build.iconst(Type::int(32), 0);
1478 let answer = build.icmp(IntPred::Ne, both, zero);
1479 build.ret(&[answer]);
1480 assert!(
1481 Narrow
1482 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1483 .changed()
1484 );
1485 assert_eq!(shape(&func, answer), (Opcode::And, vec![Type::int(1), Type::int(1)]));
1486 }
1487
1488 #[test]
1491 fn a_widened_bit_that_something_else_reads_is_left_alone() {
1492 let (mut func, block) = blank();
1493 let p = func.append_param(block, Type::int(1));
1494 let q = func.append_param(block, Type::int(1));
1495 let mut build = Builder::new(&mut func, block);
1496 let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1497 let wide_q = build.unary(Opcode::ZExt, q, Type::int(32));
1498 let both = build.binary(Opcode::And, wide_p, wide_q, Flags::NONE);
1499 let zero = build.iconst(Type::int(32), 0);
1500 let answer = build.icmp(IntPred::Ne, both, zero);
1501 build.ret(&[answer, wide_p]);
1502 assert!(
1503 !Narrow
1504 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1505 .changed()
1506 );
1507 }
1508
1509 #[test]
1511 fn a_bitwise_operation_on_widened_bits_compared_against_one_is_left_alone() {
1512 let (mut func, block) = blank();
1513 let p = func.append_param(block, Type::int(1));
1514 let q = func.append_param(block, Type::int(1));
1515 let mut build = Builder::new(&mut func, block);
1516 let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1517 let wide_q = build.unary(Opcode::ZExt, q, Type::int(32));
1518 let both = build.binary(Opcode::Or, wide_p, wide_q, Flags::NONE);
1519 let one = build.iconst(Type::int(32), 1);
1520 let answer = build.icmp(IntPred::Ne, both, one);
1521 build.ret(&[answer]);
1522 assert!(
1523 !Narrow
1524 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1525 .changed()
1526 );
1527 }
1528
1529 #[test]
1534 fn a_bitwise_operation_on_widened_bits_taken_wider_is_done_at_one_bit() {
1535 for kind in [Opcode::ZExt, Opcode::SExt] {
1536 let (mut func, block) = blank();
1537 let p = func.append_param(block, Type::int(1));
1538 let q = func.append_param(block, Type::int(1));
1539 let mut build = Builder::new(&mut func, block);
1540 let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1541 let wide_q = build.unary(Opcode::ZExt, q, Type::int(32));
1542 let both = build.binary(Opcode::And, wide_p, wide_q, Flags::NONE);
1543 let wider = build.unary(kind, both, Type::int(64));
1544 build.ret(&[wider]);
1545 assert!(
1546 Narrow
1547 .run(
1548 &mut func,
1549 &mut crate::machine::fixtures::analyses(),
1550 &mut Fuel::unlimited()
1551 )
1552 .changed(),
1553 "{kind:?}"
1554 );
1555 assert_eq!(shape(&func, wider), (Opcode::ZExt, vec![Type::int(1)]), "{kind:?}");
1556 let bit = under(&func, wider);
1557 let want = (Opcode::And, vec![Type::int(1), Type::int(1)]);
1558 assert_eq!(shape(&func, bit), want, "{kind:?}");
1559 }
1560 }
1561
1562 #[test]
1565 fn a_bitwise_operation_on_two_bit_constants_is_left_to_the_folder() {
1566 let (mut func, block) = blank();
1567 let mut build = Builder::new(&mut func, block);
1568 let zero = build.iconst(Type::int(32), 0);
1569 let one = build.iconst(Type::int(32), 1);
1570 let both = build.binary(Opcode::And, zero, one, Flags::NONE);
1571 let wider = build.unary(Opcode::ZExt, both, Type::int(64));
1572 build.ret(&[wider]);
1573 assert!(
1574 !Narrow
1575 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1576 .changed()
1577 );
1578 }
1579
1580 #[test]
1584 fn a_bitwise_operation_already_at_one_bit_is_not_done_again() {
1585 let (mut func, block) = blank();
1586 let p = func.append_param(block, Type::int(1));
1587 let q = func.append_param(block, Type::int(1));
1588 let mut build = Builder::new(&mut func, block);
1589 let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1590 let wide_q = build.unary(Opcode::ZExt, q, Type::int(32));
1591 let both = build.binary(Opcode::Xor, wide_p, wide_q, Flags::NONE);
1592 let wider = build.unary(Opcode::SExt, both, Type::int(64));
1593 build.ret(&[wider]);
1594 let mut an = crate::machine::fixtures::analyses();
1595 assert!(Narrow.run(&mut func, &mut an, &mut Fuel::unlimited()).changed());
1596 assert!(!Narrow.run(&mut func, &mut an, &mut Fuel::unlimited()).changed());
1597 }
1598
1599 #[test]
1602 fn a_bit_constant_comes_over_under_an_extension_too() {
1603 let (mut func, block) = blank();
1604 let p = func.append_param(block, Type::int(1));
1605 let mut build = Builder::new(&mut func, block);
1606 let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1607 let one = build.iconst(Type::int(32), 1);
1608 let both = build.binary(Opcode::And, wide_p, one, Flags::NONE);
1609 let wider = build.unary(Opcode::SExt, both, Type::int(64));
1610 build.ret(&[wider]);
1611 assert!(
1612 Narrow
1613 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1614 .changed()
1615 );
1616 assert_eq!(shape(&func, wider), (Opcode::ZExt, vec![Type::int(1)]));
1617 assert_eq!(shape(&func, under(&func, wider)), (Opcode::And, vec![Type::int(1); 2]));
1618 }
1619
1620 #[test]
1623 fn an_extension_of_a_bitwise_operation_on_bytes_is_left_alone() {
1624 let (mut func, block) = blank();
1625 let a = func.append_param(block, Type::int(8));
1626 let b = func.append_param(block, Type::int(8));
1627 let mut build = Builder::new(&mut func, block);
1628 let wide_a = build.unary(Opcode::ZExt, a, Type::int(32));
1629 let wide_b = build.unary(Opcode::ZExt, b, Type::int(32));
1630 let both = build.binary(Opcode::And, wide_a, wide_b, Flags::NONE);
1631 let wider = build.unary(Opcode::SExt, both, Type::int(64));
1632 build.ret(&[wider]);
1633 assert!(
1634 !Narrow
1635 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1636 .changed()
1637 );
1638 }
1639
1640 #[test]
1643 fn an_extension_of_a_sum_of_two_widened_bits_is_left_alone() {
1644 let (mut func, block) = blank();
1645 let p = func.append_param(block, Type::int(1));
1646 let q = func.append_param(block, Type::int(1));
1647 let mut build = Builder::new(&mut func, block);
1648 let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1649 let wide_q = build.unary(Opcode::ZExt, q, Type::int(32));
1650 let both = build.binary(Opcode::Add, wide_p, wide_q, Flags::NONE);
1651 let wider = build.unary(Opcode::SExt, both, Type::int(64));
1652 build.ret(&[wider]);
1653 assert!(
1654 !Narrow
1655 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1656 .changed()
1657 );
1658 }
1659
1660 #[test]
1661 fn fuel_stops_the_narrowing_and_not_the_looking() {
1662 let (mut func, block) = blank();
1663 let a = func.append_param(block, Type::int(8));
1664 let b = func.append_param(block, Type::int(8));
1665 let mut build = Builder::new(&mut func, block);
1666 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
1667 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
1668 let first = build.icmp(IntPred::Slt, wide_a, wide_b);
1669 let second = build.icmp(IntPred::Sgt, wide_a, wide_b);
1670 build.ret(&[first, second]);
1671 let mut fuel = Fuel::of(1);
1672 assert!(
1673 Narrow.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut fuel).changed()
1674 );
1675 assert_eq!(shape(&func, first).1, vec![Type::int(8), Type::int(8)]);
1676 assert_eq!(shape(&func, second).1, vec![Type::int(32), Type::int(32)]);
1677 }
1678
1679 #[test]
1680 fn a_block_that_narrows_nothing_is_left_exactly_as_it_was() {
1681 let (mut func, block) = blank();
1682 let a = func.append_param(block, Type::int(32));
1683 let mut build = Builder::new(&mut func, block);
1684 let sum = build.binary(Opcode::Add, a, a, Flags::NONE);
1685 build.ret(&[sum]);
1686 assert!(
1687 !Narrow
1688 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1689 .changed()
1690 );
1691 assert_eq!(left(&func, block), 2);
1692 assert_eq!(func[last(&func, block)].opcode, Opcode::Return);
1693 }
1694}