Skip to main content

rucc_target/
short.rs

1//! The instructions that have a shorter spelling of the same answer, and what the shorter one is.
2//!
3//! Design: `spec/optimizer/37-machine-level-optimization.md` section 37.4.
4//!
5//! There is more than one instruction for putting a number in a register, and on a machine with
6//! variable length instructions they are not the same number of bytes. Putting zero there is the
7//! case worth having a description for: `movl $0, %eax` spells the zero out and is five bytes, and
8//! `xorl %eax, %eax` says it without spelling it and is two. GCC writes the second one everywhere
9//! and rucc writes the first, which over the corpus at `-Os` is twenty thousand instructions
10//! against seven.
11//!
12//! What stops it being a thing the encoder does on its own is that the two are not the same
13//! instruction. The exclusive or writes the condition state and the move does not, so the rewrite
14//! is legal where nothing reads the state before something else writes it and is wrong where
15//! anything does. That is a question about the instructions behind it rather than about the
16//! instruction itself, which is what makes it a pass rather than a choice of encoding, and
17//! [`crate::FlagInsts`] is the description that pass asks.
18//!
19//! The other thing described here is width. A number that is not negative and fits in thirty-two
20//! bits goes into a sixty-four bit register either way, because writing the low half of a register
21//! on this machine clears the high half rather than leaving it alone, and the instruction that
22//! writes the low half is the shorter of the two. So `movq $7, %rax` and `movl $7, %eax` leave the
23//! same number in the same place and are seven bytes and five.
24//!
25//! That one an encoder could do without asking anything, since neither instruction touches the
26//! condition state and the register ends up holding the same number. It is not done there because
27//! an encoder that wrote `movl` where it was handed `movq` would be writing bytes the listing beside
28//! them does not say, and the listing and the bytes saying the same thing is worth more than the
29//! two bytes. Choosing the instruction is this pass's job and spelling the one it chose is the
30//! encoder's.
31//!
32//! The third thing described here is a comparison against zero. `cmpl $0, %eax` asks whether what
33//! is in the register is zero, is above it or is below it, and `testl %eax, %eax` asks the machine
34//! the same three questions of the same register without a constant on the instruction, which is
35//! three bytes against two. Both leave the sign, the zero and the parity of what is in the register,
36//! and both clear the carry and the overflow, so every condition behind either of them reads the
37//! same answer.
38//!
39//! That one is an encoder's rewrite even less than the width one is, since the two instructions are
40//! not even the same length of operand list, and it is here for the same reason: the listing and the
41//! bytes say the same thing because the pass chose the instruction the encoder then spells.
42//!
43//! The fourth thing described here is adding one and taking one away. `addl $1, %eax` is three
44//! bytes, one for the opcode, one saying which register and one for the number, and `incl %eax` is
45//! two, the number being part of the opcode rather than written after it.
46//!
47//! That one is the first thing here that is not free. The addition writes the carry and the
48//! increment leaves it as it found it, so the two are the same only where nothing behind reads a
49//! carry the addition would have set, which is the question [`crate::FlagInsts`] answers and is the
50//! same shape of question the exclusive or asks. It is also the first thing here that is a trade
51//! rather than a saving: an instruction that leaves part of the condition state alone leaves the
52//! next instruction to write that state having to merge with what it left, which costs where the
53//! code is hot and is worth the byte where the goal is size. So a target says which instructions
54//! these are and the pass asks the goal before it writes one, which is what tamnd/rucc#741 is about.
55//!
56//! The fifth thing described here is an address computation that computes no address. The
57//! instruction that works an address out and keeps it takes a whole addressing mode, and an
58//! addressing mode naming one register and adding nothing to it is that register. So `leaq (%rsp),
59//! %rax` puts in `rax` what is already in `rsp`, which is what `movq %rsp, %rax` does.
60//!
61//! The byte comes from the shape of the addressing mode rather than from the opcode. An address
62//! counted from the stack pointer cannot be written without the extra byte that says there is no
63//! index, so the address computation is four bytes where the move is three, and the stack pointer
64//! is the register this shape turns up on, because what makes it is taking the address of the local
65//! that happens to sit at the bottom of the frame. It is not only a byte either: a move between
66//! registers is a thing the machine can do by renaming rather than by computing, and an address
67//! computation is arithmetic whatever the numbers in it are.
68//!
69//! Neither instruction touches the condition state, so unlike the exclusive or and the increment
70//! this one asks nothing about what is behind it. What it asks about is the addressing mode, which
71//! is why the entry names the two opcodes and the pass looks at the mode: a description cannot say
72//! which addressing modes an instruction will turn out to have.
73//!
74//! It is here rather than in the pass for the reason [`crate::FlagInsts`] and
75//! [`crate::BranchInsts`] are here. The pass is in a pipeline crate and `spec/10-backend.md`
76//! section 10.8 says a pipeline crate holds no target-specific code, so what the pass knows about
77//! a machine arrives as a description rather than as a name it says out loud.
78
79/// The shorter spellings this target has.
80#[derive(Debug)]
81pub struct ShortInsts {
82    /// What a rule file and the machine IR put in front of this target's opcodes, such as `x64.`.
83    pub prefix: &'static str,
84    /// Every instruction that puts a constant in a register and has a shorter way of putting zero
85    /// there.
86    pub zeroing: &'static [Zeroed],
87    /// Every instruction that puts a constant in a register and has a narrower one that writes the
88    /// same register and clears the rest of it.
89    pub narrowing: &'static [Narrowed],
90    /// Every instruction that compares a register against a constant and has a shorter one that
91    /// asks the same thing of the register against itself when the constant is zero.
92    pub testing: &'static [Tested],
93    /// Every instruction that adds a constant to a register and has a shorter one that carries the
94    /// number in its opcode, for the one or two numbers that shorter one is about.
95    pub stepping: &'static [Stepped],
96    /// Every instruction that works out an address and keeps it, and the move that says the same
97    /// thing when the address is one register and nothing else.
98    pub copying: &'static [Copied],
99}
100
101impl ShortInsts {
102    /// The shorter way of writing zero into a register, for the instruction of that name.
103    #[must_use]
104    pub fn zeroed(&self, name: &str) -> Option<&'static str> {
105        self.zeroing.iter().find(|entry| entry.name == name).map(|entry| entry.into)
106    }
107
108    /// The narrower instruction that writes the same register, for the instruction of that name.
109    #[must_use]
110    pub fn narrowed(&self, name: &str) -> Option<Narrowed> {
111        self.narrowing.iter().find(|entry| entry.name == name).copied()
112    }
113
114    /// The shorter way of comparing a register against zero, for the instruction of that name.
115    #[must_use]
116    pub fn tested(&self, name: &str) -> Option<&'static str> {
117        self.testing.iter().find(|entry| entry.name == name).map(|entry| entry.into)
118    }
119
120    /// The shorter way of adding that number to a register, for the instruction of that name.
121    ///
122    /// Both halves are the key. One instruction has a shorter spelling for more than one number,
123    /// since an addition of one and a subtraction of one are each other going the other way, and
124    /// which of the two shorter instructions is meant depends on which number was written.
125    #[must_use]
126    pub fn stepped(&self, name: &str, by: i64) -> Option<&'static str> {
127        self.stepping
128            .iter()
129            .find(|entry| entry.name == name && entry.by == by)
130            .map(|entry| entry.into)
131    }
132
133    /// The move that says the same thing as the address computation of that name.
134    ///
135    /// Only the name is the key, because what makes the two the same is the addressing mode rather
136    /// than anything about the opcode, and the addressing mode is not a thing a table of names could
137    /// hold. The pass is what looks at it.
138    #[must_use]
139    pub fn copied(&self, name: &str) -> Option<&'static str> {
140        self.copying.iter().find(|entry| entry.name == name).map(|entry| entry.into)
141    }
142
143    /// Whether the instruction of that name is one of the shorter ones that leaves the carry alone.
144    ///
145    /// Asked of an instruction the walk is looking past rather than of one it is thinking about
146    /// rewriting, so what it is really asking is whether this instruction ends the life of a carry
147    /// something in front of it set. One of these does not, which is the whole of what makes it
148    /// shorter and the whole of what makes the rewrite conditional.
149    #[must_use]
150    pub fn steps(&self, name: &str) -> bool {
151        self.stepping.iter().any(|entry| entry.into == name)
152    }
153}
154
155/// One instruction that writes a constant, and the instruction that writes zero in fewer bytes.
156#[derive(Debug, Clone, Copy, PartialEq, Eq)]
157pub struct Zeroed {
158    /// The opcode that takes the constant.
159    pub name: &'static str,
160    /// The opcode that writes zero into the one register the first one wrote, reading that same
161    /// register twice. It writes the condition state, which is the whole of why this is a
162    /// description and not a rewrite anyone could do without looking around.
163    pub into: &'static str,
164}
165
166/// One instruction that writes a constant, and the narrower one that writes the same register.
167///
168/// Narrower means fewer bytes of the number written out, and on this machine it also means fewer
169/// bytes of instruction: the sixty-four bit move carries a prefix byte saying so and the thirty-two
170/// bit move does not, and a number too wide to sign extend from thirty-two bits is written out
171/// whole where the narrower instruction writes four bytes of it.
172///
173/// It says the same thing only for the numbers the narrower instruction can hold and only on a
174/// machine where writing part of a register clears the rest of it, which is why [`Narrowed::writes`]
175/// is here rather than the pass working the range out from the name.
176#[derive(Debug, Clone, Copy, PartialEq, Eq)]
177pub struct Narrowed {
178    /// The opcode that takes the constant.
179    pub name: &'static str,
180    /// The opcode that takes the same constant in fewer bytes.
181    pub into: &'static str,
182    /// How many bits of the register the narrower opcode writes. The rest of the register is
183    /// cleared rather than left alone, so the two say the same thing for a number that is not
184    /// negative and fits in this many bits, and disagree for every other number.
185    pub writes: u32,
186}
187
188/// One comparison against a constant, and the shorter instruction that asks it against zero.
189///
190/// The shorter one reads the register it is given and reads it again instead of the constant, so it
191/// names one register where the first names a register and a number, and the bytes it saves are the
192/// bytes the number was written in. It says the same thing only for zero: a comparison of a register
193/// against zero and a bitwise and of the register with itself leave the same sign, the same zero and
194/// the same parity, and both leave the carry and the overflow clear.
195#[derive(Debug, Clone, Copy, PartialEq, Eq)]
196pub struct Tested {
197    /// The opcode that takes the constant.
198    pub name: &'static str,
199    /// The opcode that asks the same question of the register alone. It writes the condition state
200    /// exactly as the first one does, which is why nothing about where the state is live is a
201    /// question this rewrite has to ask.
202    pub into: &'static str,
203}
204
205/// One addition of a constant, and the shorter instruction that adds that constant and no other.
206///
207/// Shorter because the number is in the opcode. An addition of a small constant spells the constant
208/// out in a byte after the one saying which register, and an increment says both in the opcode and
209/// the byte after it, so the saving is the byte the number was written in whatever the width.
210///
211/// It says the same thing about the register and not about the condition state. The addition writes
212/// the carry and the increment leaves the carry as it found it, so the two agree wherever nothing
213/// reads a carry between the instruction and the next thing to write one, and disagree everywhere
214/// else. That is a question about the instructions behind rather than about this one, which is what
215/// keeps this a description a pass asks rather than a spelling an encoder chooses.
216#[derive(Debug, Clone, Copy, PartialEq, Eq)]
217pub struct Stepped {
218    /// The opcode that takes the constant.
219    pub name: &'static str,
220    /// The constant it has to be carrying, as the number written on the instruction rather than as
221    /// the number the instruction adds. So a subtraction that takes one away is here as one and
222    /// turns into the instruction that takes one away, and the two agree about the register without
223    /// the pass having to know which of the two opcodes is the subtraction.
224    pub by: i64,
225    /// The opcode that adds that number without writing it out. It writes every part of the
226    /// condition state the first one writes except the carry, which it leaves alone.
227    pub into: &'static str,
228}
229
230/// One address computation, and the move that says the same thing when the address is a register.
231///
232/// The two are the same instruction only for an addressing mode that names a base and nothing else:
233/// no index, no constant added, no symbol and no label, since each of those is arithmetic the move
234/// does not do. That is a question about the instruction in hand rather than about its name, so the
235/// entry names the pair and the pass asks the mode.
236///
237/// Neither of them writes the condition state, which is what keeps this rewrite out of the walk that
238/// the exclusive or and the increment wait on. It saves a byte where the base is one of the
239/// registers an addressing mode cannot name on its own, and it saves the machine an addition
240/// everywhere, since a move between registers is a rename rather than work.
241#[derive(Debug, Clone, Copy, PartialEq, Eq)]
242pub struct Copied {
243    /// The opcode that takes the addressing mode.
244    pub name: &'static str,
245    /// The opcode that moves one register into another, which writes the register the first one
246    /// wrote and reads the one its addressing mode named.
247    pub into: &'static str,
248}