1use rucc_base::float::{Float, Status};
82use rucc_ir::{Block, Def, Extra, Flags, Func, Imm, Inst, InstData, IntPred, Opcode, Type, Value};
83
84use crate::{Analyses, Analysis, Fuel, Pass, Preserved, Stats};
85
86const FOLDED: &str = "instruction with constant operands folded to a constant";
88
89const NO_FUEL: &str = "instruction not folded, the pass ran out of fuel";
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub struct Fold;
99
100impl Pass for Fold {
101 fn name(&self) -> &'static str {
102 "fold"
103 }
104
105 fn describe(&self) -> &'static str {
106 "an instruction whose operands are all constants becomes a constant"
107 }
108
109 fn preserves(&self) -> Preserved {
110 Preserved::ALL.without(Analysis::Liveness)
116 }
117
118 fn run(&self, func: &mut Func, _an: &mut Analyses, fuel: &mut Fuel) -> Stats {
119 fold_in(func, fuel)
120 }
121}
122
123pub(crate) fn fold_in(func: &mut Func, fuel: &mut Fuel) -> Stats {
129 let blocks: Vec<Block> = func.blocks().collect();
130 let mut stats = Stats::new();
131 for block in blocks {
132 let insts: Vec<Inst> = func.insts(block).collect();
133 for inst in insts {
134 let Some(folded) = evaluate(func, inst) else { continue };
135 if !fuel.take() {
136 stats.missed(NO_FUEL);
141 continue;
142 }
143 let ty = func[result_of(func, inst)].ty;
144 let at = func.add_imm(folded);
145 let data = &mut func[inst];
146 data.opcode = if ty.is_int() { Opcode::IConst } else { Opcode::FConst };
151 data.flags = Flags::NONE;
152 data.args = rucc_ir::ValueList::EMPTY;
153 data.extra = Extra::Imm(at);
154 stats.optimized(FOLDED);
155 }
156 }
157 stats
158}
159
160fn result_of(func: &Func, inst: Inst) -> Value {
162 func[inst].results().next().expect("an instruction that folds produces a value")
163}
164
165fn evaluate(func: &Func, inst: Inst) -> Option<Imm> {
170 let data = &func[inst];
171 if data.results != 1 {
172 return None;
173 }
174 let result = data.results().next()?;
175 let ty = func[result].ty;
176 if !ty.is_scalar() {
180 return None;
181 }
182 let args = &func[data.args];
183 match data.opcode {
187 Opcode::FNeg => return negated(func, *args.first()?, ty),
188 Opcode::Bitcast => return reinterpreted(func, *args.first()?, ty),
189 _ => {}
190 }
191 if !ty.is_int() {
192 return None;
193 }
194 match data.opcode {
195 Opcode::FPToSI | Opcode::FPToUI => {
196 let value = floating(func, *args.first()?)?;
197 to_integer(value, ty, data.opcode == Opcode::FPToSI)
198 }
199 _ => arithmetic(data, args, ty, &|value| constant(func, value)),
200 }
201}
202
203fn arithmetic(
211 data: &InstData,
212 args: &[Value],
213 ty: Type,
214 operand: &dyn Fn(Value) -> Option<(Imm, Type)>,
215) -> Option<Imm> {
216 match data.opcode {
217 Opcode::Trunc | Opcode::SExt | Opcode::ZExt => {
218 let (value, from) = operand(*args.first()?)?;
219 Some(convert(data.opcode, value, from, ty))
220 }
221 Opcode::Shl | Opcode::LShr | Opcode::AShr => {
222 let (value, from) = operand(*args.first()?)?;
223 let (count, count_ty) = operand(*args.get(1)?)?;
224 shift(data.opcode, value, from, count, count_ty, ty, data.flags)
225 }
226 Opcode::Add | Opcode::Sub | Opcode::Mul | Opcode::And | Opcode::Or | Opcode::Xor => {
227 let (lhs, lhs_ty) = operand(*args.first()?)?;
228 let (rhs, _) = operand(*args.get(1)?)?;
229 binary(data.opcode, lhs, rhs, lhs_ty, ty, data.flags)
230 }
231 Opcode::Ctlz | Opcode::Cttz | Opcode::Ctpop | Opcode::Bswap | Opcode::Bitreverse => {
232 let (value, from) = operand(*args.first()?)?;
233 count(data.opcode, value, from, ty)
234 }
235 Opcode::ICmp => {
236 let Extra::IntPred(pred) = data.extra else { return None };
237 let (lhs, from) = operand(*args.first()?)?;
238 let (rhs, _) = operand(*args.get(1)?)?;
239 Some(Imm::int(i128::from(compare(pred, lhs, rhs, from)), ty))
240 }
241 Opcode::Select => {
246 let (then, _) = operand(*args.get(1)?)?;
247 let (other, _) = operand(*args.get(2)?)?;
248 (then.signed(ty) == other.signed(ty)).then_some(then)
249 }
250 _ => None,
251 }
252}
253
254pub(crate) fn evaluated(func: &Func, value: Value, depth: u32) -> Option<(Imm, Type)> {
263 if let Some(found) = constant(func, value) {
264 return Some(found);
265 }
266 let next = depth.checked_sub(1)?;
267 let Def::Result { inst, .. } = func[value].def else { return None };
268 let data = &func[inst];
269 let ty = func[value].ty;
270 if data.results != 1 || !ty.is_int() || !ty.is_scalar() {
271 return None;
272 }
273 let found = arithmetic(data, &func[data.args], ty, &|arg| evaluated(func, arg, next))?;
274 Some((found, ty))
275}
276
277fn bits_of(func: &Func, value: Value) -> Option<u128> {
283 let Def::Result { inst, .. } = func[value].def else { return None };
284 let data = &func[inst];
285 if !matches!(data.opcode, Opcode::IConst | Opcode::FConst) {
286 return None;
287 }
288 let Extra::Imm(at) = data.extra else { return None };
289 Some(func[at].bits())
290}
291
292fn negated(func: &Func, operand: Value, ty: Type) -> Option<Imm> {
310 if !ty.is_float() {
311 return None;
312 }
313 let bits = bits_of(func, operand)?;
314 Some(Imm::from_bits(bits ^ 1u128 << (ty.bits() - 1)))
315}
316
317fn reinterpreted(func: &Func, operand: Value, ty: Type) -> Option<Imm> {
328 let from = func[operand].ty;
329 if !from.is_scalar() || from.bits() != ty.bits() {
330 return None;
331 }
332 let bits = bits_of(func, operand)?;
333 Some(Imm::from_bits(bits))
334}
335
336pub(crate) fn constant(func: &Func, value: Value) -> Option<(Imm, Type)> {
341 let Def::Result { inst, .. } = func[value].def else { return None };
342 if func[inst].opcode != Opcode::IConst {
343 return None;
344 }
345 let Extra::Imm(at) = func[inst].extra else { return None };
346 let ty = func[value].ty;
347 ty.is_int().then(|| (func[at], ty))
348}
349
350fn floating(func: &Func, value: Value) -> Option<Float> {
356 let Def::Result { inst, .. } = func[value].def else { return None };
357 if func[inst].opcode != Opcode::FConst {
358 return None;
359 }
360 let Extra::Imm(at) = func[inst].extra else { return None };
361 let format = func[value].ty.format()?.encoding();
362 Some(Float::from_bits(format, func[at].bits()))
363}
364
365fn to_integer(value: Float, to: Type, signed: bool) -> Option<Imm> {
374 let (number, status) = value.to_integer(to.bits(), signed);
375 (!status.has(Status::INVALID)).then(|| Imm::int(number, to))
376}
377
378pub(crate) fn convert(opcode: Opcode, value: Imm, from: Type, to: Type) -> Imm {
380 match opcode {
381 Opcode::Trunc | Opcode::SExt => Imm::int(value.signed(from), to),
384 _ => Imm::int(value.unsigned() as i128, to),
387 }
388}
389
390fn shift(
396 opcode: Opcode,
397 value: Imm,
398 from: Type,
399 count: Imm,
400 count_ty: Type,
401 to: Type,
402 flags: Flags,
403) -> Option<Imm> {
404 let by = count.unsigned();
405 if by >= u128::from(to.bits()) || count.signed(count_ty) < 0 {
406 return None;
407 }
408 let by = by as u32;
409 let exact = match opcode {
410 Opcode::Shl => value.signed(from).checked_shl(by)?,
411 Opcode::LShr => (value.unsigned() >> by) as i128,
415 _ => value.signed(from) >> by,
416 };
417 if opcode == Opcode::Shl && overflowed(exact, to, flags) {
418 return None;
419 }
420 Some(Imm::int(exact, to))
421}
422
423fn binary(opcode: Opcode, lhs: Imm, rhs: Imm, from: Type, to: Type, flags: Flags) -> Option<Imm> {
425 let (a, b) = (lhs.signed(from), rhs.signed(from));
426 let exact = match opcode {
427 Opcode::And => a & b,
430 Opcode::Or => a | b,
431 Opcode::Xor => a ^ b,
432 Opcode::Add => a.checked_add(b)?,
436 Opcode::Sub => a.checked_sub(b)?,
437 _ => a.checked_mul(b)?,
438 };
439 if overflowed(exact, to, flags) {
440 return None;
441 }
442 Some(Imm::int(exact, to))
443}
444
445pub(crate) fn compare(pred: IntPred, lhs: Imm, rhs: Imm, ty: Type) -> bool {
457 match pred {
458 IntPred::Eq => lhs == rhs,
459 IntPred::Ne => lhs != rhs,
460 IntPred::Slt => lhs.signed(ty) < rhs.signed(ty),
461 IntPred::Sle => lhs.signed(ty) <= rhs.signed(ty),
462 IntPred::Sgt => lhs.signed(ty) > rhs.signed(ty),
463 IntPred::Sge => lhs.signed(ty) >= rhs.signed(ty),
464 IntPred::Ult => lhs.unsigned() < rhs.unsigned(),
465 IntPred::Ule => lhs.unsigned() <= rhs.unsigned(),
466 IntPred::Ugt => lhs.unsigned() > rhs.unsigned(),
467 IntPred::Uge => lhs.unsigned() >= rhs.unsigned(),
468 }
469}
470
471fn count(opcode: Opcode, value: Imm, from: Type, to: Type) -> Option<Imm> {
488 let width = from.bits();
489 if width == 0 || width > 128 {
490 return None;
491 }
492 let spare = 128 - width;
496 let bits = value.unsigned();
497 let answer = match opcode {
498 Opcode::Ctpop => i128::from(bits.count_ones()),
499 Opcode::Ctlz => i128::from(bits.leading_zeros() - spare),
502 Opcode::Cttz => i128::from(bits.trailing_zeros().min(width)),
505 Opcode::Bswap if width % 8 == 0 => (bits.swap_bytes() >> spare) as i128,
506 Opcode::Bitreverse => (bits.reverse_bits() >> spare) as i128,
507 _ => return None,
508 };
509 Some(Imm::int(answer, to))
510}
511
512fn overflowed(exact: i128, to: Type, flags: Flags) -> bool {
517 let stored = Imm::int(exact, to);
518 if flags.contains(Flags::NSW) && stored.signed(to) != exact {
519 return true;
520 }
521 flags.contains(Flags::NUW) && (exact < 0 || stored.unsigned() != exact as u128)
522}
523
524#[cfg(test)]
525mod tests {
526 use rucc_base::Interner;
527 use rucc_base::float::Format;
528 use rucc_ir::{
529 Block, Builder, Extra, Flags, Float, Func, IntPred, Module, Opcode, Signature, Type, Value,
530 };
531 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
532
533 use crate::stats::Kind;
534 use crate::{Fuel, Pass, fold::Fold};
535
536 fn blank() -> (Interner, Func, Block) {
538 let mut names = Interner::new();
539 let name = names.intern("f");
540 let mut func = Func::new(name, Signature::new().with_returns(&[Type::int(64)]));
541 let block = func.create_block();
542 (names, func, block)
543 }
544
545 fn fold(func: &mut Func) -> bool {
548 Fold.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited()).changed()
549 }
550
551 fn number(build: &mut Builder<'_>, text: &str, ty: Type) -> Value {
557 let format = ty.format().expect("a floating point type").encoding();
558 let (value, _) = super::Float::parse(text, format).expect("a number");
559 build.fconst(ty, value.to_bits())
560 }
561
562 fn value_of(func: &Func, value: Value, ty: Type) -> Option<i128> {
564 let rucc_ir::Def::Result { inst, .. } = func[value].def else { return None };
565 if func[inst].opcode != Opcode::IConst {
566 return None;
567 }
568 let Extra::Imm(at) = func[inst].extra else { return None };
569 Some(func[at].signed(ty))
570 }
571
572 fn float_bits(func: &Func, value: Value) -> Option<u128> {
574 let rucc_ir::Def::Result { inst, .. } = func[value].def else { return None };
575 if func[inst].opcode != Opcode::FConst {
576 return None;
577 }
578 let Extra::Imm(at) = func[inst].extra else { return None };
579 Some(func[at].bits())
580 }
581
582 #[test]
585 fn a_negated_floating_constant_becomes_a_constant() {
586 let (_, mut func, block) = blank();
587 let ty = Type::float(Float::F64);
588 let mut build = Builder::new(&mut func, block);
589 let one = number(&mut build, "1.0", ty);
590 let minus = build.unary(Opcode::FNeg, one, ty);
591 build.ret(&[minus]);
592 assert!(fold(&mut func));
593 assert_eq!(float_bits(&func, minus), Some(0xbff0_0000_0000_0000));
594 }
595
596 #[test]
600 fn a_negated_zero_keeps_its_sign_bit() {
601 let (_, mut func, block) = blank();
602 let ty = Type::float(Float::F64);
603 let mut build = Builder::new(&mut func, block);
604 let zero = number(&mut build, "0.0", ty);
605 let minus = build.unary(Opcode::FNeg, zero, ty);
606 build.ret(&[minus]);
607 assert!(fold(&mut func));
608 assert_eq!(float_bits(&func, minus), Some(1 << 63));
609 }
610
611 #[test]
615 fn a_negated_nan_keeps_its_payload() {
616 let (_, mut func, block) = blank();
617 let ty = Type::float(Float::F64);
618 let mut build = Builder::new(&mut func, block);
619 let nan = build.fconst(ty, 0x7ff8_0000_dead_beef);
620 let minus = build.unary(Opcode::FNeg, nan, ty);
621 build.ret(&[minus]);
622 assert!(fold(&mut func));
623 assert_eq!(float_bits(&func, minus), Some(0xfff8_0000_dead_beef));
624 }
625
626 #[test]
630 fn the_sign_bit_of_an_x87_value_is_the_top_bit_of_its_width() {
631 let (_, mut func, block) = blank();
632 let ty = Type::float(Float::F80);
633 let mut build = Builder::new(&mut func, block);
634 let one = number(&mut build, "1.0", ty);
635 let minus = build.unary(Opcode::FNeg, one, ty);
636 build.ret(&[minus]);
637 assert!(fold(&mut func));
638 let bits = float_bits(&func, minus).expect("a constant");
639 assert_eq!(bits >> 79 & 1, 1, "the sign bit is set");
640 assert_eq!(bits >> 80, 0, "nothing above the value is touched");
641 }
642
643 #[test]
647 fn a_bitcast_of_a_constant_is_the_same_bits() {
648 let (_, mut func, block) = blank();
649 let ty = Type::float(Float::F64);
650 let bits = Type::int(64);
651 let mut build = Builder::new(&mut func, block);
652 let value = number(&mut build, "-3.5", ty);
653 let number = build.unary(Opcode::Bitcast, value, bits);
654 let mask = build.iconst(bits, i128::from(i64::MAX));
655 let cleared = build.binary(Opcode::And, number, mask, Flags::NONE);
656 let back = build.unary(Opcode::Bitcast, cleared, ty);
657 build.ret(&[back]);
658 assert!(fold(&mut func));
659 assert_eq!(float_bits(&func, back), Some(0x400c_0000_0000_0000));
660 }
661
662 #[test]
665 fn a_bitcast_of_something_that_is_not_a_constant_is_left_alone() {
666 let mut names = Interner::new();
667 let name = names.intern("f");
668 let ty = Type::float(Float::F64);
669 let signature = Signature::new().with_params(&[ty]).with_returns(&[Type::int(64)]);
670 let mut func = Func::new(name, signature);
671 let block = func.create_block();
672 let x = func.append_param(block, ty);
673 let mut build = Builder::new(&mut func, block);
674 let number = build.unary(Opcode::Bitcast, x, Type::int(64));
675 build.ret(&[number]);
676 assert!(!fold(&mut func));
677 assert_eq!(value_of(&func, number, Type::int(64)), None);
678 }
679
680 #[test]
681 fn a_widened_constant_becomes_a_constant_of_the_wider_type() {
682 let (_, mut func, block) = blank();
683 let mut build = Builder::new(&mut func, block);
684 let narrow = build.iconst(Type::int(32), 7);
685 let wide = build.unary(Opcode::SExt, narrow, Type::int(64));
686 build.ret(&[wide]);
687 assert!(fold(&mut func));
688 assert_eq!(value_of(&func, wide, Type::int(64)), Some(7));
689 }
690
691 #[test]
692 fn sign_extension_copies_the_sign_and_zero_extension_does_not() {
693 for (opcode, expected) in [(Opcode::SExt, -1_i128), (Opcode::ZExt, 0xffff_ffff)] {
694 let (_, mut func, block) = blank();
695 let mut build = Builder::new(&mut func, block);
696 let narrow = build.iconst(Type::int(32), -1);
697 let wide = build.unary(opcode, narrow, Type::int(64));
698 build.ret(&[wide]);
699 assert!(fold(&mut func));
700 assert_eq!(value_of(&func, wide, Type::int(64)), Some(expected), "{opcode:?}");
701 }
702 }
703
704 #[test]
705 fn truncation_keeps_the_low_bits_and_reads_them_at_the_narrow_width() {
706 let (_, mut func, block) = blank();
707 let mut build = Builder::new(&mut func, block);
708 let wide = build.iconst(Type::int(32), 0x1234_5680);
709 let narrow = build.unary(Opcode::Trunc, wide, Type::int(8));
710 build.ret(&[narrow]);
711 assert!(fold(&mut func));
712 assert_eq!(value_of(&func, narrow, Type::int(8)), Some(-128));
713 }
714
715 #[test]
716 fn the_arithmetic_and_the_bitwise_operations_are_evaluated() {
717 let cases = [
718 (Opcode::Add, 6_i128, 7_i128, 13_i128),
719 (Opcode::Sub, 6, 7, -1),
720 (Opcode::Mul, 6, 7, 42),
721 (Opcode::And, 0b1100, 0b1010, 0b1000),
722 (Opcode::Or, 0b1100, 0b1010, 0b1110),
723 (Opcode::Xor, 0b1100, 0b1010, 0b0110),
724 ];
725 for (opcode, a, b, want) in cases {
726 let (_, mut func, block) = blank();
727 let mut build = Builder::new(&mut func, block);
728 let lhs = build.iconst(Type::int(64), a);
729 let rhs = build.iconst(Type::int(64), b);
730 let out = build.binary(opcode, lhs, rhs, Flags::NONE);
731 build.ret(&[out]);
732 assert!(fold(&mut func), "{opcode:?}");
733 assert_eq!(value_of(&func, out, Type::int(64)), Some(want), "{opcode:?}");
734 }
735 }
736
737 #[test]
740 fn a_select_between_two_equal_constants_is_that_constant() {
741 for (other, folds) in [(2_i128, true), (3, false)] {
742 let mut names = Interner::new();
743 let signature = Signature::new().with_params(&[Type::int(32)]);
744 let mut func = Func::new(names.intern("f"), signature.with_returns(&[Type::int(32)]));
745 let block = func.create_block();
746 let x = func.append_param(block, Type::int(32));
747 let mut build = Builder::new(&mut func, block);
748 let zero = build.iconst(Type::int(32), 0);
749 let test = build.icmp(IntPred::Slt, x, zero);
750 let then = build.iconst(Type::int(32), 2);
751 let other = build.iconst(Type::int(32), other);
752 let out = build.select(test, then, other);
753 build.ret(&[out]);
754 assert_eq!(fold(&mut func), folds);
755 let want = folds.then_some(2);
756 assert_eq!(value_of(&func, out, Type::int(32)), want);
757 }
758 }
759
760 #[test]
761 fn the_three_shifts_are_evaluated_and_the_two_right_ones_differ_on_the_sign() {
762 let cases = [(Opcode::Shl, -8_i128, 1_i128, -16_i128), (Opcode::AShr, -8, 1, -4)];
763 for (opcode, a, b, want) in cases {
764 let (_, mut func, block) = blank();
765 let mut build = Builder::new(&mut func, block);
766 let lhs = build.iconst(Type::int(64), a);
767 let rhs = build.iconst(Type::int(64), b);
768 let out = build.binary(opcode, lhs, rhs, Flags::NONE);
769 build.ret(&[out]);
770 assert!(fold(&mut func), "{opcode:?}");
771 assert_eq!(value_of(&func, out, Type::int(64)), Some(want), "{opcode:?}");
772 }
773 let (_, mut func, block) = blank();
776 let mut build = Builder::new(&mut func, block);
777 let lhs = build.iconst(Type::int(64), -8);
778 let rhs = build.iconst(Type::int(64), 1);
779 let out = build.binary(Opcode::LShr, lhs, rhs, Flags::NONE);
780 build.ret(&[out]);
781 assert!(fold(&mut func));
782 assert_eq!(value_of(&func, out, Type::int(64)), Some(i128::from(i64::MAX) - 3));
783 }
784
785 fn one(opcode: Opcode, ty: Type, arg: i128) -> Option<i128> {
787 let (_, mut func, block) = blank();
788 let mut build = Builder::new(&mut func, block);
789 let value = build.iconst(ty, arg);
790 let out = build.unary(opcode, value, ty);
791 build.ret(&[out]);
792 fold(&mut func);
793 value_of(&func, out, ty)
794 }
795
796 #[test]
797 fn the_bit_counts_are_evaluated_at_the_width_they_were_asked_at() {
798 let cases = [
799 (Opcode::Ctlz, 64, 0x0000_1000_0000_0000_i128, 19_i128),
800 (Opcode::Ctlz, 32, 0x0000_1000, 19),
801 (Opcode::Cttz, 64, 0x0000_1000_0000_0000, 44),
802 (Opcode::Cttz, 32, 0x0000_1000, 12),
803 (Opcode::Ctpop, 64, 0x0000_1000_0000_0000, 1),
804 (Opcode::Ctpop, 32, -1, 32),
805 (Opcode::Ctpop, 64, -1, 64),
806 ];
807 for (opcode, width, arg, want) in cases {
808 let ty = Type::int(width);
809 assert_eq!(one(opcode, ty, arg), Some(want), "{opcode:?} at {width} of {arg:#x}");
810 }
811 }
812
813 #[test]
814 fn a_search_for_a_bit_in_a_zero_answers_the_width_the_expansion_answers() {
815 for width in [8_u32, 16, 32, 64] {
816 let ty = Type::int(width);
817 let want = Some(i128::from(width));
818 assert_eq!(one(Opcode::Ctlz, ty, 0), want, "leading, at {width}");
819 assert_eq!(one(Opcode::Cttz, ty, 0), want, "trailing, at {width}");
820 assert_eq!(one(Opcode::Ctpop, ty, 0), Some(0), "count, at {width}");
821 }
822 }
823
824 #[test]
825 fn the_two_reversals_are_evaluated_and_a_byte_swap_of_a_part_of_a_byte_is_not() {
826 let ty = Type::int(32);
827 assert_eq!(one(Opcode::Bswap, ty, 0x1234_5678), Some(0x7856_3412));
828 assert_eq!(one(Opcode::Bswap, Type::int(16), 0x1234), Some(0x3412));
829 assert_eq!(one(Opcode::Bitreverse, Type::int(8), 0b1010_1100), Some(0b0011_0101));
830 let (_, mut func, block) = blank();
833 let mut build = Builder::new(&mut func, block);
834 let value = build.iconst(Type::int(4), 0b1010);
835 let out = build.unary(Opcode::Bswap, value, Type::int(4));
836 build.ret(&[out]);
837 assert!(!fold(&mut func));
838 }
839
840 #[test]
841 fn a_comparison_of_two_constants_becomes_a_one_or_a_nought() {
842 let cases = [
843 (IntPred::Eq, 7_i128, 7_i128, true),
844 (IntPred::Eq, 7, 8, false),
845 (IntPred::Ne, 7, 8, true),
846 (IntPred::Slt, -1, 1, true),
847 (IntPred::Sle, -1, -1, true),
848 (IntPred::Sgt, -1, 1, false),
849 (IntPred::Sge, 1, -1, true),
850 (IntPred::Ult, -1, 1, false),
853 (IntPred::Ule, -1, 1, false),
854 (IntPred::Ugt, -1, 1, true),
855 (IntPred::Uge, -1, 1, true),
856 ];
857 for (pred, a, b, want) in cases {
858 let (_, mut func, block) = blank();
859 let mut build = Builder::new(&mut func, block);
860 let lhs = build.iconst(Type::int(64), a);
861 let rhs = build.iconst(Type::int(64), b);
862 let out = build.icmp(pred, lhs, rhs);
863 build.ret(&[out]);
864 assert!(fold(&mut func), "{pred:?} {a} {b}");
865 let got = value_of(&func, out, Type::I1).expect("the comparison folded");
868 assert_eq!(got != 0, want, "{pred:?} {a} {b}");
869 }
870 }
871
872 #[test]
873 fn a_comparison_at_a_narrow_width_is_read_at_that_width() {
874 let ty = Type::int(8);
877 for (pred, want) in [(IntPred::Slt, true), (IntPred::Ult, false)] {
878 let (_, mut func, block) = blank();
879 let mut build = Builder::new(&mut func, block);
880 let lhs = build.iconst(ty, 255);
881 let rhs = build.iconst(ty, 1);
882 let out = build.icmp(pred, lhs, rhs);
883 build.ret(&[out]);
884 assert!(fold(&mut func), "{pred:?}");
885 let got = value_of(&func, out, Type::I1).expect("the comparison folded");
886 assert_eq!(got != 0, want, "{pred:?}");
887 }
888 }
889
890 #[test]
891 fn a_comparison_with_one_constant_operand_is_left_alone() {
892 let (_, mut func, block) = blank();
893 let ty = Type::int(64);
894 let param = func.append_param(block, ty);
895 let mut build = Builder::new(&mut func, block);
896 let rhs = build.iconst(ty, 3);
897 let out = build.icmp(IntPred::Eq, param, rhs);
898 build.ret(&[out]);
899 assert!(!fold(&mut func));
900 }
901
902 #[test]
903 fn a_bit_count_of_something_that_is_not_a_constant_is_left_alone() {
904 for opcode in [Opcode::Ctlz, Opcode::Cttz, Opcode::Ctpop, Opcode::Bswap] {
905 let (_, mut func, block) = blank();
906 let ty = Type::int(64);
907 let param = func.append_param(block, ty);
908 let mut build = Builder::new(&mut func, block);
909 let out = build.unary(opcode, param, ty);
910 build.ret(&[out]);
911 assert!(!fold(&mut func), "{opcode:?}");
912 }
913 }
914
915 #[test]
916 fn a_shift_by_the_width_or_more_is_left_alone_because_the_language_does_not_define_it() {
917 for count in [64_i128, 65, -1] {
918 let (_, mut func, block) = blank();
919 let mut build = Builder::new(&mut func, block);
920 let lhs = build.iconst(Type::int(64), 1);
921 let rhs = build.iconst(Type::int(64), count);
922 let out = build.binary(Opcode::Shl, lhs, rhs, Flags::NONE);
923 build.ret(&[out]);
924 assert!(!fold(&mut func), "a shift by {count} was folded");
925 }
926 }
927
928 #[test]
929 fn an_operation_that_wraps_folds_and_the_same_one_promising_it_will_not_does_not() {
930 let big = i128::from(i32::MAX);
931 for (flags, folds) in [(Flags::NONE, true), (Flags::NSW, false)] {
932 let (_, mut func, block) = blank();
933 let mut build = Builder::new(&mut func, block);
934 let lhs = build.iconst(Type::int(32), big);
935 let rhs = build.iconst(Type::int(32), 1);
936 let out = build.binary(Opcode::Add, lhs, rhs, flags);
937 build.ret(&[out]);
938 assert_eq!(fold(&mut func), folds, "{flags}");
939 if folds {
940 assert_eq!(value_of(&func, out, Type::int(32)), Some(i128::from(i32::MIN)));
941 }
942 }
943 }
944
945 #[test]
946 fn an_unsigned_promise_is_broken_by_a_negative_result_as_well_as_by_a_large_one() {
947 let (_, mut func, block) = blank();
948 let mut build = Builder::new(&mut func, block);
949 let lhs = build.iconst(Type::int(32), 1);
950 let rhs = build.iconst(Type::int(32), 2);
951 let out = build.binary(Opcode::Sub, lhs, rhs, Flags::NUW);
952 build.ret(&[out]);
953 assert!(!fold(&mut func));
954 }
955
956 #[test]
957 fn an_operation_with_one_constant_operand_is_left_alone() {
958 let (_, mut func, block) = blank();
959 let param = func.append_param(block, Type::int(64));
960 let mut build = Builder::new(&mut func, block);
961 let rhs = build.iconst(Type::int(64), 7);
962 let out = build.binary(Opcode::Add, param, rhs, Flags::NONE);
963 build.ret(&[out]);
964 assert!(!fold(&mut func));
965 assert_eq!(func[out_inst(&func, out)].opcode, Opcode::Add);
966 }
967
968 #[test]
969 fn a_conversion_to_an_integer_truncates_toward_zero() {
970 for (text, expected) in [("2.75", 2_i128), ("-2.75", -2), ("0.5", 0), ("-0.5", 0)] {
971 let (_, mut func, block) = blank();
972 let mut build = Builder::new(&mut func, block);
973 let value = number(&mut build, text, Type::float(Float::F64));
974 let out = build.unary(Opcode::FPToSI, value, Type::int(32));
975 build.ret(&[out]);
976 assert!(fold(&mut func), "{text}");
977 assert_eq!(value_of(&func, out, Type::int(32)), Some(expected), "{text}");
978 }
979 }
980
981 #[test]
982 fn a_negative_number_converts_to_an_unsigned_type_only_when_truncating_lands_on_zero() {
983 for (text, expected) in [("-0.5", Some(0)), ("-1.5", None)] {
984 let (_, mut func, block) = blank();
985 let mut build = Builder::new(&mut func, block);
986 let value = number(&mut build, text, Type::float(Float::F64));
987 let out = build.unary(Opcode::FPToUI, value, Type::int(32));
988 build.ret(&[out]);
989 assert_eq!(fold(&mut func), expected.is_some(), "{text}");
990 assert_eq!(value_of(&func, out, Type::int(32)), expected, "{text}");
991 }
992 }
993
994 #[test]
995 fn a_number_the_destination_type_has_no_room_for_is_left_alone() {
996 let (_, mut func, block) = blank();
997 let mut build = Builder::new(&mut func, block);
998 let value = number(&mut build, "1e30", Type::float(Float::F64));
999 let out = build.unary(Opcode::FPToSI, value, Type::int(32));
1000 build.ret(&[out]);
1001 assert!(!fold(&mut func));
1002 assert_eq!(func[out_inst(&func, out)].opcode, Opcode::FPToSI);
1003 }
1004
1005 #[test]
1006 fn a_nan_is_left_alone() {
1007 let (_, mut func, block) = blank();
1008 let mut build = Builder::new(&mut func, block);
1009 let value = build.fconst(Type::float(Float::F64), 0x7ff8_0000_0000_0000);
1010 let out = build.unary(Opcode::FPToSI, value, Type::int(32));
1011 build.ret(&[out]);
1012 assert!(!fold(&mut func));
1013 }
1014
1015 #[test]
1016 fn a_constant_is_read_in_the_format_its_own_type_gives_it() {
1017 let bits = super::Float::parse("3.0", Format::X87Extended).expect("a number").0.to_bits();
1020 for (float, expected) in [(Float::F80, 3_i128), (Float::F128, 0)] {
1021 let (_, mut func, block) = blank();
1022 let mut build = Builder::new(&mut func, block);
1023 let value = build.fconst(Type::float(float), bits);
1024 let out = build.unary(Opcode::FPToSI, value, Type::int(32));
1025 build.ret(&[out]);
1026 assert!(fold(&mut func), "{float}");
1027 assert_eq!(value_of(&func, out, Type::int(32)), Some(expected), "{float}");
1028 }
1029 }
1030
1031 #[test]
1032 fn a_conversion_of_something_that_is_not_a_constant_is_left_alone() {
1033 let (_, mut func, block) = blank();
1034 let param = func.append_param(block, Type::float(Float::F64));
1035 let mut build = Builder::new(&mut func, block);
1036 let out = build.unary(Opcode::FPToSI, param, Type::int(32));
1037 build.ret(&[out]);
1038 assert!(!fold(&mut func));
1039 }
1040
1041 #[test]
1042 fn a_divide_is_not_folded_even_when_both_operands_are_constants() {
1043 for opcode in [Opcode::SDiv, Opcode::UDiv, Opcode::SRem, Opcode::URem] {
1044 let (_, mut func, block) = blank();
1045 let mut build = Builder::new(&mut func, block);
1046 let lhs = build.iconst(Type::int(64), 42);
1047 let rhs = build.iconst(Type::int(64), 7);
1048 let out = build.binary(opcode, lhs, rhs, Flags::NONE);
1049 build.ret(&[out]);
1050 assert!(!fold(&mut func), "{opcode:?}");
1051 }
1052 }
1053
1054 #[test]
1055 fn folding_leaves_the_function_something_the_verifier_accepts() {
1056 let mut names = Interner::new();
1057 let name = names.intern("f");
1058 let mut func = Func::new(name, Signature::new().with_returns(&[Type::int(64)]));
1059 let block = func.create_block();
1060 let mut build = Builder::new(&mut func, block);
1061 let narrow = build.iconst(Type::int(32), 7);
1062 let wide = build.unary(Opcode::SExt, narrow, Type::int(64));
1063 build.ret(&[wide]);
1064 assert!(fold(&mut func));
1065 let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
1066 let module_name = names.intern("m");
1067 let mut module = Module::new(module_name, &target);
1068 module.add_func(func);
1069 rucc_ir::verify(&module, &names).expect("folding does not break the IR");
1070 }
1071
1072 #[test]
1073 fn fuel_stops_the_transformation_and_not_the_walk() {
1074 let build_two = |func: &mut Func, block: Block| {
1075 let mut build = Builder::new(func, block);
1076 let a = build.iconst(Type::int(32), 7);
1077 let wide_a = build.unary(Opcode::SExt, a, Type::int(64));
1078 let b = build.iconst(Type::int(32), 9);
1079 let wide_b = build.unary(Opcode::SExt, b, Type::int(64));
1080 let sum = build.binary(Opcode::Add, wide_a, wide_b, Flags::NONE);
1081 build.ret(&[sum]);
1082 (wide_a, wide_b)
1083 };
1084
1085 let (_, mut none, block) = blank();
1086 let (first, _) = build_two(&mut none, block);
1087 let stats =
1088 Fold.run(&mut none, &mut crate::machine::fixtures::analyses(), &mut Fuel::of(0));
1089 assert!(!stats.changed());
1090 assert_eq!(none[out_inst(&none, first)].opcode, Opcode::SExt);
1091 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 2);
1094
1095 let (_, mut one, block) = blank();
1096 let (first, second) = build_two(&mut one, block);
1097 let mut fuel = Fuel::of(1);
1098 let stats = Fold.run(&mut one, &mut crate::machine::fixtures::analyses(), &mut fuel);
1099 assert!(stats.changed());
1100 assert_eq!(fuel.spent(), 1);
1101 assert_eq!(stats.count(Kind::Optimized, super::FOLDED), 1);
1102 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
1103 assert_eq!(one[out_inst(&one, first)].opcode, Opcode::IConst);
1104 assert_eq!(one[out_inst(&one, second)].opcode, Opcode::SExt);
1105 }
1106
1107 #[test]
1108 fn folding_one_operation_uncovers_the_next() {
1109 let (_, mut func, block) = blank();
1110 let mut build = Builder::new(&mut func, block);
1111 let a = build.iconst(Type::int(32), 7);
1112 let wide = build.unary(Opcode::SExt, a, Type::int(64));
1113 let b = build.iconst(Type::int(64), 9);
1114 let sum = build.binary(Opcode::Add, wide, b, Flags::NONE);
1115 build.ret(&[sum]);
1116 assert!(fold(&mut func));
1117 assert_eq!(value_of(&func, sum, Type::int(64)), Some(16));
1120 }
1121
1122 #[test]
1123 fn a_constant_is_left_where_it_is_and_folding_it_again_changes_nothing() {
1124 let (_, mut func, block) = blank();
1125 let mut build = Builder::new(&mut func, block);
1126 let a = build.iconst(Type::int(32), 7);
1127 let wide = build.unary(Opcode::SExt, a, Type::int(64));
1128 build.ret(&[wide]);
1129 assert!(fold(&mut func));
1130 assert!(!fold(&mut func), "a second run found something to do");
1131 }
1132
1133 fn out_inst(func: &Func, value: Value) -> rucc_ir::Inst {
1135 match func[value].def {
1136 rucc_ir::Def::Result { inst, .. } => inst,
1137 rucc_ir::Def::Param { .. } => panic!("a parameter has no instruction"),
1138 }
1139 }
1140}