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