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