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}
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}