1use rucc_base::float::{Float, Status};
82use rucc_ir::{Block, Def, Extra, Flags, Func, Imm, Inst, 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::Trunc | Opcode::SExt | Opcode::ZExt => {
196 let (value, from) = constant(func, *args.first()?)?;
197 Some(convert(data.opcode, value, from, ty))
198 }
199 Opcode::Shl | Opcode::LShr | Opcode::AShr => {
200 let (value, from) = constant(func, *args.first()?)?;
201 let (count, count_ty) = constant(func, *args.get(1)?)?;
202 shift(data.opcode, value, from, count, count_ty, ty, data.flags)
203 }
204 Opcode::Add | Opcode::Sub | Opcode::Mul | Opcode::And | Opcode::Or | Opcode::Xor => {
205 let (lhs, lhs_ty) = constant(func, *args.first()?)?;
206 let (rhs, _) = constant(func, *args.get(1)?)?;
207 binary(data.opcode, lhs, rhs, lhs_ty, ty, data.flags)
208 }
209 Opcode::Ctlz | Opcode::Cttz | Opcode::Ctpop | Opcode::Bswap | Opcode::Bitreverse => {
210 let (value, from) = constant(func, *args.first()?)?;
211 count(data.opcode, value, from, ty)
212 }
213 Opcode::FPToSI | Opcode::FPToUI => {
214 let value = floating(func, *args.first()?)?;
215 to_integer(value, ty, data.opcode == Opcode::FPToSI)
216 }
217 Opcode::ICmp => {
218 let Extra::IntPred(pred) = data.extra else { return None };
219 let (lhs, from) = constant(func, *args.first()?)?;
220 let (rhs, _) = constant(func, *args.get(1)?)?;
221 Some(Imm::int(i128::from(compare(pred, lhs, rhs, from)), ty))
222 }
223 _ => None,
224 }
225}
226
227fn bits_of(func: &Func, value: Value) -> Option<u128> {
233 let Def::Result { inst, .. } = func[value].def else { return None };
234 let data = &func[inst];
235 if !matches!(data.opcode, Opcode::IConst | Opcode::FConst) {
236 return None;
237 }
238 let Extra::Imm(at) = data.extra else { return None };
239 Some(func[at].bits())
240}
241
242fn negated(func: &Func, operand: Value, ty: Type) -> Option<Imm> {
260 if !ty.is_float() {
261 return None;
262 }
263 let bits = bits_of(func, operand)?;
264 Some(Imm::from_bits(bits ^ 1u128 << (ty.bits() - 1)))
265}
266
267fn reinterpreted(func: &Func, operand: Value, ty: Type) -> Option<Imm> {
278 let from = func[operand].ty;
279 if !from.is_scalar() || from.bits() != ty.bits() {
280 return None;
281 }
282 let bits = bits_of(func, operand)?;
283 Some(Imm::from_bits(bits))
284}
285
286pub(crate) fn constant(func: &Func, value: Value) -> Option<(Imm, Type)> {
291 let Def::Result { inst, .. } = func[value].def else { return None };
292 if func[inst].opcode != Opcode::IConst {
293 return None;
294 }
295 let Extra::Imm(at) = func[inst].extra else { return None };
296 let ty = func[value].ty;
297 ty.is_int().then(|| (func[at], ty))
298}
299
300fn floating(func: &Func, value: Value) -> Option<Float> {
306 let Def::Result { inst, .. } = func[value].def else { return None };
307 if func[inst].opcode != Opcode::FConst {
308 return None;
309 }
310 let Extra::Imm(at) = func[inst].extra else { return None };
311 let format = func[value].ty.format()?.encoding();
312 Some(Float::from_bits(format, func[at].bits()))
313}
314
315fn to_integer(value: Float, to: Type, signed: bool) -> Option<Imm> {
324 let (number, status) = value.to_integer(to.bits(), signed);
325 (!status.has(Status::INVALID)).then(|| Imm::int(number, to))
326}
327
328fn convert(opcode: Opcode, value: Imm, from: Type, to: Type) -> Imm {
330 match opcode {
331 Opcode::Trunc | Opcode::SExt => Imm::int(value.signed(from), to),
334 _ => Imm::int(value.unsigned() as i128, to),
337 }
338}
339
340fn shift(
346 opcode: Opcode,
347 value: Imm,
348 from: Type,
349 count: Imm,
350 count_ty: Type,
351 to: Type,
352 flags: Flags,
353) -> Option<Imm> {
354 let by = count.unsigned();
355 if by >= u128::from(to.bits()) || count.signed(count_ty) < 0 {
356 return None;
357 }
358 let by = by as u32;
359 let exact = match opcode {
360 Opcode::Shl => value.signed(from).checked_shl(by)?,
361 Opcode::LShr => (value.unsigned() >> by) as i128,
365 _ => value.signed(from) >> by,
366 };
367 if opcode == Opcode::Shl && overflowed(exact, to, flags) {
368 return None;
369 }
370 Some(Imm::int(exact, to))
371}
372
373fn binary(opcode: Opcode, lhs: Imm, rhs: Imm, from: Type, to: Type, flags: Flags) -> Option<Imm> {
375 let (a, b) = (lhs.signed(from), rhs.signed(from));
376 let exact = match opcode {
377 Opcode::And => a & b,
380 Opcode::Or => a | b,
381 Opcode::Xor => a ^ b,
382 Opcode::Add => a.checked_add(b)?,
386 Opcode::Sub => a.checked_sub(b)?,
387 _ => a.checked_mul(b)?,
388 };
389 if overflowed(exact, to, flags) {
390 return None;
391 }
392 Some(Imm::int(exact, to))
393}
394
395pub(crate) fn compare(pred: IntPred, lhs: Imm, rhs: Imm, ty: Type) -> bool {
407 match pred {
408 IntPred::Eq => lhs == rhs,
409 IntPred::Ne => lhs != rhs,
410 IntPred::Slt => lhs.signed(ty) < rhs.signed(ty),
411 IntPred::Sle => lhs.signed(ty) <= rhs.signed(ty),
412 IntPred::Sgt => lhs.signed(ty) > rhs.signed(ty),
413 IntPred::Sge => lhs.signed(ty) >= rhs.signed(ty),
414 IntPred::Ult => lhs.unsigned() < rhs.unsigned(),
415 IntPred::Ule => lhs.unsigned() <= rhs.unsigned(),
416 IntPred::Ugt => lhs.unsigned() > rhs.unsigned(),
417 IntPred::Uge => lhs.unsigned() >= rhs.unsigned(),
418 }
419}
420
421fn count(opcode: Opcode, value: Imm, from: Type, to: Type) -> Option<Imm> {
438 let width = from.bits();
439 if width == 0 || width > 128 {
440 return None;
441 }
442 let spare = 128 - width;
446 let bits = value.unsigned();
447 let answer = match opcode {
448 Opcode::Ctpop => i128::from(bits.count_ones()),
449 Opcode::Ctlz => i128::from(bits.leading_zeros() - spare),
452 Opcode::Cttz => i128::from(bits.trailing_zeros().min(width)),
455 Opcode::Bswap if width % 8 == 0 => (bits.swap_bytes() >> spare) as i128,
456 Opcode::Bitreverse => (bits.reverse_bits() >> spare) as i128,
457 _ => return None,
458 };
459 Some(Imm::int(answer, to))
460}
461
462fn overflowed(exact: i128, to: Type, flags: Flags) -> bool {
467 let stored = Imm::int(exact, to);
468 if flags.contains(Flags::NSW) && stored.signed(to) != exact {
469 return true;
470 }
471 flags.contains(Flags::NUW) && (exact < 0 || stored.unsigned() != exact as u128)
472}
473
474#[cfg(test)]
475mod tests {
476 use rucc_base::Interner;
477 use rucc_base::float::Format;
478 use rucc_ir::{
479 Block, Builder, Extra, Flags, Float, Func, IntPred, Module, Opcode, Signature, Type, Value,
480 };
481 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
482
483 use crate::stats::Kind;
484 use crate::{Fuel, Pass, fold::Fold};
485
486 fn blank() -> (Interner, Func, Block) {
488 let mut names = Interner::new();
489 let name = names.intern("f");
490 let mut func = Func::new(name, Signature::new().with_returns(&[Type::int(64)]));
491 let block = func.create_block();
492 (names, func, block)
493 }
494
495 fn fold(func: &mut Func) -> bool {
498 Fold.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited()).changed()
499 }
500
501 fn number(build: &mut Builder<'_>, text: &str, ty: Type) -> Value {
507 let format = ty.format().expect("a floating point type").encoding();
508 let (value, _) = super::Float::parse(text, format).expect("a number");
509 build.fconst(ty, value.to_bits())
510 }
511
512 fn value_of(func: &Func, value: Value, ty: Type) -> Option<i128> {
514 let rucc_ir::Def::Result { inst, .. } = func[value].def else { return None };
515 if func[inst].opcode != Opcode::IConst {
516 return None;
517 }
518 let Extra::Imm(at) = func[inst].extra else { return None };
519 Some(func[at].signed(ty))
520 }
521
522 fn float_bits(func: &Func, value: Value) -> Option<u128> {
524 let rucc_ir::Def::Result { inst, .. } = func[value].def else { return None };
525 if func[inst].opcode != Opcode::FConst {
526 return None;
527 }
528 let Extra::Imm(at) = func[inst].extra else { return None };
529 Some(func[at].bits())
530 }
531
532 #[test]
535 fn a_negated_floating_constant_becomes_a_constant() {
536 let (_, mut func, block) = blank();
537 let ty = Type::float(Float::F64);
538 let mut build = Builder::new(&mut func, block);
539 let one = number(&mut build, "1.0", ty);
540 let minus = build.unary(Opcode::FNeg, one, ty);
541 build.ret(&[minus]);
542 assert!(fold(&mut func));
543 assert_eq!(float_bits(&func, minus), Some(0xbff0_0000_0000_0000));
544 }
545
546 #[test]
550 fn a_negated_zero_keeps_its_sign_bit() {
551 let (_, mut func, block) = blank();
552 let ty = Type::float(Float::F64);
553 let mut build = Builder::new(&mut func, block);
554 let zero = number(&mut build, "0.0", ty);
555 let minus = build.unary(Opcode::FNeg, zero, ty);
556 build.ret(&[minus]);
557 assert!(fold(&mut func));
558 assert_eq!(float_bits(&func, minus), Some(1 << 63));
559 }
560
561 #[test]
565 fn a_negated_nan_keeps_its_payload() {
566 let (_, mut func, block) = blank();
567 let ty = Type::float(Float::F64);
568 let mut build = Builder::new(&mut func, block);
569 let nan = build.fconst(ty, 0x7ff8_0000_dead_beef);
570 let minus = build.unary(Opcode::FNeg, nan, ty);
571 build.ret(&[minus]);
572 assert!(fold(&mut func));
573 assert_eq!(float_bits(&func, minus), Some(0xfff8_0000_dead_beef));
574 }
575
576 #[test]
580 fn the_sign_bit_of_an_x87_value_is_the_top_bit_of_its_width() {
581 let (_, mut func, block) = blank();
582 let ty = Type::float(Float::F80);
583 let mut build = Builder::new(&mut func, block);
584 let one = number(&mut build, "1.0", ty);
585 let minus = build.unary(Opcode::FNeg, one, ty);
586 build.ret(&[minus]);
587 assert!(fold(&mut func));
588 let bits = float_bits(&func, minus).expect("a constant");
589 assert_eq!(bits >> 79 & 1, 1, "the sign bit is set");
590 assert_eq!(bits >> 80, 0, "nothing above the value is touched");
591 }
592
593 #[test]
597 fn a_bitcast_of_a_constant_is_the_same_bits() {
598 let (_, mut func, block) = blank();
599 let ty = Type::float(Float::F64);
600 let bits = Type::int(64);
601 let mut build = Builder::new(&mut func, block);
602 let value = number(&mut build, "-3.5", ty);
603 let number = build.unary(Opcode::Bitcast, value, bits);
604 let mask = build.iconst(bits, i128::from(i64::MAX));
605 let cleared = build.binary(Opcode::And, number, mask, Flags::NONE);
606 let back = build.unary(Opcode::Bitcast, cleared, ty);
607 build.ret(&[back]);
608 assert!(fold(&mut func));
609 assert_eq!(float_bits(&func, back), Some(0x400c_0000_0000_0000));
610 }
611
612 #[test]
615 fn a_bitcast_of_something_that_is_not_a_constant_is_left_alone() {
616 let mut names = Interner::new();
617 let name = names.intern("f");
618 let ty = Type::float(Float::F64);
619 let signature = Signature::new().with_params(&[ty]).with_returns(&[Type::int(64)]);
620 let mut func = Func::new(name, signature);
621 let block = func.create_block();
622 let x = func.append_param(block, ty);
623 let mut build = Builder::new(&mut func, block);
624 let number = build.unary(Opcode::Bitcast, x, Type::int(64));
625 build.ret(&[number]);
626 assert!(!fold(&mut func));
627 assert_eq!(value_of(&func, number, Type::int(64)), None);
628 }
629
630 #[test]
631 fn a_widened_constant_becomes_a_constant_of_the_wider_type() {
632 let (_, mut func, block) = blank();
633 let mut build = Builder::new(&mut func, block);
634 let narrow = build.iconst(Type::int(32), 7);
635 let wide = build.unary(Opcode::SExt, narrow, Type::int(64));
636 build.ret(&[wide]);
637 assert!(fold(&mut func));
638 assert_eq!(value_of(&func, wide, Type::int(64)), Some(7));
639 }
640
641 #[test]
642 fn sign_extension_copies_the_sign_and_zero_extension_does_not() {
643 for (opcode, expected) in [(Opcode::SExt, -1_i128), (Opcode::ZExt, 0xffff_ffff)] {
644 let (_, mut func, block) = blank();
645 let mut build = Builder::new(&mut func, block);
646 let narrow = build.iconst(Type::int(32), -1);
647 let wide = build.unary(opcode, narrow, Type::int(64));
648 build.ret(&[wide]);
649 assert!(fold(&mut func));
650 assert_eq!(value_of(&func, wide, Type::int(64)), Some(expected), "{opcode:?}");
651 }
652 }
653
654 #[test]
655 fn truncation_keeps_the_low_bits_and_reads_them_at_the_narrow_width() {
656 let (_, mut func, block) = blank();
657 let mut build = Builder::new(&mut func, block);
658 let wide = build.iconst(Type::int(32), 0x1234_5680);
659 let narrow = build.unary(Opcode::Trunc, wide, Type::int(8));
660 build.ret(&[narrow]);
661 assert!(fold(&mut func));
662 assert_eq!(value_of(&func, narrow, Type::int(8)), Some(-128));
663 }
664
665 #[test]
666 fn the_arithmetic_and_the_bitwise_operations_are_evaluated() {
667 let cases = [
668 (Opcode::Add, 6_i128, 7_i128, 13_i128),
669 (Opcode::Sub, 6, 7, -1),
670 (Opcode::Mul, 6, 7, 42),
671 (Opcode::And, 0b1100, 0b1010, 0b1000),
672 (Opcode::Or, 0b1100, 0b1010, 0b1110),
673 (Opcode::Xor, 0b1100, 0b1010, 0b0110),
674 ];
675 for (opcode, a, b, want) in cases {
676 let (_, mut func, block) = blank();
677 let mut build = Builder::new(&mut func, block);
678 let lhs = build.iconst(Type::int(64), a);
679 let rhs = build.iconst(Type::int(64), b);
680 let out = build.binary(opcode, lhs, rhs, Flags::NONE);
681 build.ret(&[out]);
682 assert!(fold(&mut func), "{opcode:?}");
683 assert_eq!(value_of(&func, out, Type::int(64)), Some(want), "{opcode:?}");
684 }
685 }
686
687 #[test]
688 fn the_three_shifts_are_evaluated_and_the_two_right_ones_differ_on_the_sign() {
689 let cases = [(Opcode::Shl, -8_i128, 1_i128, -16_i128), (Opcode::AShr, -8, 1, -4)];
690 for (opcode, a, b, want) in cases {
691 let (_, mut func, block) = blank();
692 let mut build = Builder::new(&mut func, block);
693 let lhs = build.iconst(Type::int(64), a);
694 let rhs = build.iconst(Type::int(64), b);
695 let out = build.binary(opcode, lhs, rhs, Flags::NONE);
696 build.ret(&[out]);
697 assert!(fold(&mut func), "{opcode:?}");
698 assert_eq!(value_of(&func, out, Type::int(64)), Some(want), "{opcode:?}");
699 }
700 let (_, mut func, block) = blank();
703 let mut build = Builder::new(&mut func, block);
704 let lhs = build.iconst(Type::int(64), -8);
705 let rhs = build.iconst(Type::int(64), 1);
706 let out = build.binary(Opcode::LShr, lhs, rhs, Flags::NONE);
707 build.ret(&[out]);
708 assert!(fold(&mut func));
709 assert_eq!(value_of(&func, out, Type::int(64)), Some(i128::from(i64::MAX) - 3));
710 }
711
712 fn one(opcode: Opcode, ty: Type, arg: i128) -> Option<i128> {
714 let (_, mut func, block) = blank();
715 let mut build = Builder::new(&mut func, block);
716 let value = build.iconst(ty, arg);
717 let out = build.unary(opcode, value, ty);
718 build.ret(&[out]);
719 fold(&mut func);
720 value_of(&func, out, ty)
721 }
722
723 #[test]
724 fn the_bit_counts_are_evaluated_at_the_width_they_were_asked_at() {
725 let cases = [
726 (Opcode::Ctlz, 64, 0x0000_1000_0000_0000_i128, 19_i128),
727 (Opcode::Ctlz, 32, 0x0000_1000, 19),
728 (Opcode::Cttz, 64, 0x0000_1000_0000_0000, 44),
729 (Opcode::Cttz, 32, 0x0000_1000, 12),
730 (Opcode::Ctpop, 64, 0x0000_1000_0000_0000, 1),
731 (Opcode::Ctpop, 32, -1, 32),
732 (Opcode::Ctpop, 64, -1, 64),
733 ];
734 for (opcode, width, arg, want) in cases {
735 let ty = Type::int(width);
736 assert_eq!(one(opcode, ty, arg), Some(want), "{opcode:?} at {width} of {arg:#x}");
737 }
738 }
739
740 #[test]
741 fn a_search_for_a_bit_in_a_zero_answers_the_width_the_expansion_answers() {
742 for width in [8_u32, 16, 32, 64] {
743 let ty = Type::int(width);
744 let want = Some(i128::from(width));
745 assert_eq!(one(Opcode::Ctlz, ty, 0), want, "leading, at {width}");
746 assert_eq!(one(Opcode::Cttz, ty, 0), want, "trailing, at {width}");
747 assert_eq!(one(Opcode::Ctpop, ty, 0), Some(0), "count, at {width}");
748 }
749 }
750
751 #[test]
752 fn the_two_reversals_are_evaluated_and_a_byte_swap_of_a_part_of_a_byte_is_not() {
753 let ty = Type::int(32);
754 assert_eq!(one(Opcode::Bswap, ty, 0x1234_5678), Some(0x7856_3412));
755 assert_eq!(one(Opcode::Bswap, Type::int(16), 0x1234), Some(0x3412));
756 assert_eq!(one(Opcode::Bitreverse, Type::int(8), 0b1010_1100), Some(0b0011_0101));
757 let (_, mut func, block) = blank();
760 let mut build = Builder::new(&mut func, block);
761 let value = build.iconst(Type::int(4), 0b1010);
762 let out = build.unary(Opcode::Bswap, value, Type::int(4));
763 build.ret(&[out]);
764 assert!(!fold(&mut func));
765 }
766
767 #[test]
768 fn a_comparison_of_two_constants_becomes_a_one_or_a_nought() {
769 let cases = [
770 (IntPred::Eq, 7_i128, 7_i128, true),
771 (IntPred::Eq, 7, 8, false),
772 (IntPred::Ne, 7, 8, true),
773 (IntPred::Slt, -1, 1, true),
774 (IntPred::Sle, -1, -1, true),
775 (IntPred::Sgt, -1, 1, false),
776 (IntPred::Sge, 1, -1, true),
777 (IntPred::Ult, -1, 1, false),
780 (IntPred::Ule, -1, 1, false),
781 (IntPred::Ugt, -1, 1, true),
782 (IntPred::Uge, -1, 1, true),
783 ];
784 for (pred, a, b, want) in cases {
785 let (_, mut func, block) = blank();
786 let mut build = Builder::new(&mut func, block);
787 let lhs = build.iconst(Type::int(64), a);
788 let rhs = build.iconst(Type::int(64), b);
789 let out = build.icmp(pred, lhs, rhs);
790 build.ret(&[out]);
791 assert!(fold(&mut func), "{pred:?} {a} {b}");
792 let got = value_of(&func, out, Type::I1).expect("the comparison folded");
795 assert_eq!(got != 0, want, "{pred:?} {a} {b}");
796 }
797 }
798
799 #[test]
800 fn a_comparison_at_a_narrow_width_is_read_at_that_width() {
801 let ty = Type::int(8);
804 for (pred, want) in [(IntPred::Slt, true), (IntPred::Ult, false)] {
805 let (_, mut func, block) = blank();
806 let mut build = Builder::new(&mut func, block);
807 let lhs = build.iconst(ty, 255);
808 let rhs = build.iconst(ty, 1);
809 let out = build.icmp(pred, lhs, rhs);
810 build.ret(&[out]);
811 assert!(fold(&mut func), "{pred:?}");
812 let got = value_of(&func, out, Type::I1).expect("the comparison folded");
813 assert_eq!(got != 0, want, "{pred:?}");
814 }
815 }
816
817 #[test]
818 fn a_comparison_with_one_constant_operand_is_left_alone() {
819 let (_, mut func, block) = blank();
820 let ty = Type::int(64);
821 let param = func.append_param(block, ty);
822 let mut build = Builder::new(&mut func, block);
823 let rhs = build.iconst(ty, 3);
824 let out = build.icmp(IntPred::Eq, param, rhs);
825 build.ret(&[out]);
826 assert!(!fold(&mut func));
827 }
828
829 #[test]
830 fn a_bit_count_of_something_that_is_not_a_constant_is_left_alone() {
831 for opcode in [Opcode::Ctlz, Opcode::Cttz, Opcode::Ctpop, Opcode::Bswap] {
832 let (_, mut func, block) = blank();
833 let ty = Type::int(64);
834 let param = func.append_param(block, ty);
835 let mut build = Builder::new(&mut func, block);
836 let out = build.unary(opcode, param, ty);
837 build.ret(&[out]);
838 assert!(!fold(&mut func), "{opcode:?}");
839 }
840 }
841
842 #[test]
843 fn a_shift_by_the_width_or_more_is_left_alone_because_the_language_does_not_define_it() {
844 for count in [64_i128, 65, -1] {
845 let (_, mut func, block) = blank();
846 let mut build = Builder::new(&mut func, block);
847 let lhs = build.iconst(Type::int(64), 1);
848 let rhs = build.iconst(Type::int(64), count);
849 let out = build.binary(Opcode::Shl, lhs, rhs, Flags::NONE);
850 build.ret(&[out]);
851 assert!(!fold(&mut func), "a shift by {count} was folded");
852 }
853 }
854
855 #[test]
856 fn an_operation_that_wraps_folds_and_the_same_one_promising_it_will_not_does_not() {
857 let big = i128::from(i32::MAX);
858 for (flags, folds) in [(Flags::NONE, true), (Flags::NSW, false)] {
859 let (_, mut func, block) = blank();
860 let mut build = Builder::new(&mut func, block);
861 let lhs = build.iconst(Type::int(32), big);
862 let rhs = build.iconst(Type::int(32), 1);
863 let out = build.binary(Opcode::Add, lhs, rhs, flags);
864 build.ret(&[out]);
865 assert_eq!(fold(&mut func), folds, "{flags}");
866 if folds {
867 assert_eq!(value_of(&func, out, Type::int(32)), Some(i128::from(i32::MIN)));
868 }
869 }
870 }
871
872 #[test]
873 fn an_unsigned_promise_is_broken_by_a_negative_result_as_well_as_by_a_large_one() {
874 let (_, mut func, block) = blank();
875 let mut build = Builder::new(&mut func, block);
876 let lhs = build.iconst(Type::int(32), 1);
877 let rhs = build.iconst(Type::int(32), 2);
878 let out = build.binary(Opcode::Sub, lhs, rhs, Flags::NUW);
879 build.ret(&[out]);
880 assert!(!fold(&mut func));
881 }
882
883 #[test]
884 fn an_operation_with_one_constant_operand_is_left_alone() {
885 let (_, mut func, block) = blank();
886 let param = func.append_param(block, Type::int(64));
887 let mut build = Builder::new(&mut func, block);
888 let rhs = build.iconst(Type::int(64), 7);
889 let out = build.binary(Opcode::Add, param, rhs, Flags::NONE);
890 build.ret(&[out]);
891 assert!(!fold(&mut func));
892 assert_eq!(func[out_inst(&func, out)].opcode, Opcode::Add);
893 }
894
895 #[test]
896 fn a_conversion_to_an_integer_truncates_toward_zero() {
897 for (text, expected) in [("2.75", 2_i128), ("-2.75", -2), ("0.5", 0), ("-0.5", 0)] {
898 let (_, mut func, block) = blank();
899 let mut build = Builder::new(&mut func, block);
900 let value = number(&mut build, text, Type::float(Float::F64));
901 let out = build.unary(Opcode::FPToSI, value, Type::int(32));
902 build.ret(&[out]);
903 assert!(fold(&mut func), "{text}");
904 assert_eq!(value_of(&func, out, Type::int(32)), Some(expected), "{text}");
905 }
906 }
907
908 #[test]
909 fn a_negative_number_converts_to_an_unsigned_type_only_when_truncating_lands_on_zero() {
910 for (text, expected) in [("-0.5", Some(0)), ("-1.5", None)] {
911 let (_, mut func, block) = blank();
912 let mut build = Builder::new(&mut func, block);
913 let value = number(&mut build, text, Type::float(Float::F64));
914 let out = build.unary(Opcode::FPToUI, value, Type::int(32));
915 build.ret(&[out]);
916 assert_eq!(fold(&mut func), expected.is_some(), "{text}");
917 assert_eq!(value_of(&func, out, Type::int(32)), expected, "{text}");
918 }
919 }
920
921 #[test]
922 fn a_number_the_destination_type_has_no_room_for_is_left_alone() {
923 let (_, mut func, block) = blank();
924 let mut build = Builder::new(&mut func, block);
925 let value = number(&mut build, "1e30", Type::float(Float::F64));
926 let out = build.unary(Opcode::FPToSI, value, Type::int(32));
927 build.ret(&[out]);
928 assert!(!fold(&mut func));
929 assert_eq!(func[out_inst(&func, out)].opcode, Opcode::FPToSI);
930 }
931
932 #[test]
933 fn a_nan_is_left_alone() {
934 let (_, mut func, block) = blank();
935 let mut build = Builder::new(&mut func, block);
936 let value = build.fconst(Type::float(Float::F64), 0x7ff8_0000_0000_0000);
937 let out = build.unary(Opcode::FPToSI, value, Type::int(32));
938 build.ret(&[out]);
939 assert!(!fold(&mut func));
940 }
941
942 #[test]
943 fn a_constant_is_read_in_the_format_its_own_type_gives_it() {
944 let bits = super::Float::parse("3.0", Format::X87Extended).expect("a number").0.to_bits();
947 for (float, expected) in [(Float::F80, 3_i128), (Float::F128, 0)] {
948 let (_, mut func, block) = blank();
949 let mut build = Builder::new(&mut func, block);
950 let value = build.fconst(Type::float(float), bits);
951 let out = build.unary(Opcode::FPToSI, value, Type::int(32));
952 build.ret(&[out]);
953 assert!(fold(&mut func), "{float}");
954 assert_eq!(value_of(&func, out, Type::int(32)), Some(expected), "{float}");
955 }
956 }
957
958 #[test]
959 fn a_conversion_of_something_that_is_not_a_constant_is_left_alone() {
960 let (_, mut func, block) = blank();
961 let param = func.append_param(block, Type::float(Float::F64));
962 let mut build = Builder::new(&mut func, block);
963 let out = build.unary(Opcode::FPToSI, param, Type::int(32));
964 build.ret(&[out]);
965 assert!(!fold(&mut func));
966 }
967
968 #[test]
969 fn a_divide_is_not_folded_even_when_both_operands_are_constants() {
970 for opcode in [Opcode::SDiv, Opcode::UDiv, Opcode::SRem, Opcode::URem] {
971 let (_, mut func, block) = blank();
972 let mut build = Builder::new(&mut func, block);
973 let lhs = build.iconst(Type::int(64), 42);
974 let rhs = build.iconst(Type::int(64), 7);
975 let out = build.binary(opcode, lhs, rhs, Flags::NONE);
976 build.ret(&[out]);
977 assert!(!fold(&mut func), "{opcode:?}");
978 }
979 }
980
981 #[test]
982 fn folding_leaves_the_function_something_the_verifier_accepts() {
983 let mut names = Interner::new();
984 let name = names.intern("f");
985 let mut func = Func::new(name, Signature::new().with_returns(&[Type::int(64)]));
986 let block = func.create_block();
987 let mut build = Builder::new(&mut func, block);
988 let narrow = build.iconst(Type::int(32), 7);
989 let wide = build.unary(Opcode::SExt, narrow, Type::int(64));
990 build.ret(&[wide]);
991 assert!(fold(&mut func));
992 let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
993 let module_name = names.intern("m");
994 let mut module = Module::new(module_name, &target);
995 module.add_func(func);
996 rucc_ir::verify(&module, &names).expect("folding does not break the IR");
997 }
998
999 #[test]
1000 fn fuel_stops_the_transformation_and_not_the_walk() {
1001 let build_two = |func: &mut Func, block: Block| {
1002 let mut build = Builder::new(func, block);
1003 let a = build.iconst(Type::int(32), 7);
1004 let wide_a = build.unary(Opcode::SExt, a, Type::int(64));
1005 let b = build.iconst(Type::int(32), 9);
1006 let wide_b = build.unary(Opcode::SExt, b, Type::int(64));
1007 let sum = build.binary(Opcode::Add, wide_a, wide_b, Flags::NONE);
1008 build.ret(&[sum]);
1009 (wide_a, wide_b)
1010 };
1011
1012 let (_, mut none, block) = blank();
1013 let (first, _) = build_two(&mut none, block);
1014 let stats =
1015 Fold.run(&mut none, &mut crate::machine::fixtures::analyses(), &mut Fuel::of(0));
1016 assert!(!stats.changed());
1017 assert_eq!(none[out_inst(&none, first)].opcode, Opcode::SExt);
1018 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 2);
1021
1022 let (_, mut one, block) = blank();
1023 let (first, second) = build_two(&mut one, block);
1024 let mut fuel = Fuel::of(1);
1025 let stats = Fold.run(&mut one, &mut crate::machine::fixtures::analyses(), &mut fuel);
1026 assert!(stats.changed());
1027 assert_eq!(fuel.spent(), 1);
1028 assert_eq!(stats.count(Kind::Optimized, super::FOLDED), 1);
1029 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
1030 assert_eq!(one[out_inst(&one, first)].opcode, Opcode::IConst);
1031 assert_eq!(one[out_inst(&one, second)].opcode, Opcode::SExt);
1032 }
1033
1034 #[test]
1035 fn folding_one_operation_uncovers_the_next() {
1036 let (_, mut func, block) = blank();
1037 let mut build = Builder::new(&mut func, block);
1038 let a = build.iconst(Type::int(32), 7);
1039 let wide = build.unary(Opcode::SExt, a, Type::int(64));
1040 let b = build.iconst(Type::int(64), 9);
1041 let sum = build.binary(Opcode::Add, wide, b, Flags::NONE);
1042 build.ret(&[sum]);
1043 assert!(fold(&mut func));
1044 assert_eq!(value_of(&func, sum, Type::int(64)), Some(16));
1047 }
1048
1049 #[test]
1050 fn a_constant_is_left_where_it_is_and_folding_it_again_changes_nothing() {
1051 let (_, mut func, block) = blank();
1052 let mut build = Builder::new(&mut func, block);
1053 let a = build.iconst(Type::int(32), 7);
1054 let wide = build.unary(Opcode::SExt, a, Type::int(64));
1055 build.ret(&[wide]);
1056 assert!(fold(&mut func));
1057 assert!(!fold(&mut func), "a second run found something to do");
1058 }
1059
1060 fn out_inst(func: &Func, value: Value) -> rucc_ir::Inst {
1062 match func[value].def {
1063 rucc_ir::Def::Result { inst, .. } => inst,
1064 rucc_ir::Def::Param { .. } => panic!("a parameter has no instruction"),
1065 }
1066 }
1067}