Skip to main content

rucc_opt/
simplify.rs

1//! Peephole rewrites: a small pattern of instructions becomes a smaller one.
2//!
3//! The third pass, and the one that will eventually not exist. Section 9.3 of
4//! `spec/09-optimizer.md` says the value level optimizer is an acyclic e-graph, and that an
5//! e-graph replaces what would otherwise be a folding pass, a peephole pass, a GVN pass, a
6//! reassociation pass and an instcombine pass, all with a pass ordering problem between them.
7//! This is the peephole pass, written now because the e-graph is a milestone away and because
8//! there is a rewrite that unblocks twelve lowering rules today.
9//!
10//! Every rewrite here has to survive being moved into the rule set later, so each one is stated
11//! as a pattern and a replacement in its own function and nothing shares state with anything.
12//!
13//! # The rewrites
14//!
15//! Two kinds. The rules of `rules/`, one file per tier, which are matched against every
16//! instruction and are where anything new goes, and one rewrite written out by hand below them.
17//!
18//! ## The rules
19//!
20//! Four tiers of `spec/optimizer/13-rewrite-rules.md` section 13.4 so far.
21//!
22//! Tier one is the identities. Adding nothing, multiplying by one, and'ing a value with itself.
23//! None of them needs anything known about the operands and each leaves a term strictly smaller
24//! than the one it replaced.
25//!
26//! Tier two is the strength reductions, which swap an operation for a cheaper one rather than
27//! taking one away: multiplying by two is an addition, and multiplying or dividing by minus one is
28//! a subtraction from nothing. Tier one is tried first because losing an operation beats swapping
29//! one.
30//!
31//! Tier four is the width rules, the algebra of truncation and extension. Truncating an extension
32//! back to the width it came from is the value that was there before either of them, and an
33//! extension of an extension is one extension. This is the tier
34//! the specification says pays on real C, and the reason is C rather than anything about this
35//! compiler: the integer promotions widen nearly every operand of nearly every expression, and
36//! most of those widenings compute something the instruction after them throws away.
37//!
38//! Tier three is the canonicalisations, which put the constant of a commutative operation on the
39//! right. They make nothing smaller and nothing faster. What they do is halve how many ways a term
40//! can be written, so that every rule above them needs one variant where it needs two today, and
41//! so that hash consing can see two spellings of one expression as one. They are tried last rather
42//! than third, because rearranging a term is only worth doing when no rule that improves it fires.
43//!
44//! Every rule in all four has been proved against `crates/rucc-ir/rules/ir.model` by
45//! `rucc-verify` before it may be used.
46//!
47//! Which plans a tier is matched under belongs to the tier. Tiers one and two are matched with
48//! either operand offered as a number, since a rule about a constant should fire whichever side it
49//! was written on. Tier three is matched with the left operand offered as a number and the right
50//! one refused if it is one, which is what makes a rule that moves the constant across fire once
51//! rather than forever. Tier four is matched with the operand expanded into the instruction that
52//! computed it, which is what a rule about two instructions at once needs and what none of the
53//! others wants.
54//!
55//! What a rule leaves behind is one of four things. `(value.iN x)` means the result is a value
56//! the function already has, so every use of the result is pointed at that value and the
57//! instruction is left for [`crate::dce`]. `(iconst.iN k)` means the result is a constant, and the
58//! instruction becomes that constant where it stands, which keeps the result value and is why
59//! nothing else has to be rewritten for that half. An instruction means this one becomes that one
60//! where it stands, which keeps the result value for the same reason, and an operand of it the
61//! rule wrote as a number gets an `iconst` in front of the instruction to hold it. A conversion is
62//! that same rewrite in place with one operand instead of two, and it is its own case because a
63//! conversion is the one instruction whose operand is not the width of its result.
64//!
65//! ## The one written by hand
66//!
67//! An exclusive or of a comparison with an `i1` of all ones is that comparison with the opposite
68//! predicate. That is issue 379, and it is worth more than the instruction it saves.
69//!
70//! C spells eight of the sixteen floating point predicates. The six relational and equality
71//! operators give the six ordered ones, `!=` gives `une`, and `__builtin_isunordered` gives `uno`.
72//! The other eight are what the negation of one of those means, and the front end writes a
73//! negation as an exclusive or rather than as a flipped predicate, so `!(x < y)` lowers to an
74//! `fcmp olt` and an `xor` where the machine has an `fcmp uge`. Twelve rules in the x86-64 rule
75//! set are written on those predicates and none of them has ever fired, over the whole torture
76//! suite at every optimization level, because no IR that reaches selection contains one.
77//!
78//! The integer case comes with it. `!(a < b)` on integers is the same shape, the same rewrite and
79//! the same saving, and leaving it out because the coverage report did not complain about it would
80//! be picking the rewrite by what measures it rather than by what it does.
81//!
82//! # Why it needs dead code elimination after it
83//!
84//! The rewrite turns the `xor` into the comparison and leaves the original comparison where it
85//! was, used by nothing when the negation was its only reader. Rewriting in place keeps the
86//! result value, so every use of it is already correct and there is nothing to rewrite, and what
87//! is left over is exactly what [`crate::dce`] takes out. That is why the pipeline runs the two in
88//! this order, and it is why the pass before the dead code eliminator was written first.
89//!
90//! An identity that produces a value leaves the same kind of litter for the same reason. The
91//! instruction it fired on reads what it always read and nothing reads it, so it is dead, and
92//! taking it out here would mean deciding whether its operands are still read by anything, which
93//! is the question the dead code eliminator answers for the whole function at once.
94
95use std::collections::HashMap;
96use std::sync::OnceLock;
97
98use rucc_ir::term::{PLAIN, Plan, Shown, Term, Terms};
99use rucc_ir::{Block, Def, Extra, Flags, Func, Imm, Inst, InstData, IntPred, Opcode, Type, Value};
100
101use crate::rules::{Match, Piece, Table, canonical, compare, identities, strength, width};
102use crate::uses::{count, substitute};
103use crate::{Analyses, Analysis, Fuel, Pass, Preserved, Stats};
104
105/// Recorded once for each negation folded into the comparison under it.
106const FLIPPED: &str = "comparison negated by an exclusive or rewritten as the opposite comparison";
107
108/// Recorded for a negation that would have folded if there had been fuel for it.
109const NO_FUEL: &str = "negated comparison left alone, the pass ran out of fuel";
110
111/// Recorded for a rule that would have fired if there had been fuel for it.
112const NO_FUEL_RULE: &str = "rewrite left alone, the pass ran out of fuel";
113
114/// How each operand of an instruction is shown to the matcher, and in what order the ways are
115/// tried.
116///
117/// The two with a constant come first, because a rule about a number is the more specific one and
118/// an operand that is not a constant declines it at the first node of the trie. Nothing here
119/// expands an operand into the instruction that computed it, since no tier one identity is about
120/// two instructions at once.
121const PLANS: [Plan; 3] =
122    [[Shown::Reg, Shown::Const, Shown::Reg], [Shown::Const, Shown::Reg, Shown::Reg], PLAIN];
123
124/// How the operands are shown to a canonicalisation, which is the one plan tier three is matched
125/// under.
126///
127/// A canonicalisation moves the constant to the right, so the left operand has to be the number
128/// and the right one has to be something that is not, or the rule swaps a pair of constants back
129/// and forth until the pass runs out of fuel. [`Shown::Var`] is what says the right one is not a
130/// number. The plans above cannot be reused here for exactly that reason: the second of them
131/// shows a constant left operand as a number and a constant right operand as a register, which is
132/// the cycling match.
133const CANONICAL: [Plan; 1] = [[Shown::Const, Shown::Var, Shown::Reg]];
134
135/// How the operands are shown to a width rule, which is the one plan tier four is matched under.
136///
137/// Every rule in that tier is about two instructions at once, a conversion and the conversion or
138/// value under it, so the operand it is about has to be shown as the instruction that computed it
139/// rather than as a register holding the answer. That is [`Shown::Expand`], and it is the first
140/// plan here to use it.
141///
142/// One operand, because every instruction the tier matches has one. The other two entries are
143/// never read and say [`Shown::Reg`] because that is what an operand nobody asks about is.
144const EXPAND: [Plan; 1] = [[Shown::Expand, Shown::Reg, Shown::Reg]];
145
146/// How the operands are shown to a comparison rule, which is the one plan tier five is matched
147/// under.
148///
149/// Every rule in that tier compares something against a constant, and writes the constant on the
150/// right, so the right operand has to be shown as a number and the left one has to be shown as a
151/// register. That is the first of [`PLANS`] and this is the same triple, spelled again rather than
152/// borrowed, because the tier is matched under one plan and the other two would be tried for
153/// nothing: a comparison with the constant on the left matches no rule here, and neither does one
154/// with no constant at all.
155///
156/// The constant on the left is not the missing half of the tier. A comparison is not commutative,
157/// so `0 < x` is not `x < 0` with the operands swapped, it is `x > 0`, and turning the first into
158/// the second is a canonicalisation that belongs in tier three rather than four more rules here.
159const COMPARE: [Plan; 1] = [[Shown::Reg, Shown::Const, Shown::Reg]];
160
161/// The rule tables, one per tier, in the order they are tried, each with the plans it is matched
162/// under.
163///
164/// Tier one first, because an identity takes an operation away and a strength reduction swaps one
165/// for another, so a term both have something to say about is better off losing the operation.
166/// Tier four after those two and tier three last, because a canonicalisation only makes a term
167/// easier for another rule to be about and there is no reason to reach for it while a rule that
168/// improves the code still fires. Nothing turns on the order of those last two anyway: tier three
169/// is about a commutative operation with a constant in it and tier four is about a conversion, so
170/// no instruction is one both have something to say about.
171///
172/// The plans belong to the table rather than to the loop because a tier is written against them.
173/// Tier three is only correct under the one plan that refuses a constant on the right, and a
174/// table matched under a plan it was not written for is a table whose rules mean something else.
175/// Tier four is the other way round: its rules mean nothing at all under a plan that does not
176/// expand, since the second level of every one of its patterns is an instruction.
177///
178/// Tier five sits where it does because nothing turns on it either. It is the only table about a
179/// comparison and no other table mentions one, so there is no instruction two of them have
180/// something to say about and no order in which one of them gets there first.
181const TABLES: [(&Table, &[Plan]); 5] = [
182    (&identities::TABLE, &PLANS),
183    (&strength::TABLE, &PLANS),
184    (&width::TABLE, &EXPAND),
185    (&compare::TABLE, &COMPARE),
186    (&canonical::TABLE, &CANONICAL),
187];
188
189/// The pass. It holds nothing, because a peephole needs to know nothing beyond the pattern.
190#[derive(Debug, Clone, Copy, PartialEq, Eq)]
191pub struct Simplify;
192
193impl Pass for Simplify {
194    fn name(&self) -> &'static str {
195        "simplify"
196    }
197
198    fn describe(&self) -> &'static str {
199        "the identities, the strength reductions, the canonicalisations, and a negated comparison \
200         as the opposite one"
201    }
202
203    fn preserves(&self) -> Preserved {
204        // Everything about the shape of the function. No block is added, none is removed and no
205        // edge moves, so the graph and everything built out of it stand.
206        //
207        // The liveness does not, and that is the whole of the difference. An identity that
208        // produces a value points every reader of one value at another, which is one more place
209        // the second is live and one fewer the first is, and the same is true of the negation
210        // below, which reads the comparison's operands where it used to read its result.
211        //
212        // A rule that writes an instruction with a constant in it puts one in the block, and that
213        // is still the same answer. It adds a value nothing else mentions, in the block it is
214        // read in, and it ends every path it starts on, so nothing about the shape of the
215        // function moves and the only analysis with something new to say about it is the one
216        // already given up.
217        Preserved::ALL.without(Analysis::Liveness)
218    }
219
220    fn run(&self, func: &mut Func, _an: &mut Analyses, fuel: &mut Fuel) -> Stats {
221        let mut stats = Stats::new();
222        // What a rule that produced a value decided, applied to the whole function at the end.
223        // Rewriting each one where it is found would be a walk over every instruction for every
224        // rewrite, and there is nothing to be gained by it: what a pattern asks about is the
225        // instruction and its operands, and neither changes under a redirection.
226        let mut forward: HashMap<Value, Value> = HashMap::new();
227        // Who reads what, so that an instruction nothing reads is left alone. A rule that fires
228        // on one changes no program, because what it does is point the readers somewhere else and
229        // there are none, and it would still spend fuel and still report having optimized
230        // something. That matters here more than it would in a pass that runs once: this pass is
231        // named twice in every pipeline above `-O0`, an identity it takes stays in the function
232        // until dead code elimination removes it, and without this the second run would rewrite
233        // everything the first run did all over again and say so.
234        //
235        // Stale by design. It is what the function looked like when this run started, and a
236        // rewrite below only ever removes readers, so a value this says nothing reads is a value
237        // nothing reads.
238        let uses = count(func);
239        let dead = |func: &Func, inst: Inst| match func[inst].first_result {
240            Some(result) => uses[result.index()] == 0,
241            None => false,
242        };
243        for block in func.blocks().collect::<Vec<Block>>() {
244            for inst in func.insts(block).collect::<Vec<Inst>>() {
245                if dead(func, inst) {
246                    continue;
247                }
248                if let Some(flip) = negated_comparison(func, inst) {
249                    if !fuel.take() {
250                        // Out of fuel, which stops the transforming rather than the looking, the
251                        // same way the other two passes treat it. The walk is the same walk at
252                        // every fuel setting, which is what makes bisecting over it monotonic.
253                        stats.missed(NO_FUEL);
254                        continue;
255                    }
256                    let args = func.push_values(&[flip.lhs, flip.rhs]);
257                    let data = &mut func[inst];
258                    data.opcode = flip.opcode;
259                    data.flags = flip.flags;
260                    data.args = args;
261                    data.extra = flip.extra;
262                    stats.optimized(FLIPPED);
263                    continue;
264                }
265                let Some((rewrite, pattern)) = identity(func, inst) else { continue };
266                if !fuel.take() {
267                    stats.missed(NO_FUEL_RULE);
268                    continue;
269                }
270                match rewrite {
271                    Rewrite::Value(value) => {
272                        let result = func[inst].first_result.expect("the rule matched a result");
273                        forward.insert(result, value);
274                    }
275                    Rewrite::Constant(number) => become_constant(func, inst, number),
276                    Rewrite::Built { opcode, pred, lhs, rhs } => {
277                        become_instruction(func, inst, opcode, pred, lhs, rhs);
278                    }
279                    Rewrite::Converted { opcode, from } => {
280                        become_conversion(func, inst, opcode, from);
281                    }
282                }
283                stats.optimized(pattern);
284            }
285        }
286        if !forward.is_empty() {
287            substitute(func, &forward);
288        }
289        stats
290    }
291}
292
293/// What a rule says an instruction's result is instead.
294#[derive(Clone, Copy, Debug, PartialEq, Eq)]
295enum Rewrite {
296    /// A value the function already has, which every reader of the result is pointed at.
297    Value(Value),
298    /// A number, which the instruction becomes where it stands.
299    Constant(i128),
300    /// Another instruction, which this one becomes where it stands.
301    Built {
302        /// What it is.
303        opcode: Opcode,
304        /// Which comparison it is, when it is one.
305        ///
306        /// The predicate is not part of the opcode. Every one of the ten integer comparisons is
307        /// `ICmp` and the predicate is beside it, so an opcode on its own does not say what a
308        /// rule asked for, and a rule that wrote `icmp_sge` and got the predicate of the
309        /// instruction it replaced would compute the opposite rather than something else.
310        pred: Option<IntPred>,
311        /// Its left operand.
312        lhs: Operand,
313        /// Its right operand.
314        rhs: Operand,
315    },
316    /// A conversion, which this one becomes where it stands.
317    ///
318    /// Separate from [`Rewrite::Built`] rather than one variant with a list of operands, because a
319    /// conversion is the one instruction a rule writes whose operand is not the width of its
320    /// result. That is what makes it the one whose operand cannot be a number the rule wrote:
321    /// there would be no width to give the constant, and every rule that writes one of these
322    /// writes a value the pattern bound.
323    Converted {
324        /// Which of the three it is.
325        opcode: Opcode,
326        /// What it converts, which is always a value the pattern bound.
327        from: Value,
328    },
329}
330
331/// One operand of an instruction a rule writes.
332#[derive(Clone, Copy, Debug, PartialEq, Eq)]
333enum Operand {
334    /// A value the pattern bound.
335    Value(Value),
336    /// A number the rule wrote, which needs an `iconst` in front of the instruction before it is
337    /// an operand at all, because an operand in this IR is a value and a number is not one until
338    /// something defines it.
339    Constant {
340        /// The number.
341        number: i128,
342        /// How wide it is, which is the width the `iconst.iN` head named.
343        ///
344        /// Taken from the rule rather than from the instruction's result, because the two are
345        /// the same width for everything above and are not for a comparison: the result of one
346        /// is a single bit and its operands are as wide as what was compared. A constant built
347        /// at the result's width would be a one bit zero standing where a thirty two bit one
348        /// was asked for.
349        bits: u32,
350    },
351}
352
353/// The rule that fires on this instruction, and the pattern it came from.
354///
355/// The plans are tried in order and the first that matches wins. A plan is how the operands are
356/// shown rather than what they are, so trying three of them is three walks over a trie, each of
357/// which fails in its first node or two when the instruction is not one any rule is about.
358fn identity(func: &Func, inst: Inst) -> Option<(Rewrite, &'static str)> {
359    let result = func[inst].first_result?;
360    for (table, plan) in
361        TABLES.into_iter().flat_map(|(table, plans)| plans.iter().map(move |&plan| (table, plan)))
362    {
363        let terms = Terms::new(func, inst, plan);
364        let Some(found) = table.find(&terms, Term::Root) else { continue };
365        let rule = table.rule(&found);
366        let rewrite = match rule.replacement {
367            // A value the pattern bound, which is a register because that is the only thing a
368            // `value.iN` binds.
369            [Piece::App { head, arity: 1 }, Piece::Var { index, .. }]
370                if head.starts_with("value.") =>
371            {
372                match found.bindings.get(*index) {
373                    Some(&Term::Reg(value)) => Rewrite::Value(value),
374                    _ => continue,
375                }
376            }
377            // A constant written in the rule. Only at a width the instruction's result has, which
378            // it always does: an `iconst.iN` names an integer width and a rule is proved at the
379            // width it is written at.
380            [Piece::App { head, arity: 1 }, Piece::Int(number)]
381                if head.starts_with("iconst.") && func[result].ty.is_int() =>
382            {
383                Rewrite::Constant(*number)
384            }
385            // An instruction the rule writes, which this one becomes. That is the third shape and
386            // the last one: a replacement deeper than one instruction would need somewhere to put
387            // the ones under it, and a rule that wanted it can be written as two rules that each
388            // leave one.
389            pieces => match built(pieces, &found) {
390                Some(rewrite) => rewrite,
391                // Any other shape, which no rule in the file has. A test below says so, because a
392                // rule that fell through here would be a rule that never fires and nothing would
393                // say it had stopped.
394                None => continue,
395            },
396        };
397        return Some((rewrite, rule.pattern));
398    }
399    None
400}
401
402/// The instruction a rule writes, out of the pieces its replacement flattened into.
403///
404/// Two operands under a head that names an opcode, each of them either a value the pattern bound
405/// or a number the rule wrote. Anything else is nothing this pass can build, and the answer to
406/// one is that the rule does not fire, which the test over the whole table turns into a failure
407/// rather than a silence.
408fn built(pieces: &'static [Piece], found: &Match<Term>) -> Option<Rewrite> {
409    if let Some(rewrite) = converted(pieces, found) {
410        return Some(rewrite);
411    }
412    let [Piece::App { head, arity: 2 }, rest @ ..] = pieces else { return None };
413    let opcode = opcode_of(head)?;
414    // The predicate comes from the same head the opcode did, so a rule whose replacement this
415    // pass can build is a rule written in the vocabulary it matched with, predicate and all.
416    let pred = rucc_ir::term::int_pred(head);
417    if (opcode == Opcode::ICmp) != pred.is_some() {
418        // A comparison whose head names no predicate, or a predicate on something that is not a
419        // comparison. Neither is a head the vocabulary produces, so neither is a rule anybody
420        // wrote, and building the instruction anyway would mean guessing at one of the two.
421        return None;
422    }
423    let (lhs, rest) = operand(rest, found)?;
424    let (rhs, rest) = operand(rest, found)?;
425    rest.is_empty().then_some(Rewrite::Built { opcode, pred, lhs, rhs })
426}
427
428/// The conversion a rule writes, if it wrote one.
429///
430/// Three heads rather than any head of one operand, because the width rules are the only tier that
431/// writes an instruction with one, and being specific is what keeps this from claiming a
432/// replacement it cannot build. A `value.iN` or an `iconst.iN` is also a head of one operand and
433/// neither is an instruction, and [`identity`] has already dealt with both by the time anything
434/// gets here, so a test would not catch the day one slipped past.
435///
436/// The operand is a value the pattern bound, and nothing else. A number would need a width to be
437/// written at and the result's width is the wrong one for a conversion, which is the whole reason
438/// this is separate from [`built`].
439fn converted(pieces: &'static [Piece], found: &Match<Term>) -> Option<Rewrite> {
440    let [Piece::App { head, arity: 1 }, rest @ ..] = pieces else { return None };
441    let opcode = match opcode_of(head)? {
442        opcode @ (Opcode::SExt | Opcode::ZExt | Opcode::Trunc) => opcode,
443        _ => return None,
444    };
445    let [Piece::App { head: inner, arity: 1 }, Piece::Var { index, .. }] = rest else {
446        return None;
447    };
448    if !inner.starts_with("value.") {
449        return None;
450    }
451    match found.bindings.get(*index) {
452        Some(&Term::Reg(from)) => Some(Rewrite::Converted { opcode, from }),
453        _ => None,
454    }
455}
456
457/// One operand of that instruction, and the pieces after it.
458fn operand(pieces: &'static [Piece], found: &Match<Term>) -> Option<(Operand, &'static [Piece])> {
459    match pieces {
460        [Piece::App { head, arity: 1 }, Piece::Var { index, .. }, rest @ ..]
461            if head.starts_with("value.") =>
462        {
463            match found.bindings.get(*index) {
464                Some(&Term::Reg(value)) => Some((Operand::Value(value), rest)),
465                _ => None,
466            }
467        }
468        [Piece::App { head, arity: 1 }, Piece::Int(number), rest @ ..]
469            if head.starts_with("iconst.") =>
470        {
471            Some((Operand::Constant { number: *number, bits: bits_of(head)? }, rest))
472        }
473        // A number the pattern bound rather than one the rule wrote. This is what a
474        // canonicalisation needs: it moves the operand it matched to the other side, and what it
475        // matched was whatever number happened to be there.
476        [Piece::App { head, arity: 1 }, Piece::Var { index, .. }, rest @ ..]
477            if head.starts_with("iconst.") =>
478        {
479            match found.bindings.get(*index) {
480                Some(&Term::Num(number)) => {
481                    Some((Operand::Constant { number, bits: bits_of(head)? }, rest))
482                }
483                _ => None,
484            }
485        }
486        _ => None,
487    }
488}
489
490/// The width a head names, out of the `iN` after its last dot.
491///
492/// Every head that takes a width ends in one, and reading it off the name is what keeps the width
493/// a rule was written at attached to the rule rather than inferred from whatever the instruction
494/// being replaced happened to be. A head with no width, or one whose width is not a number, is a
495/// head this cannot build an operand for, and the answer to that is that the rule does not fire.
496fn bits_of(head: &str) -> Option<u32> {
497    head.rsplit_once('.')?.1.strip_prefix('i')?.parse().ok()
498}
499
500/// The opcode a replacement head names, or nothing if the rules have no instruction by that name.
501///
502/// Built the once out of [`rucc_ir::term::heads`], which is where the name of the instruction a
503/// pattern matched comes from as well, so a rule whose replacement this pass can build is a rule
504/// written in the vocabulary it matched with. A table here would be a second vocabulary and the
505/// two would drift.
506///
507/// A name two opcodes answer to belongs to the first of them, which is the general one:
508/// `ptr_add` is an add at the address width and is named as one, and a rule that writes `add` is
509/// asking for the add.
510fn opcode_of(head: &str) -> Option<Opcode> {
511    static NAMES: OnceLock<HashMap<&'static str, Opcode>> = OnceLock::new();
512    let names = NAMES.get_or_init(|| {
513        let mut names = HashMap::new();
514        for (opcode, name) in rucc_ir::term::heads() {
515            names.entry(name).or_insert(opcode);
516        }
517        names
518    });
519    names.get(head).copied()
520}
521
522/// Turns an instruction into the one a rule says computes the same thing.
523///
524/// In place, like the constant below and for the same reason: the result value survives, so every
525/// reader of it is already right and there is nothing to redirect.
526fn become_instruction(
527    func: &mut Func,
528    inst: Inst,
529    opcode: Opcode,
530    pred: Option<IntPred>,
531    lhs: Operand,
532    rhs: Operand,
533) {
534    let result = func[inst].first_result.expect("the rule matched a result");
535    let ty = func[result].ty;
536    let lhs = defined(func, inst, ty, lhs);
537    let rhs = defined(func, inst, ty, rhs);
538    let args = func.push_values(&[lhs, rhs]);
539    let data = &mut func[inst];
540    data.opcode = opcode;
541    data.args = args;
542    // The predicate the rule named, and nothing else a rule writes carries an extra. What was
543    // there belonged to the instruction that is gone, which is the case that matters: a rule
544    // rewriting a comparison into an addition that left the predicate behind would leave an
545    // addition claiming to be `slt`, and one rewriting a comparison into another comparison that
546    // kept the old predicate would compute the opposite of what it said.
547    data.extra = match pred {
548        Some(pred) => Extra::IntPred(pred),
549        None => Extra::None,
550    };
551    // The flags go with the instruction that had them, the same as for a constant. An `nsw` on a
552    // multiplication is a promise about that multiplication, and the addition that replaces it is
553    // a different instruction. The promise may well still hold, and carrying one across a rewrite
554    // because it probably still holds is how a wrong one gets made. Dropping it costs a later
555    // pass an assumption and costs no program its meaning.
556    data.flags = Flags::NONE;
557}
558
559/// Turns an instruction into the conversion a rule says computes the same thing.
560///
561/// In place, for the same reason as the two above: the result value survives, so every reader of
562/// it is already right.
563///
564/// The result keeps the type it had, which is the type the rule wrote. A replacement head names
565/// both widths it converts between, `rucc-verify` refuses a replacement narrower than the pattern
566/// and the rules are written with the two the same, so the width the head names on the way out is
567/// the width the instruction already produces.
568fn become_conversion(func: &mut Func, inst: Inst, opcode: Opcode, from: Value) {
569    let args = func.push_values(&[from]);
570    let data = &mut func[inst];
571    data.opcode = opcode;
572    data.args = args;
573    // Nothing a rule writes carries an extra, and the flags belonged to the instruction that is
574    // gone. Both for the reasons `become_instruction` gives.
575    data.extra = Extra::None;
576    data.flags = Flags::NONE;
577}
578
579/// An operand as a value, defining it in front of the instruction if the rule wrote a number.
580///
581/// `ty` is the type of the instruction's result, which is the width the constant is built at for
582/// everything whose operands are as wide as what it produces. A comparison is the exception and
583/// the reason the rule's own width is carried this far: its result is one bit and its operands are
584/// as wide as what was compared, so the width comes from the `iconst.iN` the rule wrote and the
585/// result's type is used only for its shape.
586fn defined(func: &mut Func, before: Inst, ty: Type, operand: Operand) -> Value {
587    match operand {
588        Operand::Value(value) => value,
589        Operand::Constant { number, bits } => {
590            let ty = if ty.lane() == Type::int(bits) { ty } else { Type::int(bits) };
591            let at = func.add_imm(Imm::int(number, ty.lane()));
592            let data = InstData { extra: Extra::Imm(at), ..InstData::new(Opcode::IConst) };
593            let span = func.span(before);
594            let iconst = func.create_inst(data, &[ty], span);
595            func.insert_before(iconst, before);
596            func[iconst].first_result.expect("one result was asked for")
597        }
598    }
599}
600
601/// Turns an instruction into the constant a rule says its result is.
602///
603/// In place, so the result value survives and every reader of it is already right. That is what
604/// makes this the half of the pass with nothing to redirect.
605fn become_constant(func: &mut Func, inst: Inst, number: i128) {
606    let result = func[inst].first_result.expect("the rule matched a result");
607    let ty = func[result].ty;
608    let imm = func.add_imm(Imm::int(number, ty.lane()));
609    let args = func.push_values(&[]);
610    let data = &mut func[inst];
611    data.opcode = Opcode::IConst;
612    data.args = args;
613    data.extra = Extra::Imm(imm);
614    // The flags go with the instruction that had them. An `nsw` on an add is a promise about an
615    // addition, and a constant makes no promise because it performs nothing.
616    data.flags = Flags::NONE;
617}
618
619/// What an instruction should become, when it is a comparison written as a negation.
620struct Flip {
621    /// `ICmp` or `FCmp`, whichever the comparison underneath was.
622    opcode: Opcode,
623    /// The flags of the comparison, which is where a fast math promise lives.
624    flags: Flags,
625    /// The opposite predicate.
626    extra: Extra,
627    /// The comparison's left operand.
628    lhs: Value,
629    /// Its right operand.
630    rhs: Value,
631}
632
633/// Whether this instruction is `xor (cmp p a b), true`, and what it becomes if it is.
634///
635/// The exclusive or is commutative, so the constant is looked for on both sides. Nothing else
636/// about the shape is negotiable: the result has to be an `i1`, because an exclusive or with one
637/// is a negation only at that width, and the constant has to be all ones, because the front end
638/// writes it as `iconst.i1 -1` and a reader who assumed the literal 1 would match nothing.
639fn negated_comparison(func: &Func, inst: Inst) -> Option<Flip> {
640    let data = &func[inst];
641    if data.opcode != Opcode::Xor {
642        return None;
643    }
644    let args = &func[data.args];
645    let (&first, &second) = (args.first()?, args.get(1)?);
646    if func[first].ty != Type::int(1) {
647        return None;
648    }
649    let cmp = match (all_ones(func, first), all_ones(func, second)) {
650        (true, false) => second,
651        (false, true) => first,
652        // Both, which folding would have turned into a constant, or neither, which is an
653        // exclusive or of two comparisons and is not this pattern.
654        _ => return None,
655    };
656    let Def::Result { inst: cmp, .. } = func[cmp].def else { return None };
657    let data = &func[cmp];
658    let extra = match (data.opcode, data.extra) {
659        (Opcode::ICmp, Extra::IntPred(pred)) => Extra::IntPred(pred.inverse()),
660        (Opcode::FCmp, Extra::FloatPred(pred)) => Extra::FloatPred(pred.inverse()),
661        _ => return None,
662    };
663    let args = &func[data.args];
664    Some(Flip {
665        opcode: data.opcode,
666        flags: data.flags,
667        extra,
668        lhs: *args.first()?,
669        rhs: *args.get(1)?,
670    })
671}
672
673/// Whether this value is a constant with every bit of its type set.
674fn all_ones(func: &Func, value: Value) -> bool {
675    let ty = func[value].ty;
676    let Def::Result { inst, .. } = func[value].def else { return false };
677    let data = &func[inst];
678    let Extra::Imm(at) = data.extra else { return false };
679    if data.opcode != Opcode::IConst {
680        return false;
681    }
682    // Read as signed, because an all ones value of any width is minus one that way and reading
683    // it unsigned would need the width to build the mask from.
684    func[at].signed(ty) == -1
685}
686
687#[cfg(test)]
688mod tests {
689    use rucc_base::Interner;
690    use rucc_ir::{
691        Block, Builder, Extra, Flags, Float, FloatPred, Func, IntPred, Module, Opcode, Signature,
692        Type, Value,
693    };
694    use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
695
696    use super::{
697        CANONICAL, COMPARE, EXPAND, PLANS, Shown, TABLES, canonical, compare, identities, strength,
698        width,
699    };
700    use crate::rules::Piece;
701    use crate::stats::Kind;
702    use crate::{Analyses, Fuel, Pass, simplify::Simplify};
703
704    /// A function with one block, ready to have instructions appended to it.
705    fn blank() -> (Interner, Func, Block) {
706        let mut names = Interner::new();
707        let name = names.intern("f");
708        let mut func = Func::new(name, Signature::new().with_returns(&[Type::int(1)]));
709        let block = func.create_block();
710        (names, func, block)
711    }
712
713    /// The same, at the width the test is about and taking a parameter of it, since every identity
714    /// below needs an operand that is not itself a constant.
715    fn one_block(ty: Type) -> (Interner, Func, Block) {
716        let mut names = Interner::new();
717        let name = names.intern("f");
718        let signature = Signature::new().with_params(&[ty]).with_returns(&[ty]);
719        let mut func = Func::new(name, signature);
720        let block = func.create_block();
721        (names, func, block)
722    }
723
724    /// Runs the pass with as much fuel as it wants, and says whether it rewrote anything.
725    fn simplify(func: &mut Func) -> bool {
726        Simplify.run(func, &mut Analyses::new(), &mut Fuel::unlimited()).changed()
727    }
728
729    /// The opcode and the predicate the value now comes from.
730    fn came_from(func: &Func, value: Value) -> (Opcode, Extra) {
731        let rucc_ir::Def::Result { inst, .. } = func[value].def else { panic!("not a result") };
732        (func[inst].opcode, func[inst].extra)
733    }
734
735    /// What the block gives back, which is where every identity test reads its answer. A rule
736    /// that produces a value is only worth anything if the readers move, so the readers are what
737    /// the test looks at rather than the instruction that fired.
738    fn returned(func: &Func, block: Block) -> Value {
739        let inst = func.terminator(block).expect("the block has a terminator");
740        func[func[inst].args][0]
741    }
742
743    /// The operands of the instruction a value comes from.
744    fn operands(func: &Func, value: Value) -> Vec<Value> {
745        let rucc_ir::Def::Result { inst, .. } = func[value].def else { panic!("not a result") };
746        func[func[inst].args].to_vec()
747    }
748
749    /// The number a value is, which panics unless it is a constant.
750    fn number(func: &Func, value: Value) -> i128 {
751        let rucc_ir::Def::Result { inst, .. } = func[value].def else { panic!("not a result") };
752        let data = &func[inst];
753        assert_eq!(data.opcode, Opcode::IConst, "not a constant");
754        let Extra::Imm(at) = data.extra else { panic!("a constant with no number") };
755        func[at].signed(func[value].ty)
756    }
757
758    /// Every rule in every table leaves one of the four shapes the pass knows how to apply.
759    ///
760    /// A rule that left anything else would be matched, found to be none of them, and skipped, and
761    /// nothing at run time would say so: the rewrite would simply stop happening. So it is said
762    /// here instead, once, over every table.
763    #[test]
764    fn every_rule_leaves_a_shape_the_pass_knows_what_to_do_with() {
765        for (table, _) in TABLES {
766            for rule in table.rules {
767                let known = matches!(
768                    rule.replacement,
769                    [Piece::App { head, arity: 1 }, Piece::Var { .. }]
770                        if head.starts_with("value.")
771                ) || matches!(
772                    rule.replacement,
773                    [Piece::App { head, arity: 1 }, Piece::Int(_)]
774                        if head.starts_with("iconst.")
775                ) || matches!(
776                    rule.replacement,
777                    [Piece::App { arity: 2, .. }, ..] if instruction(rule.replacement)
778                ) || conversion(rule.replacement);
779                assert!(known, "{} leaves a shape the pass would skip", rule.pattern);
780            }
781        }
782    }
783
784    /// The pieces of a replacement that is a conversion, read the way [`super::converted`] reads
785    /// them, and shape only for the same reason [`instruction`] is: there are no bindings here to
786    /// resolve the operand against.
787    fn conversion(pieces: &'static [Piece]) -> bool {
788        let [Piece::App { head, arity: 1 }, rest @ ..] = pieces else { return false };
789        let converts =
790            matches!(super::opcode_of(head), Some(Opcode::SExt | Opcode::ZExt | Opcode::Trunc));
791        converts
792            && matches!(
793                rest,
794                [Piece::App { head, arity: 1 }, Piece::Var { .. }] if head.starts_with("value.")
795            )
796    }
797
798    /// Every rule in the width table writes a term ending at the width the one it matched ended
799    /// at.
800    ///
801    /// The pass rewrites in place and leaves the result type where it was, so a rule whose
802    /// replacement converted to some other width would quietly produce a value of the wrong one.
803    /// `rucc-verify` refuses a replacement narrower than what it replaces and says nothing about a
804    /// wider one, so this is the half of that pair the solver does not cover.
805    #[test]
806    fn a_width_rule_writes_a_term_that_ends_where_the_one_it_matched_ended() {
807        for rule in width::TABLE.rules {
808            let [Piece::App { head, .. }, ..] = rule.replacement else {
809                panic!("{} writes no head", rule.pattern)
810            };
811            let wrote = head.rsplit_once('.').expect("a replacement head names a width").1;
812            let matched = rule
813                .pattern
814                .trim_start_matches('(')
815                .split([' ', ')'])
816                .next()
817                .and_then(|head| head.rsplit_once('.'))
818                .expect("a pattern head names a width")
819                .1;
820            assert_eq!(wrote, matched, "{} ends somewhere else", rule.pattern);
821        }
822    }
823
824    /// The pieces of a replacement that is an instruction, read the way the pass reads them, so
825    /// that the check above is the pass's own answer rather than a second opinion about it.
826    ///
827    /// The bindings are empty, which is why a `value.iN` operand fails to resolve and this only
828    /// says the shape is one the pass would take rather than that it would take it here.
829    fn instruction(pieces: &'static [Piece]) -> bool {
830        let [Piece::App { head, arity: 2 }, rest @ ..] = pieces else { return false };
831        if super::opcode_of(head).is_none() {
832            return false;
833        }
834        let operand = |pieces: &'static [Piece]| match pieces {
835            [Piece::App { head, arity: 1 }, Piece::Var { .. }, rest @ ..]
836                if head.starts_with("value.") =>
837            {
838                Some(rest)
839            }
840            [Piece::App { head, arity: 1 }, Piece::Int(_), rest @ ..]
841                if head.starts_with("iconst.") =>
842            {
843                Some(rest)
844            }
845            [Piece::App { head, arity: 1 }, Piece::Var { .. }, rest @ ..]
846                if head.starts_with("iconst.") =>
847            {
848                Some(rest)
849            }
850            _ => None,
851        };
852        operand(rest).and_then(operand).is_some_and(<[Piece]>::is_empty)
853    }
854
855    /// And each table holds every rule its file writes. The tables are generated, so this is
856    /// asking whether the generator saw the whole file, which is the one thing about it worth
857    /// doubting.
858    #[test]
859    fn each_table_holds_every_rule_its_file_writes() {
860        let tier_one = include_str!("../rules/simplify.rules");
861        let tier_two = include_str!("../rules/strength.rules");
862        let tier_three = include_str!("../rules/canonical.rules");
863        let tier_four = include_str!("../rules/width.rules");
864        let tier_five = include_str!("../rules/compare.rules");
865        let count = |text: &str| text.matches("(rule (simplify ").count();
866        assert_eq!(identities::TABLE.rules.len(), count(tier_one));
867        assert_eq!(strength::TABLE.rules.len(), count(tier_two));
868        assert_eq!(canonical::TABLE.rules.len(), count(tier_three));
869        assert_eq!(width::TABLE.rules.len(), count(tier_four));
870        assert_eq!(compare::TABLE.rules.len(), count(tier_five));
871        assert!(
872            identities::TABLE.rules.len() > 100,
873            "tier one is about a hundred rules and there are fewer"
874        );
875        assert!(
876            strength::TABLE.rules.len() > 20,
877            "tier two is the multiplications and the divisions and there are fewer"
878        );
879        assert_eq!(
880            canonical::TABLE.rules.len(),
881            20,
882            "tier three is five commutative operators at four widths"
883        );
884        assert_eq!(
885            width::TABLE.rules.len(),
886            44,
887            "tier four is the truncation and extension algebra over four widths"
888        );
889        assert_eq!(
890            compare::TABLE.rules.len(),
891            64,
892            "tier five is four predicates against each of four constants at four widths"
893        );
894    }
895
896    /// Three ways of showing an operand and no more, since a fourth would be a plan nothing
897    /// tries and a rule written for it would never fire.
898    #[test]
899    fn a_pattern_is_reached_by_one_of_the_plans() {
900        assert_eq!(PLANS.len(), 3);
901    }
902
903    /// Tier four is matched with its operand expanded, and nothing else is.
904    ///
905    /// Every pattern in that tier has an instruction at its second level, so under any of the
906    /// plans above it every rule in it would fail at the first node and the whole tier would be a
907    /// file nobody matched with. Asserted rather than left to be read, because that failure is
908    /// silent.
909    #[test]
910    fn a_width_rule_is_only_matched_with_its_operand_expanded() {
911        let (_, plans) = TABLES[2];
912        assert_eq!(plans.len(), 1);
913        assert_eq!(plans[0], EXPAND[0]);
914        assert_eq!(plans[0][0], Shown::Expand);
915        for plan in PLANS {
916            assert_ne!(plan, plans[0], "no shared plan expands an operand");
917        }
918        assert_ne!(CANONICAL[0], plans[0]);
919    }
920
921    /// Tier three is matched under its own plan and no other.
922    ///
923    /// This is what makes the rules terminate rather than swap a pair of constants back and forth
924    /// until the fuel runs out. It is asserted rather than left to be read, because the cost of
925    /// somebody adding the shared plans to the tier three row is a pass that does not stop.
926    #[test]
927    fn a_canonicalisation_is_only_matched_with_the_right_operand_refused() {
928        let (_, plans) = TABLES[4];
929        assert_eq!(plans.len(), 1);
930        assert_eq!(plans[0], CANONICAL[0]);
931        assert_eq!(plans[0][1], Shown::Var);
932        for plan in PLANS {
933            assert_ne!(plan, plans[0], "a shared plan would let a canonicalisation cycle");
934        }
935    }
936
937    /// Tier five is matched with the constant on the right and no other way.
938    ///
939    /// Every rule in it writes the constant there, so under the plan that shows a constant left
940    /// operand as a number none of them would match and under the plan that refuses a constant on
941    /// the right none of them would either. One plan, and it is the first of the shared three.
942    #[test]
943    fn a_comparison_rule_is_only_matched_with_the_constant_on_the_right() {
944        let (_, plans) = TABLES[3];
945        assert_eq!(plans.len(), 1);
946        assert_eq!(plans[0], COMPARE[0]);
947        assert_eq!(plans[0][0], Shown::Reg);
948        assert_eq!(plans[0][1], Shown::Const);
949    }
950
951    /// The edge of a type, at each width, read each way.
952    ///
953    /// The least and greatest unsigned value and the least and greatest signed one, which are the
954    /// four constants tier five is written against.
955    fn edges(width: u32) -> [(i128, bool); 4] {
956        let signed = 1i128 << (width - 1);
957        [(0, false), (-1, false), (-signed, true), (signed - 1, true)]
958    }
959
960    /// A comparison that its type has already answered becomes the answer.
961    ///
962    /// Nothing unsigned is below zero, everything unsigned is at least zero, and the same pair of
963    /// sentences holds at each of the other three edges. Thirty two rules, run as one test,
964    /// because what is being checked is the same sentence at four constants and four widths.
965    #[test]
966    fn a_comparison_against_the_edge_of_its_type_folds_to_a_bit() {
967        for width in [8u32, 16, 32, 64] {
968            let ty = Type::int(width);
969            for (edge, signed) in edges(width) {
970                // Below the edge is false at the bottom and above it is false at the top, and the
971                // other of each pair is the negation, so one table gives all four.
972                let below = edge == 0 || edge == -(1i128 << (width - 1));
973                let (false_pred, true_pred) = match (signed, below) {
974                    (false, true) => (IntPred::Ult, IntPred::Uge),
975                    (false, false) => (IntPred::Ugt, IntPred::Ule),
976                    (true, true) => (IntPred::Slt, IntPred::Sge),
977                    (true, false) => (IntPred::Sgt, IntPred::Sle),
978                };
979                // Minus one for the true bit, because the rule writes `(iconst.i1 1)` and one bit
980                // holding a one read signed is minus one, which is the same bit pattern and the
981                // reading everything else in the compiler takes of a true condition.
982                for (pred, answer) in [(false_pred, 0), (true_pred, -1)] {
983                    let (_, mut func, block) = blank();
984                    let x = func.append_param(block, ty);
985                    let mut build = Builder::new(&mut func, block);
986                    let bound = build.iconst(ty, edge);
987                    let cmp = build.icmp(pred, x, bound);
988                    build.ret(&[cmp]);
989                    assert!(simplify(&mut func), "i{width} {pred:?} {edge} was left alone");
990                    let got = returned(&func, block);
991                    assert_eq!(
992                        came_from(&func, got).0,
993                        Opcode::IConst,
994                        "i{width} {pred:?} {edge} did not fold"
995                    );
996                    assert_eq!(number(&func, got), answer, "i{width} {pred:?} {edge}");
997                    assert_eq!(func[got].ty, Type::int(1), "i{width} {pred:?} {edge} is a bit");
998                }
999            }
1000        }
1001    }
1002
1003    /// And one that is true or false for exactly one value becomes the test for that value.
1004    ///
1005    /// The predicate has to come from the rule. Every case here matched an ordering and every one
1006    /// of them has to leave `eq` or `ne`, so a rewriter that took the predicate from the
1007    /// instruction it replaced would leave the ordering in place and this would say so.
1008    #[test]
1009    fn a_comparison_true_for_one_value_becomes_a_test_for_that_value() {
1010        for width in [8u32, 16, 32, 64] {
1011            let ty = Type::int(width);
1012            for (edge, signed) in edges(width) {
1013                let below = edge == 0 || edge == -(1i128 << (width - 1));
1014                // At most the bottom is equality and above it is inequality, and at the top the
1015                // two swap over.
1016                let (eq_pred, ne_pred) = match (signed, below) {
1017                    (false, true) => (IntPred::Ule, IntPred::Ugt),
1018                    (false, false) => (IntPred::Uge, IntPred::Ult),
1019                    (true, true) => (IntPred::Sle, IntPred::Sgt),
1020                    (true, false) => (IntPred::Sge, IntPred::Slt),
1021                };
1022                for (pred, left) in [(eq_pred, IntPred::Eq), (ne_pred, IntPred::Ne)] {
1023                    let (_, mut func, block) = blank();
1024                    let x = func.append_param(block, ty);
1025                    let mut build = Builder::new(&mut func, block);
1026                    let bound = build.iconst(ty, edge);
1027                    let cmp = build.icmp(pred, x, bound);
1028                    build.ret(&[cmp]);
1029                    assert!(simplify(&mut func), "i{width} {pred:?} {edge} was left alone");
1030                    let got = returned(&func, block);
1031                    assert_eq!(
1032                        came_from(&func, got),
1033                        (Opcode::ICmp, Extra::IntPred(left)),
1034                        "i{width} {pred:?} {edge} kept the predicate it matched"
1035                    );
1036                    let args = operands(&func, got);
1037                    assert_eq!(args[0], x, "i{width} {pred:?} {edge} lost its value");
1038                    assert_eq!(number(&func, args[1]), edge, "i{width} {pred:?} {edge}");
1039                    // The width the rule was written at, which is the width of what is being
1040                    // compared and not the width of the answer. A constant built at the result's
1041                    // type would be a one bit zero standing where a wider one was asked for.
1042                    assert_eq!(func[args[1]].ty, ty, "i{width} {pred:?} {edge} narrowed its bound");
1043                }
1044            }
1045        }
1046    }
1047
1048    /// Every commutative operator tier three writes moves its constant to the right.
1049    ///
1050    /// One test over the five rather than five tests, because what is being checked is the same
1051    /// thing five times and the operator is the only part that differs.
1052    #[test]
1053    fn a_constant_on_the_left_of_a_commutative_operation_moves_to_the_right() {
1054        for opcode in [Opcode::Add, Opcode::Mul, Opcode::And, Opcode::Or, Opcode::Xor] {
1055            for width in [8, 16, 32, 64] {
1056                let ty = Type::int(width);
1057                let (_, mut func, block) = one_block(ty);
1058                let x = func.append_param(block, ty);
1059                let mut build = Builder::new(&mut func, block);
1060                // Three, because it is a number no identity in tier one is about and no strength
1061                // reduction in tier two is about, so the only rule that can fire is the one this
1062                // test is here for.
1063                let three = build.iconst(ty, 3);
1064                let value = build.binary(opcode, three, x, Flags::NONE);
1065                build.ret(&[value]);
1066                assert!(simplify(&mut func), "{opcode:?} at i{width} was left alone");
1067                let args = operands(&func, returned(&func, block));
1068                assert_eq!(came_from(&func, returned(&func, block)).0, opcode);
1069                assert_eq!(args[0], x, "{opcode:?} at i{width} kept the value on the right");
1070                assert_eq!(number(&func, args[1]), 3, "{opcode:?} at i{width} lost its constant");
1071            }
1072        }
1073    }
1074
1075    /// And an operation whose operands are both constants is left where it is.
1076    ///
1077    /// This is the termination argument, run rather than read. Without the plan that refuses a
1078    /// constant on the right, the rule above would match this, swap the two, match the swapped
1079    /// form, and go on doing it until the fuel ran out. Folding is what this instruction is for
1080    /// and `crate::fold` is where it happens.
1081    #[test]
1082    fn an_operation_on_two_constants_is_not_swapped_back_and_forth() {
1083        let i32 = Type::int(32);
1084        let (_, mut func, block) = one_block(i32);
1085        let mut build = Builder::new(&mut func, block);
1086        let three = build.iconst(i32, 3);
1087        let five = build.iconst(i32, 5);
1088        let sum = build.binary(Opcode::Add, three, five, Flags::NONE);
1089        build.ret(&[sum]);
1090        assert!(!simplify(&mut func), "the constants were rearranged rather than left to folding");
1091        let args = operands(&func, returned(&func, block));
1092        assert_eq!(number(&func, args[0]), 3);
1093        assert_eq!(number(&func, args[1]), 5);
1094    }
1095
1096    /// A constant already on the right stays there and nothing fires.
1097    ///
1098    /// The other half of the same argument. A canonicalisation that fired on the shape it produces
1099    /// would be a canonicalisation with no direction, which is what section 13.5 refuses.
1100    #[test]
1101    fn a_constant_already_on_the_right_is_left_alone() {
1102        let i32 = Type::int(32);
1103        let (_, mut func, block) = one_block(i32);
1104        let x = func.append_param(block, i32);
1105        let mut build = Builder::new(&mut func, block);
1106        let three = build.iconst(i32, 3);
1107        let sum = build.binary(Opcode::Add, x, three, Flags::NONE);
1108        build.ret(&[sum]);
1109        assert!(!simplify(&mut func));
1110        let args = operands(&func, returned(&func, block));
1111        assert_eq!(args[0], x);
1112        assert_eq!(number(&func, args[1]), 3);
1113    }
1114
1115    /// A subtraction is not commutative and nothing moves its constant.
1116    ///
1117    /// Turning `c - x` into anything is not what tier three does, and the rules are written per
1118    /// opcode rather than over a set of them, so this is asking whether the wrong opcode found its
1119    /// way into the file.
1120    #[test]
1121    fn a_subtraction_keeps_its_operands_where_they_are() {
1122        let i32 = Type::int(32);
1123        let (_, mut func, block) = one_block(i32);
1124        let x = func.append_param(block, i32);
1125        let mut build = Builder::new(&mut func, block);
1126        let three = build.iconst(i32, 3);
1127        let difference = build.binary(Opcode::Sub, three, x, Flags::NONE);
1128        build.ret(&[difference]);
1129        assert!(!simplify(&mut func));
1130        let args = operands(&func, returned(&func, block));
1131        assert_eq!(number(&func, args[0]), 3);
1132        assert_eq!(args[1], x);
1133    }
1134
1135    /// A block whose parameter and whose result are different widths, which is what every width
1136    /// rule needs and what `one_block` cannot give.
1137    fn narrow_to_wide(takes: Type, gives: Type) -> (Interner, Func, Block) {
1138        let mut names = Interner::new();
1139        let name = names.intern("f");
1140        let signature = Signature::new().with_params(&[takes]).with_returns(&[gives]);
1141        let mut func = Func::new(name, signature);
1142        let block = func.create_block();
1143        (names, func, block)
1144    }
1145
1146    /// A conversion of a conversion of a parameter, which is the shape every width rule matches.
1147    ///
1148    /// The parameter is at `from`, the inner conversion takes it to `through` and the outer one
1149    /// takes that to `to`, and what comes back is the function, the block and the parameter.
1150    fn chain(
1151        inner: Opcode,
1152        outer: Opcode,
1153        from: Type,
1154        through: Type,
1155        to: Type,
1156    ) -> (Func, Block, Value) {
1157        let (_, mut func, block) = narrow_to_wide(from, to);
1158        let x = func.append_param(block, from);
1159        let mut build = Builder::new(&mut func, block);
1160        let middle = build.unary(inner, x, through);
1161        let outside = build.unary(outer, middle, to);
1162        build.ret(&[outside]);
1163        (func, block, x)
1164    }
1165
1166    /// Truncating an extension back to the width it came from is the value that was there.
1167    ///
1168    /// Every pair of widths and both extensions, because the rule file writes all twelve and a
1169    /// test of one of them would say nothing about the other eleven.
1170    #[test]
1171    fn truncating_an_extension_back_to_its_own_width_gives_the_value_back() {
1172        for extend in [Opcode::SExt, Opcode::ZExt] {
1173            for (narrow, wide) in [(8, 16), (8, 32), (8, 64), (16, 32), (16, 64), (32, 64)] {
1174                let (from, through) = (Type::int(narrow), Type::int(wide));
1175                let (mut func, block, x) = chain(extend, Opcode::Trunc, from, through, from);
1176                assert!(simplify(&mut func), "{extend:?} i{narrow} to i{wide} was left alone");
1177                assert_eq!(
1178                    returned(&func, block),
1179                    x,
1180                    "{extend:?} i{narrow} to i{wide} and back did not give the value back"
1181                );
1182            }
1183        }
1184    }
1185
1186    /// Truncating an extension to a width still above the source is the same extension, stopping
1187    /// earlier.
1188    #[test]
1189    fn truncating_an_extension_above_its_source_is_a_shorter_extension() {
1190        let (mut func, block, x) =
1191            chain(Opcode::SExt, Opcode::Trunc, Type::int(8), Type::int(64), Type::int(16));
1192        assert!(simplify(&mut func));
1193        let result = returned(&func, block);
1194        assert_eq!(came_from(&func, result).0, Opcode::SExt);
1195        assert_eq!(operands(&func, result), vec![x]);
1196        assert_eq!(func[result].ty, Type::int(16));
1197    }
1198
1199    /// Truncating an extension to a width below the source is a truncation of the source, and
1200    /// which extension it was never mattered.
1201    #[test]
1202    fn truncating_an_extension_below_its_source_is_a_truncation_of_the_source() {
1203        let (mut func, block, x) =
1204            chain(Opcode::ZExt, Opcode::Trunc, Type::int(16), Type::int(32), Type::int(8));
1205        assert!(simplify(&mut func));
1206        let result = returned(&func, block);
1207        assert_eq!(came_from(&func, result).0, Opcode::Trunc);
1208        assert_eq!(operands(&func, result), vec![x]);
1209        assert_eq!(func[result].ty, Type::int(8));
1210    }
1211
1212    /// An extension of an extension is one extension, and a sign extension of a zero extension is
1213    /// a zero extension rather than a sign extension.
1214    #[test]
1215    fn an_extension_of_an_extension_is_one_extension() {
1216        for (inner, outer, want) in [
1217            (Opcode::ZExt, Opcode::ZExt, Opcode::ZExt),
1218            (Opcode::SExt, Opcode::SExt, Opcode::SExt),
1219            (Opcode::ZExt, Opcode::SExt, Opcode::ZExt),
1220        ] {
1221            let (mut func, block, x) =
1222                chain(inner, outer, Type::int(8), Type::int(16), Type::int(64));
1223            assert!(simplify(&mut func), "{outer:?} of {inner:?} was left alone");
1224            let result = returned(&func, block);
1225            assert_eq!(came_from(&func, result).0, want, "{outer:?} of {inner:?}");
1226            assert_eq!(operands(&func, result), vec![x]);
1227            assert_eq!(func[result].ty, Type::int(64));
1228        }
1229    }
1230
1231    /// A truncation of a truncation is one truncation, straight to the width the outer one asked
1232    /// for.
1233    ///
1234    /// The inner one threw away bits the outer one was going to throw away as well, so the width
1235    /// in the middle was never read and the rule goes to the outer width from the source. Both
1236    /// orderings of the three widths are tried, because a rule that picked the middle width rather
1237    /// than the outer one would still pass a test that only went from sixty four to eight through
1238    /// thirty two.
1239    #[test]
1240    fn a_truncation_of_a_truncation_is_one_truncation() {
1241        for (from, through, to) in [(64u32, 32u32, 16u32), (64, 32, 8), (64, 16, 8), (32, 16, 8)] {
1242            let (mut func, block, x) = chain(
1243                Opcode::Trunc,
1244                Opcode::Trunc,
1245                Type::int(from),
1246                Type::int(through),
1247                Type::int(to),
1248            );
1249            assert!(simplify(&mut func), "i{from} to i{through} to i{to} was left alone");
1250            let result = returned(&func, block);
1251            assert_eq!(came_from(&func, result).0, Opcode::Trunc, "i{from} to i{through} to i{to}");
1252            assert_eq!(operands(&func, result), vec![x]);
1253            assert_eq!(func[result].ty, Type::int(to));
1254        }
1255    }
1256
1257    /// And zero extending a sign extension is not one, because the bits the sign extension copied
1258    /// are bits of the value now and nothing above them is a function of the source alone.
1259    #[test]
1260    fn zero_extending_a_sign_extension_is_left_alone() {
1261        let (mut func, _, _) =
1262            chain(Opcode::SExt, Opcode::ZExt, Type::int(8), Type::int(16), Type::int(64));
1263        assert!(!simplify(&mut func), "a zero extension of a sign extension was rewritten");
1264    }
1265
1266    /// And zero extending a truncation is left alone, which is the rule the tier would be expected
1267    /// to have and does not.
1268    ///
1269    /// It was written and proved and then measured, and the measurement is why it went: the
1270    /// machine has one instruction for the pair already, the `and` with an immediate that replaced
1271    /// it is the longer encoding of the two, and the mask hides the narrowing from
1272    /// [`crate::narrow`]. The rule file says the whole of it. This is here so that somebody adding
1273    /// it back finds a test rather than a silence.
1274    #[test]
1275    fn zero_extending_a_truncation_is_left_alone() {
1276        let (mut func, _, _) =
1277            chain(Opcode::Trunc, Opcode::ZExt, Type::int(64), Type::int(32), Type::int(64));
1278        assert!(!simplify(&mut func), "a zero extension of a truncation became a mask");
1279    }
1280
1281    /// A width rule needs an operand something computed, and a parameter is not one.
1282    ///
1283    /// This is what the plan being an expanding one means at the bottom: there is no instruction
1284    /// under the operand to be the second level of the pattern, so nothing matches and nothing is
1285    /// rewritten. Said out loud because it is the case that would otherwise be a crash rather than
1286    /// a miss.
1287    #[test]
1288    fn a_width_rule_needs_an_operand_an_instruction_computed() {
1289        let (_, mut func, block) = narrow_to_wide(Type::int(64), Type::int(32));
1290        let x = func.append_param(block, Type::int(64));
1291        let mut build = Builder::new(&mut func, block);
1292        let narrowed = build.unary(Opcode::Trunc, x, Type::int(32));
1293        build.ret(&[narrowed]);
1294        assert!(!simplify(&mut func), "a truncation of a parameter was rewritten");
1295    }
1296
1297    #[test]
1298    fn adding_nothing_points_every_reader_at_the_operand() {
1299        let i32 = Type::int(32);
1300        let (_, mut func, block) = one_block(i32);
1301        let x = func.append_param(block, i32);
1302        let mut build = Builder::new(&mut func, block);
1303        let zero = build.iconst(i32, 0);
1304        let sum = build.binary(Opcode::Add, x, zero, Flags::NONE);
1305        build.ret(&[sum]);
1306        assert!(simplify(&mut func));
1307        // The `add` is still there, used by nothing, which is what dead code elimination is for.
1308        assert_eq!(returned(&func, block), x);
1309        assert_eq!(came_from(&func, sum).0, Opcode::Add);
1310    }
1311
1312    /// The constant on either side, since nothing puts it on the right yet and a rule written one
1313    /// way round would fire on half the additions it should.
1314    #[test]
1315    fn the_constant_is_found_on_either_side_of_an_identity() {
1316        for swapped in [false, true] {
1317            let i32 = Type::int(32);
1318            let (_, mut func, block) = one_block(i32);
1319            let x = func.append_param(block, i32);
1320            let mut build = Builder::new(&mut func, block);
1321            let zero = build.iconst(i32, 0);
1322            let (lhs, rhs) = if swapped { (zero, x) } else { (x, zero) };
1323            let sum = build.binary(Opcode::Add, lhs, rhs, Flags::NONE);
1324            build.ret(&[sum]);
1325            assert!(simplify(&mut func), "swapped {swapped}");
1326            assert_eq!(returned(&func, block), x, "swapped {swapped}");
1327        }
1328    }
1329
1330    #[test]
1331    fn multiplying_by_nothing_becomes_the_constant_where_it_stands() {
1332        let i32 = Type::int(32);
1333        let (_, mut func, block) = one_block(i32);
1334        let x = func.append_param(block, i32);
1335        let mut build = Builder::new(&mut func, block);
1336        let zero = build.iconst(i32, 0);
1337        let product = build.binary(Opcode::Mul, x, zero, Flags::NONE);
1338        build.ret(&[product]);
1339        assert!(simplify(&mut func));
1340        // The result value survives, which is the whole reason this half rewrites in place.
1341        assert_eq!(returned(&func, block), product);
1342        assert_eq!(came_from(&func, product).0, Opcode::IConst);
1343        assert_eq!(number(&func, product), 0);
1344    }
1345
1346    /// The two identities a pattern that writes one name twice exists for, at every width they
1347    /// are written at.
1348    #[test]
1349    fn a_value_against_itself() {
1350        for bits in [8, 16, 32, 64] {
1351            let ty = Type::int(bits);
1352            let (_, mut func, block) = one_block(ty);
1353            let x = func.append_param(block, ty);
1354            let mut build = Builder::new(&mut func, block);
1355            let both = build.binary(Opcode::And, x, x, Flags::NONE);
1356            build.ret(&[both]);
1357            assert!(simplify(&mut func), "{bits} bits");
1358            assert_eq!(returned(&func, block), x, "{bits} bits");
1359
1360            let (_, mut func, block) = one_block(ty);
1361            let x = func.append_param(block, ty);
1362            let mut build = Builder::new(&mut func, block);
1363            let nothing = build.binary(Opcode::Sub, x, x, Flags::NONE);
1364            build.ret(&[nothing]);
1365            assert!(simplify(&mut func), "{bits} bits");
1366            assert_eq!(number(&func, nothing), 0, "{bits} bits");
1367        }
1368    }
1369
1370    /// A remainder by one is nothing, and a division by one is the value. The pair is worth a
1371    /// test of its own because they are the two identities that produce different shapes from the
1372    /// same operands.
1373    #[test]
1374    fn dividing_by_one_and_the_remainder_that_goes_with_it() {
1375        let i32 = Type::int(32);
1376        let (_, mut func, block) = one_block(i32);
1377        let x = func.append_param(block, i32);
1378        let mut build = Builder::new(&mut func, block);
1379        let one = build.iconst(i32, 1);
1380        let quotient = build.binary(Opcode::SDiv, x, one, Flags::NONE);
1381        let rest = build.binary(Opcode::SRem, x, one, Flags::NONE);
1382        let sum = build.binary(Opcode::Add, quotient, rest, Flags::NONE);
1383        build.ret(&[sum]);
1384        assert!(simplify(&mut func));
1385        assert_eq!(number(&func, rest), 0);
1386        // The add reads the value the division was of, which is what the redirection did.
1387        let rucc_ir::Def::Result { inst, .. } = func[sum].def else { panic!("not a result") };
1388        assert_eq!(func[func[inst].args][0], x);
1389    }
1390
1391    /// All ones at one bit is the `1` the rule file writes, and the front end writes it as `-1`.
1392    /// The two are the same bit and the rule has to fire on what the front end wrote.
1393    #[test]
1394    fn all_ones_at_one_bit_is_the_one_the_front_end_writes() {
1395        for written in [-1, 1] {
1396            let bit = Type::int(1);
1397            let (_, mut func, block) = one_block(bit);
1398            let x = func.append_param(block, bit);
1399            let mut build = Builder::new(&mut func, block);
1400            let ones = build.iconst(bit, written);
1401            let kept = build.binary(Opcode::And, x, ones, Flags::NONE);
1402            build.ret(&[kept]);
1403            assert!(simplify(&mut func), "written as {written}");
1404            assert_eq!(returned(&func, block), x, "written as {written}");
1405        }
1406    }
1407
1408    /// One identity feeding another is followed all the way, so the second is worth as much as
1409    /// the first. The redirections are applied once at the end of the run, and this is what says
1410    /// that costs nothing.
1411    #[test]
1412    fn one_identity_feeding_another_is_followed_to_the_end() {
1413        let i32 = Type::int(32);
1414        let (_, mut func, block) = one_block(i32);
1415        let x = func.append_param(block, i32);
1416        let mut build = Builder::new(&mut func, block);
1417        let zero = build.iconst(i32, 0);
1418        let one = build.iconst(i32, 1);
1419        let sum = build.binary(Opcode::Add, x, zero, Flags::NONE);
1420        let product = build.binary(Opcode::Mul, sum, one, Flags::NONE);
1421        let shifted = build.binary(Opcode::Shl, product, zero, Flags::NONE);
1422        build.ret(&[shifted]);
1423        assert!(simplify(&mut func));
1424        assert_eq!(returned(&func, block), x);
1425    }
1426
1427    #[test]
1428    fn an_instruction_no_rule_is_about_is_left_alone() {
1429        // Multiplying by three. Two is tier two and is an addition, and one and zero are tier one,
1430        // so three is the smallest constant no tier written yet has anything to say about. Turning
1431        // it into a shift and an add is the rest of tier two and is issue 523.
1432        let i32 = Type::int(32);
1433        let (_, mut func, block) = one_block(i32);
1434        let x = func.append_param(block, i32);
1435        let mut build = Builder::new(&mut func, block);
1436        let three = build.iconst(i32, 3);
1437        let tripled = build.binary(Opcode::Mul, x, three, Flags::NONE);
1438        build.ret(&[tripled]);
1439        assert!(!simplify(&mut func), "no rule is about multiplying by three");
1440        assert_eq!(returned(&func, block), tripled);
1441        assert_eq!(came_from(&func, tripled).0, Opcode::Mul);
1442    }
1443
1444    #[test]
1445    fn multiplying_by_two_becomes_an_addition_of_the_value_with_itself() {
1446        let i32 = Type::int(32);
1447        let (_, mut func, block) = one_block(i32);
1448        let x = func.append_param(block, i32);
1449        let mut build = Builder::new(&mut func, block);
1450        let two = build.iconst(i32, 2);
1451        let doubled = build.binary(Opcode::Mul, x, two, Flags::NONE);
1452        build.ret(&[doubled]);
1453        assert!(simplify(&mut func));
1454        // In place, so the value the return reads is the one it always read.
1455        assert_eq!(returned(&func, block), doubled);
1456        assert_eq!(came_from(&func, doubled).0, Opcode::Add);
1457        assert_eq!(operands(&func, doubled), [x, x]);
1458    }
1459
1460    #[test]
1461    fn multiplying_by_minus_one_becomes_a_subtraction_from_a_zero_the_rewrite_defines() {
1462        // The other shape of operand: nothing in the function holds a zero, so the rewrite has to
1463        // put one in front of the instruction it is rewriting.
1464        let i32 = Type::int(32);
1465        let (_, mut func, block) = one_block(i32);
1466        let x = func.append_param(block, i32);
1467        let mut build = Builder::new(&mut func, block);
1468        let minus = build.iconst(i32, -1);
1469        let negated = build.binary(Opcode::Mul, x, minus, Flags::NONE);
1470        build.ret(&[negated]);
1471        assert!(simplify(&mut func));
1472        assert_eq!(returned(&func, block), negated);
1473        assert_eq!(came_from(&func, negated).0, Opcode::Sub);
1474        let args = operands(&func, negated);
1475        assert_eq!(number(&func, args[0]), 0);
1476        assert_eq!(args[1], x);
1477    }
1478
1479    #[test]
1480    fn the_flags_of_the_instruction_a_strength_reduction_replaces_do_not_come_with_it() {
1481        // An `nsw` on a multiplication is a promise about that multiplication. The addition below
1482        // may well keep it, and a promise carried across a rewrite because it probably still holds
1483        // is how a wrong one gets made.
1484        let i32 = Type::int(32);
1485        let (_, mut func, block) = one_block(i32);
1486        let x = func.append_param(block, i32);
1487        let mut build = Builder::new(&mut func, block);
1488        let two = build.iconst(i32, 2);
1489        let doubled = build.binary(Opcode::Mul, x, two, Flags::NSW);
1490        build.ret(&[doubled]);
1491        assert!(simplify(&mut func));
1492        let rucc_ir::Def::Result { inst, .. } = func[doubled].def else { panic!("not a result") };
1493        assert_eq!(func[inst].flags, Flags::NONE);
1494    }
1495
1496    #[test]
1497    fn a_strength_reduction_leaves_the_verifier_nothing_to_complain_about() {
1498        // The zero the negation needs is defined in front of the instruction that reads it, and
1499        // whether it really is in front of it is a question about the block rather than about the
1500        // instruction, which is what the verifier is for.
1501        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
1502        let i32 = Type::int(32);
1503        let (mut names, mut func, block) = one_block(i32);
1504        let mut module = Module::new(names.intern("test.c"), &target);
1505        let x = func.append_param(block, i32);
1506        let mut build = Builder::new(&mut func, block);
1507        let minus = build.iconst(i32, -1);
1508        let negated = build.binary(Opcode::Mul, x, minus, Flags::NONE);
1509        let two = build.iconst(i32, 2);
1510        let doubled = build.binary(Opcode::Mul, negated, two, Flags::NONE);
1511        build.ret(&[doubled]);
1512        assert!(simplify(&mut func));
1513        module.add_func(func);
1514        rucc_ir::verify(&module, &names).expect("the pass left the function verifiable");
1515    }
1516
1517    /// The function the pass leaves is still one the verifier accepts. Pointing a reader at a
1518    /// different value and turning an instruction into a constant are both things a rewrite could
1519    /// get wrong in a way none of the tests above would notice, because each of those asks about
1520    /// one instruction and this asks about the function.
1521    #[test]
1522    fn the_pass_leaves_the_verifier_nothing_to_complain_about() {
1523        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
1524        let i32 = Type::int(32);
1525        let (mut names, mut func, block) = one_block(i32);
1526        let mut module = Module::new(names.intern("test.c"), &target);
1527        let x = func.append_param(block, i32);
1528        let mut build = Builder::new(&mut func, block);
1529        let zero = build.iconst(i32, 0);
1530        let one = build.iconst(i32, 1);
1531        let sum = build.binary(Opcode::Add, x, zero, Flags::NONE);
1532        let product = build.binary(Opcode::Mul, sum, one, Flags::NONE);
1533        let gone = build.binary(Opcode::Sub, product, product, Flags::NONE);
1534        let total = build.binary(Opcode::Add, product, gone, Flags::NONE);
1535        build.ret(&[total]);
1536        assert!(simplify(&mut func));
1537        module.add_func(func);
1538        rucc_ir::verify(&module, &names).expect("the pass left the function verifiable");
1539    }
1540
1541    #[test]
1542    fn fuel_stops_an_identity_and_not_the_walk() {
1543        let i32 = Type::int(32);
1544        let (_, mut func, block) = one_block(i32);
1545        let x = func.append_param(block, i32);
1546        let mut build = Builder::new(&mut func, block);
1547        let zero = build.iconst(i32, 0);
1548        let first = build.binary(Opcode::Add, x, zero, Flags::NONE);
1549        let second = build.binary(Opcode::Sub, x, zero, Flags::NONE);
1550        let sum = build.binary(Opcode::Add, first, second, Flags::NONE);
1551        build.ret(&[sum]);
1552        let stats = Simplify.run(&mut func, &mut Analyses::new(), &mut Fuel::of(1));
1553        assert!(stats.changed());
1554        assert_eq!(stats.total(Kind::Optimized), 1);
1555        assert_eq!(stats.count(Kind::Missed, super::NO_FUEL_RULE), 1);
1556        // The first fired and the second did not, and the second is still read by the add.
1557        let rucc_ir::Def::Result { inst, .. } = func[sum].def else { panic!("not a result") };
1558        assert_eq!(func[func[inst].args], [x, second]);
1559    }
1560
1561    #[test]
1562    fn a_negated_float_comparison_becomes_the_opposite_predicate() {
1563        // Every ordered predicate and its opposite, which is the table `!(x < y)` is `x >= y`
1564        // or unordered lives in, and the one place a sign error would hide.
1565        for pred in FloatPred::all() {
1566            let (_, mut func, block) = blank();
1567            let mut build = Builder::new(&mut func, block);
1568            let x = build.iconst(Type::int(64), 0);
1569            let x = build.unary(Opcode::Bitcast, x, Type::float(Float::F64));
1570            let cmp = build.fcmp(pred, x, x, Flags::NONE);
1571            let ones = build.iconst(Type::int(1), -1);
1572            let not = build.binary(Opcode::Xor, cmp, ones, Flags::NONE);
1573            build.ret(&[not]);
1574            assert!(simplify(&mut func), "{pred:?}");
1575            assert_eq!(
1576                came_from(&func, not),
1577                (Opcode::FCmp, Extra::FloatPred(pred.inverse())),
1578                "{pred:?}"
1579            );
1580        }
1581    }
1582
1583    #[test]
1584    fn a_negated_integer_comparison_becomes_the_opposite_predicate() {
1585        for pred in IntPred::all() {
1586            let (_, mut func, block) = blank();
1587            let mut build = Builder::new(&mut func, block);
1588            let x = build.iconst(Type::int(32), 3);
1589            let cmp = build.icmp(pred, x, x);
1590            let ones = build.iconst(Type::int(1), -1);
1591            let not = build.binary(Opcode::Xor, cmp, ones, Flags::NONE);
1592            build.ret(&[not]);
1593            assert!(simplify(&mut func), "{pred:?}");
1594            assert_eq!(
1595                came_from(&func, not),
1596                (Opcode::ICmp, Extra::IntPred(pred.inverse())),
1597                "{pred:?}"
1598            );
1599        }
1600    }
1601
1602    #[test]
1603    fn the_constant_is_found_on_either_side() {
1604        for swapped in [false, true] {
1605            let (_, mut func, block) = blank();
1606            let mut build = Builder::new(&mut func, block);
1607            let x = build.iconst(Type::int(32), 3);
1608            let cmp = build.icmp(IntPred::Slt, x, x);
1609            let ones = build.iconst(Type::int(1), -1);
1610            let (lhs, rhs) = if swapped { (ones, cmp) } else { (cmp, ones) };
1611            let not = build.binary(Opcode::Xor, lhs, rhs, Flags::NONE);
1612            build.ret(&[not]);
1613            assert!(simplify(&mut func), "swapped {swapped}");
1614            assert_eq!(came_from(&func, not).1, Extra::IntPred(IntPred::Sge));
1615        }
1616    }
1617
1618    #[test]
1619    fn an_exclusive_or_of_two_comparisons_is_left_alone() {
1620        let (_, mut func, block) = blank();
1621        let mut build = Builder::new(&mut func, block);
1622        let x = build.iconst(Type::int(32), 3);
1623        let a = build.icmp(IntPred::Slt, x, x);
1624        let b = build.icmp(IntPred::Sgt, x, x);
1625        let differ = build.binary(Opcode::Xor, a, b, Flags::NONE);
1626        build.ret(&[differ]);
1627        assert!(!simplify(&mut func));
1628        assert_eq!(came_from(&func, differ).0, Opcode::Xor);
1629    }
1630
1631    #[test]
1632    fn an_exclusive_or_of_something_that_is_not_a_comparison_is_left_alone() {
1633        let (_, mut func, block) = blank();
1634        let mut build = Builder::new(&mut func, block);
1635        let x = build.iconst(Type::int(32), 3);
1636        let narrow = build.unary(Opcode::Trunc, x, Type::int(1));
1637        let ones = build.iconst(Type::int(1), -1);
1638        let not = build.binary(Opcode::Xor, narrow, ones, Flags::NONE);
1639        build.ret(&[not]);
1640        assert!(!simplify(&mut func));
1641        assert_eq!(came_from(&func, not).0, Opcode::Xor);
1642    }
1643
1644    #[test]
1645    fn a_wider_exclusive_or_with_one_is_not_a_negation_and_is_left_alone() {
1646        let (_, mut func, block) = blank();
1647        let mut build = Builder::new(&mut func, block);
1648        let x = build.iconst(Type::int(32), 3);
1649        let cmp = build.icmp(IntPred::Slt, x, x);
1650        let wide = build.unary(Opcode::ZExt, cmp, Type::int(32));
1651        let one = build.iconst(Type::int(32), 1);
1652        let flipped = build.binary(Opcode::Xor, wide, one, Flags::NONE);
1653        let narrow = build.unary(Opcode::Trunc, flipped, Type::int(1));
1654        build.ret(&[narrow]);
1655        assert!(!simplify(&mut func), "an i32 xor 1 flips one bit of thirty two");
1656        assert_eq!(came_from(&func, flipped).0, Opcode::Xor);
1657    }
1658
1659    #[test]
1660    fn the_comparisons_flags_travel_with_the_predicate() {
1661        let (_, mut func, block) = blank();
1662        let mut build = Builder::new(&mut func, block);
1663        let x = build.iconst(Type::int(64), 0);
1664        let x = build.unary(Opcode::Bitcast, x, Type::float(Float::F64));
1665        let cmp = build.fcmp(FloatPred::Olt, x, x, Flags::FAST);
1666        let ones = build.iconst(Type::int(1), -1);
1667        let not = build.binary(Opcode::Xor, cmp, ones, Flags::NONE);
1668        build.ret(&[not]);
1669        assert!(simplify(&mut func));
1670        let rucc_ir::Def::Result { inst, .. } = func[not].def else { panic!("not a result") };
1671        // The promise the original comparison was made under, not the exclusive or's absence of
1672        // one. Dropping it would be correct and would quietly undo a fast math flag.
1673        assert_eq!(func[inst].flags, Flags::FAST);
1674    }
1675
1676    #[test]
1677    fn fuel_stops_the_transformation_and_not_the_walk() {
1678        let (_, mut func, block) = blank();
1679        let mut build = Builder::new(&mut func, block);
1680        let x = build.iconst(Type::int(32), 3);
1681        let a = build.icmp(IntPred::Slt, x, x);
1682        let b = build.icmp(IntPred::Sgt, x, x);
1683        let ones = build.iconst(Type::int(1), -1);
1684        let first = build.binary(Opcode::Xor, a, ones, Flags::NONE);
1685        let second = build.binary(Opcode::Xor, b, ones, Flags::NONE);
1686        let both = build.binary(Opcode::And, first, second, Flags::NONE);
1687        build.ret(&[both]);
1688        let stats = Simplify.run(&mut func, &mut Analyses::new(), &mut Fuel::of(1));
1689        assert!(stats.changed());
1690        assert_eq!(stats.count(Kind::Optimized, super::FLIPPED), 1);
1691        assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
1692        assert_eq!(came_from(&func, first).0, Opcode::ICmp);
1693        assert_eq!(came_from(&func, second).0, Opcode::Xor);
1694    }
1695}