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}