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    let result = data.first_result?;
257    let ty = func[result].ty;
258    match data.opcode {
259        Opcode::IConst => iconst_head(ty),
260        Opcode::ICmp => {
261            let Extra::IntPred(pred) = data.extra else { return None };
262            Some(icmp_head(pred))
263        }
264        Opcode::SExt | Opcode::ZExt | Opcode::Trunc => {
265            let from = func[*func[data.args].first()?].ty;
266            convert_head(data.opcode, from, ty)
267        }
268        opcode => binary_head(opcode, ty),
269    }
270}
271
272/// Which of the four widths a type is, or nothing for a width no rule is written at.
273fn slot(ty: Type) -> Option<usize> {
274    match ty.is_int().then(|| ty.bits())? {
275        8 => Some(0),
276        16 => Some(1),
277        32 => Some(2),
278        64 => Some(3),
279        _ => None,
280    }
281}
282
283/// What a value in a register is called at that width.
284fn value_head(ty: Type) -> Option<&'static str> {
285    Some(["value.i8", "value.i16", "value.i32", "value.i64"][slot(ty)?])
286}
287
288/// What a constant is called at that width.
289fn iconst_head(ty: Type) -> Option<&'static str> {
290    Some(["iconst.i8", "iconst.i16", "iconst.i32", "iconst.i64"][slot(ty)?])
291}
292
293/// What a comparison is called, which does not carry the width of what it compared: the result
294/// is one bit whatever the operands were, and the operands say how wide they are themselves.
295fn icmp_head(pred: IntPred) -> &'static str {
296    match pred {
297        IntPred::Eq => "icmp_eq.i1",
298        IntPred::Ne => "icmp_ne.i1",
299        IntPred::Slt => "icmp_slt.i1",
300        IntPred::Sle => "icmp_sle.i1",
301        IntPred::Sgt => "icmp_sgt.i1",
302        IntPred::Sge => "icmp_sge.i1",
303        IntPred::Ult => "icmp_ult.i1",
304        IntPred::Ule => "icmp_ule.i1",
305        IntPred::Ugt => "icmp_ugt.i1",
306        IntPred::Uge => "icmp_uge.i1",
307    }
308}
309
310/// What a conversion is called, which is the two widths it is between.
311fn convert_head(opcode: Opcode, from: Type, to: Type) -> Option<&'static str> {
312    let table: &[[Option<&'static str>; 4]; 4] = match opcode {
313        Opcode::SExt => &SEXT,
314        Opcode::ZExt => &ZEXT,
315        Opcode::Trunc => &TRUNC,
316        _ => return None,
317    };
318    table[slot(from)?][slot(to)?]
319}
320
321/// What each of the binary operations is called at each width.
322fn binary_head(opcode: Opcode, ty: Type) -> Option<&'static str> {
323    let names: &[&'static str; 4] = match opcode {
324        Opcode::Add => &["add.i8", "add.i16", "add.i32", "add.i64"],
325        Opcode::Sub => &["sub.i8", "sub.i16", "sub.i32", "sub.i64"],
326        Opcode::Mul => &["mul.i8", "mul.i16", "mul.i32", "mul.i64"],
327        Opcode::SDiv => &["sdiv.i8", "sdiv.i16", "sdiv.i32", "sdiv.i64"],
328        Opcode::UDiv => &["udiv.i8", "udiv.i16", "udiv.i32", "udiv.i64"],
329        Opcode::SRem => &["srem.i8", "srem.i16", "srem.i32", "srem.i64"],
330        Opcode::URem => &["urem.i8", "urem.i16", "urem.i32", "urem.i64"],
331        Opcode::And => &["and.i8", "and.i16", "and.i32", "and.i64"],
332        Opcode::Or => &["or.i8", "or.i16", "or.i32", "or.i64"],
333        Opcode::Xor => &["xor.i8", "xor.i16", "xor.i32", "xor.i64"],
334        Opcode::Shl => &["shl.i8", "shl.i16", "shl.i32", "shl.i64"],
335        Opcode::LShr => &["lshr.i8", "lshr.i16", "lshr.i32", "lshr.i64"],
336        Opcode::AShr => &["ashr.i8", "ashr.i16", "ashr.i32", "ashr.i64"],
337        _ => return None,
338    };
339    Some(names[slot(ty)?])
340}
341
342/// The widening conversions, from the width down the side to the width across the top. The
343/// diagonal and everything below it is empty, because a sign extension to a width it already
344/// has is not an instruction and the IR does not have one.
345static SEXT: [[Option<&str>; 4]; 4] = [
346    [None, Some("sext.i8.i16"), Some("sext.i8.i32"), Some("sext.i8.i64")],
347    [None, None, Some("sext.i16.i32"), Some("sext.i16.i64")],
348    [None, None, None, Some("sext.i32.i64")],
349    [None, None, None, None],
350];
351
352static ZEXT: [[Option<&str>; 4]; 4] = [
353    [None, Some("zext.i8.i16"), Some("zext.i8.i32"), Some("zext.i8.i64")],
354    [None, None, Some("zext.i16.i32"), Some("zext.i16.i64")],
355    [None, None, None, Some("zext.i32.i64")],
356    [None, None, None, None],
357];
358
359/// The narrowing ones, which fill the other corner for the same reason.
360static TRUNC: [[Option<&str>; 4]; 4] = [
361    [None, None, None, None],
362    [Some("trunc.i16.i8"), None, None, None],
363    [Some("trunc.i32.i8"), Some("trunc.i32.i16"), None, None],
364    [Some("trunc.i64.i8"), Some("trunc.i64.i16"), Some("trunc.i64.i32"), None],
365];
366
367#[cfg(test)]
368mod tests {
369    use rucc_base::Interner;
370    use rucc_ir::{Builder, Flags, Signature};
371
372    use super::*;
373    use crate::select::Subject;
374
375    /// A function with one block, and the builder to put instructions in it.
376    fn func() -> (Func, rucc_ir::Block) {
377        let mut names = Interner::new();
378        let mut func = Func::new(names.intern("f"), Signature::new());
379        let block = func.create_block();
380        (func, block)
381    }
382
383    /// The instruction that computed a value, which every value in these tests has.
384    fn inst_of(func: &Func, value: Value) -> Inst {
385        match func[value].def {
386            Def::Result { inst, .. } => inst,
387            Def::Param { .. } => unreachable!(),
388        }
389    }
390
391    #[test]
392    fn an_instruction_is_the_term_the_rule_file_names_it_by() {
393        let (mut func, block) = func();
394        let i32 = Type::int(32);
395        let mut build = Builder::new(&mut func, block);
396        let k = build.iconst(i32, 7);
397        let x = build.iconst(i32, 3);
398        let sum = build.binary(Opcode::Add, x, k, Flags::default());
399        let add = inst_of(&func, sum);
400
401        let terms = Terms::new(&func, add, PLAIN);
402        assert_eq!(terms.head(Term::Root), Some(("add.i32", 2)));
403        assert_eq!(terms.head(Term::Arg(0)), Some(("value.i32", 1)));
404        assert_eq!(terms.arg(Term::Arg(0), 0), Term::Reg(x));
405        assert_eq!(terms.head(Term::Reg(x)), None);
406        assert_eq!(terms.int(Term::Reg(x)), None);
407    }
408
409    #[test]
410    fn an_operand_shown_as_a_constant_gives_the_number_up() {
411        let (mut func, block) = func();
412        let i32 = Type::int(32);
413        let mut build = Builder::new(&mut func, block);
414        let x = build.iconst(i32, 3);
415        let k = build.iconst(i32, -7);
416        let sum = build.binary(Opcode::Add, x, k, Flags::default());
417        let add = inst_of(&func, sum);
418
419        let terms = Terms::new(&func, add, [Shown::Reg, Shown::Const, Shown::Reg]);
420        assert_eq!(terms.head(Term::Arg(1)), Some(("iconst.i32", 1)));
421        assert_eq!(terms.arg(Term::Arg(1), 0), Term::Num(-7));
422        assert_eq!(terms.int(Term::Num(-7)), Some(-7));
423        // The same operand shown as a register is a register, and a guard asking what number it
424        // is gets no answer, which is what makes a rule about a number decline it.
425        let plain = Terms::new(&func, add, PLAIN);
426        assert_eq!(plain.head(Term::Arg(1)), Some(("value.i32", 1)));
427        assert_eq!(plain.int(plain.arg(Term::Arg(1), 0)), None);
428    }
429
430    #[test]
431    fn a_constant_is_a_term_of_one_argument_and_has_no_operands() {
432        let (mut func, block) = func();
433        let mut build = Builder::new(&mut func, block);
434        let k = build.iconst(Type::int(64), 12);
435        let inst = inst_of(&func, k);
436
437        let terms = Terms::new(&func, inst, PLAIN);
438        assert_eq!(terms.head(Term::Root), Some(("iconst.i64", 1)));
439        assert_eq!(terms.arg(Term::Root, 0), Term::Num(12));
440    }
441
442    #[test]
443    fn an_expanded_operand_is_the_instruction_that_computed_it() {
444        let (mut func, block) = func();
445        let i64 = Type::int(64);
446        // A parameter, because the point of the test is an operand that is not a constant.
447        let y = func.append_param(block, i64);
448        let mut build = Builder::new(&mut func, block);
449        let x = build.iconst(i64, 1);
450        let four = build.iconst(i64, 4);
451        let scaled = build.binary(Opcode::Mul, y, four, Flags::default());
452        let sum = build.binary(Opcode::Add, x, scaled, Flags::default());
453        let add = inst_of(&func, sum);
454
455        let terms = Terms::new(&func, add, [Shown::Reg, Shown::Expand, Shown::Reg]);
456        assert_eq!(terms.head(Term::Root), Some(("add.i64", 2)));
457        assert_eq!(terms.head(Term::Arg(1)), Some(("mul.i64", 2)));
458        assert_eq!(terms.head(Term::Deep(1, 0)), Some(("value.i64", 1)));
459        assert_eq!(terms.arg(Term::Deep(1, 0), 0), Term::Reg(y));
460        // The constant inside an expansion is shown as one without being asked to be.
461        assert_eq!(terms.head(Term::Deep(1, 1)), Some(("iconst.i64", 1)));
462        assert_eq!(terms.arg(Term::Deep(1, 1), 0), Term::Num(4));
463    }
464
465    #[test]
466    fn a_comparison_says_which_one_it_is_and_a_conversion_says_both_widths() {
467        let (mut func, block) = func();
468        let mut build = Builder::new(&mut func, block);
469        let x = build.iconst(Type::int(32), 1);
470        let y = build.iconst(Type::int(32), 2);
471        let less = build.icmp(IntPred::Slt, x, y);
472        let wide = build.unary(Opcode::SExt, x, Type::int(64));
473        let narrow = build.unary(Opcode::Trunc, x, Type::int(8));
474        let cmp = inst_of(&func, less);
475        assert_eq!(Terms::new(&func, cmp, PLAIN).head(Term::Root), Some(("icmp_slt.i1", 2)));
476        let sext = inst_of(&func, wide);
477        assert_eq!(Terms::new(&func, sext, PLAIN).head(Term::Root), Some(("sext.i32.i64", 1)));
478        let trunc = inst_of(&func, narrow);
479        assert_eq!(Terms::new(&func, trunc, PLAIN).head(Term::Root), Some(("trunc.i32.i8", 1)));
480    }
481
482    #[test]
483    fn a_width_no_rule_is_written_at_has_no_name() {
484        let (mut func, block) = func();
485        let mut build = Builder::new(&mut func, block);
486        let x = build.iconst(Type::int(128), 1);
487        let inst = inst_of(&func, x);
488        assert_eq!(Terms::new(&func, inst, PLAIN).head(Term::Root), None);
489    }
490}