Skip to main content

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    /// A file made of those classes, numbered in the order they are given.
86    #[must_use]
87    pub const fn new(classes: &'static [ClassInfo]) -> Self {
88        Self { classes }
89    }
90
91    /// Its classes, each with the number it is known by.
92    pub fn classes(&self) -> impl Iterator<Item = (RegClass, &'static ClassInfo)> + use<> {
93        self.classes.iter().enumerate().map(|(number, info)| (RegClass::new(number as u8), info))
94    }
95
96    /// What is in one class.
97    #[must_use]
98    pub fn class(&self, class: RegClass) -> Option<&'static ClassInfo> {
99        self.classes.get(usize::from(class.number()))
100    }
101
102    /// The class of that name, such as `gpr`.
103    #[must_use]
104    pub fn class_named(&self, name: &str) -> Option<RegClass> {
105        self.classes().find(|(_, info)| info.name == name).map(|(class, _)| class)
106    }
107
108    /// How many registers are in a class, which is one past the largest number in it.
109    #[must_use]
110    pub fn len(&self, class: RegClass) -> usize {
111        self.class(class).map_or(0, |info| info.regs.len())
112    }
113
114    /// Whether the file has no classes at all, which is a target that has not described one.
115    #[must_use]
116    pub fn is_empty(&self) -> bool {
117        self.classes.is_empty()
118    }
119
120    /// What one register is called.
121    #[must_use]
122    pub fn name(&self, class: RegClass, reg: PhysReg) -> Option<&'static str> {
123        self.class(class)?.regs.get(usize::from(reg.number())).copied()
124    }
125
126    /// The register of that name, and the class it is in.
127    ///
128    /// The name is written without the sigil, so `rax` rather than `$rax`.
129    #[must_use]
130    pub fn reg_named(&self, name: &str) -> Option<(RegClass, PhysReg)> {
131        for (class, info) in self.classes() {
132            if let Some(number) = info.regs.iter().position(|&reg| reg == name) {
133                return Some((class, PhysReg::new(number as u8)));
134            }
135        }
136        None
137    }
138
139    /// A name this file gives to two registers, if it gives one to two.
140    ///
141    /// Reading a dump back needs every name to say which register it means, and a target that
142    /// breaks that produces text that cannot be parsed rather than an error at the point of the
143    /// mistake. So every target's own test asks this, which is why it is here and public.
144    #[must_use]
145    pub fn duplicate(&self) -> Option<&'static str> {
146        let mut seen: Vec<&'static str> = Vec::new();
147        for (_, info) in self.classes() {
148            for &reg in info.regs {
149                if seen.contains(&reg) {
150                    return Some(reg);
151                }
152                seen.push(reg);
153            }
154        }
155        None
156    }
157}
158
159impl fmt::Display for RegFile {
160    /// The file as a dump reads it, one class to a line.
161    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162        for (_, info) in self.classes() {
163            writeln!(f, "class {} : i{} = {}", info.name, info.bits, info.regs.join(", "))?;
164        }
165        Ok(())
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172
173    static GPR: [&str; 3] = ["rax", "rcx", "rdx"];
174    static XMM: [&str; 2] = ["xmm0", "xmm1"];
175    static CLASSES: [ClassInfo; 2] = [
176        ClassInfo { name: "gpr", bits: 64, regs: &GPR },
177        ClassInfo { name: "xmm", bits: 128, regs: &XMM },
178    ];
179    static FILE: RegFile = RegFile::new(&CLASSES);
180
181    #[test]
182    fn a_class_is_found_by_its_name() {
183        let gpr = FILE.class_named("gpr").expect("the file has a gpr class");
184        assert_eq!(FILE.len(gpr), 3);
185        assert_eq!(FILE.class(gpr).map(|info| info.bits), Some(64));
186        assert_eq!(FILE.class_named("vec"), None);
187    }
188
189    #[test]
190    fn a_register_is_found_by_its_name_and_names_itself_back() {
191        let (class, reg) = FILE.reg_named("xmm1").expect("the file has xmm1");
192        assert_eq!(FILE.class(class).map(|info| info.name), Some("xmm"));
193        assert_eq!(reg.number(), 1);
194        assert_eq!(FILE.name(class, reg), Some("xmm1"));
195        assert_eq!(FILE.reg_named("r15"), None);
196    }
197
198    #[test]
199    fn a_number_past_the_end_of_a_class_has_no_name() {
200        let gpr = FILE.class_named("gpr").expect("the file has a gpr class");
201        assert_eq!(FILE.name(gpr, PhysReg::new(3)), None);
202        assert_eq!(FILE.name(RegClass::new(7), PhysReg::new(0)), None);
203    }
204
205    #[test]
206    fn a_file_that_names_two_registers_alike_says_so() {
207        assert_eq!(FILE.duplicate(), None);
208        static BOTH: [ClassInfo; 2] = [
209            ClassInfo { name: "gpr", bits: 64, regs: &GPR },
210            ClassInfo { name: "shadow", bits: 64, regs: &GPR },
211        ];
212        assert_eq!(RegFile::new(&BOTH).duplicate(), Some("rax"));
213    }
214
215    #[test]
216    fn the_file_prints_one_class_to_a_line() {
217        assert_eq!(
218            FILE.to_string(),
219            "class gpr : i64 = rax, rcx, rdx\nclass xmm : i128 = xmm0, xmm1\n"
220        );
221    }
222}