Skip to main content

rucc_tuple/
arch.rs

1//! The machine half of a tuple: instruction set, baseline, byte order and data model.
2
3use core::fmt;
4
5/// An instruction set family.
6///
7/// Deliberately not `#[non_exhaustive]`. Adding a variant here has to break every match that
8/// needs to change, in this workspace and in anyone else's code. That is the property
9/// `spec/cross-compile/02-the-goal.md` claim 3 is making when it says adding a target is a data change: the
10/// compiler tells you every place the data is read.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub enum Arch {
13    /// x86-64. The reference target, and the one everything is measured against first.
14    X86_64,
15    /// 32-bit x86, from i686 upwards. Not a narrower x86-64: eight registers, a PIC base
16    /// register, and x87 as the floating point unit of the base ABI.
17    X86,
18    /// 64-bit ARM.
19    Aarch64,
20    /// 32-bit ARM, including Thumb.
21    Arm,
22    /// ARM64EC. A 64-bit ARM instruction set with the x86-64 calling convention, its own symbol
23    /// namespace and thunks between the two. `spec/cross-compile/06-abis.md` section 6.8 declines to
24    /// implement it and `spec/cross-compile/04-target-matrix.md` keeps it at tier 4, so this variant exists so
25    /// that the tuple parses and `--print-config` answers, and nothing emits code for it.
26    Arm64Ec,
27    /// 64-bit RISC-V.
28    Riscv64,
29    /// 32-bit RISC-V.
30    Riscv32,
31    /// 64-bit LoongArch.
32    LoongArch64,
33    /// 64-bit PowerPC. Only ELFv2 is in scope, which is what makes the little-endian
34    /// spelling the common one.
35    PowerPc64,
36    /// IBM z/Architecture. The big-endian row, and the reason byte order is a field.
37    S390x,
38    /// WebAssembly with 32-bit addresses. A stack machine with structured control flow,
39    /// which `spec/cross-compile/05-architectures.md` section 5.9 explains is a second back end rather
40    /// than a tenth architecture.
41    Wasm32,
42}
43
44impl Arch {
45    /// The canonical name, which is the leading component of a tuple for every architecture
46    /// whose baseline is not spelled in that component. ARM is the exception and
47    /// [`crate::TargetTuple`] handles it.
48    pub const fn as_str(self) -> &'static str {
49        match self {
50            Arch::X86_64 => "x86_64",
51            Arch::X86 => "i686",
52            Arch::Aarch64 => "aarch64",
53            Arch::Arm => "arm",
54            Arch::Arm64Ec => "arm64ec",
55            Arch::Riscv64 => "riscv64",
56            Arch::Riscv32 => "riscv32",
57            Arch::LoongArch64 => "loongarch64",
58            Arch::PowerPc64 => "powerpc64",
59            Arch::S390x => "s390x",
60            Arch::Wasm32 => "wasm32",
61        }
62    }
63
64    /// The width of a general purpose register, in bits.
65    ///
66    /// This is a property of the instruction set and it is not the pointer width. A 64-bit
67    /// machine running an ILP32 data model has 64-bit registers and 32-bit pointers, which is
68    /// what `x86_64-linux-gnux32` is for.
69    pub const fn register_width(self) -> u32 {
70        match self {
71            Arch::X86_64
72            | Arch::Aarch64
73            | Arch::Arm64Ec
74            | Arch::Riscv64
75            | Arch::LoongArch64
76            | Arch::PowerPc64
77            | Arch::S390x => 64,
78            Arch::X86 | Arch::Arm | Arch::Riscv32 | Arch::Wasm32 => 32,
79        }
80    }
81
82    /// The byte order this architecture uses unless the tuple says otherwise.
83    ///
84    /// Every architecture here except s390x is little-endian by default, and five of them have
85    /// a big-endian mode that a real target uses. That is why endianness is a tuple field and
86    /// not a constant on this enum: the version of this function that returned `true` for
87    /// everything was wrong for aarch64, arm, riscv, powerpc and mips at the same time.
88    pub const fn default_endian(self) -> Endian {
89        match self {
90            Arch::S390x | Arch::PowerPc64 => Endian::Big,
91            _ => Endian::Little,
92        }
93    }
94
95    /// Whether this architecture has a big-endian mode any target in the table uses.
96    ///
97    /// x86 does not, so `x86_64eb` is a spelling error rather than a target, and saying so is
98    /// better than emitting bytes for a machine that does not exist.
99    pub const fn has_both_endians(self) -> bool {
100        matches!(self, Arch::Aarch64 | Arch::Arm | Arch::Riscv64 | Arch::Riscv32 | Arch::PowerPc64)
101    }
102
103    /// Whether a sub-architecture may be spelled for this family.
104    pub const fn takes_subarch(self) -> bool {
105        matches!(self, Arch::Arm)
106    }
107
108    /// Whether this family has more than one float calling convention to choose between.
109    ///
110    /// False for x86-64, AArch64, s390x and PowerPC, which have exactly one, so naming a float
111    /// ABI for them is a spelling error rather than a configuration. True for ARM, RISC-V and
112    /// LoongArch, where it is a real choice that changes which registers arguments arrive in.
113    pub const fn selects_float_abi(self) -> bool {
114        matches!(self, Arch::Arm | Arch::Riscv64 | Arch::Riscv32 | Arch::LoongArch64)
115    }
116}
117
118impl fmt::Display for Arch {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        f.write_str(self.as_str())
121    }
122}
123
124/// A baseline within an architecture family.
125///
126/// This carries only the baselines that appear in the leading component of a tuple, which in
127/// practice means the ARM ones. `spec/cross-compile/03-target-model.md` section 3.3 also lists micro
128/// architecture levels such as `x86-64-v3` and RISC-V profiles such as `rva23u64` as
129/// candidates for this field, and they are not here on purpose.
130///
131/// The reason is the rule that section states two paragraphs later: a thing belongs in the
132/// tuple if it changes how a function is called or how a struct is laid out. Two objects built
133/// for `x86-64-v2` and `x86-64-v3` link together and call each other correctly, so they are
134/// the same target and a different `-march`. Two objects built for `armv7a` and `armv6m` are
135/// not. Keeping levels out of the tuple is what keeps the tuple's own invariant true, namely
136/// that equal tuples means interchangeable objects, and that invariant is what lets a tuple be
137/// a cache key. Levels and profiles arrive with the feature set in a later milestone.
138#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
139pub enum SubArch {
140    /// The family's own baseline, and the only legal value for every family except ARM.
141    #[default]
142    None,
143    /// ARMv5TE, the floor for the older soft-float Linux ports.
144    ArmV5Te,
145    /// ARMv6.
146    ArmV6,
147    /// ARMv6-M, the Cortex-M0 class. Thumb only.
148    ArmV6M,
149    /// ARMv7-A, which is what almost every 32-bit Linux ARM target means.
150    ArmV7A,
151    /// ARMv7-R, the real-time profile.
152    ArmV7R,
153    /// ARMv7-M, the Cortex-M3 and M4 class. Thumb only.
154    ArmV7M,
155    /// ARMv7E-M.
156    ArmV7Em,
157    /// ARMv8-A in its 32-bit form.
158    ArmV8A,
159    /// ARMv8-M baseline and mainline, folded into one value because the difference is a
160    /// feature set rather than a different tuple.
161    ArmV8M,
162}
163
164impl SubArch {
165    /// The text this baseline contributes to the leading component, or the empty string.
166    pub const fn as_str(self) -> &'static str {
167        match self {
168            SubArch::None => "",
169            SubArch::ArmV5Te => "v5te",
170            SubArch::ArmV6 => "v6",
171            SubArch::ArmV6M => "v6m",
172            // The A profile baselines are spelled without the profile letter, because `armv7`
173            // is what the matrix, every distribution and every other toolchain write. The
174            // parser takes `v7a` and `v8a` as well.
175            SubArch::ArmV7A => "v7",
176            SubArch::ArmV7R => "v7r",
177            SubArch::ArmV7M => "v7m",
178            SubArch::ArmV7Em => "v7em",
179            SubArch::ArmV8A => "v8",
180            SubArch::ArmV8M => "v8m",
181        }
182    }
183
184    /// Whether this baseline belongs to the given family.
185    pub const fn belongs_to(self, arch: Arch) -> bool {
186        match self {
187            SubArch::None => true,
188            _ => matches!(arch, Arch::Arm),
189        }
190    }
191
192    /// Whether the baseline executes Thumb instructions only, which decides the default
193    /// instruction set and, on the M profile, rules out the A profile's float ABIs.
194    pub const fn is_thumb_only(self) -> bool {
195        matches!(self, SubArch::ArmV6M | SubArch::ArmV7M | SubArch::ArmV7Em | SubArch::ArmV8M)
196    }
197}
198
199/// Byte order.
200#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
201pub enum Endian {
202    /// Least significant byte first.
203    Little,
204    /// Most significant byte first. s390x, and the big-endian modes of five other families.
205    Big,
206}
207
208impl Endian {
209    /// The name used in diagnostics and in the print queries.
210    pub const fn as_str(self) -> &'static str {
211        match self {
212            Endian::Little => "little",
213            Endian::Big => "big",
214        }
215    }
216}
217
218impl fmt::Display for Endian {
219    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220        f.write_str(self.as_str())
221    }
222}
223
224/// The widths of `int`, `long` and a pointer, as one choice rather than three.
225///
226/// Only a handful of combinations exist and every other combination is a bug, so this is an
227/// enumeration and not three independent numbers. The individual widths are still readable,
228/// because that is what the type layout wants, but they are read from here rather than
229/// guessed from the architecture.
230#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
231pub enum DataModel {
232    /// 32-bit `int`, 32-bit `long`, 32-bit pointer, on a 32-bit machine. i686 and armv7.
233    Ilp32,
234    /// 32-bit `int`, 64-bit `long`, 64-bit pointer. Every 64-bit Unix.
235    Lp64,
236    /// 32-bit `int`, 32-bit `long`, 64-bit pointer. Windows, and only Windows.
237    Llp64,
238    /// 32-bit `int`, 32-bit `long`, 32-bit pointer, on a machine with 64-bit registers.
239    /// `x86_64-linux-gnux32` and the ILP32 modes of AArch64 and RISC-V.
240    ///
241    /// This exists in the enumeration mostly to prove the field is real. A model that is
242    /// derived from the architecture cannot express it, and the version of this code that
243    /// derived it silently produced 64-bit pointers for a target whose pointers are 32 bits.
244    Ilp32On64,
245}
246
247impl DataModel {
248    /// The name used in diagnostics and in the print queries.
249    pub const fn as_str(self) -> &'static str {
250        match self {
251            DataModel::Ilp32 => "ilp32",
252            DataModel::Lp64 => "lp64",
253            DataModel::Llp64 => "llp64",
254            DataModel::Ilp32On64 => "ilp32-on-64",
255        }
256    }
257
258    /// The width of `int` in bits, which is 32 on every target in the table.
259    pub const fn int_width(self) -> u32 {
260        32
261    }
262
263    /// The width of `long` in bits. This is the field that separates the LP64 world from
264    /// Windows.
265    pub const fn long_width(self) -> u32 {
266        match self {
267            DataModel::Lp64 => 64,
268            DataModel::Ilp32 | DataModel::Llp64 | DataModel::Ilp32On64 => 32,
269        }
270    }
271
272    /// The width of `long long` in bits, which is 64 everywhere.
273    pub const fn long_long_width(self) -> u32 {
274        64
275    }
276
277    /// The width of a pointer in bits.
278    pub const fn pointer_width(self) -> u32 {
279        match self {
280            DataModel::Lp64 | DataModel::Llp64 => 64,
281            DataModel::Ilp32 | DataModel::Ilp32On64 => 32,
282        }
283    }
284
285    /// The number of bits of a pointer that are an address.
286    ///
287    /// Equal to [`DataModel::pointer_width`] for every target in the table, and separate from
288    /// it because on CHERI it is not: a purecap pointer is 128 bits carrying a 64-bit address
289    /// plus bounds, permissions and a tag. `spec/cross-compile/03-target-model.md` section 3.9 puts CHERI out
290    /// of scope and keeps this accessor, because adding the distinction later is an audit of
291    /// every use of the pointer width and adding it now costs one function.
292    pub const fn address_width(self) -> u32 {
293        self.pointer_width()
294    }
295}
296
297impl fmt::Display for DataModel {
298    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299        f.write_str(self.as_str())
300    }
301}