rucc_target/regs.rs
1//! The register file: what registers a target has, and what classes they fall into.
2//!
3//! Design: `spec/10-backend.md` section 10.8.
4//!
5//! A register file is data rather than code, which is the same claim the rest of this crate
6//! makes and the one `M10` puts a number on. A class is a set of registers that an operand of
7//! that class may be assigned to, and a physical register is its number inside its class, so
8//! the allocator works in dense small integers and only the printer and the parser ever deal in
9//! names.
10//!
11//! The file lives here rather than in `rucc-mir` because more than one thing reads it. The
12//! machine IR needs it to print, the allocator needs the set it may assign from, and the ABI
13//! description needs to name the registers arguments arrive in. All three are above this crate,
14//! and the alternative is the register file living in whichever of them happens to be lowest,
15//! which is how a layering ends up describing itself as historical.
16//!
17//! Names are unique across the whole file, not merely inside a class. That is what lets a
18//! register be written `$rax` in a dump rather than `$gpr.0`, and it is a real constraint on a
19//! target that gives one register two classes: it has to say which class it is in, or use two
20//! names. [`RegFile::duplicate`] is what a target's own test asks to find out.
21
22use std::fmt;
23
24/// One class of registers, and the registers in it.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub struct ClassInfo {
27 /// What the class is called in a dump, such as `gpr`.
28 pub name: &'static str,
29 /// How wide one of its registers is, in bits.
30 pub bits: u32,
31 /// The registers, in the order their numbers run, without the sigil a dump writes.
32 pub regs: &'static [&'static str],
33}
34
35/// Which class a register or an operand belongs to.
36///
37/// A number into the file's classes rather than a name, because it is on every operand of every
38/// instruction and it is compared far more often than it is printed.
39#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
40pub struct RegClass(u8);
41
42impl RegClass {
43 /// The class with that number.
44 #[must_use]
45 pub const fn new(number: u8) -> Self {
46 Self(number)
47 }
48
49 /// Its number, which is what indexes the file.
50 #[must_use]
51 pub const fn number(self) -> u8 {
52 self.0
53 }
54}
55
56/// One physical register, as its number inside its class.
57///
58/// The class is not in here. An operand carries its class already, and a fixed-register
59/// constraint is a constraint on an operand, so repeating the class would be a second copy of
60/// something that can disagree with the first.
61#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
62pub struct PhysReg(u8);
63
64impl PhysReg {
65 /// The register with that number in its class.
66 #[must_use]
67 pub const fn new(number: u8) -> Self {
68 Self(number)
69 }
70
71 /// Its number inside its class.
72 #[must_use]
73 pub const fn number(self) -> u8 {
74 self.0
75 }
76}
77
78/// Every register a target has.
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub struct RegFile {
81 classes: &'static [ClassInfo],
82}
83
84impl RegFile {
85 /// The file of a target whose registers nothing has described yet.
86 ///
87 /// A target reaches 1.0 with a real one. Until it has one, the honest answer to what
88 /// registers it has is that nobody has written them down, and that is a file with no
89 /// classes in it rather than a panic or a plausible guess.
90 pub const EMPTY: Self = Self::new(&[]);
91
92 /// A file made of those classes, numbered in the order they are given.
93 #[must_use]
94 pub const fn new(classes: &'static [ClassInfo]) -> Self {
95 Self { classes }
96 }
97
98 /// Its classes, each with the number it is known by.
99 pub fn classes(&self) -> impl Iterator<Item = (RegClass, &'static ClassInfo)> + use<> {
100 self.classes.iter().enumerate().map(|(number, info)| (RegClass::new(number as u8), info))
101 }
102
103 /// What is in one class.
104 #[must_use]
105 pub fn class(&self, class: RegClass) -> Option<&'static ClassInfo> {
106 self.classes.get(usize::from(class.number()))
107 }
108
109 /// The class of that name, such as `gpr`.
110 #[must_use]
111 pub fn class_named(&self, name: &str) -> Option<RegClass> {
112 self.classes().find(|(_, info)| info.name == name).map(|(class, _)| class)
113 }
114
115 /// How many registers are in a class, which is one past the largest number in it.
116 #[must_use]
117 pub fn len(&self, class: RegClass) -> usize {
118 self.class(class).map_or(0, |info| info.regs.len())
119 }
120
121 /// Whether the file has no classes at all, which is a target that has not described one.
122 #[must_use]
123 pub fn is_empty(&self) -> bool {
124 self.classes.is_empty()
125 }
126
127 /// What one register is called.
128 #[must_use]
129 pub fn name(&self, class: RegClass, reg: PhysReg) -> Option<&'static str> {
130 self.class(class)?.regs.get(usize::from(reg.number())).copied()
131 }
132
133 /// The register of that name, and the class it is in.
134 ///
135 /// The name is written without the sigil, so `rax` rather than `$rax`.
136 #[must_use]
137 pub fn reg_named(&self, name: &str) -> Option<(RegClass, PhysReg)> {
138 for (class, info) in self.classes() {
139 if let Some(number) = info.regs.iter().position(|®| reg == name) {
140 return Some((class, PhysReg::new(number as u8)));
141 }
142 }
143 None
144 }
145
146 /// A name this file gives to two registers, if it gives one to two.
147 ///
148 /// Reading a dump back needs every name to say which register it means, and a target that
149 /// breaks that produces text that cannot be parsed rather than an error at the point of the
150 /// mistake. So every target's own test asks this, which is why it is here and public.
151 #[must_use]
152 pub fn duplicate(&self) -> Option<&'static str> {
153 let mut seen: Vec<&'static str> = Vec::new();
154 for (_, info) in self.classes() {
155 for ® in info.regs {
156 if seen.contains(®) {
157 return Some(reg);
158 }
159 seen.push(reg);
160 }
161 }
162 None
163 }
164}
165
166/// Which registers a calling convention gives which job.
167///
168/// This is the second half of a target description and it is separate from [`RegFile`] because
169/// the two do not vary together. x86-64 has one register file and two conventions over it, and
170/// they disagree about nearly everything below: `rdi` is where the first argument arrives on
171/// SysV and a register a callee has to preserve on Windows, and a Windows caller reserves
172/// thirty two bytes below the call that a SysV caller does not.
173///
174/// The allocation order is here rather than on a class because it is a consequence of what a
175/// call clobbers. A value that does not live across a call belongs in a register the callee is
176/// free to destroy, because putting it in a preserved one costs a push and a pop in the
177/// prologue of whichever function ends up owning it.
178///
179/// Every register named here is a register of the file the same target describes, and each list
180/// is in the order the convention uses them, so the fourth integer argument is `int_args[3]` and
181/// nothing has to count.
182#[derive(Debug, Clone, Copy, PartialEq, Eq)]
183pub struct CallRegs {
184 /// The class the general purpose registers named here are in.
185 ///
186 /// A register is a number inside its class, so a list of them says nothing about which
187 /// registers they are without this. Everything else could get the class from the operand it
188 /// came off, and a frame cannot, because a saved register is not an operand of anything.
189 pub int_class: RegClass,
190 /// The class the vector registers named here are in.
191 pub sse_class: RegClass,
192 /// The general purpose registers integer arguments arrive in, in order.
193 pub int_args: &'static [PhysReg],
194 /// The vector registers floating point arguments arrive in, in order.
195 ///
196 /// Whether an argument's position counts against both lists or only against its own is the
197 /// convention's own answer and is not recorded here: SysV counts each separately, so a
198 /// `double` after six integers is still in `xmm0`, and Windows counts one position for
199 /// both, so a `double` in the third position is in `xmm2` and `r8` is skipped.
200 pub sse_args: &'static [PhysReg],
201 /// The general purpose registers an integer return value comes back in.
202 pub int_returns: &'static [PhysReg],
203 /// The vector registers a floating point return value comes back in.
204 pub sse_returns: &'static [PhysReg],
205 /// The x87 registers a `long double` comes back in, which is empty on a target whose
206 /// `long double` is a `double`.
207 pub x87_returns: &'static [PhysReg],
208 /// The general purpose registers a call leaves alone, so a value in one survives it.
209 pub int_saved: &'static [PhysReg],
210 /// The vector registers a call leaves alone, which is none of them on SysV.
211 pub sse_saved: &'static [PhysReg],
212 /// The general purpose registers the allocator may hand out, in the order it prefers them.
213 ///
214 /// The stack pointer is never in this list, and neither is the frame pointer, which a
215 /// target could allocate when nothing needs a frame and which nothing here does yet.
216 pub int_order: &'static [PhysReg],
217 /// The vector registers the allocator may hand out, in the order it prefers them.
218 pub sse_order: &'static [PhysReg],
219 /// The stack pointer.
220 pub stack_pointer: PhysReg,
221 /// The frame pointer, which is the register a prologue puts the old stack pointer in.
222 pub frame_pointer: PhysReg,
223 /// Where a variadic call says how many vector registers it passed arguments in, when the
224 /// convention makes it say.
225 ///
226 /// SysV puts the count in `al` and a variadic callee reads it to decide whether to save the
227 /// vector argument registers at all, which is what makes a call to `printf` with no
228 /// floating point argument cheap.
229 pub vector_count: Option<PhysReg>,
230 /// How many bytes below the stack pointer a leaf function may use without moving it.
231 ///
232 /// A hundred and twenty eight on SysV and nothing on Windows. It is nothing in kernel code
233 /// on either, because an interrupt handler runs on the interrupted stack and writes over
234 /// exactly this, which is what `-mno-red-zone` is for.
235 pub red_zone: u32,
236 /// How many bytes a caller reserves below the call for the callee to spill its register
237 /// arguments into, which is thirty two on Windows and nothing on SysV.
238 pub shadow: u32,
239 /// What the stack pointer has to be a multiple of at the instruction that makes a call.
240 ///
241 /// Sixteen on every convention here, and it is a real obligation rather than a preference,
242 /// because a callee is entitled to use an aligned vector store on its own frame and gets a
243 /// fault rather than a wrong answer when a caller got this wrong.
244 pub stack_align: u32,
245 /// How many bytes the call instruction itself pushes before the callee starts running.
246 ///
247 /// Eight on x86-64, where the return address is on the stack, and nothing on a machine that
248 /// leaves it in a register. It is what makes the stack pointer misaligned on entry by
249 /// exactly one word, which every frame layout has to undo.
250 pub return_address: u32,
251 /// How many bytes one general purpose register takes when it is saved on the stack.
252 pub word: u32,
253}
254
255impl CallRegs {
256 /// Whether a call preserves that general purpose register.
257 #[must_use]
258 pub fn preserves_int(&self, reg: PhysReg) -> bool {
259 self.int_saved.contains(®)
260 }
261
262 /// Whether a call preserves that vector register.
263 #[must_use]
264 pub fn preserves_sse(&self, reg: PhysReg) -> bool {
265 self.sse_saved.contains(®)
266 }
267}
268
269impl fmt::Display for RegFile {
270 /// The file as a dump reads it, one class to a line.
271 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272 for (_, info) in self.classes() {
273 writeln!(f, "class {} : i{} = {}", info.name, info.bits, info.regs.join(", "))?;
274 }
275 Ok(())
276 }
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282
283 static GPR: [&str; 3] = ["rax", "rcx", "rdx"];
284 static XMM: [&str; 2] = ["xmm0", "xmm1"];
285 static CLASSES: [ClassInfo; 2] = [
286 ClassInfo { name: "gpr", bits: 64, regs: &GPR },
287 ClassInfo { name: "xmm", bits: 128, regs: &XMM },
288 ];
289 static FILE: RegFile = RegFile::new(&CLASSES);
290
291 #[test]
292 fn a_class_is_found_by_its_name() {
293 let gpr = FILE.class_named("gpr").expect("the file has a gpr class");
294 assert_eq!(FILE.len(gpr), 3);
295 assert_eq!(FILE.class(gpr).map(|info| info.bits), Some(64));
296 assert_eq!(FILE.class_named("vec"), None);
297 }
298
299 #[test]
300 fn a_register_is_found_by_its_name_and_names_itself_back() {
301 let (class, reg) = FILE.reg_named("xmm1").expect("the file has xmm1");
302 assert_eq!(FILE.class(class).map(|info| info.name), Some("xmm"));
303 assert_eq!(reg.number(), 1);
304 assert_eq!(FILE.name(class, reg), Some("xmm1"));
305 assert_eq!(FILE.reg_named("r15"), None);
306 }
307
308 #[test]
309 fn a_number_past_the_end_of_a_class_has_no_name() {
310 let gpr = FILE.class_named("gpr").expect("the file has a gpr class");
311 assert_eq!(FILE.name(gpr, PhysReg::new(3)), None);
312 assert_eq!(FILE.name(RegClass::new(7), PhysReg::new(0)), None);
313 }
314
315 #[test]
316 fn a_file_that_names_two_registers_alike_says_so() {
317 assert_eq!(FILE.duplicate(), None);
318 static BOTH: [ClassInfo; 2] = [
319 ClassInfo { name: "gpr", bits: 64, regs: &GPR },
320 ClassInfo { name: "shadow", bits: 64, regs: &GPR },
321 ];
322 assert_eq!(RegFile::new(&BOTH).duplicate(), Some("rax"));
323 }
324
325 #[test]
326 fn the_file_prints_one_class_to_a_line() {
327 assert_eq!(
328 FILE.to_string(),
329 "class gpr : i64 = rax, rcx, rdx\nclass xmm : i128 = xmm0, xmm1\n"
330 );
331 }
332}