Skip to main content

rucc_codegen/
term.rs

1//! The IR as something a lowering rule can match against.
2//!
3//! Design: `spec/10-backend.md` section 10.2.
4//!
5//! A rule is written about a term and the compiler has no terms. It has a function full of
6//! instructions, and what a pattern is about is one of them together with whatever its operands
7//! were computed from. So this is the [`Subject`] the matcher asks its three questions of, and
8//! the answers come out of the IR: nothing is built and nothing is thrown away.
9//!
10//! # How an operand is shown
11//!
12//! The same IR value can be several different terms. `(add.i32 (value.i32 x) (iconst.i32 k))`
13//! and `(add.i32 (value.i32 x) (value.i32 y))` are two patterns over one instruction, and which
14//! one it is depends on whether the second operand is a constant and on whether the rule that
15//! wants a constant will take this one. `(add.i64 (value.i64 x) (mul.i64 (value.i64 y)
16//! (iconst.i64 4)))` is a third, and it is about two instructions rather than one.
17//!
18//! The matcher does not backtrack across alternatives for one node: [`Subject::head`] gives one
19//! answer and the walk believes it. So the choice is made before the walk rather than during it.
20//! A [`Plan`] says how each operand of the instruction is shown, the selector tries the plans in
21//! order, and the first that matches is the one that fires. There are at most three ways to show
22//! an operand and at most two operands in any pattern this rule set has, so the whole of the
23//! search is a handful of walks over a trie, each of which fails in its first node or two.
24//!
25//! # How deep it goes
26//!
27//! One level. An operand may be shown as the instruction that computed it, and that
28//! instruction's own operands are shown as a register or as a constant and never expanded
29//! again, which is as deep as any pattern in `x86-64.rules` reaches. A rule set that wants three
30//! levels needs this to grow a level, and it would be found by the rule failing to fire rather
31//! than by anything going wrong.
32
33use rucc_ir::{Def, Extra, Func, Inst, IntPred, Opcode, Type, Value};
34
35use crate::select::Subject;
36
37/// How many operands of one instruction a plan can speak about.
38///
39/// Two is what every pattern in the rule set needs, and a third costs nothing to carry. An
40/// instruction with more operands than this is one no rule matches, which is the same answer it
41/// would get from a plan that could describe it.
42pub const MAX_ARGS: usize = 3;
43
44/// How one operand is shown to the matcher.
45#[derive(Clone, Copy, Debug, PartialEq, Eq)]
46pub enum Shown {
47    /// As a value sitting in a register, which is what `(value.iN x)` matches.
48    Reg,
49    /// As a constant the selector has in hand, which is what `(iconst.iN k)` matches.
50    Const,
51    /// As the instruction that computed it, so a rule can be about two instructions at once.
52    Expand,
53}
54
55/// How every operand of one instruction is shown.
56pub type Plan = [Shown; MAX_ARGS];
57
58/// Everything shown as a register, which is the plan that matches when no other does.
59pub const PLAIN: Plan = [Shown::Reg; MAX_ARGS];
60
61/// One node of the term the matcher is walking.
62///
63/// A position rather than a term, because the term does not exist. Two of these are values in
64/// their own right, and they are the two a pattern can bind: the register a `value` wraps and
65/// the number an `iconst` wraps.
66#[derive(Clone, Copy, Debug, PartialEq, Eq)]
67pub enum Term {
68    /// The instruction being selected.
69    Root,
70    /// Operand `i` of the root, shown the way the plan says to show it.
71    Arg(u8),
72    /// Operand `j` of the instruction that computed operand `i` of the root.
73    Deep(u8, u8),
74    /// A value in a register, which is what a pattern binds when it writes `(value.iN x)`.
75    Reg(Value),
76    /// A constant, which is what a pattern binds or tests inside an `(iconst.iN k)`.
77    Num(i128),
78}
79
80/// One instruction of a function, as the terms a rule could match.
81#[derive(Debug)]
82pub struct Terms<'a> {
83    func: &'a Func,
84    root: Inst,
85    plan: Plan,
86}
87
88impl<'a> Terms<'a> {
89    /// The instruction, shown the way the plan says.
90    #[must_use]
91    pub fn new(func: &'a Func, root: Inst, plan: Plan) -> Self {
92        Self { func, root, plan }
93    }
94
95    /// The instruction this is about.
96    #[must_use]
97    pub fn root(&self) -> Inst {
98        self.root
99    }
100
101    /// What the root, or an instruction one of its operands was expanded into, is called in a
102    /// rule file.
103    #[must_use]
104    pub fn name(&self, inst: Inst) -> Option<&'static str> {
105        head_of(self.func, inst)
106    }
107
108    /// The value operands of an instruction.
109    fn args(&self, inst: Inst) -> &[Value] {
110        &self.func[self.func[inst].args]
111    }
112
113    /// Operand `index` of the root, or nothing if it has no such operand.
114    fn arg_value(&self, index: u8) -> Option<Value> {
115        self.args(self.root).get(usize::from(index)).copied()
116    }
117
118    /// The instruction a value is the result of, or nothing for a block parameter.
119    fn def_of(&self, value: Value) -> Option<Inst> {
120        match self.func[value].def {
121            Def::Result { inst, .. } => Some(inst),
122            Def::Param { .. } => None,
123        }
124    }
125
126    /// What a value is, if it is a constant.
127    #[must_use]
128    pub fn constant(&self, value: Value) -> Option<i128> {
129        let inst = self.def_of(value)?;
130        let data = &self.func[inst];
131        if data.opcode != Opcode::IConst {
132            return None;
133        }
134        let Extra::Imm(imm) = data.extra else { return None };
135        let ty = self.func[value].ty;
136        ty.is_int().then(|| self.func[imm].signed(ty))
137    }
138
139    /// The head of a value shown as a register or as a constant, which is a term of one
140    /// argument either way: the thing the pattern binds.
141    fn leaf_head(&self, value: Value, shown: Shown) -> Option<(&'static str, usize)> {
142        let ty = self.func[value].ty;
143        let name = match shown {
144            Shown::Reg => value_head(ty)?,
145            Shown::Const => iconst_head(ty)?,
146            // An expansion is not a leaf, and nothing asks this about one.
147            Shown::Expand => return None,
148        };
149        Some((name, 1))
150    }
151
152    /// What a value shown as a register or as a constant binds, which is the value itself or
153    /// the number it is.
154    fn leaf_arg(&self, value: Value, shown: Shown) -> Term {
155        match shown {
156            Shown::Const => self.constant(value).map_or(Term::Reg(value), Term::Num),
157            Shown::Reg | Shown::Expand => Term::Reg(value),
158        }
159    }
160
161    /// How an operand of an expanded operand is shown, which is as a constant when it is one
162    /// and as a register otherwise.
163    ///
164    /// There is no choice to make here. The reason to show a constant as a register is that no
165    /// rule would take it as an immediate, and the answer to that inside an expansion is to
166    /// stop expanding, which is a plan the selector tries anyway.
167    fn deep_shown(&self, value: Value) -> Shown {
168        if self.constant(value).is_some() { Shown::Const } else { Shown::Reg }
169    }
170
171    /// The instruction an expanded operand of the root was computed by, with its operands.
172    fn expansion(&self, index: u8) -> Option<(Inst, &[Value])> {
173        let value = self.arg_value(index)?;
174        let inst = self.def_of(value)?;
175        Some((inst, self.args(inst)))
176    }
177}
178
179impl Subject for Terms<'_> {
180    type Node = Term;
181
182    fn head(&self, node: Term) -> Option<(&str, usize)> {
183        match node {
184            Term::Root => {
185                let name = head_of(self.func, self.root)?;
186                let data = &self.func[self.root];
187                // A constant has no operands and its term has one, which is the constant, so it
188                // is the one instruction whose arity is not the length of its operand list.
189                let arity =
190                    if data.opcode == Opcode::IConst { 1 } else { self.args(self.root).len() };
191                Some((name, arity))
192            }
193            Term::Arg(index) => {
194                let value = self.arg_value(index)?;
195                match self.plan[usize::from(index)] {
196                    Shown::Expand => {
197                        let (inst, args) = self.expansion(index)?;
198                        Some((head_of(self.func, inst)?, args.len()))
199                    }
200                    shown => self.leaf_head(value, shown),
201                }
202            }
203            Term::Deep(outer, inner) => {
204                let (_, args) = self.expansion(outer)?;
205                let value = *args.get(usize::from(inner))?;
206                self.leaf_head(value, self.deep_shown(value))
207            }
208            Term::Reg(_) | Term::Num(_) => None,
209        }
210    }
211
212    fn arg(&self, node: Term, index: usize) -> Term {
213        let index = u8::try_from(index).unwrap_or(u8::MAX);
214        match node {
215            Term::Root => {
216                let data = &self.func[self.root];
217                if data.opcode == Opcode::IConst {
218                    let value = data.first_result.expect("a constant has a result");
219                    return self.leaf_arg(value, Shown::Const);
220                }
221                Term::Arg(index)
222            }
223            Term::Arg(outer) => match self.plan[usize::from(outer)] {
224                Shown::Expand => Term::Deep(outer, index),
225                shown => {
226                    self.arg_value(outer).map_or(Term::Num(0), |value| self.leaf_arg(value, shown))
227                }
228            },
229            Term::Deep(outer, inner) => {
230                let value = self
231                    .expansion(outer)
232                    .and_then(|(_, args)| args.get(usize::from(inner)).copied());
233                value.map_or(Term::Num(0), |value| self.leaf_arg(value, self.deep_shown(value)))
234            }
235            // Neither has a head, so nothing asks either of them for an argument.
236            Term::Reg(_) | Term::Num(_) => node,
237        }
238    }
239
240    fn int(&self, node: Term) -> Option<i128> {
241        match node {
242            Term::Num(value) => Some(value),
243            _ => None,
244        }
245    }
246}
247
248/// What an instruction is called in a rule file, or nothing if the rules have no name for it.
249///
250/// The name carries the width, because a rule file that did not say how wide a term is would be
251/// a file whose reader has to look at the line above to find out. Which widths there are names
252/// for is the rule language's business and not this crate's: an instruction at a width nothing
253/// is written about has no name here, and the answer to it is that no rule matches.
254fn head_of(func: &Func, inst: Inst) -> Option<&'static str> {
255    let data = &func[inst];
256
257    // A store is the one instruction with a name here that computes nothing, so the width in
258    // its name is the width of what it is storing and has to come from an operand. That operand
259    // is the first one, which is the order `rucc_ir::Builder::store` puts them in and the order
260    // a pattern for one is written in.
261    //
262    // Nothing looks at the flags or the ordering, and both of those are worth saying out loud.
263    // A `volatile` access has to happen exactly once and must not move, and neither of those is
264    // something selection does: one IR load is one instruction whatever its flags say, and
265    // folding the address arithmetic into the addressing mode does not change how many times
266    // memory is touched. An ordering would be a different matter, because a store that releases
267    // is not a plain `mov` on any machine where it means anything, but an ordered access is
268    // `atomic_load` or `atomic_store` and those are different opcodes with no name here. The IR
269    // verifier is what makes that true rather than merely usual: it rejects an ordering on a
270    // plain access, so by the time anything is selected there is none to miss.
271    if data.opcode == Opcode::Store {
272        let value = *func[data.args].first()?;
273        return store_head(func[value].ty);
274    }
275
276    // A return is the other one, and the width comes from the operand for the same reason. A
277    // return of nothing has no name, and neither has a return of more than one value: a rule
278    // for either would have to say where each of them goes, and where a value goes is a fact
279    // about the convention rather than about a term, so the rule language has nothing to say
280    // about it. A return of nothing needs no rule at all, since the epilogue is the whole of it.
281    if data.opcode == Opcode::Return {
282        let [value] = &func[data.args] else { return None };
283        return ret_head(func[*value].ty);
284    }
285
286    // A conditional branch is the third instruction here that computes nothing. Where it goes is
287    // not part of its name and not part of any pattern: a machine IR block holds its own
288    // successors, so a rule for a branch never has to say a block, and what is left for it to say
289    // is what the branch is about, which is the condition.
290    if data.opcode == Opcode::BrIf {
291        let [cond] = &func[data.args] else { return None };
292        return (func[*cond].ty == Type::int(1)).then_some("brif.i1");
293    }
294
295    let result = data.first_result?;
296    let ty = func[result].ty;
297    match data.opcode {
298        Opcode::IConst => iconst_head(ty),
299        Opcode::Load => load_head(ty),
300        Opcode::ICmp => {
301            let Extra::IntPred(pred) = data.extra else { return None };
302            Some(icmp_head(pred))
303        }
304        Opcode::SExt | Opcode::ZExt | Opcode::Trunc => {
305            let from = func[*func[data.args].first()?].ty;
306            convert_head(data.opcode, from, ty)
307        }
308        // Address arithmetic is an add at the address width, which is all it is once both
309        // operands are in registers: the offset is already in bytes, which the IR guarantees and
310        // the front end is what did the multiplying. Calling it that is what lets every rule
311        // written about an add reach it, including the ones that fold it into an addressing mode,
312        // and there is nothing in any of them it could get wrong.
313        Opcode::PtrAdd => binary_head(Opcode::Add, ty),
314        opcode => binary_head(opcode, ty),
315    }
316}
317
318/// How wide an address is on the machine this lowers for.
319///
320/// The rule set has no term for a pointer and needs none. An address in a register is an integer
321/// of the machine's address width, every rule that could compute one is a rule about an integer
322/// of that width, and the only thing missing was a name. [`slot`] used to ask the type how wide
323/// it was, and a pointer answers nothing, because how wide an address is belongs to the target
324/// rather than to the IR. So this is where the target's answer is written down.
325///
326/// Sixty four, and it is a constant for the same reason the `x64.` prefix and the table in
327/// [`crate::select::x86_64`] are: this crate lowers for one machine. Every architecture
328/// `rucc_target::Arch` names is a sixty four bit one, so there is no target in the compiler that
329/// would want a different number, and a thirty two bit one would want more from this crate than
330/// a number.
331const ADDRESS: u32 = 64;
332
333/// Which of the four widths a type is, or nothing for a width no rule is written at.
334///
335/// A pointer is one of them, at [`ADDRESS`]. A vector is none of them however wide its lane is,
336/// because a rule at a width says nothing about how many lanes it acts on and lowering an add of
337/// four lanes to an add of one would be wrong rather than incomplete.
338pub(crate) fn slot(ty: Type) -> Option<usize> {
339    if !ty.is_scalar() {
340        return None;
341    }
342    let bits = if ty.is_ptr() { ADDRESS } else { ty.is_int().then(|| ty.bits())? };
343    match bits {
344        8 => Some(0),
345        16 => Some(1),
346        32 => Some(2),
347        64 => Some(3),
348        _ => None,
349    }
350}
351
352/// What a value in a register is called at that width.
353///
354/// One bit is a width here and is not one in [`slot`], because it is a width a value comes in and
355/// not a width anything is computed at. A comparison produces one, a branch reads one, and the
356/// machine holds it in a whole byte register with the other seven bits zero, which is what a
357/// `setcc` leaves behind and what the model already abstracts over for every comparison.
358fn value_head(ty: Type) -> Option<&'static str> {
359    if ty.is_scalar() && ty.is_int() && ty.bits() == 1 {
360        return Some("value.i1");
361    }
362    Some(["value.i8", "value.i16", "value.i32", "value.i64"][slot(ty)?])
363}
364
365/// What a constant is called at that width.
366///
367/// An integer and not an address, unlike everything else here. What a pattern binds inside one of
368/// these is the number, and [`Terms::constant`] only has a number for an integer, so a term that
369/// named an address would be one a rule could match and then find nothing behind.
370fn iconst_head(ty: Type) -> Option<&'static str> {
371    if !ty.is_int() {
372        return None;
373    }
374    Some(["iconst.i8", "iconst.i16", "iconst.i32", "iconst.i64"][slot(ty)?])
375}
376
377/// What a load is called, which is the width of the value it produced.
378fn load_head(ty: Type) -> Option<&'static str> {
379    Some(["load.i8", "load.i16", "load.i32", "load.i64"][slot(ty)?])
380}
381
382/// What a store is called, which is the width of the value it writes, since it produces nothing
383/// to take a width from.
384fn store_head(ty: Type) -> Option<&'static str> {
385    Some(["store.i8", "store.i16", "store.i32", "store.i64"][slot(ty)?])
386}
387
388/// What a return is called, which is the width of the value it gives back, for the same reason.
389fn ret_head(ty: Type) -> Option<&'static str> {
390    Some(["ret.i8", "ret.i16", "ret.i32", "ret.i64"][slot(ty)?])
391}
392
393/// What a comparison is called, which does not carry the width of what it compared: the result
394/// is one bit whatever the operands were, and the operands say how wide they are themselves.
395fn icmp_head(pred: IntPred) -> &'static str {
396    match pred {
397        IntPred::Eq => "icmp_eq.i1",
398        IntPred::Ne => "icmp_ne.i1",
399        IntPred::Slt => "icmp_slt.i1",
400        IntPred::Sle => "icmp_sle.i1",
401        IntPred::Sgt => "icmp_sgt.i1",
402        IntPred::Sge => "icmp_sge.i1",
403        IntPred::Ult => "icmp_ult.i1",
404        IntPred::Ule => "icmp_ule.i1",
405        IntPred::Ugt => "icmp_ugt.i1",
406        IntPred::Uge => "icmp_uge.i1",
407    }
408}
409
410/// What a conversion is called, which is the two widths it is between.
411fn convert_head(opcode: Opcode, from: Type, to: Type) -> Option<&'static str> {
412    let table: &[[Option<&'static str>; 4]; 4] = match opcode {
413        Opcode::SExt => &SEXT,
414        Opcode::ZExt => &ZEXT,
415        Opcode::Trunc => &TRUNC,
416        _ => return None,
417    };
418    table[slot(from)?][slot(to)?]
419}
420
421/// What each of the binary operations is called at each width.
422fn binary_head(opcode: Opcode, ty: Type) -> Option<&'static str> {
423    let names: &[&'static str; 4] = match opcode {
424        Opcode::Add => &["add.i8", "add.i16", "add.i32", "add.i64"],
425        Opcode::Sub => &["sub.i8", "sub.i16", "sub.i32", "sub.i64"],
426        Opcode::Mul => &["mul.i8", "mul.i16", "mul.i32", "mul.i64"],
427        Opcode::SDiv => &["sdiv.i8", "sdiv.i16", "sdiv.i32", "sdiv.i64"],
428        Opcode::UDiv => &["udiv.i8", "udiv.i16", "udiv.i32", "udiv.i64"],
429        Opcode::SRem => &["srem.i8", "srem.i16", "srem.i32", "srem.i64"],
430        Opcode::URem => &["urem.i8", "urem.i16", "urem.i32", "urem.i64"],
431        Opcode::And => &["and.i8", "and.i16", "and.i32", "and.i64"],
432        Opcode::Or => &["or.i8", "or.i16", "or.i32", "or.i64"],
433        Opcode::Xor => &["xor.i8", "xor.i16", "xor.i32", "xor.i64"],
434        Opcode::Shl => &["shl.i8", "shl.i16", "shl.i32", "shl.i64"],
435        Opcode::LShr => &["lshr.i8", "lshr.i16", "lshr.i32", "lshr.i64"],
436        Opcode::AShr => &["ashr.i8", "ashr.i16", "ashr.i32", "ashr.i64"],
437        _ => return None,
438    };
439    Some(names[slot(ty)?])
440}
441
442/// The widening conversions, from the width down the side to the width across the top. The
443/// diagonal and everything below it is empty, because a sign extension to a width it already
444/// has is not an instruction and the IR does not have one.
445static SEXT: [[Option<&str>; 4]; 4] = [
446    [None, Some("sext.i8.i16"), Some("sext.i8.i32"), Some("sext.i8.i64")],
447    [None, None, Some("sext.i16.i32"), Some("sext.i16.i64")],
448    [None, None, None, Some("sext.i32.i64")],
449    [None, None, None, None],
450];
451
452static ZEXT: [[Option<&str>; 4]; 4] = [
453    [None, Some("zext.i8.i16"), Some("zext.i8.i32"), Some("zext.i8.i64")],
454    [None, None, Some("zext.i16.i32"), Some("zext.i16.i64")],
455    [None, None, None, Some("zext.i32.i64")],
456    [None, None, None, None],
457];
458
459/// The narrowing ones, which fill the other corner for the same reason.
460static TRUNC: [[Option<&str>; 4]; 4] = [
461    [None, None, None, None],
462    [Some("trunc.i16.i8"), None, None, None],
463    [Some("trunc.i32.i8"), Some("trunc.i32.i16"), None, None],
464    [Some("trunc.i64.i8"), Some("trunc.i64.i16"), Some("trunc.i64.i32"), None],
465];
466
467#[cfg(test)]
468mod tests {
469    use rucc_base::Interner;
470    use rucc_ir::{Builder, Flags, Signature};
471
472    use super::*;
473    use crate::select::Subject;
474
475    /// A function with one block, and the builder to put instructions in it.
476    fn func() -> (Func, rucc_ir::Block) {
477        let mut names = Interner::new();
478        let mut func = Func::new(names.intern("f"), Signature::new());
479        let block = func.create_block();
480        (func, block)
481    }
482
483    /// The instruction that computed a value, which every value in these tests has.
484    fn inst_of(func: &Func, value: Value) -> Inst {
485        match func[value].def {
486            Def::Result { inst, .. } => inst,
487            Def::Param { .. } => unreachable!(),
488        }
489    }
490
491    #[test]
492    fn an_instruction_is_the_term_the_rule_file_names_it_by() {
493        let (mut func, block) = func();
494        let i32 = Type::int(32);
495        let mut build = Builder::new(&mut func, block);
496        let k = build.iconst(i32, 7);
497        let x = build.iconst(i32, 3);
498        let sum = build.binary(Opcode::Add, x, k, Flags::default());
499        let add = inst_of(&func, sum);
500
501        let terms = Terms::new(&func, add, PLAIN);
502        assert_eq!(terms.head(Term::Root), Some(("add.i32", 2)));
503        assert_eq!(terms.head(Term::Arg(0)), Some(("value.i32", 1)));
504        assert_eq!(terms.arg(Term::Arg(0), 0), Term::Reg(x));
505        assert_eq!(terms.head(Term::Reg(x)), None);
506        assert_eq!(terms.int(Term::Reg(x)), None);
507    }
508
509    #[test]
510    fn an_operand_shown_as_a_constant_gives_the_number_up() {
511        let (mut func, block) = func();
512        let i32 = Type::int(32);
513        let mut build = Builder::new(&mut func, block);
514        let x = build.iconst(i32, 3);
515        let k = build.iconst(i32, -7);
516        let sum = build.binary(Opcode::Add, x, k, Flags::default());
517        let add = inst_of(&func, sum);
518
519        let terms = Terms::new(&func, add, [Shown::Reg, Shown::Const, Shown::Reg]);
520        assert_eq!(terms.head(Term::Arg(1)), Some(("iconst.i32", 1)));
521        assert_eq!(terms.arg(Term::Arg(1), 0), Term::Num(-7));
522        assert_eq!(terms.int(Term::Num(-7)), Some(-7));
523        // The same operand shown as a register is a register, and a guard asking what number it
524        // is gets no answer, which is what makes a rule about a number decline it.
525        let plain = Terms::new(&func, add, PLAIN);
526        assert_eq!(plain.head(Term::Arg(1)), Some(("value.i32", 1)));
527        assert_eq!(plain.int(plain.arg(Term::Arg(1), 0)), None);
528    }
529
530    #[test]
531    fn a_constant_is_a_term_of_one_argument_and_has_no_operands() {
532        let (mut func, block) = func();
533        let mut build = Builder::new(&mut func, block);
534        let k = build.iconst(Type::int(64), 12);
535        let inst = inst_of(&func, k);
536
537        let terms = Terms::new(&func, inst, PLAIN);
538        assert_eq!(terms.head(Term::Root), Some(("iconst.i64", 1)));
539        assert_eq!(terms.arg(Term::Root, 0), Term::Num(12));
540    }
541
542    #[test]
543    fn an_expanded_operand_is_the_instruction_that_computed_it() {
544        let (mut func, block) = func();
545        let i64 = Type::int(64);
546        // A parameter, because the point of the test is an operand that is not a constant.
547        let y = func.append_param(block, i64);
548        let mut build = Builder::new(&mut func, block);
549        let x = build.iconst(i64, 1);
550        let four = build.iconst(i64, 4);
551        let scaled = build.binary(Opcode::Mul, y, four, Flags::default());
552        let sum = build.binary(Opcode::Add, x, scaled, Flags::default());
553        let add = inst_of(&func, sum);
554
555        let terms = Terms::new(&func, add, [Shown::Reg, Shown::Expand, Shown::Reg]);
556        assert_eq!(terms.head(Term::Root), Some(("add.i64", 2)));
557        assert_eq!(terms.head(Term::Arg(1)), Some(("mul.i64", 2)));
558        assert_eq!(terms.head(Term::Deep(1, 0)), Some(("value.i64", 1)));
559        assert_eq!(terms.arg(Term::Deep(1, 0), 0), Term::Reg(y));
560        // The constant inside an expansion is shown as one without being asked to be.
561        assert_eq!(terms.head(Term::Deep(1, 1)), Some(("iconst.i64", 1)));
562        assert_eq!(terms.arg(Term::Deep(1, 1), 0), Term::Num(4));
563    }
564
565    #[test]
566    fn a_comparison_says_which_one_it_is_and_a_conversion_says_both_widths() {
567        let (mut func, block) = func();
568        let mut build = Builder::new(&mut func, block);
569        let x = build.iconst(Type::int(32), 1);
570        let y = build.iconst(Type::int(32), 2);
571        let less = build.icmp(IntPred::Slt, x, y);
572        let wide = build.unary(Opcode::SExt, x, Type::int(64));
573        let narrow = build.unary(Opcode::Trunc, x, Type::int(8));
574        let cmp = inst_of(&func, less);
575        assert_eq!(Terms::new(&func, cmp, PLAIN).head(Term::Root), Some(("icmp_slt.i1", 2)));
576        let sext = inst_of(&func, wide);
577        assert_eq!(Terms::new(&func, sext, PLAIN).head(Term::Root), Some(("sext.i32.i64", 1)));
578        let trunc = inst_of(&func, narrow);
579        assert_eq!(Terms::new(&func, trunc, PLAIN).head(Term::Root), Some(("trunc.i32.i8", 1)));
580    }
581
582    #[test]
583    fn a_width_no_rule_is_written_at_has_no_name() {
584        let (mut func, block) = func();
585        let mut build = Builder::new(&mut func, block);
586        let x = build.iconst(Type::int(128), 1);
587        let inst = inst_of(&func, x);
588        assert_eq!(Terms::new(&func, inst, PLAIN).head(Term::Root), None);
589    }
590
591    /// An address is an integer of the machine's width to every term here, which is what lets one
592    /// be loaded from, stored through, returned and added to by rules written about integers.
593    #[test]
594    fn an_address_is_an_integer_as_wide_as_the_machine_addresses() {
595        assert_eq!(value_head(Type::PTR), Some("value.i64"));
596        assert_eq!(load_head(Type::PTR), Some("load.i64"));
597        assert_eq!(store_head(Type::PTR), Some("store.i64"));
598        assert_eq!(ret_head(Type::PTR), Some("ret.i64"));
599        // Not a constant, since nothing writes an address down as one.
600        assert_eq!(iconst_head(Type::PTR), None);
601    }
602
603    /// A lane count is not a width, so a rule written at a width does not get to answer for a
604    /// vector of that width. Nothing produces one yet and the day something does it should be
605    /// reported rather than lowered to an instruction that acts on one lane of it.
606    #[test]
607    fn a_vector_is_not_the_width_of_its_lane() {
608        let i32x4 = Type::vector(Type::int(32), 4);
609        assert_eq!(slot(i32x4), None);
610        assert_eq!(value_head(i32x4), None);
611        assert_eq!(binary_head(Opcode::Add, i32x4), None);
612    }
613
614    /// Address arithmetic is named as the add it is, which is what puts it in reach of every rule
615    /// written about one, including the two below that fold it into an address.
616    #[test]
617    fn address_arithmetic_is_an_add_at_the_address_width() {
618        let (mut func, block) = func();
619        let base = func.append_param(block, Type::PTR);
620        let mut build = Builder::new(&mut func, block);
621        let step = build.iconst(Type::int(64), 4);
622        let args = func.push_values(&[base, step]);
623        let next = Builder::new(&mut func, block)
624            .value(rucc_ir::InstData { args, ..rucc_ir::InstData::new(Opcode::PtrAdd) }, Type::PTR);
625        let inst = inst_of(&func, next);
626
627        let terms = Terms::new(&func, inst, [Shown::Reg, Shown::Const, Shown::Reg]);
628        assert_eq!(terms.head(Term::Root), Some(("add.i64", 2)));
629        assert_eq!(terms.head(Term::Arg(0)), Some(("value.i64", 1)));
630        assert_eq!(terms.head(Term::Arg(1)), Some(("iconst.i64", 1)));
631        assert_eq!(terms.arg(Term::Arg(1), 0), Term::Num(4));
632    }
633}