1use rucc_ir::{Block, Def, Extra, Flags, Func, Imm, Inst, Opcode, Type, Value};
46
47use crate::{Analyses, Fuel, Pass, Preserved, Stats};
48
49const FOLDED: &str = "integer instruction folded to a constant";
51
52const NO_FUEL: &str = "integer instruction not folded, the pass ran out of fuel";
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub struct Fold;
62
63impl Pass for Fold {
64 fn name(&self) -> &'static str {
65 "fold"
66 }
67
68 fn describe(&self) -> &'static str {
69 "an integer instruction whose operands are all constants becomes a constant"
70 }
71
72 fn preserves(&self) -> Preserved {
73 Preserved::ALL
77 }
78
79 fn run(&self, func: &mut Func, _an: &mut Analyses, fuel: &mut Fuel) -> Stats {
80 let blocks: Vec<Block> = func.blocks().collect();
81 let mut stats = Stats::new();
82 for block in blocks {
83 let insts: Vec<Inst> = func.insts(block).collect();
84 for inst in insts {
85 let Some(folded) = evaluate(func, inst) else { continue };
86 if !fuel.take() {
87 stats.missed(NO_FUEL);
92 continue;
93 }
94 let ty = func[result_of(func, inst)].ty;
95 let at = func.add_imm(folded);
96 let data = &mut func[inst];
97 data.opcode = Opcode::IConst;
98 data.flags = Flags::NONE;
99 data.args = rucc_ir::ValueList::EMPTY;
100 data.extra = Extra::Imm(at);
101 debug_assert!(ty.is_int(), "only an integer instruction folds");
102 stats.optimized(FOLDED);
103 }
104 }
105 stats
106 }
107}
108
109fn result_of(func: &Func, inst: Inst) -> Value {
111 func[inst].results().next().expect("an instruction that folds produces a value")
112}
113
114fn evaluate(func: &Func, inst: Inst) -> Option<Imm> {
119 let data = &func[inst];
120 if data.results != 1 {
121 return None;
122 }
123 let result = data.results().next()?;
124 let ty = func[result].ty;
125 if !ty.is_int() || !ty.is_scalar() {
128 return None;
129 }
130 let args = &func[data.args];
131 match data.opcode {
132 Opcode::Trunc | Opcode::SExt | Opcode::ZExt => {
133 let (value, from) = constant(func, *args.first()?)?;
134 Some(convert(data.opcode, value, from, ty))
135 }
136 Opcode::Shl | Opcode::LShr | Opcode::AShr => {
137 let (value, from) = constant(func, *args.first()?)?;
138 let (count, count_ty) = constant(func, *args.get(1)?)?;
139 shift(data.opcode, value, from, count, count_ty, ty, data.flags)
140 }
141 Opcode::Add | Opcode::Sub | Opcode::Mul | Opcode::And | Opcode::Or | Opcode::Xor => {
142 let (lhs, lhs_ty) = constant(func, *args.first()?)?;
143 let (rhs, _) = constant(func, *args.get(1)?)?;
144 binary(data.opcode, lhs, rhs, lhs_ty, ty, data.flags)
145 }
146 _ => None,
147 }
148}
149
150pub(crate) fn constant(func: &Func, value: Value) -> Option<(Imm, Type)> {
155 let Def::Result { inst, .. } = func[value].def else { return None };
156 if func[inst].opcode != Opcode::IConst {
157 return None;
158 }
159 let Extra::Imm(at) = func[inst].extra else { return None };
160 let ty = func[value].ty;
161 ty.is_int().then(|| (func[at], ty))
162}
163
164fn convert(opcode: Opcode, value: Imm, from: Type, to: Type) -> Imm {
166 match opcode {
167 Opcode::Trunc | Opcode::SExt => Imm::int(value.signed(from), to),
170 _ => Imm::int(value.unsigned() as i128, to),
173 }
174}
175
176fn shift(
182 opcode: Opcode,
183 value: Imm,
184 from: Type,
185 count: Imm,
186 count_ty: Type,
187 to: Type,
188 flags: Flags,
189) -> Option<Imm> {
190 let by = count.unsigned();
191 if by >= u128::from(to.bits()) || count.signed(count_ty) < 0 {
192 return None;
193 }
194 let by = by as u32;
195 let exact = match opcode {
196 Opcode::Shl => value.signed(from).checked_shl(by)?,
197 Opcode::LShr => (value.unsigned() >> by) as i128,
201 _ => value.signed(from) >> by,
202 };
203 if opcode == Opcode::Shl && overflowed(exact, to, flags) {
204 return None;
205 }
206 Some(Imm::int(exact, to))
207}
208
209fn binary(opcode: Opcode, lhs: Imm, rhs: Imm, from: Type, to: Type, flags: Flags) -> Option<Imm> {
211 let (a, b) = (lhs.signed(from), rhs.signed(from));
212 let exact = match opcode {
213 Opcode::And => a & b,
216 Opcode::Or => a | b,
217 Opcode::Xor => a ^ b,
218 Opcode::Add => a.checked_add(b)?,
222 Opcode::Sub => a.checked_sub(b)?,
223 _ => a.checked_mul(b)?,
224 };
225 if overflowed(exact, to, flags) {
226 return None;
227 }
228 Some(Imm::int(exact, to))
229}
230
231fn overflowed(exact: i128, to: Type, flags: Flags) -> bool {
236 let stored = Imm::int(exact, to);
237 if flags.contains(Flags::NSW) && stored.signed(to) != exact {
238 return true;
239 }
240 flags.contains(Flags::NUW) && (exact < 0 || stored.unsigned() != exact as u128)
241}
242
243#[cfg(test)]
244mod tests {
245 use rucc_base::Interner;
246 use rucc_ir::{Block, Builder, Extra, Flags, Func, Module, Opcode, Signature, Type, Value};
247 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
248
249 use crate::stats::Kind;
250 use crate::{Analyses, Fuel, Pass, fold::Fold};
251
252 fn blank() -> (Interner, Func, Block) {
254 let mut names = Interner::new();
255 let name = names.intern("f");
256 let mut func = Func::new(name, Signature::new().with_returns(&[Type::int(64)]));
257 let block = func.create_block();
258 (names, func, block)
259 }
260
261 fn fold(func: &mut Func) -> bool {
264 Fold.run(func, &mut Analyses::new(), &mut Fuel::unlimited()).changed()
265 }
266
267 fn value_of(func: &Func, value: Value, ty: Type) -> Option<i128> {
269 let rucc_ir::Def::Result { inst, .. } = func[value].def else { return None };
270 if func[inst].opcode != Opcode::IConst {
271 return None;
272 }
273 let Extra::Imm(at) = func[inst].extra else { return None };
274 Some(func[at].signed(ty))
275 }
276
277 #[test]
278 fn a_widened_constant_becomes_a_constant_of_the_wider_type() {
279 let (_, mut func, block) = blank();
280 let mut build = Builder::new(&mut func, block);
281 let narrow = build.iconst(Type::int(32), 7);
282 let wide = build.unary(Opcode::SExt, narrow, Type::int(64));
283 build.ret(&[wide]);
284 assert!(fold(&mut func));
285 assert_eq!(value_of(&func, wide, Type::int(64)), Some(7));
286 }
287
288 #[test]
289 fn sign_extension_copies_the_sign_and_zero_extension_does_not() {
290 for (opcode, expected) in [(Opcode::SExt, -1_i128), (Opcode::ZExt, 0xffff_ffff)] {
291 let (_, mut func, block) = blank();
292 let mut build = Builder::new(&mut func, block);
293 let narrow = build.iconst(Type::int(32), -1);
294 let wide = build.unary(opcode, narrow, Type::int(64));
295 build.ret(&[wide]);
296 assert!(fold(&mut func));
297 assert_eq!(value_of(&func, wide, Type::int(64)), Some(expected), "{opcode:?}");
298 }
299 }
300
301 #[test]
302 fn truncation_keeps_the_low_bits_and_reads_them_at_the_narrow_width() {
303 let (_, mut func, block) = blank();
304 let mut build = Builder::new(&mut func, block);
305 let wide = build.iconst(Type::int(32), 0x1234_5680);
306 let narrow = build.unary(Opcode::Trunc, wide, Type::int(8));
307 build.ret(&[narrow]);
308 assert!(fold(&mut func));
309 assert_eq!(value_of(&func, narrow, Type::int(8)), Some(-128));
310 }
311
312 #[test]
313 fn the_arithmetic_and_the_bitwise_operations_are_evaluated() {
314 let cases = [
315 (Opcode::Add, 6_i128, 7_i128, 13_i128),
316 (Opcode::Sub, 6, 7, -1),
317 (Opcode::Mul, 6, 7, 42),
318 (Opcode::And, 0b1100, 0b1010, 0b1000),
319 (Opcode::Or, 0b1100, 0b1010, 0b1110),
320 (Opcode::Xor, 0b1100, 0b1010, 0b0110),
321 ];
322 for (opcode, a, b, want) in cases {
323 let (_, mut func, block) = blank();
324 let mut build = Builder::new(&mut func, block);
325 let lhs = build.iconst(Type::int(64), a);
326 let rhs = build.iconst(Type::int(64), b);
327 let out = build.binary(opcode, lhs, rhs, Flags::NONE);
328 build.ret(&[out]);
329 assert!(fold(&mut func), "{opcode:?}");
330 assert_eq!(value_of(&func, out, Type::int(64)), Some(want), "{opcode:?}");
331 }
332 }
333
334 #[test]
335 fn the_three_shifts_are_evaluated_and_the_two_right_ones_differ_on_the_sign() {
336 let cases = [(Opcode::Shl, -8_i128, 1_i128, -16_i128), (Opcode::AShr, -8, 1, -4)];
337 for (opcode, a, b, want) in cases {
338 let (_, mut func, block) = blank();
339 let mut build = Builder::new(&mut func, block);
340 let lhs = build.iconst(Type::int(64), a);
341 let rhs = build.iconst(Type::int(64), b);
342 let out = build.binary(opcode, lhs, rhs, Flags::NONE);
343 build.ret(&[out]);
344 assert!(fold(&mut func), "{opcode:?}");
345 assert_eq!(value_of(&func, out, Type::int(64)), Some(want), "{opcode:?}");
346 }
347 let (_, mut func, block) = blank();
350 let mut build = Builder::new(&mut func, block);
351 let lhs = build.iconst(Type::int(64), -8);
352 let rhs = build.iconst(Type::int(64), 1);
353 let out = build.binary(Opcode::LShr, lhs, rhs, Flags::NONE);
354 build.ret(&[out]);
355 assert!(fold(&mut func));
356 assert_eq!(value_of(&func, out, Type::int(64)), Some(i128::from(i64::MAX) - 3));
357 }
358
359 #[test]
360 fn a_shift_by_the_width_or_more_is_left_alone_because_the_language_does_not_define_it() {
361 for count in [64_i128, 65, -1] {
362 let (_, mut func, block) = blank();
363 let mut build = Builder::new(&mut func, block);
364 let lhs = build.iconst(Type::int(64), 1);
365 let rhs = build.iconst(Type::int(64), count);
366 let out = build.binary(Opcode::Shl, lhs, rhs, Flags::NONE);
367 build.ret(&[out]);
368 assert!(!fold(&mut func), "a shift by {count} was folded");
369 }
370 }
371
372 #[test]
373 fn an_operation_that_wraps_folds_and_the_same_one_promising_it_will_not_does_not() {
374 let big = i128::from(i32::MAX);
375 for (flags, folds) in [(Flags::NONE, true), (Flags::NSW, false)] {
376 let (_, mut func, block) = blank();
377 let mut build = Builder::new(&mut func, block);
378 let lhs = build.iconst(Type::int(32), big);
379 let rhs = build.iconst(Type::int(32), 1);
380 let out = build.binary(Opcode::Add, lhs, rhs, flags);
381 build.ret(&[out]);
382 assert_eq!(fold(&mut func), folds, "{flags}");
383 if folds {
384 assert_eq!(value_of(&func, out, Type::int(32)), Some(i128::from(i32::MIN)));
385 }
386 }
387 }
388
389 #[test]
390 fn an_unsigned_promise_is_broken_by_a_negative_result_as_well_as_by_a_large_one() {
391 let (_, mut func, block) = blank();
392 let mut build = Builder::new(&mut func, block);
393 let lhs = build.iconst(Type::int(32), 1);
394 let rhs = build.iconst(Type::int(32), 2);
395 let out = build.binary(Opcode::Sub, lhs, rhs, Flags::NUW);
396 build.ret(&[out]);
397 assert!(!fold(&mut func));
398 }
399
400 #[test]
401 fn an_operation_with_one_constant_operand_is_left_alone() {
402 let (_, mut func, block) = blank();
403 let param = func.append_param(block, Type::int(64));
404 let mut build = Builder::new(&mut func, block);
405 let rhs = build.iconst(Type::int(64), 7);
406 let out = build.binary(Opcode::Add, param, rhs, Flags::NONE);
407 build.ret(&[out]);
408 assert!(!fold(&mut func));
409 assert_eq!(func[out_inst(&func, out)].opcode, Opcode::Add);
410 }
411
412 #[test]
413 fn a_divide_is_not_folded_even_when_both_operands_are_constants() {
414 for opcode in [Opcode::SDiv, Opcode::UDiv, Opcode::SRem, Opcode::URem] {
415 let (_, mut func, block) = blank();
416 let mut build = Builder::new(&mut func, block);
417 let lhs = build.iconst(Type::int(64), 42);
418 let rhs = build.iconst(Type::int(64), 7);
419 let out = build.binary(opcode, lhs, rhs, Flags::NONE);
420 build.ret(&[out]);
421 assert!(!fold(&mut func), "{opcode:?}");
422 }
423 }
424
425 #[test]
426 fn a_comparison_is_not_folded_because_nothing_lowers_the_bit_it_would_leave_behind() {
427 let (_, mut func, block) = blank();
428 let mut build = Builder::new(&mut func, block);
429 let lhs = build.iconst(Type::int(64), 1);
430 let rhs = build.iconst(Type::int(64), 2);
431 let out = build.icmp(rucc_ir::IntPred::Slt, lhs, rhs);
432 build.ret(&[out]);
433 assert!(!fold(&mut func));
434 }
435
436 #[test]
437 fn folding_leaves_the_function_something_the_verifier_accepts() {
438 let mut names = Interner::new();
439 let name = names.intern("f");
440 let mut func = Func::new(name, Signature::new().with_returns(&[Type::int(64)]));
441 let block = func.create_block();
442 let mut build = Builder::new(&mut func, block);
443 let narrow = build.iconst(Type::int(32), 7);
444 let wide = build.unary(Opcode::SExt, narrow, Type::int(64));
445 build.ret(&[wide]);
446 assert!(fold(&mut func));
447 let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
448 let module_name = names.intern("m");
449 let mut module = Module::new(module_name, &target);
450 module.add_func(func);
451 rucc_ir::verify(&module, &names).expect("folding does not break the IR");
452 }
453
454 #[test]
455 fn fuel_stops_the_transformation_and_not_the_walk() {
456 let build_two = |func: &mut Func, block: Block| {
457 let mut build = Builder::new(func, block);
458 let a = build.iconst(Type::int(32), 7);
459 let wide_a = build.unary(Opcode::SExt, a, Type::int(64));
460 let b = build.iconst(Type::int(32), 9);
461 let wide_b = build.unary(Opcode::SExt, b, Type::int(64));
462 let sum = build.binary(Opcode::Add, wide_a, wide_b, Flags::NONE);
463 build.ret(&[sum]);
464 (wide_a, wide_b)
465 };
466
467 let (_, mut none, block) = blank();
468 let (first, _) = build_two(&mut none, block);
469 let stats = Fold.run(&mut none, &mut Analyses::new(), &mut Fuel::of(0));
470 assert!(!stats.changed());
471 assert_eq!(none[out_inst(&none, first)].opcode, Opcode::SExt);
472 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 2);
475
476 let (_, mut one, block) = blank();
477 let (first, second) = build_two(&mut one, block);
478 let mut fuel = Fuel::of(1);
479 let stats = Fold.run(&mut one, &mut Analyses::new(), &mut fuel);
480 assert!(stats.changed());
481 assert_eq!(fuel.spent(), 1);
482 assert_eq!(stats.count(Kind::Optimized, super::FOLDED), 1);
483 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
484 assert_eq!(one[out_inst(&one, first)].opcode, Opcode::IConst);
485 assert_eq!(one[out_inst(&one, second)].opcode, Opcode::SExt);
486 }
487
488 #[test]
489 fn folding_one_operation_uncovers_the_next() {
490 let (_, mut func, block) = blank();
491 let mut build = Builder::new(&mut func, block);
492 let a = build.iconst(Type::int(32), 7);
493 let wide = build.unary(Opcode::SExt, a, Type::int(64));
494 let b = build.iconst(Type::int(64), 9);
495 let sum = build.binary(Opcode::Add, wide, b, Flags::NONE);
496 build.ret(&[sum]);
497 assert!(fold(&mut func));
498 assert_eq!(value_of(&func, sum, Type::int(64)), Some(16));
501 }
502
503 #[test]
504 fn a_constant_is_left_where_it_is_and_folding_it_again_changes_nothing() {
505 let (_, mut func, block) = blank();
506 let mut build = Builder::new(&mut func, block);
507 let a = build.iconst(Type::int(32), 7);
508 let wide = build.unary(Opcode::SExt, a, Type::int(64));
509 build.ret(&[wide]);
510 assert!(fold(&mut func));
511 assert!(!fold(&mut func), "a second run found something to do");
512 }
513
514 fn out_inst(func: &Func, value: Value) -> rucc_ir::Inst {
516 match func[value].def {
517 rucc_ir::Def::Result { inst, .. } => inst,
518 rucc_ir::Def::Param { .. } => panic!("a parameter has no instruction"),
519 }
520 }
521}