Skip to main content

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    /// Whether the instruction of that name makes the comparison it reads, all in the one
91    /// instruction.
92    ///
93    /// Every entry in [`Self::compares`] that keeps a byte does, and so do the ones whose operand
94    /// is in memory. Those are left out of that table on purpose, because a comparison against
95    /// memory is not one a pass can prove was already made. What they read is still what they
96    /// found, and that is all this asks. False for a name this target does not have.
97    pub compares_itself: fn(&str) -> bool,
98    /// Every instruction that reads the condition state and names which part of it.
99    pub readers: &'static [Reader],
100    /// Every instruction that leaves what a comparison of what it wrote against zero would leave.
101    pub zeroing: &'static [Zeroing],
102}
103
104/// One comparison, and what is left of it when the machine has already made it.
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106pub struct Compare {
107    /// The opcode.
108    pub name: &'static str,
109    /// What it asks, which is the same string for every condition at one width.
110    ///
111    /// Two instructions have made the same comparison when this agrees and the registers and
112    /// constants they read agree. The condition on the front of each of them is not part of it:
113    /// the machine is asked the question once and the two conditions read two answers to it, which
114    /// is exactly the case the pass is here for.
115    pub asks: &'static str,
116    /// What is left of it when the answer is already in the condition state.
117    ///
118    /// [`None`] is nothing at all, which is the entry for a comparison that keeps no answer,
119    /// because the instruction behind it is reading the condition state and the condition state is
120    /// already right. Anything else is an opcode that writes the same answer to the same register
121    /// and makes no comparison, which is what a comparison that keeps a byte becomes.
122    pub kept: Option<&'static str>,
123}
124
125/// One instruction that reads the condition state, and which part of it it reads.
126#[derive(Debug, Clone, Copy, PartialEq, Eq)]
127pub struct Reader {
128    /// The opcode.
129    ///
130    /// A comparison that keeps a byte is one of these as well as a [`Compare`], and what it reads
131    /// is what it just set rather than what it found. That is the same entry either way, since
132    /// what the entry says is which part of the condition state the condition names.
133    pub name: &'static str,
134    /// Which part of it.
135    pub reads: Reads,
136}
137
138/// Which part of what a comparison against zero left a condition is about.
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub enum Reads {
141    /// Whether the value was zero, and nothing else.
142    Zero,
143    /// Where the value sits against zero as a signed number, which is the sign and the overflow.
144    Signed,
145    /// Where it sits as an unsigned number, which is the carry, alone or with the zero.
146    Unsigned,
147    /// The carry as the instruction in front left it, which no comparison is a substitute for.
148    ///
149    /// The other three are parts of the answer to a question, so an instruction that has already
150    /// asked that question can stand in for the one that would have asked it again. An add with
151    /// carry is not reading an answer. It is reading the bit that fell off the end of the addition
152    /// in front of it, and the only thing that leaves that bit is that addition, so nothing this
153    /// pass could put there instead is the same. [`Zeroing::covers`] says no to it for every entry
154    /// there is and will go on saying no to every entry added later, which is what keeps a
155    /// comparison in front of one of these where the program put it.
156    Carry,
157    /// One bit read on its own, the sign, the overflow or the parity, which is what a jump an `asm`
158    /// template writes as `js`, `jo` or `jp` asks about.
159    ///
160    /// No comparison against zero is asked that question, so nothing this pass could put in front
161    /// of one is known to answer it, and [`Zeroing::covers`] says no here for the reason it says no
162    /// to [`Reads::Carry`].
163    Bit,
164}
165
166/// One instruction that leaves behind the comparison of what it wrote against zero.
167#[derive(Debug, Clone, Copy, PartialEq, Eq)]
168pub struct Zeroing {
169    /// The opcode.
170    pub name: &'static str,
171    /// Whether a signed comparison against zero reads what it left and gets the right answer.
172    pub signed: bool,
173    /// Whether an unsigned one does.
174    pub unsigned: bool,
175}
176
177impl FlagInsts {
178    /// The entry for the instruction of that name, if it makes a comparison.
179    #[must_use]
180    pub fn compare(&self, name: &str) -> Option<&'static Compare> {
181        self.compares.iter().find(|entry| entry.name == name)
182    }
183
184    /// Which part of the condition state the instruction of that name reads, if it reads any.
185    #[must_use]
186    pub fn reads(&self, name: &str) -> Option<Reads> {
187        self.readers.iter().find(|entry| entry.name == name).map(|entry| entry.reads)
188    }
189
190    /// Whether what the instruction of that name reads is what it wrote itself.
191    ///
192    /// A comparison that keeps a byte makes a comparison and then reads the answer, both in the one
193    /// instruction, so it is a [`Compare`] and a [`Reader`] at once. What it reads is not what it
194    /// found. An add with carry is the other kind: it reads the bit the instruction in front left,
195    /// and it is a [`Reader`] and no [`Compare`], which is what tells the two apart without a
196    /// second table. The comparisons against memory are the exception that needs one, and
197    /// [`Self::compares_itself`] is where they are named.
198    ///
199    /// The difference is the whole question for a pass asking whether some state is still needed
200    /// behind an instruction. A state arriving at an add with carry is a state that is read. A
201    /// state arriving at a comparison that keeps a byte is a state that is written over before
202    /// anything looks at it, which is to say a state that is dead there, and treating the two the
203    /// same way makes a pass decline rewrites in most of the functions a C program has.
204    #[must_use]
205    pub fn asks_what_it_reads(&self, name: &str) -> bool {
206        self.reads(name).is_some() && (self.compare(name).is_some() || (self.compares_itself)(name))
207    }
208
209    /// The entry for the instruction of that name, if it leaves a comparison against zero.
210    #[must_use]
211    pub fn zeroed(&self, name: &str) -> Option<&'static Zeroing> {
212        self.zeroing.iter().find(|entry| entry.name == name)
213    }
214}
215
216impl Zeroing {
217    /// Whether a condition about that part of the condition state may read what it left.
218    #[must_use]
219    pub const fn covers(&self, reads: Reads) -> bool {
220        match reads {
221            Reads::Zero => true,
222            Reads::Signed => self.signed,
223            Reads::Unsigned => self.unsigned,
224            Reads::Carry | Reads::Bit => false,
225        }
226    }
227}