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 let blocks: Vec<Block> = func.blocks().collect();
120 let mut stats = Stats::new();
121 for block in blocks {
122 let insts: Vec<Inst> = func.insts(block).collect();
123 for inst in insts {
124 let Some(folded) = evaluate(func, inst) else { continue };
125 if !fuel.take() {
126 stats.missed(NO_FUEL);
131 continue;
132 }
133 let ty = func[result_of(func, inst)].ty;
134 let at = func.add_imm(folded);
135 let data = &mut func[inst];
136 data.opcode = if ty.is_int() { Opcode::IConst } else { Opcode::FConst };
141 data.flags = Flags::NONE;
142 data.args = rucc_ir::ValueList::EMPTY;
143 data.extra = Extra::Imm(at);
144 stats.optimized(FOLDED);
145 }
146 }
147 stats
148 }
149}
150
151fn result_of(func: &Func, inst: Inst) -> Value {
153 func[inst].results().next().expect("an instruction that folds produces a value")
154}
155
156fn evaluate(func: &Func, inst: Inst) -> Option<Imm> {
161 let data = &func[inst];
162 if data.results != 1 {
163 return None;
164 }
165 let result = data.results().next()?;
166 let ty = func[result].ty;
167 if !ty.is_scalar() {
171 return None;
172 }
173 let args = &func[data.args];
174 match data.opcode {
178 Opcode::FNeg => return negated(func, *args.first()?, ty),
179 Opcode::Bitcast => return reinterpreted(func, *args.first()?, ty),
180 _ => {}
181 }
182 if !ty.is_int() {
183 return None;
184 }
185 match data.opcode {
186 Opcode::Trunc | Opcode::SExt | Opcode::ZExt => {
187 let (value, from) = constant(func, *args.first()?)?;
188 Some(convert(data.opcode, value, from, ty))
189 }
190 Opcode::Shl | Opcode::LShr | Opcode::AShr => {
191 let (value, from) = constant(func, *args.first()?)?;
192 let (count, count_ty) = constant(func, *args.get(1)?)?;
193 shift(data.opcode, value, from, count, count_ty, ty, data.flags)
194 }
195 Opcode::Add | Opcode::Sub | Opcode::Mul | Opcode::And | Opcode::Or | Opcode::Xor => {
196 let (lhs, lhs_ty) = constant(func, *args.first()?)?;
197 let (rhs, _) = constant(func, *args.get(1)?)?;
198 binary(data.opcode, lhs, rhs, lhs_ty, ty, data.flags)
199 }
200 Opcode::Ctlz | Opcode::Cttz | Opcode::Ctpop | Opcode::Bswap | Opcode::Bitreverse => {
201 let (value, from) = constant(func, *args.first()?)?;
202 count(data.opcode, value, from, ty)
203 }
204 Opcode::FPToSI | Opcode::FPToUI => {
205 let value = floating(func, *args.first()?)?;
206 to_integer(value, ty, data.opcode == Opcode::FPToSI)
207 }
208 Opcode::ICmp => {
209 let Extra::IntPred(pred) = data.extra else { return None };
210 let (lhs, from) = constant(func, *args.first()?)?;
211 let (rhs, _) = constant(func, *args.get(1)?)?;
212 Some(Imm::int(i128::from(compare(pred, lhs, rhs, from)), ty))
213 }
214 _ => None,
215 }
216}
217
218fn bits_of(func: &Func, value: Value) -> Option<u128> {
224 let Def::Result { inst, .. } = func[value].def else { return None };
225 let data = &func[inst];
226 if !matches!(data.opcode, Opcode::IConst | Opcode::FConst) {
227 return None;
228 }
229 let Extra::Imm(at) = data.extra else { return None };
230 Some(func[at].bits())
231}
232
233fn negated(func: &Func, operand: Value, ty: Type) -> Option<Imm> {
251 if !ty.is_float() {
252 return None;
253 }
254 let bits = bits_of(func, operand)?;
255 Some(Imm::from_bits(bits ^ 1u128 << (ty.bits() - 1)))
256}
257
258fn reinterpreted(func: &Func, operand: Value, ty: Type) -> Option<Imm> {
269 let from = func[operand].ty;
270 if !from.is_scalar() || from.bits() != ty.bits() {
271 return None;
272 }
273 let bits = bits_of(func, operand)?;
274 Some(Imm::from_bits(bits))
275}
276
277pub(crate) fn constant(func: &Func, value: Value) -> Option<(Imm, Type)> {
282 let Def::Result { inst, .. } = func[value].def else { return None };
283 if func[inst].opcode != Opcode::IConst {
284 return None;
285 }
286 let Extra::Imm(at) = func[inst].extra else { return None };
287 let ty = func[value].ty;
288 ty.is_int().then(|| (func[at], ty))
289}
290
291fn floating(func: &Func, value: Value) -> Option<Float> {
297 let Def::Result { inst, .. } = func[value].def else { return None };
298 if func[inst].opcode != Opcode::FConst {
299 return None;
300 }
301 let Extra::Imm(at) = func[inst].extra else { return None };
302 let format = func[value].ty.format()?.encoding();
303 Some(Float::from_bits(format, func[at].bits()))
304}
305
306fn to_integer(value: Float, to: Type, signed: bool) -> Option<Imm> {
315 let (number, status) = value.to_integer(to.bits(), signed);
316 (!status.has(Status::INVALID)).then(|| Imm::int(number, to))
317}
318
319fn convert(opcode: Opcode, value: Imm, from: Type, to: Type) -> Imm {
321 match opcode {
322 Opcode::Trunc | Opcode::SExt => Imm::int(value.signed(from), to),
325 _ => Imm::int(value.unsigned() as i128, to),
328 }
329}
330
331fn shift(
337 opcode: Opcode,
338 value: Imm,
339 from: Type,
340 count: Imm,
341 count_ty: Type,
342 to: Type,
343 flags: Flags,
344) -> Option<Imm> {
345 let by = count.unsigned();
346 if by >= u128::from(to.bits()) || count.signed(count_ty) < 0 {
347 return None;
348 }
349 let by = by as u32;
350 let exact = match opcode {
351 Opcode::Shl => value.signed(from).checked_shl(by)?,
352 Opcode::LShr => (value.unsigned() >> by) as i128,
356 _ => value.signed(from) >> by,
357 };
358 if opcode == Opcode::Shl && overflowed(exact, to, flags) {
359 return None;
360 }
361 Some(Imm::int(exact, to))
362}
363
364fn binary(opcode: Opcode, lhs: Imm, rhs: Imm, from: Type, to: Type, flags: Flags) -> Option<Imm> {
366 let (a, b) = (lhs.signed(from), rhs.signed(from));
367 let exact = match opcode {
368 Opcode::And => a & b,
371 Opcode::Or => a | b,
372 Opcode::Xor => a ^ b,
373 Opcode::Add => a.checked_add(b)?,
377 Opcode::Sub => a.checked_sub(b)?,
378 _ => a.checked_mul(b)?,
379 };
380 if overflowed(exact, to, flags) {
381 return None;
382 }
383 Some(Imm::int(exact, to))
384}
385
386pub(crate) fn compare(pred: IntPred, lhs: Imm, rhs: Imm, ty: Type) -> bool {
398 match pred {
399 IntPred::Eq => lhs == rhs,
400 IntPred::Ne => lhs != rhs,
401 IntPred::Slt => lhs.signed(ty) < rhs.signed(ty),
402 IntPred::Sle => lhs.signed(ty) <= rhs.signed(ty),
403 IntPred::Sgt => lhs.signed(ty) > rhs.signed(ty),
404 IntPred::Sge => lhs.signed(ty) >= rhs.signed(ty),
405 IntPred::Ult => lhs.unsigned() < rhs.unsigned(),
406 IntPred::Ule => lhs.unsigned() <= rhs.unsigned(),
407 IntPred::Ugt => lhs.unsigned() > rhs.unsigned(),
408 IntPred::Uge => lhs.unsigned() >= rhs.unsigned(),
409 }
410}
411
412fn count(opcode: Opcode, value: Imm, from: Type, to: Type) -> Option<Imm> {
429 let width = from.bits();
430 if width == 0 || width > 128 {
431 return None;
432 }
433 let spare = 128 - width;
437 let bits = value.unsigned();
438 let answer = match opcode {
439 Opcode::Ctpop => i128::from(bits.count_ones()),
440 Opcode::Ctlz => i128::from(bits.leading_zeros() - spare),
443 Opcode::Cttz => i128::from(bits.trailing_zeros().min(width)),
446 Opcode::Bswap if width % 8 == 0 => (bits.swap_bytes() >> spare) as i128,
447 Opcode::Bitreverse => (bits.reverse_bits() >> spare) as i128,
448 _ => return None,
449 };
450 Some(Imm::int(answer, to))
451}
452
453fn overflowed(exact: i128, to: Type, flags: Flags) -> bool {
458 let stored = Imm::int(exact, to);
459 if flags.contains(Flags::NSW) && stored.signed(to) != exact {
460 return true;
461 }
462 flags.contains(Flags::NUW) && (exact < 0 || stored.unsigned() != exact as u128)
463}
464
465#[cfg(test)]
466mod tests {
467 use rucc_base::Interner;
468 use rucc_base::float::Format;
469 use rucc_ir::{
470 Block, Builder, Extra, Flags, Float, Func, IntPred, Module, Opcode, Signature, Type, Value,
471 };
472 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
473
474 use crate::stats::Kind;
475 use crate::{Fuel, Pass, fold::Fold};
476
477 fn blank() -> (Interner, Func, Block) {
479 let mut names = Interner::new();
480 let name = names.intern("f");
481 let mut func = Func::new(name, Signature::new().with_returns(&[Type::int(64)]));
482 let block = func.create_block();
483 (names, func, block)
484 }
485
486 fn fold(func: &mut Func) -> bool {
489 Fold.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited()).changed()
490 }
491
492 fn number(build: &mut Builder<'_>, text: &str, ty: Type) -> Value {
498 let format = ty.format().expect("a floating point type").encoding();
499 let (value, _) = super::Float::parse(text, format).expect("a number");
500 build.fconst(ty, value.to_bits())
501 }
502
503 fn value_of(func: &Func, value: Value, ty: Type) -> Option<i128> {
505 let rucc_ir::Def::Result { inst, .. } = func[value].def else { return None };
506 if func[inst].opcode != Opcode::IConst {
507 return None;
508 }
509 let Extra::Imm(at) = func[inst].extra else { return None };
510 Some(func[at].signed(ty))
511 }
512
513 fn float_bits(func: &Func, value: Value) -> Option<u128> {
515 let rucc_ir::Def::Result { inst, .. } = func[value].def else { return None };
516 if func[inst].opcode != Opcode::FConst {
517 return None;
518 }
519 let Extra::Imm(at) = func[inst].extra else { return None };
520 Some(func[at].bits())
521 }
522
523 #[test]
526 fn a_negated_floating_constant_becomes_a_constant() {
527 let (_, mut func, block) = blank();
528 let ty = Type::float(Float::F64);
529 let mut build = Builder::new(&mut func, block);
530 let one = number(&mut build, "1.0", ty);
531 let minus = build.unary(Opcode::FNeg, one, ty);
532 build.ret(&[minus]);
533 assert!(fold(&mut func));
534 assert_eq!(float_bits(&func, minus), Some(0xbff0_0000_0000_0000));
535 }
536
537 #[test]
541 fn a_negated_zero_keeps_its_sign_bit() {
542 let (_, mut func, block) = blank();
543 let ty = Type::float(Float::F64);
544 let mut build = Builder::new(&mut func, block);
545 let zero = number(&mut build, "0.0", ty);
546 let minus = build.unary(Opcode::FNeg, zero, ty);
547 build.ret(&[minus]);
548 assert!(fold(&mut func));
549 assert_eq!(float_bits(&func, minus), Some(1 << 63));
550 }
551
552 #[test]
556 fn a_negated_nan_keeps_its_payload() {
557 let (_, mut func, block) = blank();
558 let ty = Type::float(Float::F64);
559 let mut build = Builder::new(&mut func, block);
560 let nan = build.fconst(ty, 0x7ff8_0000_dead_beef);
561 let minus = build.unary(Opcode::FNeg, nan, ty);
562 build.ret(&[minus]);
563 assert!(fold(&mut func));
564 assert_eq!(float_bits(&func, minus), Some(0xfff8_0000_dead_beef));
565 }
566
567 #[test]
571 fn the_sign_bit_of_an_x87_value_is_the_top_bit_of_its_width() {
572 let (_, mut func, block) = blank();
573 let ty = Type::float(Float::F80);
574 let mut build = Builder::new(&mut func, block);
575 let one = number(&mut build, "1.0", ty);
576 let minus = build.unary(Opcode::FNeg, one, ty);
577 build.ret(&[minus]);
578 assert!(fold(&mut func));
579 let bits = float_bits(&func, minus).expect("a constant");
580 assert_eq!(bits >> 79 & 1, 1, "the sign bit is set");
581 assert_eq!(bits >> 80, 0, "nothing above the value is touched");
582 }
583
584 #[test]
588 fn a_bitcast_of_a_constant_is_the_same_bits() {
589 let (_, mut func, block) = blank();
590 let ty = Type::float(Float::F64);
591 let bits = Type::int(64);
592 let mut build = Builder::new(&mut func, block);
593 let value = number(&mut build, "-3.5", ty);
594 let number = build.unary(Opcode::Bitcast, value, bits);
595 let mask = build.iconst(bits, i128::from(i64::MAX));
596 let cleared = build.binary(Opcode::And, number, mask, Flags::NONE);
597 let back = build.unary(Opcode::Bitcast, cleared, ty);
598 build.ret(&[back]);
599 assert!(fold(&mut func));
600 assert_eq!(float_bits(&func, back), Some(0x400c_0000_0000_0000));
601 }
602
603 #[test]
606 fn a_bitcast_of_something_that_is_not_a_constant_is_left_alone() {
607 let mut names = Interner::new();
608 let name = names.intern("f");
609 let ty = Type::float(Float::F64);
610 let signature = Signature::new().with_params(&[ty]).with_returns(&[Type::int(64)]);
611 let mut func = Func::new(name, signature);
612 let block = func.create_block();
613 let x = func.append_param(block, ty);
614 let mut build = Builder::new(&mut func, block);
615 let number = build.unary(Opcode::Bitcast, x, Type::int(64));
616 build.ret(&[number]);
617 assert!(!fold(&mut func));
618 assert_eq!(value_of(&func, number, Type::int(64)), None);
619 }
620
621 #[test]
622 fn a_widened_constant_becomes_a_constant_of_the_wider_type() {
623 let (_, mut func, block) = blank();
624 let mut build = Builder::new(&mut func, block);
625 let narrow = build.iconst(Type::int(32), 7);
626 let wide = build.unary(Opcode::SExt, narrow, Type::int(64));
627 build.ret(&[wide]);
628 assert!(fold(&mut func));
629 assert_eq!(value_of(&func, wide, Type::int(64)), Some(7));
630 }
631
632 #[test]
633 fn sign_extension_copies_the_sign_and_zero_extension_does_not() {
634 for (opcode, expected) in [(Opcode::SExt, -1_i128), (Opcode::ZExt, 0xffff_ffff)] {
635 let (_, mut func, block) = blank();
636 let mut build = Builder::new(&mut func, block);
637 let narrow = build.iconst(Type::int(32), -1);
638 let wide = build.unary(opcode, narrow, Type::int(64));
639 build.ret(&[wide]);
640 assert!(fold(&mut func));
641 assert_eq!(value_of(&func, wide, Type::int(64)), Some(expected), "{opcode:?}");
642 }
643 }
644
645 #[test]
646 fn truncation_keeps_the_low_bits_and_reads_them_at_the_narrow_width() {
647 let (_, mut func, block) = blank();
648 let mut build = Builder::new(&mut func, block);
649 let wide = build.iconst(Type::int(32), 0x1234_5680);
650 let narrow = build.unary(Opcode::Trunc, wide, Type::int(8));
651 build.ret(&[narrow]);
652 assert!(fold(&mut func));
653 assert_eq!(value_of(&func, narrow, Type::int(8)), Some(-128));
654 }
655
656 #[test]
657 fn the_arithmetic_and_the_bitwise_operations_are_evaluated() {
658 let cases = [
659 (Opcode::Add, 6_i128, 7_i128, 13_i128),
660 (Opcode::Sub, 6, 7, -1),
661 (Opcode::Mul, 6, 7, 42),
662 (Opcode::And, 0b1100, 0b1010, 0b1000),
663 (Opcode::Or, 0b1100, 0b1010, 0b1110),
664 (Opcode::Xor, 0b1100, 0b1010, 0b0110),
665 ];
666 for (opcode, a, b, want) in cases {
667 let (_, mut func, block) = blank();
668 let mut build = Builder::new(&mut func, block);
669 let lhs = build.iconst(Type::int(64), a);
670 let rhs = build.iconst(Type::int(64), b);
671 let out = build.binary(opcode, lhs, rhs, Flags::NONE);
672 build.ret(&[out]);
673 assert!(fold(&mut func), "{opcode:?}");
674 assert_eq!(value_of(&func, out, Type::int(64)), Some(want), "{opcode:?}");
675 }
676 }
677
678 #[test]
679 fn the_three_shifts_are_evaluated_and_the_two_right_ones_differ_on_the_sign() {
680 let cases = [(Opcode::Shl, -8_i128, 1_i128, -16_i128), (Opcode::AShr, -8, 1, -4)];
681 for (opcode, a, b, want) in cases {
682 let (_, mut func, block) = blank();
683 let mut build = Builder::new(&mut func, block);
684 let lhs = build.iconst(Type::int(64), a);
685 let rhs = build.iconst(Type::int(64), b);
686 let out = build.binary(opcode, lhs, rhs, Flags::NONE);
687 build.ret(&[out]);
688 assert!(fold(&mut func), "{opcode:?}");
689 assert_eq!(value_of(&func, out, Type::int(64)), Some(want), "{opcode:?}");
690 }
691 let (_, mut func, block) = blank();
694 let mut build = Builder::new(&mut func, block);
695 let lhs = build.iconst(Type::int(64), -8);
696 let rhs = build.iconst(Type::int(64), 1);
697 let out = build.binary(Opcode::LShr, lhs, rhs, Flags::NONE);
698 build.ret(&[out]);
699 assert!(fold(&mut func));
700 assert_eq!(value_of(&func, out, Type::int(64)), Some(i128::from(i64::MAX) - 3));
701 }
702
703 fn one(opcode: Opcode, ty: Type, arg: i128) -> Option<i128> {
705 let (_, mut func, block) = blank();
706 let mut build = Builder::new(&mut func, block);
707 let value = build.iconst(ty, arg);
708 let out = build.unary(opcode, value, ty);
709 build.ret(&[out]);
710 fold(&mut func);
711 value_of(&func, out, ty)
712 }
713
714 #[test]
715 fn the_bit_counts_are_evaluated_at_the_width_they_were_asked_at() {
716 let cases = [
717 (Opcode::Ctlz, 64, 0x0000_1000_0000_0000_i128, 19_i128),
718 (Opcode::Ctlz, 32, 0x0000_1000, 19),
719 (Opcode::Cttz, 64, 0x0000_1000_0000_0000, 44),
720 (Opcode::Cttz, 32, 0x0000_1000, 12),
721 (Opcode::Ctpop, 64, 0x0000_1000_0000_0000, 1),
722 (Opcode::Ctpop, 32, -1, 32),
723 (Opcode::Ctpop, 64, -1, 64),
724 ];
725 for (opcode, width, arg, want) in cases {
726 let ty = Type::int(width);
727 assert_eq!(one(opcode, ty, arg), Some(want), "{opcode:?} at {width} of {arg:#x}");
728 }
729 }
730
731 #[test]
732 fn a_search_for_a_bit_in_a_zero_answers_the_width_the_expansion_answers() {
733 for width in [8_u32, 16, 32, 64] {
734 let ty = Type::int(width);
735 let want = Some(i128::from(width));
736 assert_eq!(one(Opcode::Ctlz, ty, 0), want, "leading, at {width}");
737 assert_eq!(one(Opcode::Cttz, ty, 0), want, "trailing, at {width}");
738 assert_eq!(one(Opcode::Ctpop, ty, 0), Some(0), "count, at {width}");
739 }
740 }
741
742 #[test]
743 fn the_two_reversals_are_evaluated_and_a_byte_swap_of_a_part_of_a_byte_is_not() {
744 let ty = Type::int(32);
745 assert_eq!(one(Opcode::Bswap, ty, 0x1234_5678), Some(0x7856_3412));
746 assert_eq!(one(Opcode::Bswap, Type::int(16), 0x1234), Some(0x3412));
747 assert_eq!(one(Opcode::Bitreverse, Type::int(8), 0b1010_1100), Some(0b0011_0101));
748 let (_, mut func, block) = blank();
751 let mut build = Builder::new(&mut func, block);
752 let value = build.iconst(Type::int(4), 0b1010);
753 let out = build.unary(Opcode::Bswap, value, Type::int(4));
754 build.ret(&[out]);
755 assert!(!fold(&mut func));
756 }
757
758 #[test]
759 fn a_comparison_of_two_constants_becomes_a_one_or_a_nought() {
760 let cases = [
761 (IntPred::Eq, 7_i128, 7_i128, true),
762 (IntPred::Eq, 7, 8, false),
763 (IntPred::Ne, 7, 8, true),
764 (IntPred::Slt, -1, 1, true),
765 (IntPred::Sle, -1, -1, true),
766 (IntPred::Sgt, -1, 1, false),
767 (IntPred::Sge, 1, -1, true),
768 (IntPred::Ult, -1, 1, false),
771 (IntPred::Ule, -1, 1, false),
772 (IntPred::Ugt, -1, 1, true),
773 (IntPred::Uge, -1, 1, true),
774 ];
775 for (pred, a, b, want) in cases {
776 let (_, mut func, block) = blank();
777 let mut build = Builder::new(&mut func, block);
778 let lhs = build.iconst(Type::int(64), a);
779 let rhs = build.iconst(Type::int(64), b);
780 let out = build.icmp(pred, lhs, rhs);
781 build.ret(&[out]);
782 assert!(fold(&mut func), "{pred:?} {a} {b}");
783 let got = value_of(&func, out, Type::I1).expect("the comparison folded");
786 assert_eq!(got != 0, want, "{pred:?} {a} {b}");
787 }
788 }
789
790 #[test]
791 fn a_comparison_at_a_narrow_width_is_read_at_that_width() {
792 let ty = Type::int(8);
795 for (pred, want) in [(IntPred::Slt, true), (IntPred::Ult, false)] {
796 let (_, mut func, block) = blank();
797 let mut build = Builder::new(&mut func, block);
798 let lhs = build.iconst(ty, 255);
799 let rhs = build.iconst(ty, 1);
800 let out = build.icmp(pred, lhs, rhs);
801 build.ret(&[out]);
802 assert!(fold(&mut func), "{pred:?}");
803 let got = value_of(&func, out, Type::I1).expect("the comparison folded");
804 assert_eq!(got != 0, want, "{pred:?}");
805 }
806 }
807
808 #[test]
809 fn a_comparison_with_one_constant_operand_is_left_alone() {
810 let (_, mut func, block) = blank();
811 let ty = Type::int(64);
812 let param = func.append_param(block, ty);
813 let mut build = Builder::new(&mut func, block);
814 let rhs = build.iconst(ty, 3);
815 let out = build.icmp(IntPred::Eq, param, rhs);
816 build.ret(&[out]);
817 assert!(!fold(&mut func));
818 }
819
820 #[test]
821 fn a_bit_count_of_something_that_is_not_a_constant_is_left_alone() {
822 for opcode in [Opcode::Ctlz, Opcode::Cttz, Opcode::Ctpop, Opcode::Bswap] {
823 let (_, mut func, block) = blank();
824 let ty = Type::int(64);
825 let param = func.append_param(block, ty);
826 let mut build = Builder::new(&mut func, block);
827 let out = build.unary(opcode, param, ty);
828 build.ret(&[out]);
829 assert!(!fold(&mut func), "{opcode:?}");
830 }
831 }
832
833 #[test]
834 fn a_shift_by_the_width_or_more_is_left_alone_because_the_language_does_not_define_it() {
835 for count in [64_i128, 65, -1] {
836 let (_, mut func, block) = blank();
837 let mut build = Builder::new(&mut func, block);
838 let lhs = build.iconst(Type::int(64), 1);
839 let rhs = build.iconst(Type::int(64), count);
840 let out = build.binary(Opcode::Shl, lhs, rhs, Flags::NONE);
841 build.ret(&[out]);
842 assert!(!fold(&mut func), "a shift by {count} was folded");
843 }
844 }
845
846 #[test]
847 fn an_operation_that_wraps_folds_and_the_same_one_promising_it_will_not_does_not() {
848 let big = i128::from(i32::MAX);
849 for (flags, folds) in [(Flags::NONE, true), (Flags::NSW, false)] {
850 let (_, mut func, block) = blank();
851 let mut build = Builder::new(&mut func, block);
852 let lhs = build.iconst(Type::int(32), big);
853 let rhs = build.iconst(Type::int(32), 1);
854 let out = build.binary(Opcode::Add, lhs, rhs, flags);
855 build.ret(&[out]);
856 assert_eq!(fold(&mut func), folds, "{flags}");
857 if folds {
858 assert_eq!(value_of(&func, out, Type::int(32)), Some(i128::from(i32::MIN)));
859 }
860 }
861 }
862
863 #[test]
864 fn an_unsigned_promise_is_broken_by_a_negative_result_as_well_as_by_a_large_one() {
865 let (_, mut func, block) = blank();
866 let mut build = Builder::new(&mut func, block);
867 let lhs = build.iconst(Type::int(32), 1);
868 let rhs = build.iconst(Type::int(32), 2);
869 let out = build.binary(Opcode::Sub, lhs, rhs, Flags::NUW);
870 build.ret(&[out]);
871 assert!(!fold(&mut func));
872 }
873
874 #[test]
875 fn an_operation_with_one_constant_operand_is_left_alone() {
876 let (_, mut func, block) = blank();
877 let param = func.append_param(block, Type::int(64));
878 let mut build = Builder::new(&mut func, block);
879 let rhs = build.iconst(Type::int(64), 7);
880 let out = build.binary(Opcode::Add, param, rhs, Flags::NONE);
881 build.ret(&[out]);
882 assert!(!fold(&mut func));
883 assert_eq!(func[out_inst(&func, out)].opcode, Opcode::Add);
884 }
885
886 #[test]
887 fn a_conversion_to_an_integer_truncates_toward_zero() {
888 for (text, expected) in [("2.75", 2_i128), ("-2.75", -2), ("0.5", 0), ("-0.5", 0)] {
889 let (_, mut func, block) = blank();
890 let mut build = Builder::new(&mut func, block);
891 let value = number(&mut build, text, Type::float(Float::F64));
892 let out = build.unary(Opcode::FPToSI, value, Type::int(32));
893 build.ret(&[out]);
894 assert!(fold(&mut func), "{text}");
895 assert_eq!(value_of(&func, out, Type::int(32)), Some(expected), "{text}");
896 }
897 }
898
899 #[test]
900 fn a_negative_number_converts_to_an_unsigned_type_only_when_truncating_lands_on_zero() {
901 for (text, expected) in [("-0.5", Some(0)), ("-1.5", None)] {
902 let (_, mut func, block) = blank();
903 let mut build = Builder::new(&mut func, block);
904 let value = number(&mut build, text, Type::float(Float::F64));
905 let out = build.unary(Opcode::FPToUI, value, Type::int(32));
906 build.ret(&[out]);
907 assert_eq!(fold(&mut func), expected.is_some(), "{text}");
908 assert_eq!(value_of(&func, out, Type::int(32)), expected, "{text}");
909 }
910 }
911
912 #[test]
913 fn a_number_the_destination_type_has_no_room_for_is_left_alone() {
914 let (_, mut func, block) = blank();
915 let mut build = Builder::new(&mut func, block);
916 let value = number(&mut build, "1e30", Type::float(Float::F64));
917 let out = build.unary(Opcode::FPToSI, value, Type::int(32));
918 build.ret(&[out]);
919 assert!(!fold(&mut func));
920 assert_eq!(func[out_inst(&func, out)].opcode, Opcode::FPToSI);
921 }
922
923 #[test]
924 fn a_nan_is_left_alone() {
925 let (_, mut func, block) = blank();
926 let mut build = Builder::new(&mut func, block);
927 let value = build.fconst(Type::float(Float::F64), 0x7ff8_0000_0000_0000);
928 let out = build.unary(Opcode::FPToSI, value, Type::int(32));
929 build.ret(&[out]);
930 assert!(!fold(&mut func));
931 }
932
933 #[test]
934 fn a_constant_is_read_in_the_format_its_own_type_gives_it() {
935 let bits = super::Float::parse("3.0", Format::X87Extended).expect("a number").0.to_bits();
938 for (float, expected) in [(Float::F80, 3_i128), (Float::F128, 0)] {
939 let (_, mut func, block) = blank();
940 let mut build = Builder::new(&mut func, block);
941 let value = build.fconst(Type::float(float), bits);
942 let out = build.unary(Opcode::FPToSI, value, Type::int(32));
943 build.ret(&[out]);
944 assert!(fold(&mut func), "{float}");
945 assert_eq!(value_of(&func, out, Type::int(32)), Some(expected), "{float}");
946 }
947 }
948
949 #[test]
950 fn a_conversion_of_something_that_is_not_a_constant_is_left_alone() {
951 let (_, mut func, block) = blank();
952 let param = func.append_param(block, Type::float(Float::F64));
953 let mut build = Builder::new(&mut func, block);
954 let out = build.unary(Opcode::FPToSI, param, Type::int(32));
955 build.ret(&[out]);
956 assert!(!fold(&mut func));
957 }
958
959 #[test]
960 fn a_divide_is_not_folded_even_when_both_operands_are_constants() {
961 for opcode in [Opcode::SDiv, Opcode::UDiv, Opcode::SRem, Opcode::URem] {
962 let (_, mut func, block) = blank();
963 let mut build = Builder::new(&mut func, block);
964 let lhs = build.iconst(Type::int(64), 42);
965 let rhs = build.iconst(Type::int(64), 7);
966 let out = build.binary(opcode, lhs, rhs, Flags::NONE);
967 build.ret(&[out]);
968 assert!(!fold(&mut func), "{opcode:?}");
969 }
970 }
971
972 #[test]
973 fn folding_leaves_the_function_something_the_verifier_accepts() {
974 let mut names = Interner::new();
975 let name = names.intern("f");
976 let mut func = Func::new(name, Signature::new().with_returns(&[Type::int(64)]));
977 let block = func.create_block();
978 let mut build = Builder::new(&mut func, block);
979 let narrow = build.iconst(Type::int(32), 7);
980 let wide = build.unary(Opcode::SExt, narrow, Type::int(64));
981 build.ret(&[wide]);
982 assert!(fold(&mut func));
983 let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
984 let module_name = names.intern("m");
985 let mut module = Module::new(module_name, &target);
986 module.add_func(func);
987 rucc_ir::verify(&module, &names).expect("folding does not break the IR");
988 }
989
990 #[test]
991 fn fuel_stops_the_transformation_and_not_the_walk() {
992 let build_two = |func: &mut Func, block: Block| {
993 let mut build = Builder::new(func, block);
994 let a = build.iconst(Type::int(32), 7);
995 let wide_a = build.unary(Opcode::SExt, a, Type::int(64));
996 let b = build.iconst(Type::int(32), 9);
997 let wide_b = build.unary(Opcode::SExt, b, Type::int(64));
998 let sum = build.binary(Opcode::Add, wide_a, wide_b, Flags::NONE);
999 build.ret(&[sum]);
1000 (wide_a, wide_b)
1001 };
1002
1003 let (_, mut none, block) = blank();
1004 let (first, _) = build_two(&mut none, block);
1005 let stats =
1006 Fold.run(&mut none, &mut crate::machine::fixtures::analyses(), &mut Fuel::of(0));
1007 assert!(!stats.changed());
1008 assert_eq!(none[out_inst(&none, first)].opcode, Opcode::SExt);
1009 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 2);
1012
1013 let (_, mut one, block) = blank();
1014 let (first, second) = build_two(&mut one, block);
1015 let mut fuel = Fuel::of(1);
1016 let stats = Fold.run(&mut one, &mut crate::machine::fixtures::analyses(), &mut fuel);
1017 assert!(stats.changed());
1018 assert_eq!(fuel.spent(), 1);
1019 assert_eq!(stats.count(Kind::Optimized, super::FOLDED), 1);
1020 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
1021 assert_eq!(one[out_inst(&one, first)].opcode, Opcode::IConst);
1022 assert_eq!(one[out_inst(&one, second)].opcode, Opcode::SExt);
1023 }
1024
1025 #[test]
1026 fn folding_one_operation_uncovers_the_next() {
1027 let (_, mut func, block) = blank();
1028 let mut build = Builder::new(&mut func, block);
1029 let a = build.iconst(Type::int(32), 7);
1030 let wide = build.unary(Opcode::SExt, a, Type::int(64));
1031 let b = build.iconst(Type::int(64), 9);
1032 let sum = build.binary(Opcode::Add, wide, b, Flags::NONE);
1033 build.ret(&[sum]);
1034 assert!(fold(&mut func));
1035 assert_eq!(value_of(&func, sum, Type::int(64)), Some(16));
1038 }
1039
1040 #[test]
1041 fn a_constant_is_left_where_it_is_and_folding_it_again_changes_nothing() {
1042 let (_, mut func, block) = blank();
1043 let mut build = Builder::new(&mut func, block);
1044 let a = build.iconst(Type::int(32), 7);
1045 let wide = build.unary(Opcode::SExt, a, Type::int(64));
1046 build.ret(&[wide]);
1047 assert!(fold(&mut func));
1048 assert!(!fold(&mut func), "a second run found something to do");
1049 }
1050
1051 fn out_inst(func: &Func, value: Value) -> rucc_ir::Inst {
1053 match func[value].def {
1054 rucc_ir::Def::Result { inst, .. } => inst,
1055 rucc_ir::Def::Param { .. } => panic!("a parameter has no instruction"),
1056 }
1057 }
1058}