1use rucc_ir::{Block, Def, Extra, Flags, Func, Imm, Inst, IntPred, Opcode, Type, Value};
58
59use crate::{Analyses, Analysis, Fuel, Pass, Preserved, Stats};
60
61const FOLDED: &str = "integer instruction folded to a constant";
63
64const NO_FUEL: &str = "integer instruction not folded, the pass ran out of fuel";
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub struct Fold;
74
75impl Pass for Fold {
76 fn name(&self) -> &'static str {
77 "fold"
78 }
79
80 fn describe(&self) -> &'static str {
81 "an integer instruction whose operands are all constants becomes a constant"
82 }
83
84 fn preserves(&self) -> Preserved {
85 Preserved::ALL.without(Analysis::Liveness)
91 }
92
93 fn run(&self, func: &mut Func, _an: &mut Analyses, fuel: &mut Fuel) -> Stats {
94 let blocks: Vec<Block> = func.blocks().collect();
95 let mut stats = Stats::new();
96 for block in blocks {
97 let insts: Vec<Inst> = func.insts(block).collect();
98 for inst in insts {
99 let Some(folded) = evaluate(func, inst) else { continue };
100 if !fuel.take() {
101 stats.missed(NO_FUEL);
106 continue;
107 }
108 let ty = func[result_of(func, inst)].ty;
109 let at = func.add_imm(folded);
110 let data = &mut func[inst];
111 data.opcode = Opcode::IConst;
112 data.flags = Flags::NONE;
113 data.args = rucc_ir::ValueList::EMPTY;
114 data.extra = Extra::Imm(at);
115 debug_assert!(ty.is_int(), "only an integer instruction folds");
116 stats.optimized(FOLDED);
117 }
118 }
119 stats
120 }
121}
122
123fn result_of(func: &Func, inst: Inst) -> Value {
125 func[inst].results().next().expect("an instruction that folds produces a value")
126}
127
128fn evaluate(func: &Func, inst: Inst) -> Option<Imm> {
133 let data = &func[inst];
134 if data.results != 1 {
135 return None;
136 }
137 let result = data.results().next()?;
138 let ty = func[result].ty;
139 if !ty.is_int() || !ty.is_scalar() {
142 return None;
143 }
144 let args = &func[data.args];
145 match data.opcode {
146 Opcode::Trunc | Opcode::SExt | Opcode::ZExt => {
147 let (value, from) = constant(func, *args.first()?)?;
148 Some(convert(data.opcode, value, from, ty))
149 }
150 Opcode::Shl | Opcode::LShr | Opcode::AShr => {
151 let (value, from) = constant(func, *args.first()?)?;
152 let (count, count_ty) = constant(func, *args.get(1)?)?;
153 shift(data.opcode, value, from, count, count_ty, ty, data.flags)
154 }
155 Opcode::Add | Opcode::Sub | Opcode::Mul | Opcode::And | Opcode::Or | Opcode::Xor => {
156 let (lhs, lhs_ty) = constant(func, *args.first()?)?;
157 let (rhs, _) = constant(func, *args.get(1)?)?;
158 binary(data.opcode, lhs, rhs, lhs_ty, ty, data.flags)
159 }
160 Opcode::Ctlz | Opcode::Cttz | Opcode::Ctpop | Opcode::Bswap | Opcode::Bitreverse => {
161 let (value, from) = constant(func, *args.first()?)?;
162 count(data.opcode, value, from, ty)
163 }
164 Opcode::ICmp => {
165 let Extra::IntPred(pred) = data.extra else { return None };
166 let (lhs, from) = constant(func, *args.first()?)?;
167 let (rhs, _) = constant(func, *args.get(1)?)?;
168 Some(Imm::int(i128::from(compare(pred, lhs, rhs, from)), ty))
169 }
170 _ => None,
171 }
172}
173
174pub(crate) fn constant(func: &Func, value: Value) -> Option<(Imm, Type)> {
179 let Def::Result { inst, .. } = func[value].def else { return None };
180 if func[inst].opcode != Opcode::IConst {
181 return None;
182 }
183 let Extra::Imm(at) = func[inst].extra else { return None };
184 let ty = func[value].ty;
185 ty.is_int().then(|| (func[at], ty))
186}
187
188fn convert(opcode: Opcode, value: Imm, from: Type, to: Type) -> Imm {
190 match opcode {
191 Opcode::Trunc | Opcode::SExt => Imm::int(value.signed(from), to),
194 _ => Imm::int(value.unsigned() as i128, to),
197 }
198}
199
200fn shift(
206 opcode: Opcode,
207 value: Imm,
208 from: Type,
209 count: Imm,
210 count_ty: Type,
211 to: Type,
212 flags: Flags,
213) -> Option<Imm> {
214 let by = count.unsigned();
215 if by >= u128::from(to.bits()) || count.signed(count_ty) < 0 {
216 return None;
217 }
218 let by = by as u32;
219 let exact = match opcode {
220 Opcode::Shl => value.signed(from).checked_shl(by)?,
221 Opcode::LShr => (value.unsigned() >> by) as i128,
225 _ => value.signed(from) >> by,
226 };
227 if opcode == Opcode::Shl && overflowed(exact, to, flags) {
228 return None;
229 }
230 Some(Imm::int(exact, to))
231}
232
233fn binary(opcode: Opcode, lhs: Imm, rhs: Imm, from: Type, to: Type, flags: Flags) -> Option<Imm> {
235 let (a, b) = (lhs.signed(from), rhs.signed(from));
236 let exact = match opcode {
237 Opcode::And => a & b,
240 Opcode::Or => a | b,
241 Opcode::Xor => a ^ b,
242 Opcode::Add => a.checked_add(b)?,
246 Opcode::Sub => a.checked_sub(b)?,
247 _ => a.checked_mul(b)?,
248 };
249 if overflowed(exact, to, flags) {
250 return None;
251 }
252 Some(Imm::int(exact, to))
253}
254
255pub(crate) fn compare(pred: IntPred, lhs: Imm, rhs: Imm, ty: Type) -> bool {
267 match pred {
268 IntPred::Eq => lhs == rhs,
269 IntPred::Ne => lhs != rhs,
270 IntPred::Slt => lhs.signed(ty) < rhs.signed(ty),
271 IntPred::Sle => lhs.signed(ty) <= rhs.signed(ty),
272 IntPred::Sgt => lhs.signed(ty) > rhs.signed(ty),
273 IntPred::Sge => lhs.signed(ty) >= rhs.signed(ty),
274 IntPred::Ult => lhs.unsigned() < rhs.unsigned(),
275 IntPred::Ule => lhs.unsigned() <= rhs.unsigned(),
276 IntPred::Ugt => lhs.unsigned() > rhs.unsigned(),
277 IntPred::Uge => lhs.unsigned() >= rhs.unsigned(),
278 }
279}
280
281fn count(opcode: Opcode, value: Imm, from: Type, to: Type) -> Option<Imm> {
298 let width = from.bits();
299 if width == 0 || width > 128 {
300 return None;
301 }
302 let spare = 128 - width;
306 let bits = value.unsigned();
307 let answer = match opcode {
308 Opcode::Ctpop => i128::from(bits.count_ones()),
309 Opcode::Ctlz => i128::from(bits.leading_zeros() - spare),
312 Opcode::Cttz => i128::from(bits.trailing_zeros().min(width)),
315 Opcode::Bswap if width % 8 == 0 => (bits.swap_bytes() >> spare) as i128,
316 Opcode::Bitreverse => (bits.reverse_bits() >> spare) as i128,
317 _ => return None,
318 };
319 Some(Imm::int(answer, to))
320}
321
322fn overflowed(exact: i128, to: Type, flags: Flags) -> bool {
327 let stored = Imm::int(exact, to);
328 if flags.contains(Flags::NSW) && stored.signed(to) != exact {
329 return true;
330 }
331 flags.contains(Flags::NUW) && (exact < 0 || stored.unsigned() != exact as u128)
332}
333
334#[cfg(test)]
335mod tests {
336 use rucc_base::Interner;
337 use rucc_ir::{
338 Block, Builder, Extra, Flags, Func, IntPred, Module, Opcode, Signature, Type, Value,
339 };
340 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
341
342 use crate::stats::Kind;
343 use crate::{Fuel, Pass, fold::Fold};
344
345 fn blank() -> (Interner, Func, Block) {
347 let mut names = Interner::new();
348 let name = names.intern("f");
349 let mut func = Func::new(name, Signature::new().with_returns(&[Type::int(64)]));
350 let block = func.create_block();
351 (names, func, block)
352 }
353
354 fn fold(func: &mut Func) -> bool {
357 Fold.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited()).changed()
358 }
359
360 fn value_of(func: &Func, value: Value, ty: Type) -> Option<i128> {
362 let rucc_ir::Def::Result { inst, .. } = func[value].def else { return None };
363 if func[inst].opcode != Opcode::IConst {
364 return None;
365 }
366 let Extra::Imm(at) = func[inst].extra else { return None };
367 Some(func[at].signed(ty))
368 }
369
370 #[test]
371 fn a_widened_constant_becomes_a_constant_of_the_wider_type() {
372 let (_, mut func, block) = blank();
373 let mut build = Builder::new(&mut func, block);
374 let narrow = build.iconst(Type::int(32), 7);
375 let wide = build.unary(Opcode::SExt, narrow, Type::int(64));
376 build.ret(&[wide]);
377 assert!(fold(&mut func));
378 assert_eq!(value_of(&func, wide, Type::int(64)), Some(7));
379 }
380
381 #[test]
382 fn sign_extension_copies_the_sign_and_zero_extension_does_not() {
383 for (opcode, expected) in [(Opcode::SExt, -1_i128), (Opcode::ZExt, 0xffff_ffff)] {
384 let (_, mut func, block) = blank();
385 let mut build = Builder::new(&mut func, block);
386 let narrow = build.iconst(Type::int(32), -1);
387 let wide = build.unary(opcode, narrow, Type::int(64));
388 build.ret(&[wide]);
389 assert!(fold(&mut func));
390 assert_eq!(value_of(&func, wide, Type::int(64)), Some(expected), "{opcode:?}");
391 }
392 }
393
394 #[test]
395 fn truncation_keeps_the_low_bits_and_reads_them_at_the_narrow_width() {
396 let (_, mut func, block) = blank();
397 let mut build = Builder::new(&mut func, block);
398 let wide = build.iconst(Type::int(32), 0x1234_5680);
399 let narrow = build.unary(Opcode::Trunc, wide, Type::int(8));
400 build.ret(&[narrow]);
401 assert!(fold(&mut func));
402 assert_eq!(value_of(&func, narrow, Type::int(8)), Some(-128));
403 }
404
405 #[test]
406 fn the_arithmetic_and_the_bitwise_operations_are_evaluated() {
407 let cases = [
408 (Opcode::Add, 6_i128, 7_i128, 13_i128),
409 (Opcode::Sub, 6, 7, -1),
410 (Opcode::Mul, 6, 7, 42),
411 (Opcode::And, 0b1100, 0b1010, 0b1000),
412 (Opcode::Or, 0b1100, 0b1010, 0b1110),
413 (Opcode::Xor, 0b1100, 0b1010, 0b0110),
414 ];
415 for (opcode, a, b, want) in cases {
416 let (_, mut func, block) = blank();
417 let mut build = Builder::new(&mut func, block);
418 let lhs = build.iconst(Type::int(64), a);
419 let rhs = build.iconst(Type::int(64), b);
420 let out = build.binary(opcode, lhs, rhs, Flags::NONE);
421 build.ret(&[out]);
422 assert!(fold(&mut func), "{opcode:?}");
423 assert_eq!(value_of(&func, out, Type::int(64)), Some(want), "{opcode:?}");
424 }
425 }
426
427 #[test]
428 fn the_three_shifts_are_evaluated_and_the_two_right_ones_differ_on_the_sign() {
429 let cases = [(Opcode::Shl, -8_i128, 1_i128, -16_i128), (Opcode::AShr, -8, 1, -4)];
430 for (opcode, a, b, want) in cases {
431 let (_, mut func, block) = blank();
432 let mut build = Builder::new(&mut func, block);
433 let lhs = build.iconst(Type::int(64), a);
434 let rhs = build.iconst(Type::int(64), b);
435 let out = build.binary(opcode, lhs, rhs, Flags::NONE);
436 build.ret(&[out]);
437 assert!(fold(&mut func), "{opcode:?}");
438 assert_eq!(value_of(&func, out, Type::int(64)), Some(want), "{opcode:?}");
439 }
440 let (_, mut func, block) = blank();
443 let mut build = Builder::new(&mut func, block);
444 let lhs = build.iconst(Type::int(64), -8);
445 let rhs = build.iconst(Type::int(64), 1);
446 let out = build.binary(Opcode::LShr, lhs, rhs, Flags::NONE);
447 build.ret(&[out]);
448 assert!(fold(&mut func));
449 assert_eq!(value_of(&func, out, Type::int(64)), Some(i128::from(i64::MAX) - 3));
450 }
451
452 fn one(opcode: Opcode, ty: Type, arg: i128) -> Option<i128> {
454 let (_, mut func, block) = blank();
455 let mut build = Builder::new(&mut func, block);
456 let value = build.iconst(ty, arg);
457 let out = build.unary(opcode, value, ty);
458 build.ret(&[out]);
459 fold(&mut func);
460 value_of(&func, out, ty)
461 }
462
463 #[test]
464 fn the_bit_counts_are_evaluated_at_the_width_they_were_asked_at() {
465 let cases = [
466 (Opcode::Ctlz, 64, 0x0000_1000_0000_0000_i128, 19_i128),
467 (Opcode::Ctlz, 32, 0x0000_1000, 19),
468 (Opcode::Cttz, 64, 0x0000_1000_0000_0000, 44),
469 (Opcode::Cttz, 32, 0x0000_1000, 12),
470 (Opcode::Ctpop, 64, 0x0000_1000_0000_0000, 1),
471 (Opcode::Ctpop, 32, -1, 32),
472 (Opcode::Ctpop, 64, -1, 64),
473 ];
474 for (opcode, width, arg, want) in cases {
475 let ty = Type::int(width);
476 assert_eq!(one(opcode, ty, arg), Some(want), "{opcode:?} at {width} of {arg:#x}");
477 }
478 }
479
480 #[test]
481 fn a_search_for_a_bit_in_a_zero_answers_the_width_the_expansion_answers() {
482 for width in [8_u32, 16, 32, 64] {
483 let ty = Type::int(width);
484 let want = Some(i128::from(width));
485 assert_eq!(one(Opcode::Ctlz, ty, 0), want, "leading, at {width}");
486 assert_eq!(one(Opcode::Cttz, ty, 0), want, "trailing, at {width}");
487 assert_eq!(one(Opcode::Ctpop, ty, 0), Some(0), "count, at {width}");
488 }
489 }
490
491 #[test]
492 fn the_two_reversals_are_evaluated_and_a_byte_swap_of_a_part_of_a_byte_is_not() {
493 let ty = Type::int(32);
494 assert_eq!(one(Opcode::Bswap, ty, 0x1234_5678), Some(0x7856_3412));
495 assert_eq!(one(Opcode::Bswap, Type::int(16), 0x1234), Some(0x3412));
496 assert_eq!(one(Opcode::Bitreverse, Type::int(8), 0b1010_1100), Some(0b0011_0101));
497 let (_, mut func, block) = blank();
500 let mut build = Builder::new(&mut func, block);
501 let value = build.iconst(Type::int(4), 0b1010);
502 let out = build.unary(Opcode::Bswap, value, Type::int(4));
503 build.ret(&[out]);
504 assert!(!fold(&mut func));
505 }
506
507 #[test]
508 fn a_comparison_of_two_constants_becomes_a_one_or_a_nought() {
509 let cases = [
510 (IntPred::Eq, 7_i128, 7_i128, true),
511 (IntPred::Eq, 7, 8, false),
512 (IntPred::Ne, 7, 8, true),
513 (IntPred::Slt, -1, 1, true),
514 (IntPred::Sle, -1, -1, true),
515 (IntPred::Sgt, -1, 1, false),
516 (IntPred::Sge, 1, -1, true),
517 (IntPred::Ult, -1, 1, false),
520 (IntPred::Ule, -1, 1, false),
521 (IntPred::Ugt, -1, 1, true),
522 (IntPred::Uge, -1, 1, true),
523 ];
524 for (pred, a, b, want) in cases {
525 let (_, mut func, block) = blank();
526 let mut build = Builder::new(&mut func, block);
527 let lhs = build.iconst(Type::int(64), a);
528 let rhs = build.iconst(Type::int(64), b);
529 let out = build.icmp(pred, lhs, rhs);
530 build.ret(&[out]);
531 assert!(fold(&mut func), "{pred:?} {a} {b}");
532 let got = value_of(&func, out, Type::I1).expect("the comparison folded");
535 assert_eq!(got != 0, want, "{pred:?} {a} {b}");
536 }
537 }
538
539 #[test]
540 fn a_comparison_at_a_narrow_width_is_read_at_that_width() {
541 let ty = Type::int(8);
544 for (pred, want) in [(IntPred::Slt, true), (IntPred::Ult, false)] {
545 let (_, mut func, block) = blank();
546 let mut build = Builder::new(&mut func, block);
547 let lhs = build.iconst(ty, 255);
548 let rhs = build.iconst(ty, 1);
549 let out = build.icmp(pred, lhs, rhs);
550 build.ret(&[out]);
551 assert!(fold(&mut func), "{pred:?}");
552 let got = value_of(&func, out, Type::I1).expect("the comparison folded");
553 assert_eq!(got != 0, want, "{pred:?}");
554 }
555 }
556
557 #[test]
558 fn a_comparison_with_one_constant_operand_is_left_alone() {
559 let (_, mut func, block) = blank();
560 let ty = Type::int(64);
561 let param = func.append_param(block, ty);
562 let mut build = Builder::new(&mut func, block);
563 let rhs = build.iconst(ty, 3);
564 let out = build.icmp(IntPred::Eq, param, rhs);
565 build.ret(&[out]);
566 assert!(!fold(&mut func));
567 }
568
569 #[test]
570 fn a_bit_count_of_something_that_is_not_a_constant_is_left_alone() {
571 for opcode in [Opcode::Ctlz, Opcode::Cttz, Opcode::Ctpop, Opcode::Bswap] {
572 let (_, mut func, block) = blank();
573 let ty = Type::int(64);
574 let param = func.append_param(block, ty);
575 let mut build = Builder::new(&mut func, block);
576 let out = build.unary(opcode, param, ty);
577 build.ret(&[out]);
578 assert!(!fold(&mut func), "{opcode:?}");
579 }
580 }
581
582 #[test]
583 fn a_shift_by_the_width_or_more_is_left_alone_because_the_language_does_not_define_it() {
584 for count in [64_i128, 65, -1] {
585 let (_, mut func, block) = blank();
586 let mut build = Builder::new(&mut func, block);
587 let lhs = build.iconst(Type::int(64), 1);
588 let rhs = build.iconst(Type::int(64), count);
589 let out = build.binary(Opcode::Shl, lhs, rhs, Flags::NONE);
590 build.ret(&[out]);
591 assert!(!fold(&mut func), "a shift by {count} was folded");
592 }
593 }
594
595 #[test]
596 fn an_operation_that_wraps_folds_and_the_same_one_promising_it_will_not_does_not() {
597 let big = i128::from(i32::MAX);
598 for (flags, folds) in [(Flags::NONE, true), (Flags::NSW, false)] {
599 let (_, mut func, block) = blank();
600 let mut build = Builder::new(&mut func, block);
601 let lhs = build.iconst(Type::int(32), big);
602 let rhs = build.iconst(Type::int(32), 1);
603 let out = build.binary(Opcode::Add, lhs, rhs, flags);
604 build.ret(&[out]);
605 assert_eq!(fold(&mut func), folds, "{flags}");
606 if folds {
607 assert_eq!(value_of(&func, out, Type::int(32)), Some(i128::from(i32::MIN)));
608 }
609 }
610 }
611
612 #[test]
613 fn an_unsigned_promise_is_broken_by_a_negative_result_as_well_as_by_a_large_one() {
614 let (_, mut func, block) = blank();
615 let mut build = Builder::new(&mut func, block);
616 let lhs = build.iconst(Type::int(32), 1);
617 let rhs = build.iconst(Type::int(32), 2);
618 let out = build.binary(Opcode::Sub, lhs, rhs, Flags::NUW);
619 build.ret(&[out]);
620 assert!(!fold(&mut func));
621 }
622
623 #[test]
624 fn an_operation_with_one_constant_operand_is_left_alone() {
625 let (_, mut func, block) = blank();
626 let param = func.append_param(block, Type::int(64));
627 let mut build = Builder::new(&mut func, block);
628 let rhs = build.iconst(Type::int(64), 7);
629 let out = build.binary(Opcode::Add, param, rhs, Flags::NONE);
630 build.ret(&[out]);
631 assert!(!fold(&mut func));
632 assert_eq!(func[out_inst(&func, out)].opcode, Opcode::Add);
633 }
634
635 #[test]
636 fn a_divide_is_not_folded_even_when_both_operands_are_constants() {
637 for opcode in [Opcode::SDiv, Opcode::UDiv, Opcode::SRem, Opcode::URem] {
638 let (_, mut func, block) = blank();
639 let mut build = Builder::new(&mut func, block);
640 let lhs = build.iconst(Type::int(64), 42);
641 let rhs = build.iconst(Type::int(64), 7);
642 let out = build.binary(opcode, lhs, rhs, Flags::NONE);
643 build.ret(&[out]);
644 assert!(!fold(&mut func), "{opcode:?}");
645 }
646 }
647
648 #[test]
649 fn folding_leaves_the_function_something_the_verifier_accepts() {
650 let mut names = Interner::new();
651 let name = names.intern("f");
652 let mut func = Func::new(name, Signature::new().with_returns(&[Type::int(64)]));
653 let block = func.create_block();
654 let mut build = Builder::new(&mut func, block);
655 let narrow = build.iconst(Type::int(32), 7);
656 let wide = build.unary(Opcode::SExt, narrow, Type::int(64));
657 build.ret(&[wide]);
658 assert!(fold(&mut func));
659 let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
660 let module_name = names.intern("m");
661 let mut module = Module::new(module_name, &target);
662 module.add_func(func);
663 rucc_ir::verify(&module, &names).expect("folding does not break the IR");
664 }
665
666 #[test]
667 fn fuel_stops_the_transformation_and_not_the_walk() {
668 let build_two = |func: &mut Func, block: Block| {
669 let mut build = Builder::new(func, block);
670 let a = build.iconst(Type::int(32), 7);
671 let wide_a = build.unary(Opcode::SExt, a, Type::int(64));
672 let b = build.iconst(Type::int(32), 9);
673 let wide_b = build.unary(Opcode::SExt, b, Type::int(64));
674 let sum = build.binary(Opcode::Add, wide_a, wide_b, Flags::NONE);
675 build.ret(&[sum]);
676 (wide_a, wide_b)
677 };
678
679 let (_, mut none, block) = blank();
680 let (first, _) = build_two(&mut none, block);
681 let stats =
682 Fold.run(&mut none, &mut crate::machine::fixtures::analyses(), &mut Fuel::of(0));
683 assert!(!stats.changed());
684 assert_eq!(none[out_inst(&none, first)].opcode, Opcode::SExt);
685 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 2);
688
689 let (_, mut one, block) = blank();
690 let (first, second) = build_two(&mut one, block);
691 let mut fuel = Fuel::of(1);
692 let stats = Fold.run(&mut one, &mut crate::machine::fixtures::analyses(), &mut fuel);
693 assert!(stats.changed());
694 assert_eq!(fuel.spent(), 1);
695 assert_eq!(stats.count(Kind::Optimized, super::FOLDED), 1);
696 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
697 assert_eq!(one[out_inst(&one, first)].opcode, Opcode::IConst);
698 assert_eq!(one[out_inst(&one, second)].opcode, Opcode::SExt);
699 }
700
701 #[test]
702 fn folding_one_operation_uncovers_the_next() {
703 let (_, mut func, block) = blank();
704 let mut build = Builder::new(&mut func, block);
705 let a = build.iconst(Type::int(32), 7);
706 let wide = build.unary(Opcode::SExt, a, Type::int(64));
707 let b = build.iconst(Type::int(64), 9);
708 let sum = build.binary(Opcode::Add, wide, b, Flags::NONE);
709 build.ret(&[sum]);
710 assert!(fold(&mut func));
711 assert_eq!(value_of(&func, sum, Type::int(64)), Some(16));
714 }
715
716 #[test]
717 fn a_constant_is_left_where_it_is_and_folding_it_again_changes_nothing() {
718 let (_, mut func, block) = blank();
719 let mut build = Builder::new(&mut func, block);
720 let a = build.iconst(Type::int(32), 7);
721 let wide = build.unary(Opcode::SExt, a, Type::int(64));
722 build.ret(&[wide]);
723 assert!(fold(&mut func));
724 assert!(!fold(&mut func), "a second run found something to do");
725 }
726
727 fn out_inst(func: &Func, value: Value) -> rucc_ir::Inst {
729 match func[value].def {
730 rucc_ir::Def::Result { inst, .. } => inst,
731 rucc_ir::Def::Param { .. } => panic!("a parameter has no instruction"),
732 }
733 }
734}