rucc_target/flags.rs
1//! What each instruction leaves in the condition state, and which comparisons ask the same thing.
2//!
3//! Design: `spec/optimizer/37-machine-level-optimization.md` section 37.4.
4//!
5//! A comparison on this kind of machine computes nothing. What it does is set a few bits nobody
6//! named, and the instruction behind it reads them. So a comparison whose bits are already the
7//! bits that are there is an instruction that could not be observed to have run, and taking it out
8//! is the whole of this. There are two ways for the bits to already be there. The same comparison
9//! was made a few instructions ago and nothing has disturbed it since, which is what a program
10//! that asks whether something is zero and then whether it is not comes out as. Or the comparison
11//! is against zero and the value it is about was worked out by arithmetic, which set the same bits
12//! on its way past.
13//!
14//! It is here rather than in the pass for the reason [`crate::FrameInsts`] and
15//! [`crate::BranchInsts`] are here. The pass is in a pipeline crate and `spec/10-backend.md`
16//! section 10.8 says a pipeline crate holds no target-specific code, so what the pass knows about
17//! a machine arrives as a description rather than as a name it says out loud.
18//!
19//! # Why the second case is not every condition
20//!
21//! A comparison against zero leaves more than the answer to it. `cmpl $0, %eax` says whether the
22//! register is zero, and it says the register's sign, and it says that nothing carried and nothing
23//! overflowed, because subtracting zero from a number cannot do either. An instruction that merely
24//! happens to have written the register agrees about some of that and not all of it. `andl` agrees
25//! about all of it: the machine clears carry and overflow after one, and sets the zero and sign
26//! bits from what it wrote, which is what the comparison would have set them from. `subl` agrees
27//! about the zero bit and about nothing else, because a subtraction that overflowed says so and
28//! the comparison would have said it did not, and a condition built out of the sign and the
29//! overflow together then reads two bits that no longer belong to each other.
30//!
31//! So a [`Zeroing`] says which of the three groups of conditions it is good for, [`Reads`] is
32//! which group a condition belongs to, and a [`Reader`] is an instruction that names one. The zero
33//! group is the one every entry is good for, since every instruction here that writes the
34//! condition state at all sets the zero bit from what it wrote.
35//!
36//! The first case needs none of that. Two instructions that made the same comparison of the same
37//! values left the same bits, all of them, so what may read them is not a question.
38//!
39//! # The instruction that reads the state and is not a condition
40//!
41//! An add with carry, and the subtract that goes with it, which arrive from an `asm` template
42//! rather than from a rule. They are here for the half of this description that is not about
43//! finding a comparison: the list of what reads the state is also what the scheduler keeps in
44//! order, so an instruction missing from it is one the scheduler is free to move a comparison in
45//! front of. What they read is [`Reads::Carry`], which is a fourth group with nothing in the table
46//! good for it, so naming them here can only stop a rewrite and never allow one.
47//!
48//! # The two kinds of read
49//!
50//! An add with carry reads a bit the instruction in front of it left. A comparison that keeps a
51//! byte reads a bit it set itself a moment earlier, in the same instruction. Both are a [`Reader`]
52//! and the entry says the same thing about both, because what the entry is for is naming which
53//! part of the state a condition is about, and that is the same question either way.
54//!
55//! It is not the same question for a pass asking whether a state is still needed. State arriving at
56//! an add with carry is read. State arriving at a comparison that keeps a byte is written over
57//! before anything looks at it, so it is dead there, exactly as it would be at a plain comparison.
58//! [`FlagInsts::asks_what_it_reads`] is the question, and the answer is already in the two tables:
59//! the second kind makes a comparison and the first kind does not.
60//!
61//! # What is not a [`Zeroing`]
62//!
63//! A shift, because a shift by zero leaves the condition state exactly as it found it, and the
64//! count is in a register often enough that the compiler cannot tell. A multiply, because the
65//! machine leaves the zero bit undefined after one. An increment and a decrement, because they
66//! leave carry alone rather than clearing it. None of those is a shape a program runs into often,
67//! and each of them is a way to be quietly wrong, so the table says nothing about them and the
68//! pass believes the table.
69
70/// What a pass has to know about a machine to find a comparison the machine has already made.
71#[derive(Debug, Clone, Copy)]
72pub struct FlagInsts {
73 /// What a rule file and the machine IR put in front of this target's opcodes, such as `x64.`.
74 pub prefix: &'static str,
75 /// How many bits of the operand at that index the instruction of that name uses.
76 ///
77 /// The same question [`crate::BitInsts`] asks and the same answer, because two instructions
78 /// that agree about a register's low half and not about the rest of it have not made the same
79 /// comparison. `None` means the description does not name that operand, which the pass reads
80 /// as not knowing and so as not the same.
81 pub width: fn(&str, u8) -> Option<u32>,
82 /// Whether the instruction of that name leaves the condition state other than it found it.
83 ///
84 /// True for a name this target does not have, since an instruction nothing knows anything
85 /// about is one that may have done anything. It is the answer that makes the pass find less
86 /// rather than the one that makes it wrong.
87 pub writes: fn(&str) -> bool,
88 /// Every instruction that makes a comparison, whether or not it keeps the answer.
89 pub compares: &'static [Compare],
90 /// Every instruction that reads the condition state and names which part of it.
91 pub readers: &'static [Reader],
92 /// Every instruction that leaves what a comparison of what it wrote against zero would leave.
93 pub zeroing: &'static [Zeroing],
94}
95
96/// One comparison, and what is left of it when the machine has already made it.
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub struct Compare {
99 /// The opcode.
100 pub name: &'static str,
101 /// What it asks, which is the same string for every condition at one width.
102 ///
103 /// Two instructions have made the same comparison when this agrees and the registers and
104 /// constants they read agree. The condition on the front of each of them is not part of it:
105 /// the machine is asked the question once and the two conditions read two answers to it, which
106 /// is exactly the case the pass is here for.
107 pub asks: &'static str,
108 /// What is left of it when the answer is already in the condition state.
109 ///
110 /// [`None`] is nothing at all, which is the entry for a comparison that keeps no answer,
111 /// because the instruction behind it is reading the condition state and the condition state is
112 /// already right. Anything else is an opcode that writes the same answer to the same register
113 /// and makes no comparison, which is what a comparison that keeps a byte becomes.
114 pub kept: Option<&'static str>,
115}
116
117/// One instruction that reads the condition state, and which part of it it reads.
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119pub struct Reader {
120 /// The opcode.
121 ///
122 /// A comparison that keeps a byte is one of these as well as a [`Compare`], and what it reads
123 /// is what it just set rather than what it found. That is the same entry either way, since
124 /// what the entry says is which part of the condition state the condition names.
125 pub name: &'static str,
126 /// Which part of it.
127 pub reads: Reads,
128}
129
130/// Which part of what a comparison against zero left a condition is about.
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132pub enum Reads {
133 /// Whether the value was zero, and nothing else.
134 Zero,
135 /// Where the value sits against zero as a signed number, which is the sign and the overflow.
136 Signed,
137 /// Where it sits as an unsigned number, which is the carry, alone or with the zero.
138 Unsigned,
139 /// The carry as the instruction in front left it, which no comparison is a substitute for.
140 ///
141 /// The other three are parts of the answer to a question, so an instruction that has already
142 /// asked that question can stand in for the one that would have asked it again. An add with
143 /// carry is not reading an answer. It is reading the bit that fell off the end of the addition
144 /// in front of it, and the only thing that leaves that bit is that addition, so nothing this
145 /// pass could put there instead is the same. [`Zeroing::covers`] says no to it for every entry
146 /// there is and will go on saying no to every entry added later, which is what keeps a
147 /// comparison in front of one of these where the program put it.
148 Carry,
149}
150
151/// One instruction that leaves behind the comparison of what it wrote against zero.
152#[derive(Debug, Clone, Copy, PartialEq, Eq)]
153pub struct Zeroing {
154 /// The opcode.
155 pub name: &'static str,
156 /// Whether a signed comparison against zero reads what it left and gets the right answer.
157 pub signed: bool,
158 /// Whether an unsigned one does.
159 pub unsigned: bool,
160}
161
162impl FlagInsts {
163 /// The entry for the instruction of that name, if it makes a comparison.
164 #[must_use]
165 pub fn compare(&self, name: &str) -> Option<&'static Compare> {
166 self.compares.iter().find(|entry| entry.name == name)
167 }
168
169 /// Which part of the condition state the instruction of that name reads, if it reads any.
170 #[must_use]
171 pub fn reads(&self, name: &str) -> Option<Reads> {
172 self.readers.iter().find(|entry| entry.name == name).map(|entry| entry.reads)
173 }
174
175 /// Whether what the instruction of that name reads is what it wrote itself.
176 ///
177 /// A comparison that keeps a byte makes a comparison and then reads the answer, both in the one
178 /// instruction, so it is a [`Compare`] and a [`Reader`] at once. What it reads is not what it
179 /// found. An add with carry is the other kind: it reads the bit the instruction in front left,
180 /// and it is a [`Reader`] and no [`Compare`], which is what tells the two apart without a
181 /// second table.
182 ///
183 /// The difference is the whole question for a pass asking whether some state is still needed
184 /// behind an instruction. A state arriving at an add with carry is a state that is read. A
185 /// state arriving at a comparison that keeps a byte is a state that is written over before
186 /// anything looks at it, which is to say a state that is dead there, and treating the two the
187 /// same way makes a pass decline rewrites in most of the functions a C program has.
188 #[must_use]
189 pub fn asks_what_it_reads(&self, name: &str) -> bool {
190 self.reads(name).is_some() && self.compare(name).is_some()
191 }
192
193 /// The entry for the instruction of that name, if it leaves a comparison against zero.
194 #[must_use]
195 pub fn zeroed(&self, name: &str) -> Option<&'static Zeroing> {
196 self.zeroing.iter().find(|entry| entry.name == name)
197 }
198}
199
200impl Zeroing {
201 /// Whether a condition about that part of the condition state may read what it left.
202 #[must_use]
203 pub const fn covers(&self, reads: Reads) -> bool {
204 match reads {
205 Reads::Zero => true,
206 Reads::Signed => self.signed,
207 Reads::Unsigned => self.unsigned,
208 Reads::Carry => false,
209 }
210 }
211}