rucc_ir/term.rs
1//! The IR as something a rule can match against.
2//!
3//! Design: `spec/10-backend.md` section 10.2 and `spec/optimizer/13-rewrite-rules.md`.
4//!
5//! Two rule sets are matched against the IR. `rucc-codegen` lowers it to machine terms and
6//! `rucc-opt` rewrites it to more IR, and both of them are asking what an instruction is called
7//! and what its operands are. This is here rather than in either of them so that there is one
8//! answer to that: a rewrite rule and a lowering rule that spelled `add.i32` differently would
9//! be two vocabularies over one IR, and the day they drifted apart nothing would say so.
10//!
11//! A rule is written about a term and the compiler has no terms. It has a function full of
12//! instructions, and what a pattern is about is one of them together with whatever its operands
13//! were computed from. So this is the [`Subject`] the matcher asks its three questions of, and
14//! the answers come out of the IR: nothing is built and nothing is thrown away.
15//!
16//! # How an operand is shown
17//!
18//! The same IR value can be several different terms. `(add.i32 (value.i32 x) (iconst.i32 k))`
19//! and `(add.i32 (value.i32 x) (value.i32 y))` are two patterns over one instruction, and which
20//! one it is depends on whether the second operand is a constant and on whether the rule that
21//! wants a constant will take this one. `(add.i64 (value.i64 x) (mul.i64 (value.i64 y)
22//! (iconst.i64 4)))` is a third, and it is about two instructions rather than one.
23//!
24//! The matcher does not backtrack across alternatives for one node: [`Subject::head`] gives one
25//! answer and the walk believes it. So the choice is made before the walk rather than during it.
26//! A [`Plan`] says how each operand of the instruction is shown, the caller tries the plans in
27//! order, and the first that matches is the one that fires. There are at most three ways to show
28//! an operand and at most two operands in any pattern either rule set has, so the whole of the
29//! search is a handful of walks over a trie, each of which fails in its first node or two.
30//!
31//! # How deep it goes
32//!
33//! One level. An operand may be shown as the instruction that computed it, and that
34//! instruction's own operands are shown as a register or as a constant and never expanded
35//! again, which is as deep as any pattern in either rule file reaches. A rule set that wants
36//! three levels needs this to grow a level, and it would be found by the rule failing to fire
37//! rather than by anything going wrong.
38
39use rucc_base::rules::Subject;
40
41use crate::{Def, Extra, Float, FloatPred, Func, Inst, IntPred, Opcode, Type, Value};
42
43/// How many operands of one instruction a plan can speak about.
44///
45/// Two is what every pattern in the rule set needs, and a third costs nothing to carry. An
46/// instruction with more operands than this is one no rule matches, which is the same answer it
47/// would get from a plan that could describe it.
48pub const MAX_ARGS: usize = 3;
49
50/// How one operand is shown to the matcher.
51#[derive(Clone, Copy, Debug, PartialEq, Eq)]
52pub enum Shown {
53 /// As a value sitting in a register, which is what `(value.iN x)` matches.
54 Reg,
55 /// As a constant the caller has in hand, which is what `(iconst.iN k)` matches.
56 Const,
57 /// As the instruction that computed it, so a rule can be about two instructions at once.
58 Expand,
59}
60
61/// How every operand of one instruction is shown.
62pub type Plan = [Shown; MAX_ARGS];
63
64/// Everything shown as a register, which is the plan that matches when no other does.
65pub const PLAIN: Plan = [Shown::Reg; MAX_ARGS];
66
67/// One node of the term the matcher is walking.
68///
69/// A position rather than a term, because the term does not exist. Two of these are values in
70/// their own right, and they are the two a pattern can bind: the register a `value` wraps and
71/// the number an `iconst` wraps.
72#[derive(Clone, Copy, Debug, PartialEq, Eq)]
73pub enum Term {
74 /// The instruction being matched.
75 Root,
76 /// Operand `i` of the root, shown the way the plan says to show it.
77 Arg(u8),
78 /// Operand `j` of the instruction that computed operand `i` of the root.
79 Deep(u8, u8),
80 /// A value in a register, which is what a pattern binds when it writes `(value.iN x)`.
81 Reg(Value),
82 /// A constant, which is what a pattern binds or tests inside an `(iconst.iN k)`.
83 Num(i128),
84}
85
86/// One instruction of a function, as the terms a rule could match.
87#[derive(Debug)]
88pub struct Terms<'a> {
89 func: &'a Func,
90 root: Inst,
91 plan: Plan,
92}
93
94impl<'a> Terms<'a> {
95 /// The instruction, shown the way the plan says.
96 #[must_use]
97 pub fn new(func: &'a Func, root: Inst, plan: Plan) -> Self {
98 Self { func, root, plan }
99 }
100
101 /// The instruction this is about.
102 #[must_use]
103 pub fn root(&self) -> Inst {
104 self.root
105 }
106
107 /// What the root, or an instruction one of its operands was expanded into, is called in a
108 /// rule file.
109 #[must_use]
110 pub fn name(&self, inst: Inst) -> Option<&'static str> {
111 head_of(self.func, inst)
112 }
113
114 /// The value operands of an instruction.
115 fn args(&self, inst: Inst) -> &[Value] {
116 &self.func[self.func[inst].args]
117 }
118
119 /// Operand `index` of the root, or nothing if it has no such operand.
120 fn arg_value(&self, index: u8) -> Option<Value> {
121 self.args(self.root).get(usize::from(index)).copied()
122 }
123
124 /// The instruction a value is the result of, or nothing for a block parameter.
125 fn def_of(&self, value: Value) -> Option<Inst> {
126 match self.func[value].def {
127 Def::Result { inst, .. } => Some(inst),
128 Def::Param { .. } => None,
129 }
130 }
131
132 /// What a value is, if it is a constant.
133 #[must_use]
134 pub fn constant(&self, value: Value) -> Option<i128> {
135 let inst = self.def_of(value)?;
136 let data = &self.func[inst];
137 if data.opcode != Opcode::IConst {
138 return None;
139 }
140 let Extra::Imm(imm) = data.extra else { return None };
141 let ty = self.func[value].ty;
142 if !ty.is_int() {
143 return None;
144 }
145 // One bit is read unsigned, and every other width is read signed. The sign bit of a one
146 // bit integer is the whole of it, so the signed reading of a true is minus one, and what
147 // a rule at that width means by the number it matched is the truth value rather than a
148 // bit pattern. Reading it signed would put a byte of ones in a register where the rest of
149 // the rule set expects a zero or a one.
150 if is_bit(ty) {
151 return Some(i128::try_from(self.func[imm].unsigned()).unwrap_or(0));
152 }
153 Some(self.func[imm].signed(ty))
154 }
155
156 /// The head of a value shown as a register or as a constant, which is a term of one
157 /// argument either way: the thing the pattern binds.
158 fn leaf_head(&self, value: Value, shown: Shown) -> Option<(&'static str, usize)> {
159 let ty = self.func[value].ty;
160 let name = match shown {
161 Shown::Reg => value_head(ty)?,
162 Shown::Const => iconst_head(ty)?,
163 // An expansion is not a leaf, and nothing asks this about one.
164 Shown::Expand => return None,
165 };
166 Some((name, 1))
167 }
168
169 /// What a value shown as a register or as a constant binds, which is the value itself or
170 /// the number it is.
171 fn leaf_arg(&self, value: Value, shown: Shown) -> Term {
172 match shown {
173 Shown::Const => self.constant(value).map_or(Term::Reg(value), Term::Num),
174 Shown::Reg | Shown::Expand => Term::Reg(value),
175 }
176 }
177
178 /// How an operand of an expanded operand is shown, which is as a constant when it is one
179 /// and as a register otherwise.
180 ///
181 /// There is no choice to make here. The reason to show a constant as a register is that no
182 /// rule would take it as an immediate, and the answer to that inside an expansion is to
183 /// stop expanding, which is a plan the selector tries anyway.
184 fn deep_shown(&self, value: Value) -> Shown {
185 if self.constant(value).is_some() { Shown::Const } else { Shown::Reg }
186 }
187
188 /// The value a place holds, or nothing for a place that holds a constant rather than a
189 /// value.
190 ///
191 /// This is what makes two places comparable. A rule that writes one name twice is asking
192 /// whether both of its operands are the same value, and the two places are operand zero and
193 /// operand one, which are never equal as places.
194 fn value_at(&self, node: Term) -> Option<Value> {
195 match node {
196 Term::Root => self.func[self.root].first_result,
197 Term::Arg(index) => self.arg_value(index),
198 Term::Deep(outer, inner) => {
199 self.expansion(outer).and_then(|(_, args)| args.get(usize::from(inner)).copied())
200 }
201 Term::Reg(value) => Some(value),
202 Term::Num(_) => None,
203 }
204 }
205
206 /// The instruction an expanded operand of the root was computed by, with its operands.
207 fn expansion(&self, index: u8) -> Option<(Inst, &[Value])> {
208 let value = self.arg_value(index)?;
209 let inst = self.def_of(value)?;
210 Some((inst, self.args(inst)))
211 }
212}
213
214impl Subject for Terms<'_> {
215 type Node = Term;
216
217 fn head(&self, node: Term) -> Option<(&str, usize)> {
218 match node {
219 Term::Root => {
220 let name = head_of(self.func, self.root)?;
221 let data = &self.func[self.root];
222 // A constant has no operands and its term has one, which is the constant, so it
223 // is the one instruction whose arity is not the length of its operand list.
224 let arity =
225 if data.opcode == Opcode::IConst { 1 } else { self.args(self.root).len() };
226 Some((name, arity))
227 }
228 Term::Arg(index) => {
229 let value = self.arg_value(index)?;
230 match self.plan[usize::from(index)] {
231 Shown::Expand => {
232 let (inst, args) = self.expansion(index)?;
233 Some((head_of(self.func, inst)?, args.len()))
234 }
235 shown => self.leaf_head(value, shown),
236 }
237 }
238 Term::Deep(outer, inner) => {
239 let (_, args) = self.expansion(outer)?;
240 let value = *args.get(usize::from(inner))?;
241 self.leaf_head(value, self.deep_shown(value))
242 }
243 Term::Reg(_) | Term::Num(_) => None,
244 }
245 }
246
247 fn arg(&self, node: Term, index: usize) -> Term {
248 let index = u8::try_from(index).unwrap_or(u8::MAX);
249 match node {
250 Term::Root => {
251 let data = &self.func[self.root];
252 if data.opcode == Opcode::IConst {
253 let value = data.first_result.expect("a constant has a result");
254 return self.leaf_arg(value, Shown::Const);
255 }
256 Term::Arg(index)
257 }
258 Term::Arg(outer) => match self.plan[usize::from(outer)] {
259 Shown::Expand => Term::Deep(outer, index),
260 shown => {
261 self.arg_value(outer).map_or(Term::Num(0), |value| self.leaf_arg(value, shown))
262 }
263 },
264 Term::Deep(outer, inner) => {
265 let value = self
266 .expansion(outer)
267 .and_then(|(_, args)| args.get(usize::from(inner)).copied());
268 value.map_or(Term::Num(0), |value| self.leaf_arg(value, self.deep_shown(value)))
269 }
270 // Neither has a head, so nothing asks either of them for an argument.
271 Term::Reg(_) | Term::Num(_) => node,
272 }
273 }
274
275 fn int(&self, node: Term) -> Option<i128> {
276 match node {
277 Term::Num(value) => Some(value),
278 _ => None,
279 }
280 }
281
282 fn same(&self, a: Term, b: Term) -> bool {
283 match (self.value_at(a), self.value_at(b)) {
284 (Some(left), Some(right)) => left == right,
285 // Neither is a value, so the only other thing either can be is a constant the plan
286 // asked to be shown as one. Two constants of the same number are the same term
287 // whatever computed them, which is the one case where this is not an identity.
288 _ => match (self.int(a), self.int(b)) {
289 (Some(left), Some(right)) => left == right,
290 _ => false,
291 },
292 }
293 }
294}
295
296/// What an instruction is called in a rule file, or nothing if the rules have no name for it.
297///
298/// The one function here that a caller with an [`Inst`] and no [`Terms`] wants, which is
299/// anything reporting on a rule rather than matching one.
300///
301/// The name carries the width, because a rule file that did not say how wide a term is would be
302/// a file whose reader has to look at the line above to find out. Which widths there are names
303/// for is the rule language's business and not this crate's: an instruction at a width nothing
304/// is written about has no name here, and the answer to it is that no rule matches.
305pub fn head_of(func: &Func, inst: Inst) -> Option<&'static str> {
306 let data = &func[inst];
307
308 // A store is the one instruction with a name here that computes nothing, so the width in
309 // its name is the width of what it is storing and has to come from an operand. That operand
310 // is the first one, which is the order `crate::Builder::store` puts them in and the order
311 // a pattern for one is written in.
312 //
313 // Nothing looks at the flags or the ordering, and both of those are worth saying out loud.
314 // A `volatile` access has to happen exactly once and must not move, and neither of those is
315 // something selection does: one IR load is one instruction whatever its flags say, and
316 // folding the address arithmetic into the addressing mode does not change how many times
317 // memory is touched. An ordering would be a different matter, because a store that releases
318 // is not a plain `mov` on any machine where it means anything, but an ordered access is
319 // `atomic_load` or `atomic_store` and those are different opcodes with no name here. The IR
320 // verifier is what makes that true rather than merely usual: it rejects an ordering on a
321 // plain access, so by the time anything is selected there is none to miss.
322 if data.opcode == Opcode::Store {
323 let value = *func[data.args].first()?;
324 return store_head(func[value].ty);
325 }
326
327 // A return is the other one, and the width comes from the operand for the same reason. A
328 // return of nothing has no name, and neither has a return of more than one value: a rule
329 // for either would have to say where each of them goes, and where a value goes is a fact
330 // about the convention rather than about a term, so the rule language has nothing to say
331 // about it. A return of nothing needs no rule at all, since the epilogue is the whole of it.
332 if data.opcode == Opcode::Return {
333 let [value] = &func[data.args] else { return None };
334 return ret_head(func[*value].ty);
335 }
336
337 // A conditional branch is the third instruction here that computes nothing. Where it goes is
338 // not part of its name and not part of any pattern: a machine IR block holds its own
339 // successors, so a rule for a branch never has to say a block, and what is left for it to say
340 // is what the branch is about, which is the condition.
341 if data.opcode == Opcode::BrIf {
342 let [cond] = &func[data.args] else { return None };
343 return (func[*cond].ty == Type::int(1)).then_some(BRIF);
344 }
345
346 let result = data.first_result?;
347 let ty = func[result].ty;
348 match data.opcode {
349 Opcode::IConst => iconst_head(ty),
350 Opcode::Load => load_head(ty),
351 Opcode::ICmp => {
352 let Extra::IntPred(pred) = data.extra else { return None };
353 Some(icmp_head(pred))
354 }
355 // A float comparison, whose name comes from the operands rather than from the result: the
356 // result is one bit either way and what tells the two instructions apart is the format.
357 Opcode::FCmp => {
358 let Extra::FloatPred(pred) = data.extra else { return None };
359 fcmp_head(pred, func[*func[data.args].first()?].ty)
360 }
361 Opcode::SExt | Opcode::ZExt | Opcode::Trunc => {
362 let from = func[*func[data.args].first()?].ty;
363 convert_head(data.opcode, from, ty)
364 }
365 // The conversions with a float on one side or both. A separate row because what is on
366 // each side is part of the name and a width alone would not say which register file the
367 // value is in, which is the whole difference between these and the three above.
368 Opcode::FPExt | Opcode::FPTrunc | Opcode::FPToSI | Opcode::SIToFP | Opcode::Bitcast => {
369 let from = func[*func[data.args].first()?].ty;
370 cross_head(data.opcode, from, ty)
371 }
372 // Address arithmetic is an add at the address width, which is all it is once both
373 // operands are in registers: the offset is already in bytes, which the IR guarantees and
374 // the front end is what did the multiplying. Calling it that is what lets every rule
375 // written about an add reach it, including the ones that fold it into an addressing mode,
376 // and there is nothing in any of them it could get wrong.
377 Opcode::PtrAdd => binary_head(Opcode::Add, ty),
378 opcode => binary_head(opcode, ty),
379 }
380}
381
382/// What a conditional branch is called, which carries the width of the condition and nothing
383/// else, since where the branch goes is on the block rather than in the term.
384///
385/// A constant rather than a literal in [`head_of`] because [`heads`] says it too, and a name
386/// written in two places is a name that can differ in one of them.
387const BRIF: &str = "brif.i1";
388
389/// Every name this module can give an instruction, with the opcode it gives it to.
390///
391/// This is what a rule file could be written about, so that the back end's coverage check can ask what one
392/// is written about and say where the difference is. It comes out of the same functions
393/// [`head_of`] asks rather than out of a list, because a list of names checked against another
394/// list of names is a test that both were typed the same way, which is not the question worth
395/// asking.
396///
397/// The sweep is over every type the compiler has, including the ones nothing here has a name for.
398/// A width with no name contributes nothing and costs nothing, and the day one of them gets a name
399/// it appears here without anybody remembering to add it, which is the property that makes this
400/// worth generating rather than writing down.
401pub fn heads() -> Vec<(Opcode, &'static str)> {
402 let types = [
403 Type::int(1),
404 Type::int(8),
405 Type::int(16),
406 Type::int(32),
407 Type::int(64),
408 Type::int(128),
409 Type::PTR,
410 Type::float(Float::F32),
411 Type::float(Float::F64),
412 Type::float(Float::F80),
413 Type::vector(Type::int(32), 4),
414 ];
415
416 let mut found = Vec::new();
417 for opcode in Opcode::all() {
418 // The names that come from one type, which is the result's for most of these and an
419 // operand's for the two that compute nothing. The arms are the ones `head_of` has, in the
420 // order it has them, so that a name reachable there is reachable here.
421 for &ty in &types {
422 let name = match opcode {
423 Opcode::Store => store_head(ty),
424 Opcode::Return => ret_head(ty),
425 Opcode::IConst => iconst_head(ty),
426 Opcode::Load => load_head(ty),
427 Opcode::PtrAdd => binary_head(Opcode::Add, ty),
428 _ => binary_head(opcode, ty),
429 };
430 if let Some(name) = name {
431 found.push((opcode, name));
432 }
433 }
434 // And the names that come from a predicate or from two types at once.
435 match opcode {
436 Opcode::BrIf => found.push((opcode, BRIF)),
437 Opcode::ICmp => found.extend(IntPred::all().map(|pred| (opcode, icmp_head(pred)))),
438 Opcode::FCmp => {
439 for pred in FloatPred::all() {
440 let named = types.iter().filter_map(|&ty| fcmp_head(pred, ty));
441 found.extend(named.map(|name| (opcode, name)));
442 }
443 }
444 Opcode::SExt | Opcode::ZExt | Opcode::Trunc => {
445 for &from in &types {
446 let named = types.iter().filter_map(|&to| convert_head(opcode, from, to));
447 found.extend(named.map(|name| (opcode, name)));
448 }
449 }
450 Opcode::FPExt | Opcode::FPTrunc | Opcode::FPToSI | Opcode::SIToFP | Opcode::Bitcast => {
451 for &from in &types {
452 let named = types.iter().filter_map(|&to| cross_head(opcode, from, to));
453 found.extend(named.map(|name| (opcode, name)));
454 }
455 }
456 _ => {}
457 }
458 }
459
460 found.sort_unstable();
461 found.dedup();
462 found
463}
464
465/// How wide an address is on the machine this lowers for.
466///
467/// The rule set has no term for a pointer and needs none. An address in a register is an integer
468/// of the machine's address width, every rule that could compute one is a rule about an integer
469/// of that width, and the only thing missing was a name. [`slot`] used to ask the type how wide
470/// it was, and a pointer answers nothing, because how wide an address is belongs to the target
471/// rather than to the IR. So this is where the target's answer is written down.
472///
473/// Sixty four, and a constant rather than something asked of a target, because every
474/// architecture `rucc_target::Arch` names is a sixty four bit one. There is no target in the
475/// compiler that would want a different number, and a thirty two bit one would want more from
476/// the rule sets than a number.
477pub const ADDRESS: u32 = 64;
478
479/// Which of the four widths a type is, or nothing for a width no rule is written at.
480///
481/// A pointer is one of them, at [`ADDRESS`]. A vector is none of them however wide its lane is,
482/// because a rule at a width says nothing about how many lanes it acts on and lowering an add of
483/// four lanes to an add of one would be wrong rather than incomplete.
484pub fn slot(ty: Type) -> Option<usize> {
485 if !ty.is_scalar() {
486 return None;
487 }
488 let bits = if ty.is_ptr() { ADDRESS } else { ty.is_int().then(|| ty.bits())? };
489 match bits {
490 8 => Some(0),
491 16 => Some(1),
492 32 => Some(2),
493 64 => Some(3),
494 _ => None,
495 }
496}
497
498/// Which of the two float widths a type is, or nothing for anything that is not a float.
499///
500/// Two rather than [`slot`]'s four, and a table of its own rather than more entries in that one,
501/// because a `float` and an `int` of the same width are not the same term to any rule: they are in
502/// different register files and every instruction that touches them is a different instruction. A
503/// `long double` is none of them, since it is on the x87 stack rather than in a vector register
504/// and nothing here is written about that stack.
505pub fn float_slot(ty: Type) -> Option<usize> {
506 if !ty.is_scalar() || !ty.is_float() {
507 return None;
508 }
509 match ty.bits() {
510 32 => Some(0),
511 64 => Some(1),
512 _ => None,
513 }
514}
515
516/// Whether a type is the one bit a truth value comes in.
517///
518/// One bit is a width the rule set is written at and is not one of [`slot`]'s four, because it is
519/// not a width the machine computes in. There is no one bit register and no one bit instruction: a
520/// value of this width lives in a whole byte with the other seven bits zero, which is what a
521/// `setcc` leaves behind, and every rule written at one bit is a byte instruction chosen because
522/// it keeps that true. The model says the same thing from the other side, giving `setcc` a meaning
523/// one bit wide, so the abstraction is stated in both places rather than assumed in either.
524///
525/// What makes the invariant hold rather than merely be usual is that nothing else at this width
526/// has a name. A comparison is the only instruction that produces one, the bitwise operations
527/// below carry it through unchanged, and everything else at one bit reaches [`slot`] and gets
528/// nothing, so there is no rule that could put a byte here which is not a zero or a one.
529fn is_bit(ty: Type) -> bool {
530 ty.is_scalar() && ty.is_int() && ty.bits() == 1
531}
532
533/// What a value in a register is called at that width.
534fn value_head(ty: Type) -> Option<&'static str> {
535 if is_bit(ty) {
536 return Some("value.i1");
537 }
538 if let Some(at) = float_slot(ty) {
539 return Some(["value.f32", "value.f64"][at]);
540 }
541 Some(["value.i8", "value.i16", "value.i32", "value.i64"][slot(ty)?])
542}
543
544/// What a constant is called at that width.
545///
546/// An integer and not an address, unlike everything else here. What a pattern binds inside one of
547/// these is the number, and [`Terms::constant`] only has a number for an integer, so a term that
548/// named an address would be one a rule could match and then find nothing behind.
549fn iconst_head(ty: Type) -> Option<&'static str> {
550 if !ty.is_int() {
551 return None;
552 }
553 if is_bit(ty) {
554 return Some("iconst.i1");
555 }
556 Some(["iconst.i8", "iconst.i16", "iconst.i32", "iconst.i64"][slot(ty)?])
557}
558
559/// What a load is called, which is the width of the value it produced.
560fn load_head(ty: Type) -> Option<&'static str> {
561 if let Some(at) = float_slot(ty) {
562 return Some(["load.f32", "load.f64"][at]);
563 }
564 Some(["load.i8", "load.i16", "load.i32", "load.i64"][slot(ty)?])
565}
566
567/// What a store is called, which is the width of the value it writes, since it produces nothing
568/// to take a width from.
569fn store_head(ty: Type) -> Option<&'static str> {
570 if let Some(at) = float_slot(ty) {
571 return Some(["store.f32", "store.f64"][at]);
572 }
573 Some(["store.i8", "store.i16", "store.i32", "store.i64"][slot(ty)?])
574}
575
576/// What a return is called, which is the width of the value it gives back, for the same reason.
577fn ret_head(ty: Type) -> Option<&'static str> {
578 if let Some(at) = float_slot(ty) {
579 return Some(["ret.f32", "ret.f64"][at]);
580 }
581 Some(["ret.i8", "ret.i16", "ret.i32", "ret.i64"][slot(ty)?])
582}
583
584/// What a comparison is called, which does not carry the width of what it compared: the result
585/// is one bit whatever the operands were, and the operands say how wide they are themselves.
586fn icmp_head(pred: IntPred) -> &'static str {
587 match pred {
588 IntPred::Eq => "icmp_eq.i1",
589 IntPred::Ne => "icmp_ne.i1",
590 IntPred::Slt => "icmp_slt.i1",
591 IntPred::Sle => "icmp_sle.i1",
592 IntPred::Sgt => "icmp_sgt.i1",
593 IntPred::Sge => "icmp_sge.i1",
594 IntPred::Ult => "icmp_ult.i1",
595 IntPred::Ule => "icmp_ule.i1",
596 IntPred::Ugt => "icmp_ugt.i1",
597 IntPred::Uge => "icmp_uge.i1",
598 }
599}
600
601/// What a float comparison is called, which does carry the format of what it compared.
602///
603/// The difference from [`icmp_head`] is the whole reason this is a second function. A comparison
604/// of two integers is the same instruction whatever file they came from, because there is only one
605/// file they could have come from, so the width lives on the operands and the name says nothing
606/// about it. A comparison of two floats is a different instruction for a `float` and a `double`,
607/// and the operands are in registers that hold either, so the name has to say which.
608///
609/// The two predicates that read nothing have no name here. `false` and `true` do not look at their
610/// operands, so a rule for either would be a rule that computes a constant out of a comparison it
611/// did not make, and the front end writes neither: nothing in C spells them and nothing here folds
612/// a comparison into one yet.
613fn fcmp_head(pred: FloatPred, ty: Type) -> Option<&'static str> {
614 let at = float_slot(ty)?;
615 let names: [&'static str; 2] = match pred {
616 FloatPred::Oeq => ["fcmp_oeq.f32.i1", "fcmp_oeq.f64.i1"],
617 FloatPred::Ogt => ["fcmp_ogt.f32.i1", "fcmp_ogt.f64.i1"],
618 FloatPred::Oge => ["fcmp_oge.f32.i1", "fcmp_oge.f64.i1"],
619 FloatPred::Olt => ["fcmp_olt.f32.i1", "fcmp_olt.f64.i1"],
620 FloatPred::Ole => ["fcmp_ole.f32.i1", "fcmp_ole.f64.i1"],
621 FloatPred::One => ["fcmp_one.f32.i1", "fcmp_one.f64.i1"],
622 FloatPred::Ord => ["fcmp_ord.f32.i1", "fcmp_ord.f64.i1"],
623 FloatPred::Uno => ["fcmp_uno.f32.i1", "fcmp_uno.f64.i1"],
624 FloatPred::Ueq => ["fcmp_ueq.f32.i1", "fcmp_ueq.f64.i1"],
625 FloatPred::Ugt => ["fcmp_ugt.f32.i1", "fcmp_ugt.f64.i1"],
626 FloatPred::Uge => ["fcmp_uge.f32.i1", "fcmp_uge.f64.i1"],
627 FloatPred::Ult => ["fcmp_ult.f32.i1", "fcmp_ult.f64.i1"],
628 FloatPred::Ule => ["fcmp_ule.f32.i1", "fcmp_ule.f64.i1"],
629 FloatPred::Une => ["fcmp_une.f32.i1", "fcmp_une.f64.i1"],
630 FloatPred::False | FloatPred::True => return None,
631 };
632 Some(names[at])
633}
634
635/// What a conversion is called, which is the two widths it is between.
636///
637/// A widening from one bit is the one conversion this width has, and it is a row of its own rather
638/// than a fifth entry in the tables below. A five by five table would have a name for every
639/// conversion between one bit and every other width in both directions, and all but four of those
640/// are conversions nothing writes: a narrowing to one bit is a comparison against zero, which is a
641/// different opcode, and a sign extension from one bit is what an `unsigned` comparison result
642/// would need and there is none.
643fn convert_head(opcode: Opcode, from: Type, to: Type) -> Option<&'static str> {
644 if is_bit(from) {
645 if opcode != Opcode::ZExt {
646 return None;
647 }
648 return Some(["zext.i1.i8", "zext.i1.i16", "zext.i1.i32", "zext.i1.i64"][slot(to)?]);
649 }
650 let table: &[[Option<&'static str>; 4]; 4] = match opcode {
651 Opcode::SExt => &SEXT,
652 Opcode::ZExt => &ZEXT,
653 Opcode::Trunc => &TRUNC,
654 _ => return None,
655 };
656 table[slot(from)?][slot(to)?]
657}
658
659/// Which of the two integer widths a conversion to or from a float is written at, or nothing for
660/// any other width.
661///
662/// The machine converts at thirty two bits and at sixty four and at no width below them. A C
663/// program turning a `double` into a `short` is a conversion to `int` and a truncation after it,
664/// and the front end is what writes the truncation, so a narrower conversion arriving here has no
665/// name and is reported rather than lowered to an instruction that would round it in the wrong
666/// place.
667fn cross_slot(ty: Type) -> Option<usize> {
668 match slot(ty)? {
669 2 => Some(0),
670 3 => Some(1),
671 _ => None,
672 }
673}
674
675/// Whether that type is the integer the float at that index shares its width with.
676///
677/// A pointer is not, however wide it is. The IR has `ptrtoint` for turning an address into a
678/// number, and a `bitcast` that moved one through a vector register would be hiding that
679/// conversion rather than performing it, which is what the IR verifier says as well.
680fn paired_int(ty: Type, at: usize) -> bool {
681 ty.is_scalar() && ty.is_int() && ty.bits() == [32, 64][at]
682}
683
684/// What a conversion with a float on one side or both is called, which is what it goes between and
685/// which side each of them is on.
686///
687/// The name carries the format where an integer conversion carries a width, for the reason
688/// [`float_slot`] gives: a `float` and an `int` of the same width are in different register files
689/// and no rule written about one says anything about the other. So there is no name here that
690/// could be read as either, and a rule for `fptosi.f64.i32` cannot match anything but a `double`
691/// becoming an `int`.
692///
693/// The unsigned conversions have no name. The machine has no instruction for either below a
694/// register wider than anything this allocates, so each is several instructions and belongs in a
695/// pass that rewrites it into these rather than in a rule that would have to be several
696/// instructions long.
697fn cross_head(opcode: Opcode, from: Type, to: Type) -> Option<&'static str> {
698 match opcode {
699 // Between the two formats, one name each way. There is no third format with a name here,
700 // so these two are the whole of it rather than the first two of a table.
701 Opcode::FPExt => {
702 (float_slot(from)? == 0 && float_slot(to)? == 1).then_some("fpext.f32.f64")
703 }
704 Opcode::FPTrunc => {
705 (float_slot(from)? == 1 && float_slot(to)? == 0).then_some("fptrunc.f64.f32")
706 }
707 Opcode::FPToSI => Some(FPTOSI[float_slot(from)?][cross_slot(to)?]),
708 Opcode::SIToFP => Some(SITOFP[cross_slot(from)?][float_slot(to)?]),
709 // A reinterpretation, which is a `movd` or a `movq` between the two register files and is
710 // the one conversion here that changes no bit. Between two integers or between two floats
711 // it is nothing at all, since the IR keeps the width the same, so the four that cross the
712 // files are the four with a name.
713 Opcode::Bitcast => match (float_slot(from), float_slot(to)) {
714 (Some(at), None) if paired_int(to, at) => {
715 Some(["bitcast.f32.i32", "bitcast.f64.i64"][at])
716 }
717 (None, Some(at)) if paired_int(from, at) => {
718 Some(["bitcast.i32.f32", "bitcast.i64.f64"][at])
719 }
720 _ => None,
721 },
722 _ => None,
723 }
724}
725
726/// A float to a signed integer, from the format down the side to the width across the top.
727static FPTOSI: [[&str; 2]; 2] =
728 [["fptosi.f32.i32", "fptosi.f32.i64"], ["fptosi.f64.i32", "fptosi.f64.i64"]];
729
730/// A signed integer to a float, the other way round.
731static SITOFP: [[&str; 2]; 2] =
732 [["sitofp.i32.f32", "sitofp.i32.f64"], ["sitofp.i64.f32", "sitofp.i64.f64"]];
733
734/// What each of the binary operations is called at each width.
735///
736/// The three bitwise ones are the only ones with a name at one bit. They are what a `!=` between
737/// two truth values and a `&&` folded to one instruction become, and each of them takes two bytes
738/// that are a zero or a one to a byte that is a zero or a one. There is nothing to be gained by an
739/// add or a shift at this width and no front end writes one.
740fn binary_head(opcode: Opcode, ty: Type) -> Option<&'static str> {
741 if is_bit(ty) {
742 return match opcode {
743 Opcode::And => Some("and.i1"),
744 Opcode::Or => Some("or.i1"),
745 Opcode::Xor => Some("xor.i1"),
746 _ => None,
747 };
748 }
749 if let Some(at) = float_slot(ty) {
750 // The four the machine has one instruction each for. A remainder is not among them: there
751 // is no scalar instruction for it and what C means by `fmod` is a call, so an `frem` that
752 // reached here would find no rule and be reported rather than lowered to something else.
753 let names: &[&'static str; 2] = match opcode {
754 Opcode::FAdd => &["fadd.f32", "fadd.f64"],
755 Opcode::FSub => &["fsub.f32", "fsub.f64"],
756 Opcode::FMul => &["fmul.f32", "fmul.f64"],
757 Opcode::FDiv => &["fdiv.f32", "fdiv.f64"],
758 _ => return None,
759 };
760 return Some(names[at]);
761 }
762 let names: &[&'static str; 4] = match opcode {
763 Opcode::Add => &["add.i8", "add.i16", "add.i32", "add.i64"],
764 Opcode::Sub => &["sub.i8", "sub.i16", "sub.i32", "sub.i64"],
765 Opcode::Mul => &["mul.i8", "mul.i16", "mul.i32", "mul.i64"],
766 Opcode::SDiv => &["sdiv.i8", "sdiv.i16", "sdiv.i32", "sdiv.i64"],
767 Opcode::UDiv => &["udiv.i8", "udiv.i16", "udiv.i32", "udiv.i64"],
768 Opcode::SRem => &["srem.i8", "srem.i16", "srem.i32", "srem.i64"],
769 Opcode::URem => &["urem.i8", "urem.i16", "urem.i32", "urem.i64"],
770 Opcode::And => &["and.i8", "and.i16", "and.i32", "and.i64"],
771 Opcode::Or => &["or.i8", "or.i16", "or.i32", "or.i64"],
772 Opcode::Xor => &["xor.i8", "xor.i16", "xor.i32", "xor.i64"],
773 Opcode::Shl => &["shl.i8", "shl.i16", "shl.i32", "shl.i64"],
774 Opcode::LShr => &["lshr.i8", "lshr.i16", "lshr.i32", "lshr.i64"],
775 Opcode::AShr => &["ashr.i8", "ashr.i16", "ashr.i32", "ashr.i64"],
776 _ => return None,
777 };
778 Some(names[slot(ty)?])
779}
780
781/// The widening conversions, from the width down the side to the width across the top. The
782/// diagonal and everything below it is empty, because a sign extension to a width it already
783/// has is not an instruction and the IR does not have one.
784static SEXT: [[Option<&str>; 4]; 4] = [
785 [None, Some("sext.i8.i16"), Some("sext.i8.i32"), Some("sext.i8.i64")],
786 [None, None, Some("sext.i16.i32"), Some("sext.i16.i64")],
787 [None, None, None, Some("sext.i32.i64")],
788 [None, None, None, None],
789];
790
791static ZEXT: [[Option<&str>; 4]; 4] = [
792 [None, Some("zext.i8.i16"), Some("zext.i8.i32"), Some("zext.i8.i64")],
793 [None, None, Some("zext.i16.i32"), Some("zext.i16.i64")],
794 [None, None, None, Some("zext.i32.i64")],
795 [None, None, None, None],
796];
797
798/// The narrowing ones, which fill the other corner for the same reason.
799static TRUNC: [[Option<&str>; 4]; 4] = [
800 [None, None, None, None],
801 [Some("trunc.i16.i8"), None, None, None],
802 [Some("trunc.i32.i8"), Some("trunc.i32.i16"), None, None],
803 [Some("trunc.i64.i8"), Some("trunc.i64.i16"), Some("trunc.i64.i32"), None],
804];
805
806#[cfg(test)]
807mod tests {
808 use rucc_base::Interner;
809
810 use super::*;
811 use crate::{Builder, Flags, Signature};
812
813 /// A function with one block, and the builder to put instructions in it.
814 fn func() -> (Func, crate::Block) {
815 let mut names = Interner::new();
816 let mut func = Func::new(names.intern("f"), Signature::new());
817 let block = func.create_block();
818 (func, block)
819 }
820
821 /// The instruction that computed a value, which every value in these tests has.
822 fn inst_of(func: &Func, value: Value) -> Inst {
823 match func[value].def {
824 Def::Result { inst, .. } => inst,
825 Def::Param { .. } => unreachable!(),
826 }
827 }
828
829 #[test]
830 fn an_instruction_is_the_term_the_rule_file_names_it_by() {
831 let (mut func, block) = func();
832 let i32 = Type::int(32);
833 let mut build = Builder::new(&mut func, block);
834 let k = build.iconst(i32, 7);
835 let x = build.iconst(i32, 3);
836 let sum = build.binary(Opcode::Add, x, k, Flags::default());
837 let add = inst_of(&func, sum);
838
839 let terms = Terms::new(&func, add, PLAIN);
840 assert_eq!(terms.head(Term::Root), Some(("add.i32", 2)));
841 assert_eq!(terms.head(Term::Arg(0)), Some(("value.i32", 1)));
842 assert_eq!(terms.arg(Term::Arg(0), 0), Term::Reg(x));
843 assert_eq!(terms.head(Term::Reg(x)), None);
844 assert_eq!(terms.int(Term::Reg(x)), None);
845 }
846
847 /// What a pattern writing one name in two places asks. The two operands of `x & x` are
848 /// operand zero and operand one, so the question is about the values in them and not about
849 /// the places, and `spec/optimizer/13-rewrite-rules.md` section 13.4 has four identities
850 /// that cannot be written without it.
851 #[test]
852 fn two_places_are_the_same_term_when_the_same_value_is_in_both() {
853 let (mut func, block) = func();
854 let i32 = Type::int(32);
855 let mut build = Builder::new(&mut func, block);
856 let x = build.iconst(i32, 3);
857 let y = build.iconst(i32, 5);
858 let both = build.binary(Opcode::And, x, x, Flags::default());
859 let apart = build.binary(Opcode::And, x, y, Flags::default());
860
861 let terms = Terms::new(&func, inst_of(&func, both), PLAIN);
862 let left = terms.arg(Term::Arg(0), 0);
863 let right = terms.arg(Term::Arg(1), 0);
864 assert_ne!(Term::Arg(0), Term::Arg(1));
865 assert!(terms.same(left, right));
866
867 let terms = Terms::new(&func, inst_of(&func, apart), PLAIN);
868 let left = terms.arg(Term::Arg(0), 0);
869 let right = terms.arg(Term::Arg(1), 0);
870 assert!(!terms.same(left, right));
871 }
872
873 /// Two operands shown as constants are the same term when they are the same number, whatever
874 /// computed each of them. That is the one case where this is not identity of a value, and it
875 /// is right: a rule about `x - x` is about what the operands are, and two `3`s are one term.
876 #[test]
877 fn two_constants_of_one_number_are_the_same_term() {
878 let (mut func, block) = func();
879 let i32 = Type::int(32);
880 let mut build = Builder::new(&mut func, block);
881 let x = build.iconst(i32, 3);
882 let y = build.iconst(i32, 3);
883 let sum = build.binary(Opcode::Add, x, y, Flags::default());
884
885 let terms = Terms::new(&func, inst_of(&func, sum), [Shown::Const; MAX_ARGS]);
886 let left = terms.arg(Term::Arg(0), 0);
887 let right = terms.arg(Term::Arg(1), 0);
888 assert_ne!(x, y);
889 assert_eq!((left, right), (Term::Num(3), Term::Num(3)));
890 assert!(terms.same(left, right));
891 // And a constant is not the value beside it, because one of them has a number and the
892 // other has not.
893 assert!(!terms.same(left, Term::Reg(y)));
894 }
895
896 #[test]
897 fn an_operand_shown_as_a_constant_gives_the_number_up() {
898 let (mut func, block) = func();
899 let i32 = Type::int(32);
900 let mut build = Builder::new(&mut func, block);
901 let x = build.iconst(i32, 3);
902 let k = build.iconst(i32, -7);
903 let sum = build.binary(Opcode::Add, x, k, Flags::default());
904 let add = inst_of(&func, sum);
905
906 let terms = Terms::new(&func, add, [Shown::Reg, Shown::Const, Shown::Reg]);
907 assert_eq!(terms.head(Term::Arg(1)), Some(("iconst.i32", 1)));
908 assert_eq!(terms.arg(Term::Arg(1), 0), Term::Num(-7));
909 assert_eq!(terms.int(Term::Num(-7)), Some(-7));
910 // The same operand shown as a register is a register, and a guard asking what number it
911 // is gets no answer, which is what makes a rule about a number decline it.
912 let plain = Terms::new(&func, add, PLAIN);
913 assert_eq!(plain.head(Term::Arg(1)), Some(("value.i32", 1)));
914 assert_eq!(plain.int(plain.arg(Term::Arg(1), 0)), None);
915 }
916
917 #[test]
918 fn a_constant_is_a_term_of_one_argument_and_has_no_operands() {
919 let (mut func, block) = func();
920 let mut build = Builder::new(&mut func, block);
921 let k = build.iconst(Type::int(64), 12);
922 let inst = inst_of(&func, k);
923
924 let terms = Terms::new(&func, inst, PLAIN);
925 assert_eq!(terms.head(Term::Root), Some(("iconst.i64", 1)));
926 assert_eq!(terms.arg(Term::Root, 0), Term::Num(12));
927 }
928
929 #[test]
930 fn an_expanded_operand_is_the_instruction_that_computed_it() {
931 let (mut func, block) = func();
932 let i64 = Type::int(64);
933 // A parameter, because the point of the test is an operand that is not a constant.
934 let y = func.append_param(block, i64);
935 let mut build = Builder::new(&mut func, block);
936 let x = build.iconst(i64, 1);
937 let four = build.iconst(i64, 4);
938 let scaled = build.binary(Opcode::Mul, y, four, Flags::default());
939 let sum = build.binary(Opcode::Add, x, scaled, Flags::default());
940 let add = inst_of(&func, sum);
941
942 let terms = Terms::new(&func, add, [Shown::Reg, Shown::Expand, Shown::Reg]);
943 assert_eq!(terms.head(Term::Root), Some(("add.i64", 2)));
944 assert_eq!(terms.head(Term::Arg(1)), Some(("mul.i64", 2)));
945 assert_eq!(terms.head(Term::Deep(1, 0)), Some(("value.i64", 1)));
946 assert_eq!(terms.arg(Term::Deep(1, 0), 0), Term::Reg(y));
947 // The constant inside an expansion is shown as one without being asked to be.
948 assert_eq!(terms.head(Term::Deep(1, 1)), Some(("iconst.i64", 1)));
949 assert_eq!(terms.arg(Term::Deep(1, 1), 0), Term::Num(4));
950 }
951
952 #[test]
953 fn a_comparison_says_which_one_it_is_and_a_conversion_says_both_widths() {
954 let (mut func, block) = func();
955 let mut build = Builder::new(&mut func, block);
956 let x = build.iconst(Type::int(32), 1);
957 let y = build.iconst(Type::int(32), 2);
958 let less = build.icmp(IntPred::Slt, x, y);
959 let wide = build.unary(Opcode::SExt, x, Type::int(64));
960 let narrow = build.unary(Opcode::Trunc, x, Type::int(8));
961 let cmp = inst_of(&func, less);
962 assert_eq!(Terms::new(&func, cmp, PLAIN).head(Term::Root), Some(("icmp_slt.i1", 2)));
963 let sext = inst_of(&func, wide);
964 assert_eq!(Terms::new(&func, sext, PLAIN).head(Term::Root), Some(("sext.i32.i64", 1)));
965 let trunc = inst_of(&func, narrow);
966 assert_eq!(Terms::new(&func, trunc, PLAIN).head(Term::Root), Some(("trunc.i32.i8", 1)));
967 }
968
969 #[test]
970 fn a_width_no_rule_is_written_at_has_no_name() {
971 let (mut func, block) = func();
972 let mut build = Builder::new(&mut func, block);
973 let x = build.iconst(Type::int(128), 1);
974 let inst = inst_of(&func, x);
975 assert_eq!(Terms::new(&func, inst, PLAIN).head(Term::Root), None);
976 }
977
978 /// An address is an integer of the machine's width to every term here, which is what lets one
979 /// be loaded from, stored through, returned and added to by rules written about integers.
980 #[test]
981 fn an_address_is_an_integer_as_wide_as_the_machine_addresses() {
982 assert_eq!(value_head(Type::PTR), Some("value.i64"));
983 assert_eq!(load_head(Type::PTR), Some("load.i64"));
984 assert_eq!(store_head(Type::PTR), Some("store.i64"));
985 assert_eq!(ret_head(Type::PTR), Some("ret.i64"));
986 // Not a constant, since nothing writes an address down as one.
987 assert_eq!(iconst_head(Type::PTR), None);
988 }
989
990 /// One bit is a width with names of its own, and they are not the four the tables hold. What
991 /// has a name there is what a truth value is written with: a constant, the three bitwise
992 /// operations, and the widening that turns one into a number.
993 #[test]
994 fn one_bit_is_a_width_with_a_name_for_what_a_truth_value_is_written_with() {
995 let bit = Type::int(1);
996 assert_eq!(slot(bit), None);
997 assert_eq!(value_head(bit), Some("value.i1"));
998 assert_eq!(iconst_head(bit), Some("iconst.i1"));
999 assert_eq!(binary_head(Opcode::And, bit), Some("and.i1"));
1000 assert_eq!(binary_head(Opcode::Or, bit), Some("or.i1"));
1001 assert_eq!(binary_head(Opcode::Xor, bit), Some("xor.i1"));
1002 assert_eq!(convert_head(Opcode::ZExt, bit, Type::int(8)), Some("zext.i1.i8"));
1003 assert_eq!(convert_head(Opcode::ZExt, bit, Type::int(32)), Some("zext.i1.i32"));
1004 assert_eq!(convert_head(Opcode::ZExt, bit, Type::int(64)), Some("zext.i1.i64"));
1005 }
1006
1007 /// Everything else at one bit has no name, which is what keeps the byte holding one a zero or
1008 /// a one: an add at this width would be an instruction that leaves something else there.
1009 #[test]
1010 fn nothing_else_at_one_bit_has_a_name() {
1011 let bit = Type::int(1);
1012 assert_eq!(binary_head(Opcode::Add, bit), None);
1013 assert_eq!(binary_head(Opcode::Shl, bit), None);
1014 assert_eq!(load_head(bit), None);
1015 assert_eq!(store_head(bit), None);
1016 assert_eq!(ret_head(bit), None);
1017 // Not a sign extension either, which would be a truth value spread over every bit.
1018 assert_eq!(convert_head(Opcode::SExt, bit, Type::int(32)), None);
1019 // And not a narrowing to it, since what makes a number into a truth value is a
1020 // comparison against zero and that is a different opcode.
1021 assert_eq!(convert_head(Opcode::Trunc, Type::int(32), bit), None);
1022 }
1023
1024 /// A one bit constant is the truth value it stands for. The signed reading of a one bit
1025 /// integer turns a true into a minus one, which would put a byte of ones where every rule at
1026 /// this width expects a one.
1027 #[test]
1028 fn a_one_bit_constant_is_a_zero_or_a_one_rather_than_a_zero_or_a_minus_one() {
1029 let (mut func, block) = func();
1030 let mut build = Builder::new(&mut func, block);
1031 let bit = Type::int(1);
1032 let no = build.iconst(bit, 0);
1033 let yes = build.iconst(bit, 1);
1034 let terms = Terms::new(&func, inst_of(&func, yes), PLAIN);
1035 assert_eq!(terms.constant(no), Some(0));
1036 assert_eq!(terms.constant(yes), Some(1));
1037 assert_eq!(terms.head(Term::Root), Some(("iconst.i1", 1)));
1038 assert_eq!(terms.arg(Term::Root, 0), Term::Num(1));
1039 }
1040
1041 /// A float is a term of its own at each of the two widths the machine has instructions for.
1042 /// The same width of integer is a different term, which is what keeps a rule about one from
1043 /// ever firing on the other, and it has to be, because the two are in different register
1044 /// files.
1045 #[test]
1046 fn a_float_is_a_term_of_its_own_at_each_width_the_machine_computes_in() {
1047 let f32 = Type::float(Float::F32);
1048 let f64 = Type::float(Float::F64);
1049 assert_eq!(value_head(f32), Some("value.f32"));
1050 assert_eq!(value_head(f64), Some("value.f64"));
1051 assert_eq!(load_head(f32), Some("load.f32"));
1052 assert_eq!(store_head(f64), Some("store.f64"));
1053 assert_eq!(ret_head(f32), Some("ret.f32"));
1054 assert_eq!(binary_head(Opcode::FAdd, f32), Some("fadd.f32"));
1055 assert_eq!(binary_head(Opcode::FSub, f64), Some("fsub.f64"));
1056 assert_eq!(binary_head(Opcode::FMul, f32), Some("fmul.f32"));
1057 assert_eq!(binary_head(Opcode::FDiv, f64), Some("fdiv.f64"));
1058 // Not one of the four widths an integer rule is written at, and not a constant either,
1059 // since what a pattern binds inside an `iconst` is a number and a float is not one.
1060 assert_eq!(slot(f32), None);
1061 assert_eq!(slot(f64), None);
1062 assert_eq!(iconst_head(f64), None);
1063 // An integer add at thirty two bits is a different name from a float add at the same
1064 // width, which is the whole of what keeps the two rule sets apart.
1065 assert_ne!(binary_head(Opcode::Add, Type::int(32)), binary_head(Opcode::FAdd, f32));
1066 }
1067
1068 /// What the machine has no scalar instruction for has no name, so it is reported rather than
1069 /// lowered to something near it. A remainder is a call to `fmod` and a `long double` is on the
1070 /// x87 stack, and neither is anything a rule in this set is written about.
1071 #[test]
1072 fn a_float_operation_the_machine_lacks_has_no_name() {
1073 assert_eq!(binary_head(Opcode::FRem, Type::float(Float::F32)), None);
1074 let long = Type::float(Float::F80);
1075 assert_eq!(float_slot(long), None);
1076 assert_eq!(value_head(long), None);
1077 assert_eq!(binary_head(Opcode::FAdd, long), None);
1078 assert_eq!(ret_head(long), None);
1079 }
1080
1081 /// A lane count is not a width, so a rule written at a width does not get to answer for a
1082 /// vector of that width. Nothing produces one yet and the day something does it should be
1083 /// reported rather than lowered to an instruction that acts on one lane of it.
1084 #[test]
1085 fn a_vector_is_not_the_width_of_its_lane() {
1086 let i32x4 = Type::vector(Type::int(32), 4);
1087 assert_eq!(slot(i32x4), None);
1088 assert_eq!(value_head(i32x4), None);
1089 assert_eq!(binary_head(Opcode::Add, i32x4), None);
1090 }
1091
1092 /// The sweep says the same thing about an instruction that looking the instruction up does,
1093 /// which is the only way it is worth anything: a list of names built beside the naming rather
1094 /// than out of it would be a second table to keep in step.
1095 #[test]
1096 fn the_names_the_sweep_finds_are_the_names_an_instruction_gets() {
1097 let (mut func, block) = func();
1098 let other = func.create_block();
1099 let mut build = Builder::new(&mut func, block);
1100 let cond = build.iconst(Type::int(1), 1);
1101 let x = build.iconst(Type::int(32), 1);
1102 let sum = build.binary(Opcode::Add, x, x, Flags::default());
1103 let branch = build.br_if(cond, other, &[], other, &[]);
1104
1105 let names = heads();
1106 for inst in [inst_of(&func, sum), inst_of(&func, x), branch] {
1107 let name = head_of(&func, inst).expect("all three have a name");
1108 let opcode = func[inst].opcode;
1109 assert!(
1110 names.contains(&(opcode, name)),
1111 "an instruction is called {name} and the sweep does not know that name"
1112 );
1113 }
1114 }
1115
1116 /// Every name is there once and belongs to one opcode. A name in the list twice would count
1117 /// twice in the coverage report, and the two instructions a name could belong to are the two
1118 /// the machine has one instruction for: an add of two numbers and an add of an address.
1119 #[test]
1120 fn a_name_is_listed_once_and_an_address_add_is_the_one_name_two_opcodes_share() {
1121 let names = heads();
1122 let mut once = names.clone();
1123 once.dedup();
1124 assert_eq!(names, once, "the sweep lists a name twice");
1125 assert!(names.contains(&(Opcode::Add, "add.i64")));
1126 assert!(names.contains(&(Opcode::PtrAdd, "add.i64")));
1127 }
1128
1129 /// A width nothing is written at contributes nothing, which is what makes the sweep safe to
1130 /// run over every type there is. These four are the widths that have no name today, and each
1131 /// is an issue rather than an oversight: one bit arithmetic, `__int128`, `long double` and a
1132 /// vector of any lane count.
1133 #[test]
1134 fn a_width_with_no_name_puts_nothing_in_the_sweep() {
1135 let named: Vec<&'static str> = heads().into_iter().map(|(_, name)| name).collect();
1136 for name in &named {
1137 assert!(!name.contains("i128"), "{name} is a width no rule is written at");
1138 assert!(!name.contains("f80"), "{name} is a width no rule is written at");
1139 }
1140 // One bit is the width with some names and not others, so it is checked from the other
1141 // side: what a truth value is written with, and nothing else. A comparison is in the list
1142 // because its result is one bit, whatever it compared.
1143 let mut bit: Vec<&'static str> =
1144 named.into_iter().filter(|name| name.ends_with(".i1")).collect();
1145 bit.sort_unstable();
1146 assert_eq!(
1147 bit,
1148 [
1149 "and.i1",
1150 "brif.i1",
1151 "fcmp_oeq.f32.i1",
1152 "fcmp_oeq.f64.i1",
1153 "fcmp_oge.f32.i1",
1154 "fcmp_oge.f64.i1",
1155 "fcmp_ogt.f32.i1",
1156 "fcmp_ogt.f64.i1",
1157 "fcmp_ole.f32.i1",
1158 "fcmp_ole.f64.i1",
1159 "fcmp_olt.f32.i1",
1160 "fcmp_olt.f64.i1",
1161 "fcmp_one.f32.i1",
1162 "fcmp_one.f64.i1",
1163 "fcmp_ord.f32.i1",
1164 "fcmp_ord.f64.i1",
1165 "fcmp_ueq.f32.i1",
1166 "fcmp_ueq.f64.i1",
1167 "fcmp_uge.f32.i1",
1168 "fcmp_uge.f64.i1",
1169 "fcmp_ugt.f32.i1",
1170 "fcmp_ugt.f64.i1",
1171 "fcmp_ule.f32.i1",
1172 "fcmp_ule.f64.i1",
1173 "fcmp_ult.f32.i1",
1174 "fcmp_ult.f64.i1",
1175 "fcmp_une.f32.i1",
1176 "fcmp_une.f64.i1",
1177 "fcmp_uno.f32.i1",
1178 "fcmp_uno.f64.i1",
1179 "icmp_eq.i1",
1180 "icmp_ne.i1",
1181 "icmp_sge.i1",
1182 "icmp_sgt.i1",
1183 "icmp_sle.i1",
1184 "icmp_slt.i1",
1185 "icmp_uge.i1",
1186 "icmp_ugt.i1",
1187 "icmp_ule.i1",
1188 "icmp_ult.i1",
1189 "iconst.i1",
1190 "or.i1",
1191 "xor.i1",
1192 ]
1193 );
1194 }
1195
1196 /// Address arithmetic is named as the add it is, which is what puts it in reach of every rule
1197 /// written about one, including the two below that fold it into an address.
1198 #[test]
1199 fn address_arithmetic_is_an_add_at_the_address_width() {
1200 let (mut func, block) = func();
1201 let base = func.append_param(block, Type::PTR);
1202 let mut build = Builder::new(&mut func, block);
1203 let step = build.iconst(Type::int(64), 4);
1204 let args = func.push_values(&[base, step]);
1205 let next = Builder::new(&mut func, block)
1206 .value(crate::InstData { args, ..crate::InstData::new(Opcode::PtrAdd) }, Type::PTR);
1207 let inst = inst_of(&func, next);
1208
1209 let terms = Terms::new(&func, inst, [Shown::Reg, Shown::Const, Shown::Reg]);
1210 assert_eq!(terms.head(Term::Root), Some(("add.i64", 2)));
1211 assert_eq!(terms.head(Term::Arg(0)), Some(("value.i64", 1)));
1212 assert_eq!(terms.head(Term::Arg(1)), Some(("iconst.i64", 1)));
1213 assert_eq!(terms.arg(Term::Arg(1), 0), Term::Num(4));
1214 }
1215}