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