Skip to main content

rucc_opt/
fold.rs

1//! Constant folding: an instruction whose operands are all constants becomes a constant.
2//!
3//! The smallest transformation there is, and the one the rest of the middle end leans on. Every
4//! later pass produces constants where the source had none, and none of them should have to
5//! evaluate the arithmetic itself.
6//!
7//! It is worth having before any of them because the lowering walk produces constant arithmetic
8//! that nothing in the C asked for. The usual arithmetic conversions widen a literal to the type
9//! of the other operand, so `long y; y + 7` lowers to a 32 bit constant, a `sext` of it and an
10//! add, and nothing downstream can see that the operand of the add is a number. On x86-64 that
11//! costs two instructions and a register on every operation between a wide integer and a
12//! literal, which is most address arithmetic and most loop bounds in real code. That is issue
13//! 378.
14//!
15//! The bit counting instructions are here for a related reason. Nothing in the backend selects an
16//! instruction for any of them yet, so each one that survives to the end becomes the twenty odd
17//! instructions of the software expansion in `rucc_codegen::expand`, inlined at the site. A
18//! `__builtin_clzll` on a value the compiler can already see is the worst version of that: the
19//! answer is a number between nought and sixty four and the code that computes it is the largest
20//! thing in the function. Folding it costs one arm here. That is part of issue 310.
21//!
22//! # How it rewrites
23//!
24//! In place. An instruction that folds keeps its result value and becomes an `iconst`, because
25//! the value it produced already has the right type and every use of it is already correct. So
26//! there is no rewriting of uses, no new value, and nothing for a later pass to have to know
27//! about. What is left behind is the old operand, now used by nothing, which costs nothing in
28//! the output because the backend materializes a constant where it is wanted rather than where
29//! the IR wrote it, and which dead code elimination will take out of the printed IR when there
30//! is one.
31//!
32//! # What it does not fold
33//!
34//! Not the divides and the remainders. Both have two cases the language leaves undefined, a zero
35//! divisor and the most negative value divided by minus one, and both want guarding rather than
36//! evaluating. They belong with the strength reduction that turns a division by a constant into
37//! a multiply, which is where somebody looking for division arithmetic will look.
38//!
39//! Not floating point. Folding it means deciding what rounding mode to fold under and what to do
40//! about a signalling NaN, and `rucc_base::float` has the arithmetic but the decision about the
41//! environment belongs with the rest of the floating point work rather than in the first pass.
42//!
43//! Not an operation that overflows under `nsw` or `nuw`. The result there is poison, so any
44//! answer would be a valid refinement, and quietly picking the wrapping one hides a program that
45//! has stepped outside the language from the sanitizer that should be reporting it.
46//!
47//! Not floating point comparisons, for the reason above and one more: an ordered predicate and an
48//! unordered one differ only on a NaN, so the answer is the whole of what makes them two
49//! predicates, and evaluating it is the floating point decision rather than a step around it.
50//!
51//! Integer comparisons are folded, and were not until issue 352 was closed. An `icmp` produces an
52//! `i1`, and while nothing lowered one that was left standing on its own, folding one would have
53//! turned working code into code that does not build. There is now a rule for a one bit constant
54//! and one for a byte holding it, so the constant this leaves behind lowers wherever the
55//! comparison did.
56
57use rucc_ir::{Block, Def, Extra, Flags, Func, Imm, Inst, IntPred, Opcode, Type, Value};
58
59use crate::{Analyses, Analysis, Fuel, Pass, Preserved, Stats};
60
61/// Recorded once for each instruction that became a constant.
62const FOLDED: &str = "integer instruction folded to a constant";
63
64/// Recorded for an instruction that would have folded if there had been fuel for it.
65///
66/// Not a missed optimization in the ordinary sense, since the fuel is a person deliberately
67/// stopping the pass. It is here because it is the number a bisection is searching for: the count
68/// of sites past the cut is how far there is left to go.
69const NO_FUEL: &str = "integer instruction not folded, the pass ran out of fuel";
70
71/// The pass. It holds nothing, because folding needs to know nothing beyond the instruction.
72#[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        // An instruction becomes a constant where it stands. No block moves, no edge moves,
86        // and a terminator is not one of the instructions this folds, so every analysis in the
87        // cache is about the same graph afterwards as it was before. Not the liveness, though:
88        // the operands the folded instruction read are read by nobody now, and a value whose
89        // last reader went is live over less of the function than it was.
90        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                    // Out of fuel, which is a request to stop transforming rather than to stop
102                    // looking. Continuing the walk costs nothing and keeps the count of what
103                    // could have been folded the same at every fuel setting, which is what makes
104                    // a bisection over it monotonic.
105                    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
123/// The single result of an instruction that folded.
124fn result_of(func: &Func, inst: Inst) -> Value {
125    func[inst].results().next().expect("an instruction that folds produces a value")
126}
127
128/// What this instruction evaluates to, if it evaluates to anything.
129///
130/// `None` covers every reason not to fold and does not distinguish between them, because the
131/// answer to all of them is the same: leave the instruction alone.
132fn 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    // A vector constant is a `splat` rather than an `iconst`, so a vector fold would have to
140    // build a different instruction and would have to be right about the lane count as well.
141    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
174/// The constant this value is, with the type it has, if it is one.
175///
176/// Shared with [`crate::simplify_cfg`], which asks the same question about the condition of a
177/// branch. Asking it in two places would be two answers about what a constant is.
178pub(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
188/// A widening or a narrowing of a constant.
189fn convert(opcode: Opcode, value: Imm, from: Type, to: Type) -> Imm {
190    match opcode {
191        // Truncation is the masking that `Imm::int` does anyway, and sign extension is reading
192        // the value as signed at its own width and storing it at the wider one.
193        Opcode::Trunc | Opcode::SExt => Imm::int(value.signed(from), to),
194        // Zero extension reads the same bits as unsigned, which for a width below 128 is a
195        // non-negative number and survives the cast to the signed type `Imm::int` takes.
196        _ => Imm::int(value.unsigned() as i128, to),
197    }
198}
199
200/// A shift of a constant by a constant.
201///
202/// `None` when the count is not one the language defines, which is a count at or above the width
203/// of the value. The result there is poison and folding it would be picking an answer for a
204/// program that asked for none.
205fn 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        // A logical shift right is on the bits rather than on the number, so it reads unsigned
222        // and the cast back cannot lose anything: the value has at most `from.bits()` bits set
223        // and shifting right sets none.
224        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
233/// An arithmetic or bitwise operation on two constants.
234fn 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        // The bitwise three cannot overflow and are the same operation whichever way the
238        // operands are read, so they take the signed reading and are done.
239        Opcode::And => a & b,
240        Opcode::Or => a | b,
241        Opcode::Xor => a ^ b,
242        // The arithmetic three are computed at 128 bits and then asked whether they fit. A type
243        // of 128 bits is the one case where the checked form is doing real work rather than
244        // being a formality, and it is why these are checked rather than wrapping.
245        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
255/// What a comparison of two constants comes out as.
256///
257/// Shared with [`crate::simplify_cfg`], which asks the same question about the condition of a
258/// branch it is deciding the direction of. Two answers about what `slt` means would be one too
259/// many, and the two places would not be checked against each other by anything.
260///
261/// The type is the one the operands have rather than the `i1` the answer has, since that is the
262/// width the comparison is at and the only thing the reading depends on. The two equalities are
263/// the same question whichever way the bits are read, so they compare the immediates directly:
264/// an immediate holds its value in exactly the width of its type, which is what makes that
265/// equality the equality on the numbers.
266pub(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
281/// One of the five bit operations on a constant.
282///
283/// All five are on the bits rather than on the number, so all five read the value unsigned. An
284/// immediate is stored with everything above its own width cleared, so the bits of a value of a
285/// narrow type are already in the low end of a 128 bit word with zeroes above them, and the whole
286/// of the work here is putting the answer back at the width it was asked at.
287///
288/// The two searches answer the width for a zero argument. C leaves `__builtin_clz(0)` and
289/// `__builtin_ctz(0)` undefined so nothing is entitled to that answer, but it is the answer the
290/// software expansion in `rucc_codegen::expand` gives and `__builtin_ffs` is built on top of it, so
291/// folding to anything else here would make the same program answer two different things depending
292/// on whether the argument was visible. That is a worse outcome than either answer on its own.
293///
294/// A byte swap of a width that is not a whole number of bytes is left alone, which is what the
295/// expansion does with one too. The verifier does not allow one and quietly reversing something
296/// else would be worse than the instruction surviving to a selector that says it has no rule.
297fn 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    // The bits of the word that are above the value's own type, which is how far a whole word
303    // answer has to come back down. Both ends of the range above are ruled out for it: a shift by
304    // the width of the word is not defined and a width of nought has no bits to answer about.
305    let spare = 128 - width;
306    let bits = value.unsigned();
307    let answer = match opcode {
308        Opcode::Ctpop => i128::from(bits.count_ones()),
309        // The zeroes above the type are counted by the word and are not the value's, so they come
310        // off. For a zero value that leaves the width, which is the answer wanted.
311        Opcode::Ctlz => i128::from(bits.leading_zeros() - spare),
312        // Trailing zeroes need no correction because the zeroes above the type are above every
313        // set bit, except for a zero value, where the word answers 128 and the width is wanted.
314        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
322/// Whether storing `exact` at `to` would lose something the flags promised would not happen.
323///
324/// An operation with neither flag wraps, and wrapping is defined, so the answer there is no
325/// however far outside the type the exact result is.
326fn 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    /// A function with one block, ready to have instructions appended to it.
346    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    /// Runs the pass over the function with as much fuel as it wants, and says whether it
355    /// rewrote anything.
356    fn fold(func: &mut Func) -> bool {
357        Fold.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited()).changed()
358    }
359
360    /// The constant a value now holds, or `None` if it is not one.
361    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        // The logical shift is the one that reads the value as bits, so minus eight shifted
441        // right by one is a very large positive number rather than minus four.
442        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    /// The one instruction under test, on one constant, folded as far as the pass takes it.
453    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        // A width that is not a whole number of bytes has no byte swap, so there is nothing to
498        // evaluate and the instruction stays for the backend to refuse.
499        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            // The same pair read as bits rather than as numbers, where minus one is the largest
518            // value there is and every unsigned answer is the opposite of the signed one.
519            (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            // The answer is one bit, where a set bit read as a signed number is minus one, so
533            // the question is which of the two constants it is rather than what it prints as.
534            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        // Two hundred and fifty five stored in eight bits is minus one, so it is below one when
542        // the comparison is signed and above it when the comparison is not.
543        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        // Both of them looked at and neither of them folded, which is the count a bisection is
686        // reading: how many sites are left past where the fuel ran out.
687        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        // One walk in order is enough for this shape, because a constant is written before it
712        // is used and the walk is in the same order.
713        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    /// The instruction that defines a value, which every value in these tests has.
728    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}