rucc_target/machine.rs
1//! What a machine level pass has to ask before it may keep a rewrite.
2//!
3//! Design: `spec/optimizer/37-machine-level-optimization.md` sections 37.2 and 37.3.
4//!
5//! A machine level rewrite is speculative. Section 37.3 quotes `gcc/combine.cc` on the shape of
6//! it: substitute the earlier instruction into the later one, ask the machine description whether
7//! what came out is an instruction this target has, install it if it is and put everything back if
8//! it is not. GCC's word for the asking is `recog`, the machine description is the `.md` file, and
9//! the reason the arrangement is worth copying is in the same section: a target that adds a
10//! pattern makes the optimizer smarter without anybody editing the optimizer.
11//!
12//! This is that question for this compiler. A pass proposes an instruction and asks whether the
13//! target has one of that shape, and a target answers out of the description it already keeps for
14//! the allocator and the encoder rather than out of a second list written for this. Two lists
15//! would be two opinions about one machine and they would disagree eventually.
16//!
17//! # What it does not answer
18//!
19//! Whether the rewrite is worth making. That is the pass's own question and no target has an
20//! opinion about it.
21//!
22//! Whether the values are right. An instruction of a shape this target has can still read the
23//! wrong register, and what checks that is `rucc_regalloc::check` after allocation and the shape
24//! of the pass before it. This says the instruction exists and nothing further.
25
26use crate::operand::OperandDesc;
27
28/// What a pass has to know about a machine to tell an instruction it has from one it does not.
29///
30/// Every question is of a name, for the reason [`crate::BitInsts`] takes names: the pass is in a
31/// pipeline crate, `spec/10-backend.md` section 10.8 says a pipeline crate holds no target
32/// specific code, so an opcode is a name to it and what any name means is the target's answer.
33#[derive(Debug, Clone, Copy)]
34pub struct MachineInsts {
35 /// What a rule file and the machine IR put in front of this target's opcodes, such as `x64.`.
36 pub prefix: &'static str,
37 /// The operands an instruction of that name has, the ones it writes before the ones it reads.
38 ///
39 /// `None` for a name this target does not have, which is the answer that makes a proposal
40 /// naming an opcode nobody described a proposal the target refuses rather than one it lets
41 /// through with no opinion.
42 ///
43 /// The registers an addressing mode names are not in here, for the reason they are not in
44 /// `rucc_target::x86_64::Form::operands`: they are operands and the allocator rewrites them
45 /// like any other, but which operand each of them is belongs to the addressing mode.
46 pub operands: fn(&str) -> Option<&'static [OperandDesc]>,
47 /// Whether an instruction of that name carries an immediate.
48 pub takes_imm: fn(&str) -> bool,
49 /// Whether an instruction of that name carries an addressing mode.
50 pub takes_mem: fn(&str) -> bool,
51 /// Whether an instruction of that name reads or writes memory.
52 ///
53 /// Asked by a pass moving a memory access from where it is to somewhere later, which is safe
54 /// while nothing it passes touches memory at all. Reading and writing are one question rather
55 /// than two, because moving a read past a read is still a reordering of two accesses, and
56 /// machine IR does not say which accesses the program insisted on: a `volatile` read and an
57 /// ordinary one are the same instruction with the same operands by the time a pass here sees
58 /// them.
59 ///
60 /// It is a coarser answer than an alias analysis would give and a target does not have to know
61 /// anything it does not already know to give it. Whether two addresses are the same place is a
62 /// question nothing below selection has an analysis for, so the answer to arriving at one is to
63 /// stop rather than to guess.
64 ///
65 /// A call answers `true` here and that is still not the whole answer about a call. What a call
66 /// does to memory is not in the instruction at all, which is why [`Self::calls`] is a separate
67 /// question and why a pass that has to know what survived one has to ask that as well.
68 pub touches_mem: fn(&str) -> bool,
69 /// Whether an instruction of that name is a call.
70 ///
71 /// Asked by a pass that has to know which registers an instruction leaves alone, because a
72 /// call is the one instruction whose operands do not answer that. The registers a convention
73 /// does not preserve are gone across one, and the ones an argument travelled in are written
74 /// down as reads rather than as writes, so a pass reading the operand vector would be told a
75 /// value in an argument register survives a call it does not survive.
76 pub calls: fn(&str) -> bool,
77 /// What an addressing mode on this target may multiply its index by.
78 ///
79 /// A list rather than a range because the machines that have an index have a handful of
80 /// scales and not an interval, and a pass folding an address into a memory operand has to ask
81 /// whether the number it worked out is one of them.
82 pub scales: &'static [u8],
83}
84
85impl MachineInsts {
86 /// The name with this target's prefix taken off, which is how its own description spells it.
87 ///
88 /// The machine IR holds the prefixed spelling and every table behind these functions is
89 /// written without it, so this is the one place the two spellings meet.
90 #[must_use]
91 pub fn bare<'a>(&self, name: &'a str) -> &'a str {
92 name.strip_prefix(self.prefix).unwrap_or(name)
93 }
94
95 /// Whether this target has an instruction of that name at all.
96 #[must_use]
97 pub fn has(&self, name: &str) -> bool {
98 (self.operands)(self.bare(name)).is_some()
99 }
100
101 /// Whether an instruction of that name is a call on this target.
102 #[must_use]
103 pub fn calls(&self, name: &str) -> bool {
104 (self.calls)(self.bare(name))
105 }
106
107 /// Whether an instruction of that name reads or writes memory on this target.
108 #[must_use]
109 pub fn touches_mem(&self, name: &str) -> bool {
110 (self.touches_mem)(self.bare(name))
111 }
112
113 /// Whether this target multiplies an index by that.
114 #[must_use]
115 pub fn scales(&self, scale: u8) -> bool {
116 self.scales.contains(&scale)
117 }
118}