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::{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
727 .run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
728 .changed()
729 }
730
731 /// The opcode and the predicate the value now comes from.
732 fn came_from(func: &Func, value: Value) -> (Opcode, Extra) {
733 let rucc_ir::Def::Result { inst, .. } = func[value].def else { panic!("not a result") };
734 (func[inst].opcode, func[inst].extra)
735 }
736
737 /// What the block gives back, which is where every identity test reads its answer. A rule
738 /// that produces a value is only worth anything if the readers move, so the readers are what
739 /// the test looks at rather than the instruction that fired.
740 fn returned(func: &Func, block: Block) -> Value {
741 let inst = func.terminator(block).expect("the block has a terminator");
742 func[func[inst].args][0]
743 }
744
745 /// The operands of the instruction a value comes from.
746 fn operands(func: &Func, value: Value) -> Vec<Value> {
747 let rucc_ir::Def::Result { inst, .. } = func[value].def else { panic!("not a result") };
748 func[func[inst].args].to_vec()
749 }
750
751 /// The number a value is, which panics unless it is a constant.
752 fn number(func: &Func, value: Value) -> i128 {
753 let rucc_ir::Def::Result { inst, .. } = func[value].def else { panic!("not a result") };
754 let data = &func[inst];
755 assert_eq!(data.opcode, Opcode::IConst, "not a constant");
756 let Extra::Imm(at) = data.extra else { panic!("a constant with no number") };
757 func[at].signed(func[value].ty)
758 }
759
760 /// Every rule in every table leaves one of the four shapes the pass knows how to apply.
761 ///
762 /// A rule that left anything else would be matched, found to be none of them, and skipped, and
763 /// nothing at run time would say so: the rewrite would simply stop happening. So it is said
764 /// here instead, once, over every table.
765 #[test]
766 fn every_rule_leaves_a_shape_the_pass_knows_what_to_do_with() {
767 for (table, _) in TABLES {
768 for rule in table.rules {
769 let known = matches!(
770 rule.replacement,
771 [Piece::App { head, arity: 1 }, Piece::Var { .. }]
772 if head.starts_with("value.")
773 ) || matches!(
774 rule.replacement,
775 [Piece::App { head, arity: 1 }, Piece::Int(_)]
776 if head.starts_with("iconst.")
777 ) || matches!(
778 rule.replacement,
779 [Piece::App { arity: 2, .. }, ..] if instruction(rule.replacement)
780 ) || conversion(rule.replacement);
781 assert!(known, "{} leaves a shape the pass would skip", rule.pattern);
782 }
783 }
784 }
785
786 /// The pieces of a replacement that is a conversion, read the way [`super::converted`] reads
787 /// them, and shape only for the same reason [`instruction`] is: there are no bindings here to
788 /// resolve the operand against.
789 fn conversion(pieces: &'static [Piece]) -> bool {
790 let [Piece::App { head, arity: 1 }, rest @ ..] = pieces else { return false };
791 let converts =
792 matches!(super::opcode_of(head), Some(Opcode::SExt | Opcode::ZExt | Opcode::Trunc));
793 converts
794 && matches!(
795 rest,
796 [Piece::App { head, arity: 1 }, Piece::Var { .. }] if head.starts_with("value.")
797 )
798 }
799
800 /// Every rule in the width table writes a term ending at the width the one it matched ended
801 /// at.
802 ///
803 /// The pass rewrites in place and leaves the result type where it was, so a rule whose
804 /// replacement converted to some other width would quietly produce a value of the wrong one.
805 /// `rucc-verify` refuses a replacement narrower than what it replaces and says nothing about a
806 /// wider one, so this is the half of that pair the solver does not cover.
807 #[test]
808 fn a_width_rule_writes_a_term_that_ends_where_the_one_it_matched_ended() {
809 for rule in width::TABLE.rules {
810 let [Piece::App { head, .. }, ..] = rule.replacement else {
811 panic!("{} writes no head", rule.pattern)
812 };
813 let wrote = head.rsplit_once('.').expect("a replacement head names a width").1;
814 let matched = rule
815 .pattern
816 .trim_start_matches('(')
817 .split([' ', ')'])
818 .next()
819 .and_then(|head| head.rsplit_once('.'))
820 .expect("a pattern head names a width")
821 .1;
822 assert_eq!(wrote, matched, "{} ends somewhere else", rule.pattern);
823 }
824 }
825
826 /// The pieces of a replacement that is an instruction, read the way the pass reads them, so
827 /// that the check above is the pass's own answer rather than a second opinion about it.
828 ///
829 /// The bindings are empty, which is why a `value.iN` operand fails to resolve and this only
830 /// says the shape is one the pass would take rather than that it would take it here.
831 fn instruction(pieces: &'static [Piece]) -> bool {
832 let [Piece::App { head, arity: 2 }, rest @ ..] = pieces else { return false };
833 if super::opcode_of(head).is_none() {
834 return false;
835 }
836 let operand = |pieces: &'static [Piece]| match pieces {
837 [Piece::App { head, arity: 1 }, Piece::Var { .. }, rest @ ..]
838 if head.starts_with("value.") =>
839 {
840 Some(rest)
841 }
842 [Piece::App { head, arity: 1 }, Piece::Int(_), rest @ ..]
843 if head.starts_with("iconst.") =>
844 {
845 Some(rest)
846 }
847 [Piece::App { head, arity: 1 }, Piece::Var { .. }, rest @ ..]
848 if head.starts_with("iconst.") =>
849 {
850 Some(rest)
851 }
852 _ => None,
853 };
854 operand(rest).and_then(operand).is_some_and(<[Piece]>::is_empty)
855 }
856
857 /// And each table holds every rule its file writes. The tables are generated, so this is
858 /// asking whether the generator saw the whole file, which is the one thing about it worth
859 /// doubting.
860 #[test]
861 fn each_table_holds_every_rule_its_file_writes() {
862 let tier_one = include_str!("../rules/simplify.rules");
863 let tier_two = include_str!("../rules/strength.rules");
864 let tier_three = include_str!("../rules/canonical.rules");
865 let tier_four = include_str!("../rules/width.rules");
866 let tier_five = include_str!("../rules/compare.rules");
867 let count = |text: &str| text.matches("(rule (simplify ").count();
868 assert_eq!(identities::TABLE.rules.len(), count(tier_one));
869 assert_eq!(strength::TABLE.rules.len(), count(tier_two));
870 assert_eq!(canonical::TABLE.rules.len(), count(tier_three));
871 assert_eq!(width::TABLE.rules.len(), count(tier_four));
872 assert_eq!(compare::TABLE.rules.len(), count(tier_five));
873 assert!(
874 identities::TABLE.rules.len() > 100,
875 "tier one is about a hundred rules and there are fewer"
876 );
877 assert!(
878 strength::TABLE.rules.len() > 20,
879 "tier two is the multiplications and the divisions and there are fewer"
880 );
881 assert_eq!(
882 canonical::TABLE.rules.len(),
883 20,
884 "tier three is five commutative operators at four widths"
885 );
886 assert_eq!(
887 width::TABLE.rules.len(),
888 44,
889 "tier four is the truncation and extension algebra over four widths"
890 );
891 assert_eq!(
892 compare::TABLE.rules.len(),
893 64,
894 "tier five is four predicates against each of four constants at four widths"
895 );
896 }
897
898 /// Three ways of showing an operand and no more, since a fourth would be a plan nothing
899 /// tries and a rule written for it would never fire.
900 #[test]
901 fn a_pattern_is_reached_by_one_of_the_plans() {
902 assert_eq!(PLANS.len(), 3);
903 }
904
905 /// Tier four is matched with its operand expanded, and nothing else is.
906 ///
907 /// Every pattern in that tier has an instruction at its second level, so under any of the
908 /// plans above it every rule in it would fail at the first node and the whole tier would be a
909 /// file nobody matched with. Asserted rather than left to be read, because that failure is
910 /// silent.
911 #[test]
912 fn a_width_rule_is_only_matched_with_its_operand_expanded() {
913 let (_, plans) = TABLES[2];
914 assert_eq!(plans.len(), 1);
915 assert_eq!(plans[0], EXPAND[0]);
916 assert_eq!(plans[0][0], Shown::Expand);
917 for plan in PLANS {
918 assert_ne!(plan, plans[0], "no shared plan expands an operand");
919 }
920 assert_ne!(CANONICAL[0], plans[0]);
921 }
922
923 /// Tier three is matched under its own plan and no other.
924 ///
925 /// This is what makes the rules terminate rather than swap a pair of constants back and forth
926 /// until the fuel runs out. It is asserted rather than left to be read, because the cost of
927 /// somebody adding the shared plans to the tier three row is a pass that does not stop.
928 #[test]
929 fn a_canonicalisation_is_only_matched_with_the_right_operand_refused() {
930 let (_, plans) = TABLES[4];
931 assert_eq!(plans.len(), 1);
932 assert_eq!(plans[0], CANONICAL[0]);
933 assert_eq!(plans[0][1], Shown::Var);
934 for plan in PLANS {
935 assert_ne!(plan, plans[0], "a shared plan would let a canonicalisation cycle");
936 }
937 }
938
939 /// Tier five is matched with the constant on the right and no other way.
940 ///
941 /// Every rule in it writes the constant there, so under the plan that shows a constant left
942 /// operand as a number none of them would match and under the plan that refuses a constant on
943 /// the right none of them would either. One plan, and it is the first of the shared three.
944 #[test]
945 fn a_comparison_rule_is_only_matched_with_the_constant_on_the_right() {
946 let (_, plans) = TABLES[3];
947 assert_eq!(plans.len(), 1);
948 assert_eq!(plans[0], COMPARE[0]);
949 assert_eq!(plans[0][0], Shown::Reg);
950 assert_eq!(plans[0][1], Shown::Const);
951 }
952
953 /// The edge of a type, at each width, read each way.
954 ///
955 /// The least and greatest unsigned value and the least and greatest signed one, which are the
956 /// four constants tier five is written against.
957 fn edges(width: u32) -> [(i128, bool); 4] {
958 let signed = 1i128 << (width - 1);
959 [(0, false), (-1, false), (-signed, true), (signed - 1, true)]
960 }
961
962 /// A comparison that its type has already answered becomes the answer.
963 ///
964 /// Nothing unsigned is below zero, everything unsigned is at least zero, and the same pair of
965 /// sentences holds at each of the other three edges. Thirty two rules, run as one test,
966 /// because what is being checked is the same sentence at four constants and four widths.
967 #[test]
968 fn a_comparison_against_the_edge_of_its_type_folds_to_a_bit() {
969 for width in [8u32, 16, 32, 64] {
970 let ty = Type::int(width);
971 for (edge, signed) in edges(width) {
972 // Below the edge is false at the bottom and above it is false at the top, and the
973 // other of each pair is the negation, so one table gives all four.
974 let below = edge == 0 || edge == -(1i128 << (width - 1));
975 let (false_pred, true_pred) = match (signed, below) {
976 (false, true) => (IntPred::Ult, IntPred::Uge),
977 (false, false) => (IntPred::Ugt, IntPred::Ule),
978 (true, true) => (IntPred::Slt, IntPred::Sge),
979 (true, false) => (IntPred::Sgt, IntPred::Sle),
980 };
981 // Minus one for the true bit, because the rule writes `(iconst.i1 1)` and one bit
982 // holding a one read signed is minus one, which is the same bit pattern and the
983 // reading everything else in the compiler takes of a true condition.
984 for (pred, answer) in [(false_pred, 0), (true_pred, -1)] {
985 let (_, mut func, block) = blank();
986 let x = func.append_param(block, ty);
987 let mut build = Builder::new(&mut func, block);
988 let bound = build.iconst(ty, edge);
989 let cmp = build.icmp(pred, x, bound);
990 build.ret(&[cmp]);
991 assert!(simplify(&mut func), "i{width} {pred:?} {edge} was left alone");
992 let got = returned(&func, block);
993 assert_eq!(
994 came_from(&func, got).0,
995 Opcode::IConst,
996 "i{width} {pred:?} {edge} did not fold"
997 );
998 assert_eq!(number(&func, got), answer, "i{width} {pred:?} {edge}");
999 assert_eq!(func[got].ty, Type::int(1), "i{width} {pred:?} {edge} is a bit");
1000 }
1001 }
1002 }
1003 }
1004
1005 /// And one that is true or false for exactly one value becomes the test for that value.
1006 ///
1007 /// The predicate has to come from the rule. Every case here matched an ordering and every one
1008 /// of them has to leave `eq` or `ne`, so a rewriter that took the predicate from the
1009 /// instruction it replaced would leave the ordering in place and this would say so.
1010 #[test]
1011 fn a_comparison_true_for_one_value_becomes_a_test_for_that_value() {
1012 for width in [8u32, 16, 32, 64] {
1013 let ty = Type::int(width);
1014 for (edge, signed) in edges(width) {
1015 let below = edge == 0 || edge == -(1i128 << (width - 1));
1016 // At most the bottom is equality and above it is inequality, and at the top the
1017 // two swap over.
1018 let (eq_pred, ne_pred) = match (signed, below) {
1019 (false, true) => (IntPred::Ule, IntPred::Ugt),
1020 (false, false) => (IntPred::Uge, IntPred::Ult),
1021 (true, true) => (IntPred::Sle, IntPred::Sgt),
1022 (true, false) => (IntPred::Sge, IntPred::Slt),
1023 };
1024 for (pred, left) in [(eq_pred, IntPred::Eq), (ne_pred, IntPred::Ne)] {
1025 let (_, mut func, block) = blank();
1026 let x = func.append_param(block, ty);
1027 let mut build = Builder::new(&mut func, block);
1028 let bound = build.iconst(ty, edge);
1029 let cmp = build.icmp(pred, x, bound);
1030 build.ret(&[cmp]);
1031 assert!(simplify(&mut func), "i{width} {pred:?} {edge} was left alone");
1032 let got = returned(&func, block);
1033 assert_eq!(
1034 came_from(&func, got),
1035 (Opcode::ICmp, Extra::IntPred(left)),
1036 "i{width} {pred:?} {edge} kept the predicate it matched"
1037 );
1038 let args = operands(&func, got);
1039 assert_eq!(args[0], x, "i{width} {pred:?} {edge} lost its value");
1040 assert_eq!(number(&func, args[1]), edge, "i{width} {pred:?} {edge}");
1041 // The width the rule was written at, which is the width of what is being
1042 // compared and not the width of the answer. A constant built at the result's
1043 // type would be a one bit zero standing where a wider one was asked for.
1044 assert_eq!(func[args[1]].ty, ty, "i{width} {pred:?} {edge} narrowed its bound");
1045 }
1046 }
1047 }
1048 }
1049
1050 /// Every commutative operator tier three writes moves its constant to the right.
1051 ///
1052 /// One test over the five rather than five tests, because what is being checked is the same
1053 /// thing five times and the operator is the only part that differs.
1054 #[test]
1055 fn a_constant_on_the_left_of_a_commutative_operation_moves_to_the_right() {
1056 for opcode in [Opcode::Add, Opcode::Mul, Opcode::And, Opcode::Or, Opcode::Xor] {
1057 for width in [8, 16, 32, 64] {
1058 let ty = Type::int(width);
1059 let (_, mut func, block) = one_block(ty);
1060 let x = func.append_param(block, ty);
1061 let mut build = Builder::new(&mut func, block);
1062 // Three, because it is a number no identity in tier one is about and no strength
1063 // reduction in tier two is about, so the only rule that can fire is the one this
1064 // test is here for.
1065 let three = build.iconst(ty, 3);
1066 let value = build.binary(opcode, three, x, Flags::NONE);
1067 build.ret(&[value]);
1068 assert!(simplify(&mut func), "{opcode:?} at i{width} was left alone");
1069 let args = operands(&func, returned(&func, block));
1070 assert_eq!(came_from(&func, returned(&func, block)).0, opcode);
1071 assert_eq!(args[0], x, "{opcode:?} at i{width} kept the value on the right");
1072 assert_eq!(number(&func, args[1]), 3, "{opcode:?} at i{width} lost its constant");
1073 }
1074 }
1075 }
1076
1077 /// And an operation whose operands are both constants is left where it is.
1078 ///
1079 /// This is the termination argument, run rather than read. Without the plan that refuses a
1080 /// constant on the right, the rule above would match this, swap the two, match the swapped
1081 /// form, and go on doing it until the fuel ran out. Folding is what this instruction is for
1082 /// and `crate::fold` is where it happens.
1083 #[test]
1084 fn an_operation_on_two_constants_is_not_swapped_back_and_forth() {
1085 let i32 = Type::int(32);
1086 let (_, mut func, block) = one_block(i32);
1087 let mut build = Builder::new(&mut func, block);
1088 let three = build.iconst(i32, 3);
1089 let five = build.iconst(i32, 5);
1090 let sum = build.binary(Opcode::Add, three, five, Flags::NONE);
1091 build.ret(&[sum]);
1092 assert!(!simplify(&mut func), "the constants were rearranged rather than left to folding");
1093 let args = operands(&func, returned(&func, block));
1094 assert_eq!(number(&func, args[0]), 3);
1095 assert_eq!(number(&func, args[1]), 5);
1096 }
1097
1098 /// A constant already on the right stays there and nothing fires.
1099 ///
1100 /// The other half of the same argument. A canonicalisation that fired on the shape it produces
1101 /// would be a canonicalisation with no direction, which is what section 13.5 refuses.
1102 #[test]
1103 fn a_constant_already_on_the_right_is_left_alone() {
1104 let i32 = Type::int(32);
1105 let (_, mut func, block) = one_block(i32);
1106 let x = func.append_param(block, i32);
1107 let mut build = Builder::new(&mut func, block);
1108 let three = build.iconst(i32, 3);
1109 let sum = build.binary(Opcode::Add, x, three, Flags::NONE);
1110 build.ret(&[sum]);
1111 assert!(!simplify(&mut func));
1112 let args = operands(&func, returned(&func, block));
1113 assert_eq!(args[0], x);
1114 assert_eq!(number(&func, args[1]), 3);
1115 }
1116
1117 /// A subtraction is not commutative and nothing moves its constant.
1118 ///
1119 /// Turning `c - x` into anything is not what tier three does, and the rules are written per
1120 /// opcode rather than over a set of them, so this is asking whether the wrong opcode found its
1121 /// way into the file.
1122 #[test]
1123 fn a_subtraction_keeps_its_operands_where_they_are() {
1124 let i32 = Type::int(32);
1125 let (_, mut func, block) = one_block(i32);
1126 let x = func.append_param(block, i32);
1127 let mut build = Builder::new(&mut func, block);
1128 let three = build.iconst(i32, 3);
1129 let difference = build.binary(Opcode::Sub, three, x, Flags::NONE);
1130 build.ret(&[difference]);
1131 assert!(!simplify(&mut func));
1132 let args = operands(&func, returned(&func, block));
1133 assert_eq!(number(&func, args[0]), 3);
1134 assert_eq!(args[1], x);
1135 }
1136
1137 /// A block whose parameter and whose result are different widths, which is what every width
1138 /// rule needs and what `one_block` cannot give.
1139 fn narrow_to_wide(takes: Type, gives: Type) -> (Interner, Func, Block) {
1140 let mut names = Interner::new();
1141 let name = names.intern("f");
1142 let signature = Signature::new().with_params(&[takes]).with_returns(&[gives]);
1143 let mut func = Func::new(name, signature);
1144 let block = func.create_block();
1145 (names, func, block)
1146 }
1147
1148 /// A conversion of a conversion of a parameter, which is the shape every width rule matches.
1149 ///
1150 /// The parameter is at `from`, the inner conversion takes it to `through` and the outer one
1151 /// takes that to `to`, and what comes back is the function, the block and the parameter.
1152 fn chain(
1153 inner: Opcode,
1154 outer: Opcode,
1155 from: Type,
1156 through: Type,
1157 to: Type,
1158 ) -> (Func, Block, Value) {
1159 let (_, mut func, block) = narrow_to_wide(from, to);
1160 let x = func.append_param(block, from);
1161 let mut build = Builder::new(&mut func, block);
1162 let middle = build.unary(inner, x, through);
1163 let outside = build.unary(outer, middle, to);
1164 build.ret(&[outside]);
1165 (func, block, x)
1166 }
1167
1168 /// Truncating an extension back to the width it came from is the value that was there.
1169 ///
1170 /// Every pair of widths and both extensions, because the rule file writes all twelve and a
1171 /// test of one of them would say nothing about the other eleven.
1172 #[test]
1173 fn truncating_an_extension_back_to_its_own_width_gives_the_value_back() {
1174 for extend in [Opcode::SExt, Opcode::ZExt] {
1175 for (narrow, wide) in [(8, 16), (8, 32), (8, 64), (16, 32), (16, 64), (32, 64)] {
1176 let (from, through) = (Type::int(narrow), Type::int(wide));
1177 let (mut func, block, x) = chain(extend, Opcode::Trunc, from, through, from);
1178 assert!(simplify(&mut func), "{extend:?} i{narrow} to i{wide} was left alone");
1179 assert_eq!(
1180 returned(&func, block),
1181 x,
1182 "{extend:?} i{narrow} to i{wide} and back did not give the value back"
1183 );
1184 }
1185 }
1186 }
1187
1188 /// Truncating an extension to a width still above the source is the same extension, stopping
1189 /// earlier.
1190 #[test]
1191 fn truncating_an_extension_above_its_source_is_a_shorter_extension() {
1192 let (mut func, block, x) =
1193 chain(Opcode::SExt, Opcode::Trunc, Type::int(8), Type::int(64), Type::int(16));
1194 assert!(simplify(&mut func));
1195 let result = returned(&func, block);
1196 assert_eq!(came_from(&func, result).0, Opcode::SExt);
1197 assert_eq!(operands(&func, result), vec![x]);
1198 assert_eq!(func[result].ty, Type::int(16));
1199 }
1200
1201 /// Truncating an extension to a width below the source is a truncation of the source, and
1202 /// which extension it was never mattered.
1203 #[test]
1204 fn truncating_an_extension_below_its_source_is_a_truncation_of_the_source() {
1205 let (mut func, block, x) =
1206 chain(Opcode::ZExt, Opcode::Trunc, Type::int(16), Type::int(32), Type::int(8));
1207 assert!(simplify(&mut func));
1208 let result = returned(&func, block);
1209 assert_eq!(came_from(&func, result).0, Opcode::Trunc);
1210 assert_eq!(operands(&func, result), vec![x]);
1211 assert_eq!(func[result].ty, Type::int(8));
1212 }
1213
1214 /// An extension of an extension is one extension, and a sign extension of a zero extension is
1215 /// a zero extension rather than a sign extension.
1216 #[test]
1217 fn an_extension_of_an_extension_is_one_extension() {
1218 for (inner, outer, want) in [
1219 (Opcode::ZExt, Opcode::ZExt, Opcode::ZExt),
1220 (Opcode::SExt, Opcode::SExt, Opcode::SExt),
1221 (Opcode::ZExt, Opcode::SExt, Opcode::ZExt),
1222 ] {
1223 let (mut func, block, x) =
1224 chain(inner, outer, Type::int(8), Type::int(16), Type::int(64));
1225 assert!(simplify(&mut func), "{outer:?} of {inner:?} was left alone");
1226 let result = returned(&func, block);
1227 assert_eq!(came_from(&func, result).0, want, "{outer:?} of {inner:?}");
1228 assert_eq!(operands(&func, result), vec![x]);
1229 assert_eq!(func[result].ty, Type::int(64));
1230 }
1231 }
1232
1233 /// A truncation of a truncation is one truncation, straight to the width the outer one asked
1234 /// for.
1235 ///
1236 /// The inner one threw away bits the outer one was going to throw away as well, so the width
1237 /// in the middle was never read and the rule goes to the outer width from the source. Both
1238 /// orderings of the three widths are tried, because a rule that picked the middle width rather
1239 /// than the outer one would still pass a test that only went from sixty four to eight through
1240 /// thirty two.
1241 #[test]
1242 fn a_truncation_of_a_truncation_is_one_truncation() {
1243 for (from, through, to) in [(64u32, 32u32, 16u32), (64, 32, 8), (64, 16, 8), (32, 16, 8)] {
1244 let (mut func, block, x) = chain(
1245 Opcode::Trunc,
1246 Opcode::Trunc,
1247 Type::int(from),
1248 Type::int(through),
1249 Type::int(to),
1250 );
1251 assert!(simplify(&mut func), "i{from} to i{through} to i{to} was left alone");
1252 let result = returned(&func, block);
1253 assert_eq!(came_from(&func, result).0, Opcode::Trunc, "i{from} to i{through} to i{to}");
1254 assert_eq!(operands(&func, result), vec![x]);
1255 assert_eq!(func[result].ty, Type::int(to));
1256 }
1257 }
1258
1259 /// And zero extending a sign extension is not one, because the bits the sign extension copied
1260 /// are bits of the value now and nothing above them is a function of the source alone.
1261 #[test]
1262 fn zero_extending_a_sign_extension_is_left_alone() {
1263 let (mut func, _, _) =
1264 chain(Opcode::SExt, Opcode::ZExt, Type::int(8), Type::int(16), Type::int(64));
1265 assert!(!simplify(&mut func), "a zero extension of a sign extension was rewritten");
1266 }
1267
1268 /// And zero extending a truncation is left alone, which is the rule the tier would be expected
1269 /// to have and does not.
1270 ///
1271 /// It was written and proved and then measured, and the measurement is why it went: the
1272 /// machine has one instruction for the pair already, the `and` with an immediate that replaced
1273 /// it is the longer encoding of the two, and the mask hides the narrowing from
1274 /// [`crate::narrow`]. The rule file says the whole of it. This is here so that somebody adding
1275 /// it back finds a test rather than a silence.
1276 #[test]
1277 fn zero_extending_a_truncation_is_left_alone() {
1278 let (mut func, _, _) =
1279 chain(Opcode::Trunc, Opcode::ZExt, Type::int(64), Type::int(32), Type::int(64));
1280 assert!(!simplify(&mut func), "a zero extension of a truncation became a mask");
1281 }
1282
1283 /// A width rule needs an operand something computed, and a parameter is not one.
1284 ///
1285 /// This is what the plan being an expanding one means at the bottom: there is no instruction
1286 /// under the operand to be the second level of the pattern, so nothing matches and nothing is
1287 /// rewritten. Said out loud because it is the case that would otherwise be a crash rather than
1288 /// a miss.
1289 #[test]
1290 fn a_width_rule_needs_an_operand_an_instruction_computed() {
1291 let (_, mut func, block) = narrow_to_wide(Type::int(64), Type::int(32));
1292 let x = func.append_param(block, Type::int(64));
1293 let mut build = Builder::new(&mut func, block);
1294 let narrowed = build.unary(Opcode::Trunc, x, Type::int(32));
1295 build.ret(&[narrowed]);
1296 assert!(!simplify(&mut func), "a truncation of a parameter was rewritten");
1297 }
1298
1299 #[test]
1300 fn adding_nothing_points_every_reader_at_the_operand() {
1301 let i32 = Type::int(32);
1302 let (_, mut func, block) = one_block(i32);
1303 let x = func.append_param(block, i32);
1304 let mut build = Builder::new(&mut func, block);
1305 let zero = build.iconst(i32, 0);
1306 let sum = build.binary(Opcode::Add, x, zero, Flags::NONE);
1307 build.ret(&[sum]);
1308 assert!(simplify(&mut func));
1309 // The `add` is still there, used by nothing, which is what dead code elimination is for.
1310 assert_eq!(returned(&func, block), x);
1311 assert_eq!(came_from(&func, sum).0, Opcode::Add);
1312 }
1313
1314 /// The constant on either side, since nothing puts it on the right yet and a rule written one
1315 /// way round would fire on half the additions it should.
1316 #[test]
1317 fn the_constant_is_found_on_either_side_of_an_identity() {
1318 for swapped in [false, true] {
1319 let i32 = Type::int(32);
1320 let (_, mut func, block) = one_block(i32);
1321 let x = func.append_param(block, i32);
1322 let mut build = Builder::new(&mut func, block);
1323 let zero = build.iconst(i32, 0);
1324 let (lhs, rhs) = if swapped { (zero, x) } else { (x, zero) };
1325 let sum = build.binary(Opcode::Add, lhs, rhs, Flags::NONE);
1326 build.ret(&[sum]);
1327 assert!(simplify(&mut func), "swapped {swapped}");
1328 assert_eq!(returned(&func, block), x, "swapped {swapped}");
1329 }
1330 }
1331
1332 #[test]
1333 fn multiplying_by_nothing_becomes_the_constant_where_it_stands() {
1334 let i32 = Type::int(32);
1335 let (_, mut func, block) = one_block(i32);
1336 let x = func.append_param(block, i32);
1337 let mut build = Builder::new(&mut func, block);
1338 let zero = build.iconst(i32, 0);
1339 let product = build.binary(Opcode::Mul, x, zero, Flags::NONE);
1340 build.ret(&[product]);
1341 assert!(simplify(&mut func));
1342 // The result value survives, which is the whole reason this half rewrites in place.
1343 assert_eq!(returned(&func, block), product);
1344 assert_eq!(came_from(&func, product).0, Opcode::IConst);
1345 assert_eq!(number(&func, product), 0);
1346 }
1347
1348 /// The two identities a pattern that writes one name twice exists for, at every width they
1349 /// are written at.
1350 #[test]
1351 fn a_value_against_itself() {
1352 for bits in [8, 16, 32, 64] {
1353 let ty = Type::int(bits);
1354 let (_, mut func, block) = one_block(ty);
1355 let x = func.append_param(block, ty);
1356 let mut build = Builder::new(&mut func, block);
1357 let both = build.binary(Opcode::And, x, x, Flags::NONE);
1358 build.ret(&[both]);
1359 assert!(simplify(&mut func), "{bits} bits");
1360 assert_eq!(returned(&func, block), x, "{bits} bits");
1361
1362 let (_, mut func, block) = one_block(ty);
1363 let x = func.append_param(block, ty);
1364 let mut build = Builder::new(&mut func, block);
1365 let nothing = build.binary(Opcode::Sub, x, x, Flags::NONE);
1366 build.ret(&[nothing]);
1367 assert!(simplify(&mut func), "{bits} bits");
1368 assert_eq!(number(&func, nothing), 0, "{bits} bits");
1369 }
1370 }
1371
1372 /// A remainder by one is nothing, and a division by one is the value. The pair is worth a
1373 /// test of its own because they are the two identities that produce different shapes from the
1374 /// same operands.
1375 #[test]
1376 fn dividing_by_one_and_the_remainder_that_goes_with_it() {
1377 let i32 = Type::int(32);
1378 let (_, mut func, block) = one_block(i32);
1379 let x = func.append_param(block, i32);
1380 let mut build = Builder::new(&mut func, block);
1381 let one = build.iconst(i32, 1);
1382 let quotient = build.binary(Opcode::SDiv, x, one, Flags::NONE);
1383 let rest = build.binary(Opcode::SRem, x, one, Flags::NONE);
1384 let sum = build.binary(Opcode::Add, quotient, rest, Flags::NONE);
1385 build.ret(&[sum]);
1386 assert!(simplify(&mut func));
1387 assert_eq!(number(&func, rest), 0);
1388 // The add reads the value the division was of, which is what the redirection did.
1389 let rucc_ir::Def::Result { inst, .. } = func[sum].def else { panic!("not a result") };
1390 assert_eq!(func[func[inst].args][0], x);
1391 }
1392
1393 /// All ones at one bit is the `1` the rule file writes, and the front end writes it as `-1`.
1394 /// The two are the same bit and the rule has to fire on what the front end wrote.
1395 #[test]
1396 fn all_ones_at_one_bit_is_the_one_the_front_end_writes() {
1397 for written in [-1, 1] {
1398 let bit = Type::int(1);
1399 let (_, mut func, block) = one_block(bit);
1400 let x = func.append_param(block, bit);
1401 let mut build = Builder::new(&mut func, block);
1402 let ones = build.iconst(bit, written);
1403 let kept = build.binary(Opcode::And, x, ones, Flags::NONE);
1404 build.ret(&[kept]);
1405 assert!(simplify(&mut func), "written as {written}");
1406 assert_eq!(returned(&func, block), x, "written as {written}");
1407 }
1408 }
1409
1410 /// One identity feeding another is followed all the way, so the second is worth as much as
1411 /// the first. The redirections are applied once at the end of the run, and this is what says
1412 /// that costs nothing.
1413 #[test]
1414 fn one_identity_feeding_another_is_followed_to_the_end() {
1415 let i32 = Type::int(32);
1416 let (_, mut func, block) = one_block(i32);
1417 let x = func.append_param(block, i32);
1418 let mut build = Builder::new(&mut func, block);
1419 let zero = build.iconst(i32, 0);
1420 let one = build.iconst(i32, 1);
1421 let sum = build.binary(Opcode::Add, x, zero, Flags::NONE);
1422 let product = build.binary(Opcode::Mul, sum, one, Flags::NONE);
1423 let shifted = build.binary(Opcode::Shl, product, zero, Flags::NONE);
1424 build.ret(&[shifted]);
1425 assert!(simplify(&mut func));
1426 assert_eq!(returned(&func, block), x);
1427 }
1428
1429 #[test]
1430 fn an_instruction_no_rule_is_about_is_left_alone() {
1431 // Multiplying by three. Two is tier two and is an addition, and one and zero are tier one,
1432 // so three is the smallest constant no tier written yet has anything to say about. Turning
1433 // it into a shift and an add is the rest of tier two and is issue 523.
1434 let i32 = Type::int(32);
1435 let (_, mut func, block) = one_block(i32);
1436 let x = func.append_param(block, i32);
1437 let mut build = Builder::new(&mut func, block);
1438 let three = build.iconst(i32, 3);
1439 let tripled = build.binary(Opcode::Mul, x, three, Flags::NONE);
1440 build.ret(&[tripled]);
1441 assert!(!simplify(&mut func), "no rule is about multiplying by three");
1442 assert_eq!(returned(&func, block), tripled);
1443 assert_eq!(came_from(&func, tripled).0, Opcode::Mul);
1444 }
1445
1446 #[test]
1447 fn multiplying_by_two_becomes_an_addition_of_the_value_with_itself() {
1448 let i32 = Type::int(32);
1449 let (_, mut func, block) = one_block(i32);
1450 let x = func.append_param(block, i32);
1451 let mut build = Builder::new(&mut func, block);
1452 let two = build.iconst(i32, 2);
1453 let doubled = build.binary(Opcode::Mul, x, two, Flags::NONE);
1454 build.ret(&[doubled]);
1455 assert!(simplify(&mut func));
1456 // In place, so the value the return reads is the one it always read.
1457 assert_eq!(returned(&func, block), doubled);
1458 assert_eq!(came_from(&func, doubled).0, Opcode::Add);
1459 assert_eq!(operands(&func, doubled), [x, x]);
1460 }
1461
1462 #[test]
1463 fn multiplying_by_minus_one_becomes_a_subtraction_from_a_zero_the_rewrite_defines() {
1464 // The other shape of operand: nothing in the function holds a zero, so the rewrite has to
1465 // put one in front of the instruction it is rewriting.
1466 let i32 = Type::int(32);
1467 let (_, mut func, block) = one_block(i32);
1468 let x = func.append_param(block, i32);
1469 let mut build = Builder::new(&mut func, block);
1470 let minus = build.iconst(i32, -1);
1471 let negated = build.binary(Opcode::Mul, x, minus, Flags::NONE);
1472 build.ret(&[negated]);
1473 assert!(simplify(&mut func));
1474 assert_eq!(returned(&func, block), negated);
1475 assert_eq!(came_from(&func, negated).0, Opcode::Sub);
1476 let args = operands(&func, negated);
1477 assert_eq!(number(&func, args[0]), 0);
1478 assert_eq!(args[1], x);
1479 }
1480
1481 #[test]
1482 fn the_flags_of_the_instruction_a_strength_reduction_replaces_do_not_come_with_it() {
1483 // An `nsw` on a multiplication is a promise about that multiplication. The addition below
1484 // may well keep it, and a promise carried across a rewrite because it probably still holds
1485 // is how a wrong one gets made.
1486 let i32 = Type::int(32);
1487 let (_, mut func, block) = one_block(i32);
1488 let x = func.append_param(block, i32);
1489 let mut build = Builder::new(&mut func, block);
1490 let two = build.iconst(i32, 2);
1491 let doubled = build.binary(Opcode::Mul, x, two, Flags::NSW);
1492 build.ret(&[doubled]);
1493 assert!(simplify(&mut func));
1494 let rucc_ir::Def::Result { inst, .. } = func[doubled].def else { panic!("not a result") };
1495 assert_eq!(func[inst].flags, Flags::NONE);
1496 }
1497
1498 #[test]
1499 fn a_strength_reduction_leaves_the_verifier_nothing_to_complain_about() {
1500 // The zero the negation needs is defined in front of the instruction that reads it, and
1501 // whether it really is in front of it is a question about the block rather than about the
1502 // instruction, which is what the verifier is for.
1503 let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
1504 let i32 = Type::int(32);
1505 let (mut names, mut func, block) = one_block(i32);
1506 let mut module = Module::new(names.intern("test.c"), &target);
1507 let x = func.append_param(block, i32);
1508 let mut build = Builder::new(&mut func, block);
1509 let minus = build.iconst(i32, -1);
1510 let negated = build.binary(Opcode::Mul, x, minus, Flags::NONE);
1511 let two = build.iconst(i32, 2);
1512 let doubled = build.binary(Opcode::Mul, negated, two, Flags::NONE);
1513 build.ret(&[doubled]);
1514 assert!(simplify(&mut func));
1515 module.add_func(func);
1516 rucc_ir::verify(&module, &names).expect("the pass left the function verifiable");
1517 }
1518
1519 /// The function the pass leaves is still one the verifier accepts. Pointing a reader at a
1520 /// different value and turning an instruction into a constant are both things a rewrite could
1521 /// get wrong in a way none of the tests above would notice, because each of those asks about
1522 /// one instruction and this asks about the function.
1523 #[test]
1524 fn the_pass_leaves_the_verifier_nothing_to_complain_about() {
1525 let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
1526 let i32 = Type::int(32);
1527 let (mut names, mut func, block) = one_block(i32);
1528 let mut module = Module::new(names.intern("test.c"), &target);
1529 let x = func.append_param(block, i32);
1530 let mut build = Builder::new(&mut func, block);
1531 let zero = build.iconst(i32, 0);
1532 let one = build.iconst(i32, 1);
1533 let sum = build.binary(Opcode::Add, x, zero, Flags::NONE);
1534 let product = build.binary(Opcode::Mul, sum, one, Flags::NONE);
1535 let gone = build.binary(Opcode::Sub, product, product, Flags::NONE);
1536 let total = build.binary(Opcode::Add, product, gone, Flags::NONE);
1537 build.ret(&[total]);
1538 assert!(simplify(&mut func));
1539 module.add_func(func);
1540 rucc_ir::verify(&module, &names).expect("the pass left the function verifiable");
1541 }
1542
1543 #[test]
1544 fn fuel_stops_an_identity_and_not_the_walk() {
1545 let i32 = Type::int(32);
1546 let (_, mut func, block) = one_block(i32);
1547 let x = func.append_param(block, i32);
1548 let mut build = Builder::new(&mut func, block);
1549 let zero = build.iconst(i32, 0);
1550 let first = build.binary(Opcode::Add, x, zero, Flags::NONE);
1551 let second = build.binary(Opcode::Sub, x, zero, Flags::NONE);
1552 let sum = build.binary(Opcode::Add, first, second, Flags::NONE);
1553 build.ret(&[sum]);
1554 let stats =
1555 Simplify.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::of(1));
1556 assert!(stats.changed());
1557 assert_eq!(stats.total(Kind::Optimized), 1);
1558 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL_RULE), 1);
1559 // The first fired and the second did not, and the second is still read by the add.
1560 let rucc_ir::Def::Result { inst, .. } = func[sum].def else { panic!("not a result") };
1561 assert_eq!(func[func[inst].args], [x, second]);
1562 }
1563
1564 #[test]
1565 fn a_negated_float_comparison_becomes_the_opposite_predicate() {
1566 // Every ordered predicate and its opposite, which is the table `!(x < y)` is `x >= y`
1567 // or unordered lives in, and the one place a sign error would hide.
1568 for pred in FloatPred::all() {
1569 let (_, mut func, block) = blank();
1570 let mut build = Builder::new(&mut func, block);
1571 let x = build.iconst(Type::int(64), 0);
1572 let x = build.unary(Opcode::Bitcast, x, Type::float(Float::F64));
1573 let cmp = build.fcmp(pred, x, x, Flags::NONE);
1574 let ones = build.iconst(Type::int(1), -1);
1575 let not = build.binary(Opcode::Xor, cmp, ones, Flags::NONE);
1576 build.ret(&[not]);
1577 assert!(simplify(&mut func), "{pred:?}");
1578 assert_eq!(
1579 came_from(&func, not),
1580 (Opcode::FCmp, Extra::FloatPred(pred.inverse())),
1581 "{pred:?}"
1582 );
1583 }
1584 }
1585
1586 #[test]
1587 fn a_negated_integer_comparison_becomes_the_opposite_predicate() {
1588 for pred in IntPred::all() {
1589 let (_, mut func, block) = blank();
1590 let mut build = Builder::new(&mut func, block);
1591 let x = build.iconst(Type::int(32), 3);
1592 let cmp = build.icmp(pred, x, x);
1593 let ones = build.iconst(Type::int(1), -1);
1594 let not = build.binary(Opcode::Xor, cmp, ones, Flags::NONE);
1595 build.ret(&[not]);
1596 assert!(simplify(&mut func), "{pred:?}");
1597 assert_eq!(
1598 came_from(&func, not),
1599 (Opcode::ICmp, Extra::IntPred(pred.inverse())),
1600 "{pred:?}"
1601 );
1602 }
1603 }
1604
1605 #[test]
1606 fn the_constant_is_found_on_either_side() {
1607 for swapped in [false, true] {
1608 let (_, mut func, block) = blank();
1609 let mut build = Builder::new(&mut func, block);
1610 let x = build.iconst(Type::int(32), 3);
1611 let cmp = build.icmp(IntPred::Slt, x, x);
1612 let ones = build.iconst(Type::int(1), -1);
1613 let (lhs, rhs) = if swapped { (ones, cmp) } else { (cmp, ones) };
1614 let not = build.binary(Opcode::Xor, lhs, rhs, Flags::NONE);
1615 build.ret(&[not]);
1616 assert!(simplify(&mut func), "swapped {swapped}");
1617 assert_eq!(came_from(&func, not).1, Extra::IntPred(IntPred::Sge));
1618 }
1619 }
1620
1621 #[test]
1622 fn an_exclusive_or_of_two_comparisons_is_left_alone() {
1623 let (_, mut func, block) = blank();
1624 let mut build = Builder::new(&mut func, block);
1625 let x = build.iconst(Type::int(32), 3);
1626 let a = build.icmp(IntPred::Slt, x, x);
1627 let b = build.icmp(IntPred::Sgt, x, x);
1628 let differ = build.binary(Opcode::Xor, a, b, Flags::NONE);
1629 build.ret(&[differ]);
1630 assert!(!simplify(&mut func));
1631 assert_eq!(came_from(&func, differ).0, Opcode::Xor);
1632 }
1633
1634 #[test]
1635 fn an_exclusive_or_of_something_that_is_not_a_comparison_is_left_alone() {
1636 let (_, mut func, block) = blank();
1637 let mut build = Builder::new(&mut func, block);
1638 let x = build.iconst(Type::int(32), 3);
1639 let narrow = build.unary(Opcode::Trunc, x, Type::int(1));
1640 let ones = build.iconst(Type::int(1), -1);
1641 let not = build.binary(Opcode::Xor, narrow, ones, Flags::NONE);
1642 build.ret(&[not]);
1643 assert!(!simplify(&mut func));
1644 assert_eq!(came_from(&func, not).0, Opcode::Xor);
1645 }
1646
1647 #[test]
1648 fn a_wider_exclusive_or_with_one_is_not_a_negation_and_is_left_alone() {
1649 let (_, mut func, block) = blank();
1650 let mut build = Builder::new(&mut func, block);
1651 let x = build.iconst(Type::int(32), 3);
1652 let cmp = build.icmp(IntPred::Slt, x, x);
1653 let wide = build.unary(Opcode::ZExt, cmp, Type::int(32));
1654 let one = build.iconst(Type::int(32), 1);
1655 let flipped = build.binary(Opcode::Xor, wide, one, Flags::NONE);
1656 let narrow = build.unary(Opcode::Trunc, flipped, Type::int(1));
1657 build.ret(&[narrow]);
1658 assert!(!simplify(&mut func), "an i32 xor 1 flips one bit of thirty two");
1659 assert_eq!(came_from(&func, flipped).0, Opcode::Xor);
1660 }
1661
1662 #[test]
1663 fn the_comparisons_flags_travel_with_the_predicate() {
1664 let (_, mut func, block) = blank();
1665 let mut build = Builder::new(&mut func, block);
1666 let x = build.iconst(Type::int(64), 0);
1667 let x = build.unary(Opcode::Bitcast, x, Type::float(Float::F64));
1668 let cmp = build.fcmp(FloatPred::Olt, x, x, Flags::FAST);
1669 let ones = build.iconst(Type::int(1), -1);
1670 let not = build.binary(Opcode::Xor, cmp, ones, Flags::NONE);
1671 build.ret(&[not]);
1672 assert!(simplify(&mut func));
1673 let rucc_ir::Def::Result { inst, .. } = func[not].def else { panic!("not a result") };
1674 // The promise the original comparison was made under, not the exclusive or's absence of
1675 // one. Dropping it would be correct and would quietly undo a fast math flag.
1676 assert_eq!(func[inst].flags, Flags::FAST);
1677 }
1678
1679 #[test]
1680 fn fuel_stops_the_transformation_and_not_the_walk() {
1681 let (_, mut func, block) = blank();
1682 let mut build = Builder::new(&mut func, block);
1683 let x = build.iconst(Type::int(32), 3);
1684 let a = build.icmp(IntPred::Slt, x, x);
1685 let b = build.icmp(IntPred::Sgt, x, x);
1686 let ones = build.iconst(Type::int(1), -1);
1687 let first = build.binary(Opcode::Xor, a, ones, Flags::NONE);
1688 let second = build.binary(Opcode::Xor, b, ones, Flags::NONE);
1689 let both = build.binary(Opcode::And, first, second, Flags::NONE);
1690 build.ret(&[both]);
1691 let stats =
1692 Simplify.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::of(1));
1693 assert!(stats.changed());
1694 assert_eq!(stats.count(Kind::Optimized, super::FLIPPED), 1);
1695 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
1696 assert_eq!(came_from(&func, first).0, Opcode::ICmp);
1697 assert_eq!(came_from(&func, second).0, Opcode::Xor);
1698 }
1699}