Skip to main content

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    /// Whether an addressing mode with an index may have a displacement beside it.
84    ///
85    /// x86-64 adds all three in one mode. AArch64 adds a base to a constant or to a register and
86    /// not to both, so a pass that would fold an `add` of two registers into a load that already
87    /// has an offset is proposing an address that machine has no way to write.
88    pub index_and_disp: bool,
89}
90
91impl MachineInsts {
92    /// The name with this target's prefix taken off, which is how its own description spells it.
93    ///
94    /// The machine IR holds the prefixed spelling and every table behind these functions is
95    /// written without it, so this is the one place the two spellings meet.
96    #[must_use]
97    pub fn bare<'a>(&self, name: &'a str) -> &'a str {
98        name.strip_prefix(self.prefix).unwrap_or(name)
99    }
100
101    /// Whether this target has an instruction of that name at all.
102    #[must_use]
103    pub fn has(&self, name: &str) -> bool {
104        (self.operands)(self.bare(name)).is_some()
105    }
106
107    /// Whether an instruction of that name is a call on this target.
108    #[must_use]
109    pub fn calls(&self, name: &str) -> bool {
110        (self.calls)(self.bare(name))
111    }
112
113    /// Whether an instruction of that name reads or writes memory on this target.
114    #[must_use]
115    pub fn touches_mem(&self, name: &str) -> bool {
116        (self.touches_mem)(self.bare(name))
117    }
118
119    /// Whether this target multiplies an index by that.
120    #[must_use]
121    pub fn scales(&self, scale: u8) -> bool {
122        self.scales.contains(&scale)
123    }
124}
125
126/// What an address constructor's arguments are.
127///
128/// An addressing mode is an argument to an instruction rather than an instruction, and a rule
129/// file writes one as a term so that a rule can say which registers go where. The selector has
130/// to turn that term into a machine IR memory operand, and what each constructor's arguments
131/// mean is the same kind of target fact as an instruction's operands, so it is written here
132/// rather than in the selector.
133///
134/// The names are shared. A rule file writes an address with whichever of these its machine has,
135/// and a target with only some of them lists which, so a selector reads a constructor the same
136/// way whichever machine it is selecting for.
137///
138/// The scale and the displacement are arguments rather than part of the name because each is a
139/// number the rule matched and the machine encodes it as a number. There is none with a symbol
140/// yet, because the rules that would need one are the ones about a global and those are not
141/// written.
142///
143/// What the arguments mean is the whole of what tells these apart, and there is deliberately no
144/// predicate here that answers half the question: the same register is a base in one of these
145/// and an index in another, and the same constant is a scale in one and a displacement in
146/// another, so anything building an address out of one has to look at which it is.
147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
148pub enum Address {
149    /// A base register, an index register and a scale, in that order.
150    BaseIndexScale,
151    /// An index register and a scale, which is an address with nothing to add it to.
152    IndexScale,
153    /// A base register on its own, which is what a pointer already in a register is.
154    Base,
155    /// A base register and a constant added to it, which is every field of a structure and
156    /// every local reached through a frame pointer.
157    BaseOffset,
158}