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