Skip to main content

rucc_codegen/
select.rs

1//! Matching a target's lowering rules against a term.
2//!
3//! Design: `spec/10-backend.md` section 10.2. The rules themselves are in `rules/`, one file per
4//! target, and the automaton they compile into is generated by `rucc-rules` when this crate is
5//! built.
6//!
7//! The walk over that automaton is [`rucc_base::rules`], because `rucc-opt` matches IR against a
8//! table of rewrite rules with the same walk and neither crate can see the other. What is here
9//! is which targets there are and the tests that the x86-64 table lowers what it should. The
10//! AArch64 table has its own tests beside it.
11//!
12//! The names are re-exported rather than reached for through `rucc_base`, because the generated
13//! file refers to them through `super` and that is the whole of the contract between the two.
14
15pub mod aarch64;
16pub mod x86_64;
17
18pub use rucc_base::rules::{Guard, Match, Node, Piece, Rule, Subject, Table};
19use rucc_target::{
20    Address, BranchInsts, FrameInsts, MachineInsts, OperandDesc, PhysReg, RegClass, Segment,
21};
22
23/// What `crate::lower` has to know about the machine it selects instructions for.
24///
25/// The lowering is one walk over the IR whichever machine it is for, and everything in it that
26/// differs between two machines is a question this answers: which table the rules compiled into,
27/// what each opcode's operands are, what an address constructor's arguments mean, and the handful
28/// of instructions the walk writes itself rather than getting from a rule. A walk that reaches
29/// for a machine's module by name is a walk for that machine only, which is what this is here to
30/// stop.
31///
32/// The frame and branch instructions are the same tables `crate::pipeline::Machine` hands the
33/// passes after this one. They are in here as well so that the lowering is handed one thing,
34/// rather than a machine and the convention and the rules separately.
35#[derive(Debug)]
36pub struct Selector {
37    /// The rules, compiled.
38    pub table: &'static Table,
39    /// The shape of each opcode, and the prefix a rule file puts in front of one.
40    pub shapes: &'static MachineInsts,
41    /// What an address constructor in a replacement stands for, or `None` for a name that is not
42    /// one of this machine's.
43    pub address: fn(&str) -> Option<Address>,
44    /// The instructions that take a frame and give it back, of which the walk writes the address
45    /// of a local and the move between two registers itself.
46    pub frame: &'static FrameInsts,
47    /// The instructions a branch becomes, of which the walk writes the indirect jump itself.
48    pub branch: &'static BranchInsts,
49    /// The class an address is in.
50    pub gpr: RegClass,
51    /// The instruction a full fence is, without the prefix.
52    pub fence: &'static str,
53    /// The instruction a program that must stop here stops with, without the prefix.
54    pub trap: &'static str,
55    /// The instructions the calling convention is written with.
56    pub abi: &'static crate::abi::Insts,
57    /// The address registers held back from the allocator for the rewriter's reloads, which are
58    /// the ones the walk must not keep anything in across more than one instruction.
59    pub scratch: &'static [PhysReg],
60    /// How the walk comes by the address of a symbol, which is its own business rather than a
61    /// rule's because whether it goes through the global offset table is a fact about the link and
62    /// not about the instruction.
63    pub symbols: &'static Symbols,
64    /// The instructions a jump through a table is built from, and the address of a label.
65    pub jumps: &'static Jumps,
66}
67
68/// The instructions a place in this function is reached with: the address of a block or a jump
69/// table, and the read of one cell of a table and the add that turns it back into an address.
70#[derive(Debug)]
71pub struct Jumps {
72    /// The address of a block or a table, which is carried in the addressing mode.
73    pub near: &'static str,
74    /// A load of a 32-bit cell, sign extended to the width of an address.
75    pub cell: &'static str,
76    /// The add of two addresses.
77    pub add: &'static str,
78    /// Whether the add writes its first operand, the way it does on x86-64.
79    pub two_address: bool,
80}
81
82/// The two ways the address of a symbol is come by.
83#[derive(Debug)]
84pub struct Symbols {
85    /// A symbol this image defines, whose address is a fixed distance from the code.
86    pub near: Reach,
87    /// A symbol another image may define, whose address is read out of the global offset table.
88    pub far: Reach,
89    /// A pointer this image holds, read where the name it points to is reached through it, which
90    /// is how COFF reaches a name in a DLL or one another object may define.
91    pub slot: Reach,
92    /// How far a thread-local variable is from the thread pointer, which is read out of the global
93    /// offset table too, from a slot the link fills in with that distance.
94    pub thread: Reach,
95    /// The thread pointer itself.
96    pub pointer: Pointer,
97    /// How a Windows thread finds its copy of a thread-local variable, where this machine has
98    /// been taught. See [`Indexed`].
99    pub indexed: Option<Indexed>,
100    /// The same on a machine that keeps the TEB in a register rather than a segment, which is
101    /// AArch64. See [`Teb`].
102    pub teb: Option<Teb>,
103}
104
105/// The instructions a thread-local variable is reached with on Windows on AArch64.
106///
107/// The same walk as [`Indexed`], from the TEB Windows keeps in `x18` rather than in a segment,
108/// and the six instructions clang writes for it.
109///
110/// ```text
111/// adrp x8, _tls_index
112/// ldr  w8, [x8, :lo12:_tls_index]
113/// ldr  x9, [x18, #88]
114/// ldr  x8, [x9, x8, lsl #3]
115/// add  x8, x8, :secrel_hi12:x, lsl #12
116/// add  x0, x8, :secrel_lo12:x
117/// ```
118#[derive(Debug)]
119pub struct Teb {
120    /// The first two, which read `_tls_index`, carrying it as its symbol.
121    pub index: &'static str,
122    /// The third, which reads the array out of the TEB.
123    pub array: &'static str,
124    /// The rest, which read this thread's block out of the array with the index and add the
125    /// variable's offset in its section, carrying the variable as its symbol. It takes the array
126    /// and the index as its two sources, in that order.
127    pub block: &'static str,
128}
129
130/// The instructions a thread-local variable is reached with on Windows.
131///
132/// Every thread keeps an array of pointers, one for each image's copy of its `.tls` section. The
133/// thread block holds the array, the image's `_tls_index` says which slot is its own, and the
134/// variable is as far into the copy as it is into the section.
135///
136/// ```text
137/// movl  _tls_index(%rip), %idx
138/// movq  %gs:88, %arr
139/// movq  (%arr,%idx,8), %blk
140/// leaq  x@SECREL32(%blk), %x
141/// ```
142#[derive(Debug)]
143pub struct Indexed {
144    /// A 32-bit load, zero extended, which reads `_tls_index`.
145    pub index: &'static str,
146    /// A load of a whole word, which reads the array out of the thread block and the block out of
147    /// the array.
148    pub load: &'static str,
149    /// The segment the thread block is in, and how far into it the array is.
150    pub segment: Segment,
151    /// The same.
152    pub at: i32,
153    /// The instruction that adds an offset to a register, which takes the variable's offset in its
154    /// section as its displacement.
155    pub add: &'static str,
156}
157
158/// Where the thread pointer is read from.
159#[derive(Debug, Clone, Copy, PartialEq, Eq)]
160pub enum Pointer {
161    /// A load at zero in a segment, since the word at the front of the block is its own address.
162    /// That is x86-64, whose `%fs` is not a register a program can read.
163    Segment(&'static str, Segment),
164    /// An instruction that reads a system register, which is AArch64's `mrs` of `tpidr_el0`.
165    Own(&'static str),
166}
167
168/// One instruction that puts the address of a symbol in a register, and where it carries the
169/// symbol.
170#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171pub enum Reach {
172    /// In its addressing mode, which is how x86-64 does both: a `lea` or a `mov` relative to the
173    /// instruction pointer.
174    Mode(&'static str),
175    /// As the instruction's own symbol with no addressing mode at all, which is how AArch64 does
176    /// both: an `adrp` for the page and a second instruction for the rest, written as one opcode.
177    Own(&'static str),
178}
179
180impl Selector {
181    /// What a rule file and the machine IR put in front of this machine's opcodes.
182    #[must_use]
183    pub fn prefix(&self) -> &'static str {
184        self.shapes.prefix
185    }
186
187    /// The operands the opcode of that name has, the name written without the prefix.
188    #[must_use]
189    pub fn operands(&self, name: &str) -> Option<&'static [OperandDesc]> {
190        (self.shapes.operands)(name)
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use super::x86_64::TABLE;
197    use super::{Piece, Subject};
198
199    /// A term, in the only shape a test needs: a flat arena, because that is the shape the IR
200    /// has and answering the questions out of one is what the selector will be doing.
201    #[derive(Debug)]
202    enum Node {
203        Int(i128),
204        App(String, Vec<usize>),
205    }
206
207    #[derive(Debug, Default)]
208    struct Terms {
209        nodes: Vec<Node>,
210    }
211
212    impl Terms {
213        fn constant(&mut self, value: i128) -> usize {
214            self.nodes.push(Node::Int(value));
215            self.nodes.len() - 1
216        }
217
218        fn app(&mut self, head: &str, args: &[usize]) -> usize {
219            self.nodes.push(Node::App(head.to_owned(), args.to_vec()));
220            self.nodes.len() - 1
221        }
222
223        /// A register operand, which is a term with a head the rules write and nothing under it.
224        fn value(&mut self, width: u32, name: &str) -> usize {
225            let inner = self.app(name, &[]);
226            self.app(&format!("value.i{width}"), &[inner])
227        }
228    }
229
230    impl Subject for Terms {
231        type Node = usize;
232
233        fn head(&self, node: usize) -> Option<(&str, usize)> {
234            match &self.nodes[node] {
235                Node::App(head, args) => Some((head.as_str(), args.len())),
236                Node::Int(_) => None,
237            }
238        }
239
240        fn arg(&self, node: usize, index: usize) -> usize {
241            match &self.nodes[node] {
242                Node::App(_, args) => args[index],
243                Node::Int(_) => unreachable!("a constant has no arguments"),
244            }
245        }
246
247        fn int(&self, node: usize) -> Option<i128> {
248            match self.nodes[node] {
249                Node::Int(value) => Some(value),
250                Node::App(..) => None,
251            }
252        }
253
254        // An index into the arena is the identity of a term here, so two places are the same
255        // thing when they point at the same entry.
256        fn same(&self, a: usize, b: usize) -> bool {
257            a == b
258        }
259    }
260
261    /// What the head of the rule that fired selects, which is the answer every one of these
262    /// tests is really about.
263    fn selects(terms: &Terms, term: usize) -> Option<&'static str> {
264        let found = TABLE.find(terms, term)?;
265        TABLE.rule(&found).head()
266    }
267
268    /// No pattern is reached by reading past the ones in front of it.
269    ///
270    /// `spec/optimizer/36-lowering-and-isel.md` section 36.5 asks for the decision to be on the
271    /// shape of the term, and the root of this table is where that is worth anything: every
272    /// instruction the selector looks at arrives there, and a hundred and sixty seven different
273    /// heads are written on it. Sorted, that is eight comparisons and the walk finds the branch.
274    /// In the order the rules happen to be written it would be a hundred and sixty seven, every
275    /// time, and worst for the terms no rule covers, which are the ones the selector has to see
276    /// the most of.
277    ///
278    /// What is asserted is the property the search needs, which is that every node is in order.
279    /// A node that is not is not a slower table, it is a wrong one, because a binary search over
280    /// an unsorted list finds nothing and the rule silently stops firing.
281    #[test]
282    fn no_rule_is_reached_by_reading_past_the_rules_in_front_of_it() {
283        let root = TABLE.nodes.first().expect("the table has a root");
284        assert!(root.heads.len() > 100, "the root is the node this is about");
285        for (at, node) in TABLE.nodes.iter().enumerate() {
286            assert!(node.heads.is_sorted(), "node {at} is not in an order a search can use");
287            assert!(node.ints.is_sorted(), "node {at} is not in an order a search can use");
288        }
289    }
290
291    #[test]
292    fn the_table_holds_every_rule_the_file_writes() {
293        let text = include_str!("../rules/x86-64.rules");
294        let written = text.lines().filter(|line| line.starts_with("(rule ")).count();
295        assert_eq!(TABLE.rules.len(), written, "the table and the rule file disagree");
296        assert_eq!(TABLE.source, "rules/x86-64.rules");
297    }
298
299    #[test]
300    fn an_addition_of_two_registers_is_the_register_form() {
301        let mut terms = Terms::default();
302        let x = terms.value(64, "v0");
303        let y = terms.value(64, "v1");
304        let add = terms.app("add.i64", &[x, y]);
305        assert_eq!(selects(&terms, add), Some("x64.add_rr_64"));
306    }
307
308    /// The bindings are the operands in the order the pattern names them, and the replacement
309    /// says which of them goes where. This is the whole of what the selector will read.
310    ///
311    /// What a name is bound to is what the pattern put it under, so `(value.i32 x)` binds the
312    /// register and not the term saying it is one. That is the difference between the operand of
313    /// the instruction this becomes and a wrapper that exists to say how wide it is.
314    #[test]
315    fn a_match_gives_back_the_operands_the_pattern_named() {
316        let mut terms = Terms::default();
317        let first = terms.app("v0", &[]);
318        let second = terms.app("v1", &[]);
319        let x = terms.app("value.i32", &[first]);
320        let y = terms.app("value.i32", &[second]);
321        let sub = terms.app("sub.i32", &[x, y]);
322        let found = TABLE.find(&terms, sub).expect("a rule fires");
323        let rule = TABLE.rule(&found);
324        assert_eq!(rule.pattern, "(sub.i32 (value.i32 x) (value.i32 y))");
325        assert_eq!(found.bindings, vec![first, second]);
326        let names: Vec<&str> = rule
327            .replacement
328            .iter()
329            .filter_map(|piece| match piece {
330                Piece::Var { name, index } => {
331                    assert_eq!(found.bindings[*index], if *index == 0 { first } else { second });
332                    Some(*name)
333                }
334                _ => None,
335            })
336            .collect();
337        assert_eq!(names, ["x", "y"]);
338    }
339
340    /// An immediate the instruction has room for takes the immediate form. The rule for it is
341    /// guarded, so this is also the test that a guard which holds does not stop a rule firing.
342    #[test]
343    fn an_addition_of_an_immediate_that_fits_is_the_immediate_form() {
344        let mut terms = Terms::default();
345        let x = terms.value(64, "v0");
346        let k = terms.constant(4);
347        let k = terms.app("iconst.i64", &[k]);
348        let add = terms.app("add.i64", &[x, k]);
349        assert_eq!(selects(&terms, add), Some("x64.add_ri_64"));
350    }
351
352    /// An immediate too wide for the encoding is what the guard is there to refuse. Nothing else
353    /// matches such a term, and that is the right answer: the constant has to be put in a
354    /// register first, which is a decision for the selector and not for the table.
355    #[test]
356    fn an_addition_of_an_immediate_too_wide_for_the_form_matches_nothing() {
357        let mut terms = Terms::default();
358        let x = terms.value(64, "v0");
359        let k = terms.constant(1 << 40);
360        let k = terms.app("iconst.i64", &[k]);
361        let add = terms.app("add.i64", &[x, k]);
362        assert_eq!(selects(&terms, add), None);
363    }
364
365    /// The other shape of guard, which is a shift count the width allows.
366    #[test]
367    fn a_shift_by_a_count_the_width_allows_is_the_immediate_form() {
368        let mut terms = Terms::default();
369        let x = terms.value(64, "v0");
370        let k = terms.constant(3);
371        let k = terms.app("iconst.i64", &[k]);
372        let shl = terms.app("shl.i64", &[x, k]);
373        assert_eq!(selects(&terms, shl), Some("x64.shl_ri_64"));
374    }
375
376    #[test]
377    fn a_shift_by_a_count_the_width_does_not_allow_matches_nothing() {
378        let mut terms = Terms::default();
379        let x = terms.value(64, "v0");
380        let k = terms.constant(64);
381        let k = terms.app("iconst.i64", &[k]);
382        let shl = terms.app("shl.i64", &[x, k]);
383        assert_eq!(selects(&terms, shl), None);
384    }
385
386    /// One bit reaches the byte instructions, which is the whole of how the machine holds a truth
387    /// value. The widening is the interesting one: it is `movzbl` under a name of its own, so the
388    /// rule that fires here is not the rule a byte would have found.
389    #[test]
390    fn a_truth_value_is_lowered_to_the_byte_instructions_that_keep_it_one() {
391        let mut terms = Terms::default();
392        let x = terms.value(1, "v0");
393        let y = terms.value(1, "v1");
394        let xor = terms.app("xor.i1", &[x, y]);
395        assert_eq!(selects(&terms, xor), Some("x64.xor_rr_8"));
396
397        let x = terms.value(1, "v2");
398        let wide = terms.app("zext.i1.i32", &[x]);
399        assert_eq!(selects(&terms, wide), Some("x64.bit_to_32"));
400
401        let x = terms.value(8, "v3");
402        let byte = terms.app("zext.i8.i32", &[x]);
403        assert_eq!(selects(&terms, byte), Some("x64.movzx_8_32"));
404    }
405
406    /// The half of a truth value that is an object rather than a value in a register. A `_Bool`
407    /// in memory is a byte holding a zero or a one, so a load widens on the way in and a store
408    /// writes the byte, and both are named apart from the byte pair for the reason the widening
409    /// is named apart from the byte widening. The narrowing is the mask, and it is the one of
410    /// these that nothing in C asks for directly: a bit field one bit wide whose type is a
411    /// `_Bool` is what writes it.
412    #[test]
413    fn a_truth_value_in_memory_is_the_byte_it_lives_in() {
414        let mut terms = Terms::default();
415        let address = terms.value(64, "v0");
416        let read = terms.app("load.i1", &[address]);
417        assert_eq!(selects(&terms, read), Some("x64.mov_rm_bit"));
418
419        let value = terms.value(1, "v1");
420        let address = terms.value(64, "v2");
421        let write = terms.app("store.i1", &[value, address]);
422        assert_eq!(selects(&terms, write), Some("x64.mov_mr_bit"));
423
424        let value = terms.value(1, "v3");
425        let back = terms.app("ret.i1", &[value]);
426        assert_eq!(selects(&terms, back), Some("x64.ret_val_8"));
427
428        let x = terms.value(32, "v4");
429        let bit = terms.app("trunc.i32.i1", &[x]);
430        assert_eq!(selects(&terms, bit), Some("x64.bit_of_32"));
431    }
432
433    /// The divisions at one byte and at two, which the `narrow` pass writes for a division of two
434    /// zero extensions and, signed, for a division of two sign extensions the ranges clear.
435    #[test]
436    fn a_narrow_division_is_the_narrow_divide() {
437        let mut terms = Terms::default();
438        for width in [8, 16] {
439            let x = terms.value(width, "v0");
440            let y = terms.value(width, "v1");
441            for (op, head) in [
442                ("udiv", "div_quo"),
443                ("urem", "div_rem"),
444                ("sdiv", "idiv_quo"),
445                ("srem", "idiv_rem"),
446            ] {
447                let term = terms.app(&format!("{op}.i{width}"), &[x, y]);
448                let want = format!("x64.{head}_{width}");
449                assert_eq!(selects(&terms, term), Some(want.as_str()));
450            }
451        }
452    }
453
454    /// A term the rule set says nothing about is nothing rather than a wrong answer, which is
455    /// what the completeness check in `spec/10-backend.md` will be for.
456    #[test]
457    fn a_term_no_rule_covers_finds_no_rule() {
458        let mut terms = Terms::default();
459        let x = terms.value(64, "v0");
460        let y = terms.value(64, "v1");
461        let odd = terms.app("no.such.opcode", &[x, y]);
462        assert_eq!(selects(&terms, odd), None);
463    }
464
465    /// Every instruction a selector names outside its rules is one its machine describes, since
466    /// the lowering writes those without asking a rule and nothing else would catch a name that is
467    /// not there.
468    #[test]
469    fn a_selector_names_only_instructions_its_machine_has() {
470        for selector in [&super::x86_64::SELECTOR, &super::aarch64::SELECTOR] {
471            let named = [
472                selector.fence,
473                selector.trap,
474                selector.frame.lea,
475                selector.frame.grow,
476                selector.frame.imm,
477                selector.branch.indirect,
478            ];
479            for name in named {
480                assert!(
481                    selector.operands(name).is_some(),
482                    "{}{name} is not an instruction of its machine",
483                    selector.prefix()
484                );
485            }
486            // And the rules it is handed are the ones written for the same machine.
487            for rule in selector.table.rules {
488                let Some(Piece::App { head, .. }) = rule.replacement.first() else { continue };
489                assert!(head.starts_with(selector.prefix()), "{head} in {}", selector.table.source);
490            }
491        }
492    }
493
494    /// An address constructor is read the same way on both machines, and the one the AArch64 rules
495    /// cannot write is not one it answers for.
496    #[test]
497    fn both_machines_read_an_address_the_same_way() {
498        let (x86, a64) = (&super::x86_64::SELECTOR, &super::aarch64::SELECTOR);
499        for name in ["amode_base", "amode_base_offset"] {
500            assert_eq!((x86.address)(name), (a64.address)(name));
501            assert!((a64.address)(name).is_some());
502        }
503        assert!((x86.address)("amode_base_index_scale").is_some());
504        assert_eq!((a64.address)("amode_base_index_scale"), None);
505        assert_eq!((a64.address)("add_rr_64"), None);
506    }
507}