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 /// Whether the allocator may put a value in one of these.
34 ///
35 /// True for every class a target means the allocator to use, which is nearly all of them.
36 /// False says the registers exist and are named and are not somewhere a value may be told to
37 /// live, so a virtual register of this class is a mistake at the point it was made rather than
38 /// a value the allocator has nowhere to put.
39 ///
40 /// The x87 stack is the case this exists for, and it is worth the sentence because it is not
41 /// the usual reason a register is unavailable. `rsp` is unavailable because it has a job;
42 /// `st0` is unavailable because the machine addresses it as a stack, so which register a name
43 /// means depends on how many values are on the stack at the time, and an allocator that hands
44 /// out a name has no way to say that. So nothing allocates from it, an eighty bit value lives
45 /// in a stack slot between one operation and the next, and the stack is empty on both sides of
46 /// every group of instructions that uses it. See `spec/10-backend.md` section 10.8, which says
47 /// what a group is and why nothing the allocator inserts can get into the middle of one, and
48 /// tamnd/rucc#540.
49 ///
50 /// A register in such a class can still be named, which is the whole reason the class is
51 /// described at all: a `long double` comes back from a call in `st0` and the convention has to
52 /// be able to say so.
53 pub allocatable: bool,
54}
55
56/// Which class a register or an operand belongs to.
57///
58/// A number into the file's classes rather than a name, because it is on every operand of every
59/// instruction and it is compared far more often than it is printed.
60#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
61pub struct RegClass(u8);
62
63impl RegClass {
64 /// The class with that number.
65 #[must_use]
66 pub const fn new(number: u8) -> Self {
67 Self(number)
68 }
69
70 /// Its number, which is what indexes the file.
71 #[must_use]
72 pub const fn number(self) -> u8 {
73 self.0
74 }
75}
76
77/// One physical register, as its number inside its class.
78///
79/// The class is not in here. An operand carries its class already, and a fixed-register
80/// constraint is a constraint on an operand, so repeating the class would be a second copy of
81/// something that can disagree with the first.
82#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
83pub struct PhysReg(u8);
84
85impl PhysReg {
86 /// The register with that number in its class.
87 #[must_use]
88 pub const fn new(number: u8) -> Self {
89 Self(number)
90 }
91
92 /// Its number inside its class.
93 #[must_use]
94 pub const fn number(self) -> u8 {
95 self.0
96 }
97}
98
99/// Every register a target has.
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101pub struct RegFile {
102 classes: &'static [ClassInfo],
103}
104
105impl RegFile {
106 /// The file of a target whose registers nothing has described yet.
107 ///
108 /// A target reaches 1.0 with a real one. Until it has one, the honest answer to what
109 /// registers it has is that nobody has written them down, and that is a file with no
110 /// classes in it rather than a panic or a plausible guess.
111 pub const EMPTY: Self = Self::new(&[]);
112
113 /// A file made of those classes, numbered in the order they are given.
114 #[must_use]
115 pub const fn new(classes: &'static [ClassInfo]) -> Self {
116 Self { classes }
117 }
118
119 /// Its classes, each with the number it is known by.
120 pub fn classes(&self) -> impl Iterator<Item = (RegClass, &'static ClassInfo)> + use<> {
121 self.classes.iter().enumerate().map(|(number, info)| (RegClass::new(number as u8), info))
122 }
123
124 /// What is in one class.
125 #[must_use]
126 pub fn class(&self, class: RegClass) -> Option<&'static ClassInfo> {
127 self.classes.get(usize::from(class.number()))
128 }
129
130 /// The class of that name, such as `gpr`.
131 #[must_use]
132 pub fn class_named(&self, name: &str) -> Option<RegClass> {
133 self.classes().find(|(_, info)| info.name == name).map(|(class, _)| class)
134 }
135
136 /// Whether the allocator may put a value in that class, which is [`ClassInfo::allocatable`].
137 ///
138 /// A class the file does not have is not one either, which is the same answer as a class
139 /// nothing allocates from and is the one that keeps a caller from having to say what it means
140 /// by a class number the target never gave out.
141 #[must_use]
142 pub fn allocatable(&self, class: RegClass) -> bool {
143 self.class(class).is_some_and(|info| info.allocatable)
144 }
145
146 /// How many registers are in a class, which is one past the largest number in it.
147 #[must_use]
148 pub fn len(&self, class: RegClass) -> usize {
149 self.class(class).map_or(0, |info| info.regs.len())
150 }
151
152 /// Whether the file has no classes at all, which is a target that has not described one.
153 #[must_use]
154 pub fn is_empty(&self) -> bool {
155 self.classes.is_empty()
156 }
157
158 /// What one register is called.
159 #[must_use]
160 pub fn name(&self, class: RegClass, reg: PhysReg) -> Option<&'static str> {
161 self.class(class)?.regs.get(usize::from(reg.number())).copied()
162 }
163
164 /// The register of that name, and the class it is in.
165 ///
166 /// The name is written without the sigil, so `rax` rather than `$rax`.
167 #[must_use]
168 pub fn reg_named(&self, name: &str) -> Option<(RegClass, PhysReg)> {
169 for (class, info) in self.classes() {
170 if let Some(number) = info.regs.iter().position(|®| reg == name) {
171 return Some((class, PhysReg::new(number as u8)));
172 }
173 }
174 None
175 }
176
177 /// A name this file gives to two registers, if it gives one to two.
178 ///
179 /// Reading a dump back needs every name to say which register it means, and a target that
180 /// breaks that produces text that cannot be parsed rather than an error at the point of the
181 /// mistake. So every target's own test asks this, which is why it is here and public.
182 #[must_use]
183 pub fn duplicate(&self) -> Option<&'static str> {
184 let mut seen: Vec<&'static str> = Vec::new();
185 for (_, info) in self.classes() {
186 for ® in info.regs {
187 if seen.contains(®) {
188 return Some(reg);
189 }
190 seen.push(reg);
191 }
192 }
193 None
194 }
195}
196
197/// Which registers a calling convention gives which job.
198///
199/// This is the second half of a target description and it is separate from [`RegFile`] because
200/// the two do not vary together. x86-64 has one register file and two conventions over it, and
201/// they disagree about nearly everything below: `rdi` is where the first argument arrives on
202/// SysV and a register a callee has to preserve on Windows, and a Windows caller reserves
203/// thirty two bytes below the call that a SysV caller does not.
204///
205/// The allocation order is here rather than on a class because it is a consequence of what a
206/// call clobbers. A value that does not live across a call belongs in a register the callee is
207/// free to destroy, because putting it in a preserved one costs a push and a pop in the
208/// prologue of whichever function ends up owning it.
209///
210/// Every register named here is a register of the file the same target describes, and each list
211/// is in the order the convention uses them, so the fourth integer argument is `int_args[3]` and
212/// nothing has to count.
213#[derive(Debug, Clone, Copy, PartialEq, Eq)]
214pub struct CallRegs {
215 /// The class the general purpose registers named here are in.
216 ///
217 /// A register is a number inside its class, so a list of them says nothing about which
218 /// registers they are without this. Everything else could get the class from the operand it
219 /// came off, and a frame cannot, because a saved register is not an operand of anything.
220 pub int_class: RegClass,
221 /// The class the vector registers named here are in.
222 pub sse_class: RegClass,
223 /// The general purpose registers integer arguments arrive in, in order.
224 pub int_args: &'static [PhysReg],
225 /// The vector registers floating point arguments arrive in, in order.
226 ///
227 /// Whether an argument's position counts against both lists or only against its own is
228 /// [`CallRegs::shared_positions`].
229 pub sse_args: &'static [PhysReg],
230 /// Whether an argument's position counts against both argument lists or only against its own.
231 ///
232 /// False on SysV, which counts each separately, so a `double` after six integers is still in
233 /// `xmm0`. True on Windows, which counts one position for both, so a `double` in the third
234 /// position is in `xmm2` and `r8` is skipped.
235 pub shared_positions: bool,
236 /// The general purpose registers an integer return value comes back in.
237 pub int_returns: &'static [PhysReg],
238 /// The vector registers a floating point return value comes back in.
239 pub sse_returns: &'static [PhysReg],
240 /// The x87 registers a `long double` comes back in, which is empty on a target whose
241 /// `long double` is a `double`.
242 pub x87_returns: &'static [PhysReg],
243 /// The general purpose registers a call leaves alone, so a value in one survives it.
244 pub int_saved: &'static [PhysReg],
245 /// The vector registers a call leaves alone, which is none of them on SysV.
246 pub sse_saved: &'static [PhysReg],
247 /// The general purpose registers the allocator may hand out, in the order it prefers them.
248 ///
249 /// The stack pointer is never in this list, and neither is the frame pointer, which a
250 /// target could allocate when nothing needs a frame and which nothing here does yet.
251 pub int_order: &'static [PhysReg],
252 /// The vector registers the allocator may hand out, in the order it prefers them.
253 pub sse_order: &'static [PhysReg],
254 /// The stack pointer.
255 pub stack_pointer: PhysReg,
256 /// The frame pointer, which is the register a prologue puts the old stack pointer in.
257 pub frame_pointer: PhysReg,
258 /// Where a variadic call says how many vector registers it passed arguments in, when the
259 /// convention makes it say.
260 ///
261 /// SysV puts the count in `al` and a variadic callee reads it to decide whether to save the
262 /// vector argument registers at all, which is what makes a call to `printf` with no
263 /// floating point argument cheap.
264 pub vector_count: Option<PhysReg>,
265 /// How many bytes below the stack pointer a leaf function may use without moving it.
266 ///
267 /// A hundred and twenty eight on SysV and nothing on Windows. It is nothing in kernel code
268 /// on either, because an interrupt handler runs on the interrupted stack and writes over
269 /// exactly this, which is what `-mno-red-zone` is for.
270 pub red_zone: u32,
271 /// How many bytes a caller reserves below the call for the callee to spill its register
272 /// arguments into, which is thirty two on Windows and nothing on SysV.
273 pub shadow: u32,
274 /// What the stack pointer has to be a multiple of at the instruction that makes a call.
275 ///
276 /// Sixteen on every convention here, and it is a real obligation rather than a preference,
277 /// because a callee is entitled to use an aligned vector store on its own frame and gets a
278 /// fault rather than a wrong answer when a caller got this wrong.
279 pub stack_align: u32,
280 /// How many bytes the call instruction itself pushes before the callee starts running.
281 ///
282 /// Eight on x86-64, where the return address is on the stack, and nothing on a machine that
283 /// leaves it in a register. It is what makes the stack pointer misaligned on entry by
284 /// exactly one word, which every frame layout has to undo.
285 pub return_address: u32,
286 /// How many bytes one general purpose register takes when it is saved on the stack.
287 pub word: u32,
288}
289
290impl CallRegs {
291 /// Whether a call preserves that general purpose register.
292 #[must_use]
293 pub fn preserves_int(&self, reg: PhysReg) -> bool {
294 self.int_saved.contains(®)
295 }
296
297 /// Whether a call preserves that vector register.
298 #[must_use]
299 pub fn preserves_sse(&self, reg: PhysReg) -> bool {
300 self.sse_saved.contains(®)
301 }
302}
303
304/// Where one of the values a call passes is.
305#[derive(Debug, Clone, Copy, PartialEq, Eq)]
306pub enum Where {
307 /// In that register.
308 Reg(PhysReg),
309 /// That many bytes up the argument area, which is where the stack pointer points at the
310 /// instruction that makes the call and is one word above the return address in the callee.
311 Stack(u32),
312}
313
314/// Where the values a call passes are, worked out one after another.
315///
316/// [`crate::abi::Call`] answers a different question: whether a value travels in registers at all
317/// and in how many, which is what decides the shape of a signature and is settled before the IR
318/// for a function exists. This answers the question after it. Given values in the order the
319/// signature holds them, it says which register each one is in and how far up the argument area
320/// the ones that got no register are. Both count registers, and they agree about how many fit
321/// because they read the same lists, but they run at opposite ends of the compiler and neither
322/// can be the other.
323///
324/// Ask about each value in the order the signature holds them. Asking out of order answers about
325/// a different signature, because where a value is depends on every value before it.
326#[derive(Debug, Clone)]
327pub struct Places<'a> {
328 regs: &'a CallRegs,
329 int: usize,
330 sse: usize,
331 stack: u32,
332}
333
334impl<'a> Places<'a> {
335 /// Where the first value is, for a call under that convention.
336 #[must_use]
337 pub fn new(regs: &'a CallRegs) -> Self {
338 Self { regs, int: 0, sse: 0, stack: regs.shadow }
339 }
340
341 /// Where the next value is, when it travels in a general purpose register.
342 pub fn integer(&mut self) -> Where {
343 match self.regs.int_args.get(self.position(false)) {
344 Some(®) => {
345 self.int += 1;
346 Where::Reg(reg)
347 }
348 None => self.on_stack(self.regs.word, self.regs.word),
349 }
350 }
351
352 /// Where the next value is, when it travels in a vector register.
353 pub fn float(&mut self) -> Where {
354 match self.regs.sse_args.get(self.position(true)) {
355 Some(®) => {
356 self.sse += 1;
357 Where::Reg(reg)
358 }
359 None => self.on_stack(self.regs.word, self.regs.word),
360 }
361 }
362
363 /// Where the next value is, when it travels in memory whatever is left.
364 ///
365 /// Every argument area is a run of whole words, so a value narrower than one still takes one
366 /// and a value that is not a whole number of them is rounded up. An alignment wider than a
367 /// word is respected, which is what a sixteen byte aligned structure passed by value needs.
368 pub fn on_stack(&mut self, size: u32, align: u32) -> Where {
369 let word = self.regs.word;
370 let at = self.stack.next_multiple_of(align.max(word));
371 self.stack = at.saturating_add(size.max(word).next_multiple_of(word));
372 Where::Stack(at)
373 }
374
375 /// How many bytes of argument area the values so far need, shadow space included.
376 #[must_use]
377 pub fn size(&self) -> u32 {
378 self.stack
379 }
380
381 /// How many general purpose argument registers the values so far took.
382 ///
383 /// What a variadic callee needs and nothing else does. `va_start` has to record how far into
384 /// each of the two register sequences the arguments the signature names got, because the first
385 /// argument it does not name is the one after them, and asking here is the only way to know
386 /// that is the same count the caller worked from.
387 #[must_use]
388 pub fn integers(&self) -> usize {
389 self.int
390 }
391
392 /// How many vector argument registers the values so far took.
393 #[must_use]
394 pub fn floats(&self) -> usize {
395 self.sse
396 }
397
398 /// The position the next value of a kind is at.
399 fn position(&self, sse: bool) -> usize {
400 if self.regs.shared_positions {
401 self.int + self.sse
402 } else if sse {
403 self.sse
404 } else {
405 self.int
406 }
407 }
408}
409
410impl fmt::Display for RegFile {
411 /// The file as a dump reads it, one class to a line.
412 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
413 for (_, info) in self.classes() {
414 writeln!(f, "class {} : i{} = {}", info.name, info.bits, info.regs.join(", "))?;
415 }
416 Ok(())
417 }
418}
419
420#[cfg(test)]
421mod tests {
422 use super::*;
423
424 static GPR: [&str; 3] = ["rax", "rcx", "rdx"];
425 static XMM: [&str; 2] = ["xmm0", "xmm1"];
426 static CLASSES: [ClassInfo; 2] = [
427 ClassInfo { name: "gpr", bits: 64, regs: &GPR, allocatable: true },
428 ClassInfo { name: "xmm", bits: 128, regs: &XMM, allocatable: true },
429 ];
430 static FILE: RegFile = RegFile::new(&CLASSES);
431
432 #[test]
433 fn a_class_is_found_by_its_name() {
434 let gpr = FILE.class_named("gpr").expect("the file has a gpr class");
435 assert_eq!(FILE.len(gpr), 3);
436 assert_eq!(FILE.class(gpr).map(|info| info.bits), Some(64));
437 assert_eq!(FILE.class_named("vec"), None);
438 }
439
440 #[test]
441 fn a_register_is_found_by_its_name_and_names_itself_back() {
442 let (class, reg) = FILE.reg_named("xmm1").expect("the file has xmm1");
443 assert_eq!(FILE.class(class).map(|info| info.name), Some("xmm"));
444 assert_eq!(reg.number(), 1);
445 assert_eq!(FILE.name(class, reg), Some("xmm1"));
446 assert_eq!(FILE.reg_named("r15"), None);
447 }
448
449 #[test]
450 fn a_number_past_the_end_of_a_class_has_no_name() {
451 let gpr = FILE.class_named("gpr").expect("the file has a gpr class");
452 assert_eq!(FILE.name(gpr, PhysReg::new(3)), None);
453 assert_eq!(FILE.name(RegClass::new(7), PhysReg::new(0)), None);
454 }
455
456 #[test]
457 fn a_file_that_names_two_registers_alike_says_so() {
458 assert_eq!(FILE.duplicate(), None);
459 static BOTH: [ClassInfo; 2] = [
460 ClassInfo { name: "gpr", bits: 64, regs: &GPR, allocatable: true },
461 ClassInfo { name: "shadow", bits: 64, regs: &GPR, allocatable: true },
462 ];
463 assert_eq!(RegFile::new(&BOTH).duplicate(), Some("rax"));
464 }
465
466 #[test]
467 fn a_class_nothing_allocates_from_is_still_a_class_in_every_other_way() {
468 static WITH_STACK: [ClassInfo; 2] = [
469 ClassInfo { name: "gpr", bits: 64, regs: &GPR, allocatable: true },
470 ClassInfo { name: "x87", bits: 80, regs: &XMM, allocatable: false },
471 ];
472 let file = RegFile::new(&WITH_STACK);
473 let stack = file.class_named("x87").expect("the file has an x87 class");
474
475 assert!(!file.allocatable(stack));
476 assert!(file.allocatable(file.class_named("gpr").expect("the file has a gpr class")));
477
478 // Everything else about it works, which is the point of describing a class the allocator
479 // will not touch: the registers are counted, are named, and name themselves back.
480 assert_eq!(file.len(stack), 2);
481 assert_eq!(file.name(stack, PhysReg::new(1)), Some("xmm1"));
482 assert_eq!(file.reg_named("xmm1"), Some((stack, PhysReg::new(1))));
483 }
484
485 #[test]
486 fn a_class_the_file_does_not_have_is_not_one_to_allocate_from_either() {
487 assert!(!FILE.allocatable(RegClass::new(7)));
488 }
489
490 #[test]
491 fn the_file_prints_one_class_to_a_line() {
492 assert_eq!(
493 FILE.to_string(),
494 "class gpr : i64 = rax, rcx, rdx\nclass xmm : i128 = xmm0, xmm1\n"
495 );
496 }
497
498 /// Two integer registers, two vector registers and nothing else, so running out of them takes
499 /// three arguments rather than seven and the interesting case is the one being tested.
500 fn convention(shared: bool, shadow: u32) -> CallRegs {
501 static INT: [PhysReg; 2] = [PhysReg::new(0), PhysReg::new(1)];
502 static SSE: [PhysReg; 2] = [PhysReg::new(10), PhysReg::new(11)];
503 static NONE: [PhysReg; 0] = [];
504 CallRegs {
505 int_class: RegClass::new(0),
506 sse_class: RegClass::new(1),
507 int_args: &INT,
508 sse_args: &SSE,
509 shared_positions: shared,
510 int_returns: &INT,
511 sse_returns: &SSE,
512 x87_returns: &NONE,
513 int_saved: &NONE,
514 sse_saved: &NONE,
515 int_order: &INT,
516 sse_order: &SSE,
517 stack_pointer: PhysReg::new(4),
518 frame_pointer: PhysReg::new(5),
519 vector_count: None,
520 red_zone: 0,
521 shadow,
522 stack_align: 16,
523 return_address: 8,
524 word: 8,
525 }
526 }
527
528 #[test]
529 fn counting_each_kind_separately_leaves_the_first_vector_register_to_the_first_float() {
530 let regs = convention(false, 0);
531 let mut places = Places::new(®s);
532 assert_eq!(places.integer(), Where::Reg(PhysReg::new(0)));
533 assert_eq!(places.integer(), Where::Reg(PhysReg::new(1)));
534 // Two integers went past, and a convention that counts separately has not spent a vector
535 // register on either of them.
536 assert_eq!(places.float(), Where::Reg(PhysReg::new(10)));
537 assert_eq!(places.size(), 0);
538 }
539
540 #[test]
541 fn counting_one_position_for_both_skips_the_register_the_other_kind_would_have_used() {
542 let regs = convention(true, 0);
543 let mut places = Places::new(®s);
544 assert_eq!(places.integer(), Where::Reg(PhysReg::new(0)));
545 // The second position, so the second vector register, and the second integer register is
546 // spent whether anything is in it or not.
547 assert_eq!(places.float(), Where::Reg(PhysReg::new(11)));
548 assert_eq!(places.integer(), Where::Stack(0));
549 }
550
551 #[test]
552 fn running_out_of_one_kind_of_register_does_not_touch_the_other() {
553 let regs = convention(false, 0);
554 let mut places = Places::new(®s);
555 assert_eq!(places.integer(), Where::Reg(PhysReg::new(0)));
556 assert_eq!(places.integer(), Where::Reg(PhysReg::new(1)));
557 assert_eq!(places.integer(), Where::Stack(0));
558 assert_eq!(places.float(), Where::Reg(PhysReg::new(10)));
559 assert_eq!(places.size(), 8);
560 }
561
562 #[test]
563 fn the_argument_area_starts_above_the_shadow_space_and_keeps_every_value_aligned() {
564 let regs = convention(false, 32);
565 let mut places = Places::new(®s);
566 // A Windows caller reserves this whether it passes anything on the stack or not, which is
567 // why an empty area is thirty two bytes rather than none.
568 assert_eq!(places.size(), 32);
569 assert_eq!(places.on_stack(4, 4), Where::Stack(32));
570 // Sixteen byte alignment skips the word at 40, which is what a vector or an over-aligned
571 // structure passed by value asks for. The four byte value before it still took a whole
572 // word, which is why the skipped word is there to skip.
573 assert_eq!(places.on_stack(16, 16), Where::Stack(48));
574 assert_eq!(places.on_stack(8, 8), Where::Stack(64));
575 assert_eq!(places.size(), 72);
576 }
577}