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//! Three tiers of `spec/optimizer/13-rewrite-rules.md` section 13.4 so far, tried in the order
21//! they are numbered.
22//!
23//! Tier one is the identities. Adding nothing, multiplying by one, and'ing a value with itself.
24//! None of them needs anything known about the operands and each leaves a term strictly smaller
25//! than the one it replaced.
26//!
27//! Tier two is the strength reductions, which swap an operation for a cheaper one rather than
28//! taking one away: multiplying by two is an addition, and multiplying or dividing by minus one is
29//! a subtraction from nothing. Tier one is tried first because losing an operation beats swapping
30//! one.
31//!
32//! Tier three is the canonicalisations, which put the constant of a commutative operation on the
33//! right. They make nothing smaller and nothing faster. What they do is halve how many ways a term
34//! can be written, so that every rule above them needs one variant where it needs two today, and
35//! so that hash consing can see two spellings of one expression as one. They are tried last,
36//! because rearranging a term is only worth doing when no rule that improves it fires.
37//!
38//! Every rule in all three has been proved against `crates/rucc-ir/rules/ir.model` by
39//! `rucc-verify` before it may be used.
40//!
41//! Which plans a tier is matched under belongs to the tier. Tiers one and two are matched with
42//! either operand offered as a number, since a rule about a constant should fire whichever side it
43//! was written on. Tier three is matched with the left operand offered as a number and the right
44//! one refused if it is one, which is what makes a rule that moves the constant across fire once
45//! rather than forever.
46//!
47//! What a rule leaves behind is one of three things. `(value.iN x)` means the result is a value
48//! the function already has, so every use of the result is pointed at that value and the
49//! instruction is left for [`crate::dce`]. `(iconst.iN k)` means the result is a constant, and the
50//! instruction becomes that constant where it stands, which keeps the result value and is why
51//! nothing else has to be rewritten for that half. An instruction means this one becomes that one
52//! where it stands, which keeps the result value for the same reason, and an operand of it the
53//! rule wrote as a number gets an `iconst` in front of the instruction to hold it.
54//!
55//! ## The one written by hand
56//!
57//! An exclusive or of a comparison with an `i1` of all ones is that comparison with the opposite
58//! predicate. That is issue 379, and it is worth more than the instruction it saves.
59//!
60//! C spells eight of the sixteen floating point predicates. The six relational and equality
61//! operators give the six ordered ones, `!=` gives `une`, and `__builtin_isunordered` gives `uno`.
62//! The other eight are what the negation of one of those means, and the front end writes a
63//! negation as an exclusive or rather than as a flipped predicate, so `!(x < y)` lowers to an
64//! `fcmp olt` and an `xor` where the machine has an `fcmp uge`. Twelve rules in the x86-64 rule
65//! set are written on those predicates and none of them has ever fired, over the whole torture
66//! suite at every optimization level, because no IR that reaches selection contains one.
67//!
68//! The integer case comes with it. `!(a < b)` on integers is the same shape, the same rewrite and
69//! the same saving, and leaving it out because the coverage report did not complain about it would
70//! be picking the rewrite by what measures it rather than by what it does.
71//!
72//! # Why it needs dead code elimination after it
73//!
74//! The rewrite turns the `xor` into the comparison and leaves the original comparison where it
75//! was, used by nothing when the negation was its only reader. Rewriting in place keeps the
76//! result value, so every use of it is already correct and there is nothing to rewrite, and what
77//! is left over is exactly what [`crate::dce`] takes out. That is why the pipeline runs the two in
78//! this order, and it is why the pass before the dead code eliminator was written first.
79//!
80//! An identity that produces a value leaves the same kind of litter for the same reason. The
81//! instruction it fired on reads what it always read and nothing reads it, so it is dead, and
82//! taking it out here would mean deciding whether its operands are still read by anything, which
83//! is the question the dead code eliminator answers for the whole function at once.
84
85use std::collections::HashMap;
86use std::sync::OnceLock;
87
88use rucc_ir::term::{PLAIN, Plan, Shown, Term, Terms};
89use rucc_ir::{Block, Def, Extra, Flags, Func, Imm, Inst, InstData, Opcode, Type, Value};
90
91use crate::rules::{Match, Piece, Table, canonical, identities, strength};
92use crate::uses::count;
93use crate::{Analyses, Analysis, Fuel, Pass, Preserved, Stats};
94
95/// Recorded once for each negation folded into the comparison under it.
96const FLIPPED: &str = "comparison negated by an exclusive or rewritten as the opposite comparison";
97
98/// Recorded for a negation that would have folded if there had been fuel for it.
99const NO_FUEL: &str = "negated comparison left alone, the pass ran out of fuel";
100
101/// Recorded for a rule that would have fired if there had been fuel for it.
102const NO_FUEL_RULE: &str = "rewrite left alone, the pass ran out of fuel";
103
104/// How each operand of an instruction is shown to the matcher, and in what order the ways are
105/// tried.
106///
107/// The two with a constant come first, because a rule about a number is the more specific one and
108/// an operand that is not a constant declines it at the first node of the trie. Nothing here
109/// expands an operand into the instruction that computed it, since no tier one identity is about
110/// two instructions at once.
111const PLANS: [Plan; 3] =
112    [[Shown::Reg, Shown::Const, Shown::Reg], [Shown::Const, Shown::Reg, Shown::Reg], PLAIN];
113
114/// How the operands are shown to a canonicalisation, which is the one plan tier three is matched
115/// under.
116///
117/// A canonicalisation moves the constant to the right, so the left operand has to be the number
118/// and the right one has to be something that is not, or the rule swaps a pair of constants back
119/// and forth until the pass runs out of fuel. [`Shown::Var`] is what says the right one is not a
120/// number. The plans above cannot be reused here for exactly that reason: the second of them
121/// shows a constant left operand as a number and a constant right operand as a register, which is
122/// the cycling match.
123const CANONICAL: [Plan; 1] = [[Shown::Const, Shown::Var, Shown::Reg]];
124
125/// The rule tables, one per tier, in the order they are tried, each with the plans it is matched
126/// under.
127///
128/// Tier one first, because an identity takes an operation away and a strength reduction swaps one
129/// for another, so a term both have something to say about is better off losing the operation.
130/// Tier three last, because a canonicalisation only makes a term easier for another rule to be
131/// about and there is no reason to reach for it while a rule that improves the code still fires.
132///
133/// The plans belong to the table rather than to the loop because a tier is written against them.
134/// Tier three is only correct under the one plan that refuses a constant on the right, and a
135/// table matched under a plan it was not written for is a table whose rules mean something else.
136const TABLES: [(&Table, &[Plan]); 3] =
137    [(&identities::TABLE, &PLANS), (&strength::TABLE, &PLANS), (&canonical::TABLE, &CANONICAL)];
138
139/// The pass. It holds nothing, because a peephole needs to know nothing beyond the pattern.
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141pub struct Simplify;
142
143impl Pass for Simplify {
144    fn name(&self) -> &'static str {
145        "simplify"
146    }
147
148    fn describe(&self) -> &'static str {
149        "the identities, the strength reductions, the canonicalisations, and a negated comparison \
150         as the opposite one"
151    }
152
153    fn preserves(&self) -> Preserved {
154        // Everything about the shape of the function. No block is added, none is removed and no
155        // edge moves, so the graph and everything built out of it stand.
156        //
157        // The liveness does not, and that is the whole of the difference. An identity that
158        // produces a value points every reader of one value at another, which is one more place
159        // the second is live and one fewer the first is, and the same is true of the negation
160        // below, which reads the comparison's operands where it used to read its result.
161        //
162        // A rule that writes an instruction with a constant in it puts one in the block, and that
163        // is still the same answer. It adds a value nothing else mentions, in the block it is
164        // read in, and it ends every path it starts on, so nothing about the shape of the
165        // function moves and the only analysis with something new to say about it is the one
166        // already given up.
167        Preserved::ALL.without(Analysis::Liveness)
168    }
169
170    fn run(&self, func: &mut Func, _an: &mut Analyses, fuel: &mut Fuel) -> Stats {
171        let mut stats = Stats::new();
172        // What a rule that produced a value decided, applied to the whole function at the end.
173        // Rewriting each one where it is found would be a walk over every instruction for every
174        // rewrite, and there is nothing to be gained by it: what a pattern asks about is the
175        // instruction and its operands, and neither changes under a redirection.
176        let mut forward: HashMap<Value, Value> = HashMap::new();
177        // Who reads what, so that an instruction nothing reads is left alone. A rule that fires
178        // on one changes no program, because what it does is point the readers somewhere else and
179        // there are none, and it would still spend fuel and still report having optimized
180        // something. That matters here more than it would in a pass that runs once: this pass is
181        // named twice in every pipeline above `-O0`, an identity it takes stays in the function
182        // until dead code elimination removes it, and without this the second run would rewrite
183        // everything the first run did all over again and say so.
184        //
185        // Stale by design. It is what the function looked like when this run started, and a
186        // rewrite below only ever removes readers, so a value this says nothing reads is a value
187        // nothing reads.
188        let uses = count(func);
189        let dead = |func: &Func, inst: Inst| match func[inst].first_result {
190            Some(result) => uses[result.index()] == 0,
191            None => false,
192        };
193        for block in func.blocks().collect::<Vec<Block>>() {
194            for inst in func.insts(block).collect::<Vec<Inst>>() {
195                if dead(func, inst) {
196                    continue;
197                }
198                if let Some(flip) = negated_comparison(func, inst) {
199                    if !fuel.take() {
200                        // Out of fuel, which stops the transforming rather than the looking, the
201                        // same way the other two passes treat it. The walk is the same walk at
202                        // every fuel setting, which is what makes bisecting over it monotonic.
203                        stats.missed(NO_FUEL);
204                        continue;
205                    }
206                    let args = func.push_values(&[flip.lhs, flip.rhs]);
207                    let data = &mut func[inst];
208                    data.opcode = flip.opcode;
209                    data.flags = flip.flags;
210                    data.args = args;
211                    data.extra = flip.extra;
212                    stats.optimized(FLIPPED);
213                    continue;
214                }
215                let Some((rewrite, pattern)) = identity(func, inst) else { continue };
216                if !fuel.take() {
217                    stats.missed(NO_FUEL_RULE);
218                    continue;
219                }
220                match rewrite {
221                    Rewrite::Value(value) => {
222                        let result = func[inst].first_result.expect("the rule matched a result");
223                        forward.insert(result, value);
224                    }
225                    Rewrite::Constant(number) => become_constant(func, inst, number),
226                    Rewrite::Built { opcode, lhs, rhs } => {
227                        become_instruction(func, inst, opcode, lhs, rhs);
228                    }
229                }
230                stats.optimized(pattern);
231            }
232        }
233        if !forward.is_empty() {
234            substitute(func, &forward);
235        }
236        stats
237    }
238}
239
240/// What a rule says an instruction's result is instead.
241#[derive(Clone, Copy, Debug, PartialEq, Eq)]
242enum Rewrite {
243    /// A value the function already has, which every reader of the result is pointed at.
244    Value(Value),
245    /// A number, which the instruction becomes where it stands.
246    Constant(i128),
247    /// Another instruction, which this one becomes where it stands.
248    Built {
249        /// What it is.
250        opcode: Opcode,
251        /// Its left operand.
252        lhs: Operand,
253        /// Its right operand.
254        rhs: Operand,
255    },
256}
257
258/// One operand of an instruction a rule writes.
259#[derive(Clone, Copy, Debug, PartialEq, Eq)]
260enum Operand {
261    /// A value the pattern bound.
262    Value(Value),
263    /// A number the rule wrote, which needs an `iconst` in front of the instruction before it is
264    /// an operand at all, because an operand in this IR is a value and a number is not one until
265    /// something defines it.
266    Constant(i128),
267}
268
269/// The rule that fires on this instruction, and the pattern it came from.
270///
271/// The plans are tried in order and the first that matches wins. A plan is how the operands are
272/// shown rather than what they are, so trying three of them is three walks over a trie, each of
273/// which fails in its first node or two when the instruction is not one any rule is about.
274fn identity(func: &Func, inst: Inst) -> Option<(Rewrite, &'static str)> {
275    let result = func[inst].first_result?;
276    for (table, plan) in
277        TABLES.into_iter().flat_map(|(table, plans)| plans.iter().map(move |&plan| (table, plan)))
278    {
279        let terms = Terms::new(func, inst, plan);
280        let Some(found) = table.find(&terms, Term::Root) else { continue };
281        let rule = table.rule(&found);
282        let rewrite = match rule.replacement {
283            // A value the pattern bound, which is a register because that is the only thing a
284            // `value.iN` binds.
285            [Piece::App { head, arity: 1 }, Piece::Var { index, .. }]
286                if head.starts_with("value.") =>
287            {
288                match found.bindings.get(*index) {
289                    Some(&Term::Reg(value)) => Rewrite::Value(value),
290                    _ => continue,
291                }
292            }
293            // A constant written in the rule. Only at a width the instruction's result has, which
294            // it always does: an `iconst.iN` names an integer width and a rule is proved at the
295            // width it is written at.
296            [Piece::App { head, arity: 1 }, Piece::Int(number)]
297                if head.starts_with("iconst.") && func[result].ty.is_int() =>
298            {
299                Rewrite::Constant(*number)
300            }
301            // An instruction the rule writes, which this one becomes. That is the third shape and
302            // the last one: a replacement deeper than one instruction would need somewhere to put
303            // the ones under it, and a rule that wanted it can be written as two rules that each
304            // leave one.
305            pieces => match built(pieces, &found) {
306                Some(rewrite) => rewrite,
307                // Any other shape, which no rule in the file has. A test below says so, because a
308                // rule that fell through here would be a rule that never fires and nothing would
309                // say it had stopped.
310                None => continue,
311            },
312        };
313        return Some((rewrite, rule.pattern));
314    }
315    None
316}
317
318/// The instruction a rule writes, out of the pieces its replacement flattened into.
319///
320/// Two operands under a head that names an opcode, each of them either a value the pattern bound
321/// or a number the rule wrote. Anything else is nothing this pass can build, and the answer to
322/// one is that the rule does not fire, which the test over the whole table turns into a failure
323/// rather than a silence.
324fn built(pieces: &'static [Piece], found: &Match<Term>) -> Option<Rewrite> {
325    let [Piece::App { head, arity: 2 }, rest @ ..] = pieces else { return None };
326    let opcode = opcode_of(head)?;
327    let (lhs, rest) = operand(rest, found)?;
328    let (rhs, rest) = operand(rest, found)?;
329    rest.is_empty().then_some(Rewrite::Built { opcode, lhs, rhs })
330}
331
332/// One operand of that instruction, and the pieces after it.
333fn operand(pieces: &'static [Piece], found: &Match<Term>) -> Option<(Operand, &'static [Piece])> {
334    match pieces {
335        [Piece::App { head, arity: 1 }, Piece::Var { index, .. }, rest @ ..]
336            if head.starts_with("value.") =>
337        {
338            match found.bindings.get(*index) {
339                Some(&Term::Reg(value)) => Some((Operand::Value(value), rest)),
340                _ => None,
341            }
342        }
343        [Piece::App { head, arity: 1 }, Piece::Int(number), rest @ ..]
344            if head.starts_with("iconst.") =>
345        {
346            Some((Operand::Constant(*number), rest))
347        }
348        // A number the pattern bound rather than one the rule wrote. This is what a
349        // canonicalisation needs: it moves the operand it matched to the other side, and what it
350        // matched was whatever number happened to be there.
351        [Piece::App { head, arity: 1 }, Piece::Var { index, .. }, rest @ ..]
352            if head.starts_with("iconst.") =>
353        {
354            match found.bindings.get(*index) {
355                Some(&Term::Num(number)) => Some((Operand::Constant(number), rest)),
356                _ => None,
357            }
358        }
359        _ => None,
360    }
361}
362
363/// The opcode a replacement head names, or nothing if the rules have no instruction by that name.
364///
365/// Built the once out of [`rucc_ir::term::heads`], which is where the name of the instruction a
366/// pattern matched comes from as well, so a rule whose replacement this pass can build is a rule
367/// written in the vocabulary it matched with. A table here would be a second vocabulary and the
368/// two would drift.
369///
370/// A name two opcodes answer to belongs to the first of them, which is the general one:
371/// `ptr_add` is an add at the address width and is named as one, and a rule that writes `add` is
372/// asking for the add.
373fn opcode_of(head: &str) -> Option<Opcode> {
374    static NAMES: OnceLock<HashMap<&'static str, Opcode>> = OnceLock::new();
375    let names = NAMES.get_or_init(|| {
376        let mut names = HashMap::new();
377        for (opcode, name) in rucc_ir::term::heads() {
378            names.entry(name).or_insert(opcode);
379        }
380        names
381    });
382    names.get(head).copied()
383}
384
385/// Turns an instruction into the one a rule says computes the same thing.
386///
387/// In place, like the constant below and for the same reason: the result value survives, so every
388/// reader of it is already right and there is nothing to redirect.
389fn become_instruction(func: &mut Func, inst: Inst, opcode: Opcode, lhs: Operand, rhs: Operand) {
390    let result = func[inst].first_result.expect("the rule matched a result");
391    let ty = func[result].ty;
392    let lhs = defined(func, inst, ty, lhs);
393    let rhs = defined(func, inst, ty, rhs);
394    let args = func.push_values(&[lhs, rhs]);
395    let data = &mut func[inst];
396    data.opcode = opcode;
397    data.args = args;
398    // Nothing a rule writes carries an extra, and what was there belonged to the instruction that
399    // is gone. A predicate on a comparison is the case that matters: a rule rewriting one into an
400    // addition that left the predicate behind would leave an addition claiming to be `slt`.
401    data.extra = Extra::None;
402    // The flags go with the instruction that had them, the same as for a constant. An `nsw` on a
403    // multiplication is a promise about that multiplication, and the addition that replaces it is
404    // a different instruction. The promise may well still hold, and carrying one across a rewrite
405    // because it probably still holds is how a wrong one gets made. Dropping it costs a later
406    // pass an assumption and costs no program its meaning.
407    data.flags = Flags::NONE;
408}
409
410/// An operand as a value, defining it in front of the instruction if the rule wrote a number.
411fn defined(func: &mut Func, before: Inst, ty: Type, operand: Operand) -> Value {
412    match operand {
413        Operand::Value(value) => value,
414        Operand::Constant(number) => {
415            let at = func.add_imm(Imm::int(number, ty.lane()));
416            let data = InstData { extra: Extra::Imm(at), ..InstData::new(Opcode::IConst) };
417            let span = func.span(before);
418            let iconst = func.create_inst(data, &[ty], span);
419            func.insert_before(iconst, before);
420            func[iconst].first_result.expect("one result was asked for")
421        }
422    }
423}
424
425/// Turns an instruction into the constant a rule says its result is.
426///
427/// In place, so the result value survives and every reader of it is already right. That is what
428/// makes this the half of the pass with nothing to redirect.
429fn become_constant(func: &mut Func, inst: Inst, number: i128) {
430    let result = func[inst].first_result.expect("the rule matched a result");
431    let ty = func[result].ty;
432    let imm = func.add_imm(Imm::int(number, ty.lane()));
433    let args = func.push_values(&[]);
434    let data = &mut func[inst];
435    data.opcode = Opcode::IConst;
436    data.args = args;
437    data.extra = Extra::Imm(imm);
438    // The flags go with the instruction that had them. An `nsw` on an add is a promise about an
439    // addition, and a constant makes no promise because it performs nothing.
440    data.flags = Flags::NONE;
441}
442
443/// Where a redirection ends up, following the ones the rest of this run decided.
444///
445/// A chain forms when one identity feeds another, `x + 0` read by `y * 1`, and following it is
446/// what makes the second rewrite worth as much as the first. A rule points a result at one of its
447/// own operands and an operand is defined before the instruction that reads it, so every step
448/// goes further back and the chain cannot come round to where it started.
449fn chase(forward: &HashMap<Value, Value>, value: Value) -> Value {
450    let mut value = value;
451    while let Some(&next) = forward.get(&value) {
452        value = next;
453    }
454    value
455}
456
457/// Points every reader of a rewritten result at what the rule said it is.
458///
459/// The arguments of each instruction and the arguments of the blocks it branches to, which is the
460/// whole of what an instruction can read and is the same pair [`crate::uses::operands`] walks.
461fn substitute(func: &mut Func, forward: &HashMap<Value, Value>) {
462    let with = |value: Value| chase(forward, value);
463    for block in func.blocks().collect::<Vec<Block>>() {
464        for inst in func.insts(block).collect::<Vec<Inst>>() {
465            let args = func[inst].args;
466            func.rewrite(args, with);
467            for call in func.successors(inst).collect::<Vec<_>>() {
468                func.rewrite(call.args, with);
469            }
470        }
471    }
472}
473
474/// What an instruction should become, when it is a comparison written as a negation.
475struct Flip {
476    /// `ICmp` or `FCmp`, whichever the comparison underneath was.
477    opcode: Opcode,
478    /// The flags of the comparison, which is where a fast math promise lives.
479    flags: Flags,
480    /// The opposite predicate.
481    extra: Extra,
482    /// The comparison's left operand.
483    lhs: Value,
484    /// Its right operand.
485    rhs: Value,
486}
487
488/// Whether this instruction is `xor (cmp p a b), true`, and what it becomes if it is.
489///
490/// The exclusive or is commutative, so the constant is looked for on both sides. Nothing else
491/// about the shape is negotiable: the result has to be an `i1`, because an exclusive or with one
492/// is a negation only at that width, and the constant has to be all ones, because the front end
493/// writes it as `iconst.i1 -1` and a reader who assumed the literal 1 would match nothing.
494fn negated_comparison(func: &Func, inst: Inst) -> Option<Flip> {
495    let data = &func[inst];
496    if data.opcode != Opcode::Xor {
497        return None;
498    }
499    let args = &func[data.args];
500    let (&first, &second) = (args.first()?, args.get(1)?);
501    if func[first].ty != Type::int(1) {
502        return None;
503    }
504    let cmp = match (all_ones(func, first), all_ones(func, second)) {
505        (true, false) => second,
506        (false, true) => first,
507        // Both, which folding would have turned into a constant, or neither, which is an
508        // exclusive or of two comparisons and is not this pattern.
509        _ => return None,
510    };
511    let Def::Result { inst: cmp, .. } = func[cmp].def else { return None };
512    let data = &func[cmp];
513    let extra = match (data.opcode, data.extra) {
514        (Opcode::ICmp, Extra::IntPred(pred)) => Extra::IntPred(pred.inverse()),
515        (Opcode::FCmp, Extra::FloatPred(pred)) => Extra::FloatPred(pred.inverse()),
516        _ => return None,
517    };
518    let args = &func[data.args];
519    Some(Flip {
520        opcode: data.opcode,
521        flags: data.flags,
522        extra,
523        lhs: *args.first()?,
524        rhs: *args.get(1)?,
525    })
526}
527
528/// Whether this value is a constant with every bit of its type set.
529fn all_ones(func: &Func, value: Value) -> bool {
530    let ty = func[value].ty;
531    let Def::Result { inst, .. } = func[value].def else { return false };
532    let data = &func[inst];
533    let Extra::Imm(at) = data.extra else { return false };
534    if data.opcode != Opcode::IConst {
535        return false;
536    }
537    // Read as signed, because an all ones value of any width is minus one that way and reading
538    // it unsigned would need the width to build the mask from.
539    func[at].signed(ty) == -1
540}
541
542#[cfg(test)]
543mod tests {
544    use rucc_base::Interner;
545    use rucc_ir::{
546        Block, Builder, Extra, Flags, Float, FloatPred, Func, IntPred, Module, Opcode, Signature,
547        Type, Value,
548    };
549    use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
550
551    use super::{CANONICAL, PLANS, Shown, TABLES, canonical, identities, strength};
552    use crate::rules::Piece;
553    use crate::stats::Kind;
554    use crate::{Analyses, Fuel, Pass, simplify::Simplify};
555
556    /// A function with one block, ready to have instructions appended to it.
557    fn blank() -> (Interner, Func, Block) {
558        let mut names = Interner::new();
559        let name = names.intern("f");
560        let mut func = Func::new(name, Signature::new().with_returns(&[Type::int(1)]));
561        let block = func.create_block();
562        (names, func, block)
563    }
564
565    /// The same, at the width the test is about and taking a parameter of it, since every identity
566    /// below needs an operand that is not itself a constant.
567    fn one_block(ty: Type) -> (Interner, Func, Block) {
568        let mut names = Interner::new();
569        let name = names.intern("f");
570        let signature = Signature::new().with_params(&[ty]).with_returns(&[ty]);
571        let mut func = Func::new(name, signature);
572        let block = func.create_block();
573        (names, func, block)
574    }
575
576    /// Runs the pass with as much fuel as it wants, and says whether it rewrote anything.
577    fn simplify(func: &mut Func) -> bool {
578        Simplify.run(func, &mut Analyses::new(), &mut Fuel::unlimited()).changed()
579    }
580
581    /// The opcode and the predicate the value now comes from.
582    fn came_from(func: &Func, value: Value) -> (Opcode, Extra) {
583        let rucc_ir::Def::Result { inst, .. } = func[value].def else { panic!("not a result") };
584        (func[inst].opcode, func[inst].extra)
585    }
586
587    /// What the block gives back, which is where every identity test reads its answer. A rule
588    /// that produces a value is only worth anything if the readers move, so the readers are what
589    /// the test looks at rather than the instruction that fired.
590    fn returned(func: &Func, block: Block) -> Value {
591        let inst = func.terminator(block).expect("the block has a terminator");
592        func[func[inst].args][0]
593    }
594
595    /// The operands of the instruction a value comes from.
596    fn operands(func: &Func, value: Value) -> Vec<Value> {
597        let rucc_ir::Def::Result { inst, .. } = func[value].def else { panic!("not a result") };
598        func[func[inst].args].to_vec()
599    }
600
601    /// The number a value is, which panics unless it is a constant.
602    fn number(func: &Func, value: Value) -> i128 {
603        let rucc_ir::Def::Result { inst, .. } = func[value].def else { panic!("not a result") };
604        let data = &func[inst];
605        assert_eq!(data.opcode, Opcode::IConst, "not a constant");
606        let Extra::Imm(at) = data.extra else { panic!("a constant with no number") };
607        func[at].signed(func[value].ty)
608    }
609
610    /// Every rule in every table leaves one of the three shapes the pass knows how to apply.
611    ///
612    /// A rule that left anything else would be matched, found to be none of them, and skipped, and
613    /// nothing at run time would say so: the rewrite would simply stop happening. So it is said
614    /// here instead, once, over every table.
615    #[test]
616    fn every_rule_leaves_a_shape_the_pass_knows_what_to_do_with() {
617        for (table, _) in TABLES {
618            for rule in table.rules {
619                let known = matches!(
620                    rule.replacement,
621                    [Piece::App { head, arity: 1 }, Piece::Var { .. }]
622                        if head.starts_with("value.")
623                ) || matches!(
624                    rule.replacement,
625                    [Piece::App { head, arity: 1 }, Piece::Int(_)]
626                        if head.starts_with("iconst.")
627                ) || matches!(
628                    rule.replacement,
629                    [Piece::App { arity: 2, .. }, ..] if instruction(rule.replacement)
630                );
631                assert!(known, "{} leaves a shape the pass would skip", rule.pattern);
632            }
633        }
634    }
635
636    /// The pieces of a replacement that is an instruction, read the way the pass reads them, so
637    /// that the check above is the pass's own answer rather than a second opinion about it.
638    ///
639    /// The bindings are empty, which is why a `value.iN` operand fails to resolve and this only
640    /// says the shape is one the pass would take rather than that it would take it here.
641    fn instruction(pieces: &'static [Piece]) -> bool {
642        let [Piece::App { head, arity: 2 }, rest @ ..] = pieces else { return false };
643        if super::opcode_of(head).is_none() {
644            return false;
645        }
646        let operand = |pieces: &'static [Piece]| match pieces {
647            [Piece::App { head, arity: 1 }, Piece::Var { .. }, rest @ ..]
648                if head.starts_with("value.") =>
649            {
650                Some(rest)
651            }
652            [Piece::App { head, arity: 1 }, Piece::Int(_), rest @ ..]
653                if head.starts_with("iconst.") =>
654            {
655                Some(rest)
656            }
657            [Piece::App { head, arity: 1 }, Piece::Var { .. }, rest @ ..]
658                if head.starts_with("iconst.") =>
659            {
660                Some(rest)
661            }
662            _ => None,
663        };
664        operand(rest).and_then(operand).is_some_and(<[Piece]>::is_empty)
665    }
666
667    /// And each table holds every rule its file writes. The tables are generated, so this is
668    /// asking whether the generator saw the whole file, which is the one thing about it worth
669    /// doubting.
670    #[test]
671    fn each_table_holds_every_rule_its_file_writes() {
672        let tier_one = include_str!("../rules/simplify.rules");
673        let tier_two = include_str!("../rules/strength.rules");
674        let tier_three = include_str!("../rules/canonical.rules");
675        let count = |text: &str| text.matches("(rule (simplify ").count();
676        assert_eq!(identities::TABLE.rules.len(), count(tier_one));
677        assert_eq!(strength::TABLE.rules.len(), count(tier_two));
678        assert_eq!(canonical::TABLE.rules.len(), count(tier_three));
679        assert!(
680            identities::TABLE.rules.len() > 100,
681            "tier one is about a hundred rules and there are fewer"
682        );
683        assert!(
684            strength::TABLE.rules.len() > 20,
685            "tier two is the multiplications and the divisions and there are fewer"
686        );
687        assert_eq!(
688            canonical::TABLE.rules.len(),
689            20,
690            "tier three is five commutative operators at four widths"
691        );
692    }
693
694    /// Three ways of showing an operand and no more, since a fourth would be a plan nothing
695    /// tries and a rule written for it would never fire.
696    #[test]
697    fn a_pattern_is_reached_by_one_of_the_plans() {
698        assert_eq!(PLANS.len(), 3);
699    }
700
701    /// Tier three is matched under its own plan and no other.
702    ///
703    /// This is what makes the rules terminate rather than swap a pair of constants back and forth
704    /// until the fuel runs out. It is asserted rather than left to be read, because the cost of
705    /// somebody adding the shared plans to the tier three row is a pass that does not stop.
706    #[test]
707    fn a_canonicalisation_is_only_matched_with_the_right_operand_refused() {
708        let (_, plans) = TABLES[2];
709        assert_eq!(plans.len(), 1);
710        assert_eq!(plans[0], CANONICAL[0]);
711        assert_eq!(plans[0][1], Shown::Var);
712        for plan in PLANS {
713            assert_ne!(plan, plans[0], "a shared plan would let a canonicalisation cycle");
714        }
715    }
716
717    /// Every commutative operator tier three writes moves its constant to the right.
718    ///
719    /// One test over the five rather than five tests, because what is being checked is the same
720    /// thing five times and the operator is the only part that differs.
721    #[test]
722    fn a_constant_on_the_left_of_a_commutative_operation_moves_to_the_right() {
723        for opcode in [Opcode::Add, Opcode::Mul, Opcode::And, Opcode::Or, Opcode::Xor] {
724            for width in [8, 16, 32, 64] {
725                let ty = Type::int(width);
726                let (_, mut func, block) = one_block(ty);
727                let x = func.append_param(block, ty);
728                let mut build = Builder::new(&mut func, block);
729                // Three, because it is a number no identity in tier one is about and no strength
730                // reduction in tier two is about, so the only rule that can fire is the one this
731                // test is here for.
732                let three = build.iconst(ty, 3);
733                let value = build.binary(opcode, three, x, Flags::NONE);
734                build.ret(&[value]);
735                assert!(simplify(&mut func), "{opcode:?} at i{width} was left alone");
736                let args = operands(&func, returned(&func, block));
737                assert_eq!(came_from(&func, returned(&func, block)).0, opcode);
738                assert_eq!(args[0], x, "{opcode:?} at i{width} kept the value on the right");
739                assert_eq!(number(&func, args[1]), 3, "{opcode:?} at i{width} lost its constant");
740            }
741        }
742    }
743
744    /// And an operation whose operands are both constants is left where it is.
745    ///
746    /// This is the termination argument, run rather than read. Without the plan that refuses a
747    /// constant on the right, the rule above would match this, swap the two, match the swapped
748    /// form, and go on doing it until the fuel ran out. Folding is what this instruction is for
749    /// and `crate::fold` is where it happens.
750    #[test]
751    fn an_operation_on_two_constants_is_not_swapped_back_and_forth() {
752        let i32 = Type::int(32);
753        let (_, mut func, block) = one_block(i32);
754        let mut build = Builder::new(&mut func, block);
755        let three = build.iconst(i32, 3);
756        let five = build.iconst(i32, 5);
757        let sum = build.binary(Opcode::Add, three, five, Flags::NONE);
758        build.ret(&[sum]);
759        assert!(!simplify(&mut func), "the constants were rearranged rather than left to folding");
760        let args = operands(&func, returned(&func, block));
761        assert_eq!(number(&func, args[0]), 3);
762        assert_eq!(number(&func, args[1]), 5);
763    }
764
765    /// A constant already on the right stays there and nothing fires.
766    ///
767    /// The other half of the same argument. A canonicalisation that fired on the shape it produces
768    /// would be a canonicalisation with no direction, which is what section 13.5 refuses.
769    #[test]
770    fn a_constant_already_on_the_right_is_left_alone() {
771        let i32 = Type::int(32);
772        let (_, mut func, block) = one_block(i32);
773        let x = func.append_param(block, i32);
774        let mut build = Builder::new(&mut func, block);
775        let three = build.iconst(i32, 3);
776        let sum = build.binary(Opcode::Add, x, three, Flags::NONE);
777        build.ret(&[sum]);
778        assert!(!simplify(&mut func));
779        let args = operands(&func, returned(&func, block));
780        assert_eq!(args[0], x);
781        assert_eq!(number(&func, args[1]), 3);
782    }
783
784    /// A subtraction is not commutative and nothing moves its constant.
785    ///
786    /// Turning `c - x` into anything is not what tier three does, and the rules are written per
787    /// opcode rather than over a set of them, so this is asking whether the wrong opcode found its
788    /// way into the file.
789    #[test]
790    fn a_subtraction_keeps_its_operands_where_they_are() {
791        let i32 = Type::int(32);
792        let (_, mut func, block) = one_block(i32);
793        let x = func.append_param(block, i32);
794        let mut build = Builder::new(&mut func, block);
795        let three = build.iconst(i32, 3);
796        let difference = build.binary(Opcode::Sub, three, x, Flags::NONE);
797        build.ret(&[difference]);
798        assert!(!simplify(&mut func));
799        let args = operands(&func, returned(&func, block));
800        assert_eq!(number(&func, args[0]), 3);
801        assert_eq!(args[1], x);
802    }
803
804    #[test]
805    fn adding_nothing_points_every_reader_at_the_operand() {
806        let i32 = Type::int(32);
807        let (_, mut func, block) = one_block(i32);
808        let x = func.append_param(block, i32);
809        let mut build = Builder::new(&mut func, block);
810        let zero = build.iconst(i32, 0);
811        let sum = build.binary(Opcode::Add, x, zero, Flags::NONE);
812        build.ret(&[sum]);
813        assert!(simplify(&mut func));
814        // The `add` is still there, used by nothing, which is what dead code elimination is for.
815        assert_eq!(returned(&func, block), x);
816        assert_eq!(came_from(&func, sum).0, Opcode::Add);
817    }
818
819    /// The constant on either side, since nothing puts it on the right yet and a rule written one
820    /// way round would fire on half the additions it should.
821    #[test]
822    fn the_constant_is_found_on_either_side_of_an_identity() {
823        for swapped in [false, true] {
824            let i32 = Type::int(32);
825            let (_, mut func, block) = one_block(i32);
826            let x = func.append_param(block, i32);
827            let mut build = Builder::new(&mut func, block);
828            let zero = build.iconst(i32, 0);
829            let (lhs, rhs) = if swapped { (zero, x) } else { (x, zero) };
830            let sum = build.binary(Opcode::Add, lhs, rhs, Flags::NONE);
831            build.ret(&[sum]);
832            assert!(simplify(&mut func), "swapped {swapped}");
833            assert_eq!(returned(&func, block), x, "swapped {swapped}");
834        }
835    }
836
837    #[test]
838    fn multiplying_by_nothing_becomes_the_constant_where_it_stands() {
839        let i32 = Type::int(32);
840        let (_, mut func, block) = one_block(i32);
841        let x = func.append_param(block, i32);
842        let mut build = Builder::new(&mut func, block);
843        let zero = build.iconst(i32, 0);
844        let product = build.binary(Opcode::Mul, x, zero, Flags::NONE);
845        build.ret(&[product]);
846        assert!(simplify(&mut func));
847        // The result value survives, which is the whole reason this half rewrites in place.
848        assert_eq!(returned(&func, block), product);
849        assert_eq!(came_from(&func, product).0, Opcode::IConst);
850        assert_eq!(number(&func, product), 0);
851    }
852
853    /// The two identities a pattern that writes one name twice exists for, at every width they
854    /// are written at.
855    #[test]
856    fn a_value_against_itself() {
857        for bits in [8, 16, 32, 64] {
858            let ty = Type::int(bits);
859            let (_, mut func, block) = one_block(ty);
860            let x = func.append_param(block, ty);
861            let mut build = Builder::new(&mut func, block);
862            let both = build.binary(Opcode::And, x, x, Flags::NONE);
863            build.ret(&[both]);
864            assert!(simplify(&mut func), "{bits} bits");
865            assert_eq!(returned(&func, block), x, "{bits} bits");
866
867            let (_, mut func, block) = one_block(ty);
868            let x = func.append_param(block, ty);
869            let mut build = Builder::new(&mut func, block);
870            let nothing = build.binary(Opcode::Sub, x, x, Flags::NONE);
871            build.ret(&[nothing]);
872            assert!(simplify(&mut func), "{bits} bits");
873            assert_eq!(number(&func, nothing), 0, "{bits} bits");
874        }
875    }
876
877    /// A remainder by one is nothing, and a division by one is the value. The pair is worth a
878    /// test of its own because they are the two identities that produce different shapes from the
879    /// same operands.
880    #[test]
881    fn dividing_by_one_and_the_remainder_that_goes_with_it() {
882        let i32 = Type::int(32);
883        let (_, mut func, block) = one_block(i32);
884        let x = func.append_param(block, i32);
885        let mut build = Builder::new(&mut func, block);
886        let one = build.iconst(i32, 1);
887        let quotient = build.binary(Opcode::SDiv, x, one, Flags::NONE);
888        let rest = build.binary(Opcode::SRem, x, one, Flags::NONE);
889        let sum = build.binary(Opcode::Add, quotient, rest, Flags::NONE);
890        build.ret(&[sum]);
891        assert!(simplify(&mut func));
892        assert_eq!(number(&func, rest), 0);
893        // The add reads the value the division was of, which is what the redirection did.
894        let rucc_ir::Def::Result { inst, .. } = func[sum].def else { panic!("not a result") };
895        assert_eq!(func[func[inst].args][0], x);
896    }
897
898    /// All ones at one bit is the `1` the rule file writes, and the front end writes it as `-1`.
899    /// The two are the same bit and the rule has to fire on what the front end wrote.
900    #[test]
901    fn all_ones_at_one_bit_is_the_one_the_front_end_writes() {
902        for written in [-1, 1] {
903            let bit = Type::int(1);
904            let (_, mut func, block) = one_block(bit);
905            let x = func.append_param(block, bit);
906            let mut build = Builder::new(&mut func, block);
907            let ones = build.iconst(bit, written);
908            let kept = build.binary(Opcode::And, x, ones, Flags::NONE);
909            build.ret(&[kept]);
910            assert!(simplify(&mut func), "written as {written}");
911            assert_eq!(returned(&func, block), x, "written as {written}");
912        }
913    }
914
915    /// One identity feeding another is followed all the way, so the second is worth as much as
916    /// the first. The redirections are applied once at the end of the run, and this is what says
917    /// that costs nothing.
918    #[test]
919    fn one_identity_feeding_another_is_followed_to_the_end() {
920        let i32 = Type::int(32);
921        let (_, mut func, block) = one_block(i32);
922        let x = func.append_param(block, i32);
923        let mut build = Builder::new(&mut func, block);
924        let zero = build.iconst(i32, 0);
925        let one = build.iconst(i32, 1);
926        let sum = build.binary(Opcode::Add, x, zero, Flags::NONE);
927        let product = build.binary(Opcode::Mul, sum, one, Flags::NONE);
928        let shifted = build.binary(Opcode::Shl, product, zero, Flags::NONE);
929        build.ret(&[shifted]);
930        assert!(simplify(&mut func));
931        assert_eq!(returned(&func, block), x);
932    }
933
934    #[test]
935    fn an_instruction_no_rule_is_about_is_left_alone() {
936        // Multiplying by three. Two is tier two and is an addition, and one and zero are tier one,
937        // so three is the smallest constant no tier written yet has anything to say about. Turning
938        // it into a shift and an add is the rest of tier two and is issue 523.
939        let i32 = Type::int(32);
940        let (_, mut func, block) = one_block(i32);
941        let x = func.append_param(block, i32);
942        let mut build = Builder::new(&mut func, block);
943        let three = build.iconst(i32, 3);
944        let tripled = build.binary(Opcode::Mul, x, three, Flags::NONE);
945        build.ret(&[tripled]);
946        assert!(!simplify(&mut func), "no rule is about multiplying by three");
947        assert_eq!(returned(&func, block), tripled);
948        assert_eq!(came_from(&func, tripled).0, Opcode::Mul);
949    }
950
951    #[test]
952    fn multiplying_by_two_becomes_an_addition_of_the_value_with_itself() {
953        let i32 = Type::int(32);
954        let (_, mut func, block) = one_block(i32);
955        let x = func.append_param(block, i32);
956        let mut build = Builder::new(&mut func, block);
957        let two = build.iconst(i32, 2);
958        let doubled = build.binary(Opcode::Mul, x, two, Flags::NONE);
959        build.ret(&[doubled]);
960        assert!(simplify(&mut func));
961        // In place, so the value the return reads is the one it always read.
962        assert_eq!(returned(&func, block), doubled);
963        assert_eq!(came_from(&func, doubled).0, Opcode::Add);
964        assert_eq!(operands(&func, doubled), [x, x]);
965    }
966
967    #[test]
968    fn multiplying_by_minus_one_becomes_a_subtraction_from_a_zero_the_rewrite_defines() {
969        // The other shape of operand: nothing in the function holds a zero, so the rewrite has to
970        // put one in front of the instruction it is rewriting.
971        let i32 = Type::int(32);
972        let (_, mut func, block) = one_block(i32);
973        let x = func.append_param(block, i32);
974        let mut build = Builder::new(&mut func, block);
975        let minus = build.iconst(i32, -1);
976        let negated = build.binary(Opcode::Mul, x, minus, Flags::NONE);
977        build.ret(&[negated]);
978        assert!(simplify(&mut func));
979        assert_eq!(returned(&func, block), negated);
980        assert_eq!(came_from(&func, negated).0, Opcode::Sub);
981        let args = operands(&func, negated);
982        assert_eq!(number(&func, args[0]), 0);
983        assert_eq!(args[1], x);
984    }
985
986    #[test]
987    fn the_flags_of_the_instruction_a_strength_reduction_replaces_do_not_come_with_it() {
988        // An `nsw` on a multiplication is a promise about that multiplication. The addition below
989        // may well keep it, and a promise carried across a rewrite because it probably still holds
990        // is how a wrong one gets made.
991        let i32 = Type::int(32);
992        let (_, mut func, block) = one_block(i32);
993        let x = func.append_param(block, i32);
994        let mut build = Builder::new(&mut func, block);
995        let two = build.iconst(i32, 2);
996        let doubled = build.binary(Opcode::Mul, x, two, Flags::NSW);
997        build.ret(&[doubled]);
998        assert!(simplify(&mut func));
999        let rucc_ir::Def::Result { inst, .. } = func[doubled].def else { panic!("not a result") };
1000        assert_eq!(func[inst].flags, Flags::NONE);
1001    }
1002
1003    #[test]
1004    fn a_strength_reduction_leaves_the_verifier_nothing_to_complain_about() {
1005        // The zero the negation needs is defined in front of the instruction that reads it, and
1006        // whether it really is in front of it is a question about the block rather than about the
1007        // instruction, which is what the verifier is for.
1008        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
1009        let i32 = Type::int(32);
1010        let (mut names, mut func, block) = one_block(i32);
1011        let mut module = Module::new(names.intern("test.c"), &target);
1012        let x = func.append_param(block, i32);
1013        let mut build = Builder::new(&mut func, block);
1014        let minus = build.iconst(i32, -1);
1015        let negated = build.binary(Opcode::Mul, x, minus, Flags::NONE);
1016        let two = build.iconst(i32, 2);
1017        let doubled = build.binary(Opcode::Mul, negated, two, Flags::NONE);
1018        build.ret(&[doubled]);
1019        assert!(simplify(&mut func));
1020        module.add_func(func);
1021        rucc_ir::verify(&module, &names).expect("the pass left the function verifiable");
1022    }
1023
1024    /// The function the pass leaves is still one the verifier accepts. Pointing a reader at a
1025    /// different value and turning an instruction into a constant are both things a rewrite could
1026    /// get wrong in a way none of the tests above would notice, because each of those asks about
1027    /// one instruction and this asks about the function.
1028    #[test]
1029    fn the_pass_leaves_the_verifier_nothing_to_complain_about() {
1030        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
1031        let i32 = Type::int(32);
1032        let (mut names, mut func, block) = one_block(i32);
1033        let mut module = Module::new(names.intern("test.c"), &target);
1034        let x = func.append_param(block, i32);
1035        let mut build = Builder::new(&mut func, block);
1036        let zero = build.iconst(i32, 0);
1037        let one = build.iconst(i32, 1);
1038        let sum = build.binary(Opcode::Add, x, zero, Flags::NONE);
1039        let product = build.binary(Opcode::Mul, sum, one, Flags::NONE);
1040        let gone = build.binary(Opcode::Sub, product, product, Flags::NONE);
1041        let total = build.binary(Opcode::Add, product, gone, Flags::NONE);
1042        build.ret(&[total]);
1043        assert!(simplify(&mut func));
1044        module.add_func(func);
1045        rucc_ir::verify(&module, &names).expect("the pass left the function verifiable");
1046    }
1047
1048    #[test]
1049    fn fuel_stops_an_identity_and_not_the_walk() {
1050        let i32 = Type::int(32);
1051        let (_, mut func, block) = one_block(i32);
1052        let x = func.append_param(block, i32);
1053        let mut build = Builder::new(&mut func, block);
1054        let zero = build.iconst(i32, 0);
1055        let first = build.binary(Opcode::Add, x, zero, Flags::NONE);
1056        let second = build.binary(Opcode::Sub, x, zero, Flags::NONE);
1057        let sum = build.binary(Opcode::Add, first, second, Flags::NONE);
1058        build.ret(&[sum]);
1059        let stats = Simplify.run(&mut func, &mut Analyses::new(), &mut Fuel::of(1));
1060        assert!(stats.changed());
1061        assert_eq!(stats.total(Kind::Optimized), 1);
1062        assert_eq!(stats.count(Kind::Missed, super::NO_FUEL_RULE), 1);
1063        // The first fired and the second did not, and the second is still read by the add.
1064        let rucc_ir::Def::Result { inst, .. } = func[sum].def else { panic!("not a result") };
1065        assert_eq!(func[func[inst].args], [x, second]);
1066    }
1067
1068    #[test]
1069    fn a_negated_float_comparison_becomes_the_opposite_predicate() {
1070        // Every ordered predicate and its opposite, which is the table `!(x < y)` is `x >= y`
1071        // or unordered lives in, and the one place a sign error would hide.
1072        for pred in FloatPred::all() {
1073            let (_, mut func, block) = blank();
1074            let mut build = Builder::new(&mut func, block);
1075            let x = build.iconst(Type::int(64), 0);
1076            let x = build.unary(Opcode::Bitcast, x, Type::float(Float::F64));
1077            let cmp = build.fcmp(pred, x, x, Flags::NONE);
1078            let ones = build.iconst(Type::int(1), -1);
1079            let not = build.binary(Opcode::Xor, cmp, ones, Flags::NONE);
1080            build.ret(&[not]);
1081            assert!(simplify(&mut func), "{pred:?}");
1082            assert_eq!(
1083                came_from(&func, not),
1084                (Opcode::FCmp, Extra::FloatPred(pred.inverse())),
1085                "{pred:?}"
1086            );
1087        }
1088    }
1089
1090    #[test]
1091    fn a_negated_integer_comparison_becomes_the_opposite_predicate() {
1092        for pred in IntPred::all() {
1093            let (_, mut func, block) = blank();
1094            let mut build = Builder::new(&mut func, block);
1095            let x = build.iconst(Type::int(32), 3);
1096            let cmp = build.icmp(pred, x, x);
1097            let ones = build.iconst(Type::int(1), -1);
1098            let not = build.binary(Opcode::Xor, cmp, ones, Flags::NONE);
1099            build.ret(&[not]);
1100            assert!(simplify(&mut func), "{pred:?}");
1101            assert_eq!(
1102                came_from(&func, not),
1103                (Opcode::ICmp, Extra::IntPred(pred.inverse())),
1104                "{pred:?}"
1105            );
1106        }
1107    }
1108
1109    #[test]
1110    fn the_constant_is_found_on_either_side() {
1111        for swapped in [false, true] {
1112            let (_, mut func, block) = blank();
1113            let mut build = Builder::new(&mut func, block);
1114            let x = build.iconst(Type::int(32), 3);
1115            let cmp = build.icmp(IntPred::Slt, x, x);
1116            let ones = build.iconst(Type::int(1), -1);
1117            let (lhs, rhs) = if swapped { (ones, cmp) } else { (cmp, ones) };
1118            let not = build.binary(Opcode::Xor, lhs, rhs, Flags::NONE);
1119            build.ret(&[not]);
1120            assert!(simplify(&mut func), "swapped {swapped}");
1121            assert_eq!(came_from(&func, not).1, Extra::IntPred(IntPred::Sge));
1122        }
1123    }
1124
1125    #[test]
1126    fn an_exclusive_or_of_two_comparisons_is_left_alone() {
1127        let (_, mut func, block) = blank();
1128        let mut build = Builder::new(&mut func, block);
1129        let x = build.iconst(Type::int(32), 3);
1130        let a = build.icmp(IntPred::Slt, x, x);
1131        let b = build.icmp(IntPred::Sgt, x, x);
1132        let differ = build.binary(Opcode::Xor, a, b, Flags::NONE);
1133        build.ret(&[differ]);
1134        assert!(!simplify(&mut func));
1135        assert_eq!(came_from(&func, differ).0, Opcode::Xor);
1136    }
1137
1138    #[test]
1139    fn an_exclusive_or_of_something_that_is_not_a_comparison_is_left_alone() {
1140        let (_, mut func, block) = blank();
1141        let mut build = Builder::new(&mut func, block);
1142        let x = build.iconst(Type::int(32), 3);
1143        let narrow = build.unary(Opcode::Trunc, x, Type::int(1));
1144        let ones = build.iconst(Type::int(1), -1);
1145        let not = build.binary(Opcode::Xor, narrow, ones, Flags::NONE);
1146        build.ret(&[not]);
1147        assert!(!simplify(&mut func));
1148        assert_eq!(came_from(&func, not).0, Opcode::Xor);
1149    }
1150
1151    #[test]
1152    fn a_wider_exclusive_or_with_one_is_not_a_negation_and_is_left_alone() {
1153        let (_, mut func, block) = blank();
1154        let mut build = Builder::new(&mut func, block);
1155        let x = build.iconst(Type::int(32), 3);
1156        let cmp = build.icmp(IntPred::Slt, x, x);
1157        let wide = build.unary(Opcode::ZExt, cmp, Type::int(32));
1158        let one = build.iconst(Type::int(32), 1);
1159        let flipped = build.binary(Opcode::Xor, wide, one, Flags::NONE);
1160        let narrow = build.unary(Opcode::Trunc, flipped, Type::int(1));
1161        build.ret(&[narrow]);
1162        assert!(!simplify(&mut func), "an i32 xor 1 flips one bit of thirty two");
1163        assert_eq!(came_from(&func, flipped).0, Opcode::Xor);
1164    }
1165
1166    #[test]
1167    fn the_comparisons_flags_travel_with_the_predicate() {
1168        let (_, mut func, block) = blank();
1169        let mut build = Builder::new(&mut func, block);
1170        let x = build.iconst(Type::int(64), 0);
1171        let x = build.unary(Opcode::Bitcast, x, Type::float(Float::F64));
1172        let cmp = build.fcmp(FloatPred::Olt, x, x, Flags::FAST);
1173        let ones = build.iconst(Type::int(1), -1);
1174        let not = build.binary(Opcode::Xor, cmp, ones, Flags::NONE);
1175        build.ret(&[not]);
1176        assert!(simplify(&mut func));
1177        let rucc_ir::Def::Result { inst, .. } = func[not].def else { panic!("not a result") };
1178        // The promise the original comparison was made under, not the exclusive or's absence of
1179        // one. Dropping it would be correct and would quietly undo a fast math flag.
1180        assert_eq!(func[inst].flags, Flags::FAST);
1181    }
1182
1183    #[test]
1184    fn fuel_stops_the_transformation_and_not_the_walk() {
1185        let (_, mut func, block) = blank();
1186        let mut build = Builder::new(&mut func, block);
1187        let x = build.iconst(Type::int(32), 3);
1188        let a = build.icmp(IntPred::Slt, x, x);
1189        let b = build.icmp(IntPred::Sgt, x, x);
1190        let ones = build.iconst(Type::int(1), -1);
1191        let first = build.binary(Opcode::Xor, a, ones, Flags::NONE);
1192        let second = build.binary(Opcode::Xor, b, ones, Flags::NONE);
1193        let both = build.binary(Opcode::And, first, second, Flags::NONE);
1194        build.ret(&[both]);
1195        let stats = Simplify.run(&mut func, &mut Analyses::new(), &mut Fuel::of(1));
1196        assert!(stats.changed());
1197        assert_eq!(stats.count(Kind::Optimized, super::FLIPPED), 1);
1198        assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
1199        assert_eq!(came_from(&func, first).0, Opcode::ICmp);
1200        assert_eq!(came_from(&func, second).0, Opcode::Xor);
1201    }
1202}