rucc_target/lib.rs
1//! Target descriptions: triples, and the facts about a target that the rest of the
2//! compiler reads rather than hard-codes.
3//!
4//! Design: `spec/12-abi-and-runtime.md`. Layer rank 1, see `spec/18-package-layout.md`.
5//!
6//! The rule from `spec/18-package-layout.md` section 18.2 is that there is no
7//! target-specific code outside this crate and the per-target rule sets. Everything a pass
8//! needs to know about a target is a field it can read here. That rule is what makes the
9//! claim in `spec/10-backend.md` testable, namely that a new target is a rule set and a few
10//! data files, and `M10` brings up a fourth target specifically to put a number on it.
11//!
12//! [`TargetInfo::call`] is the other half of that rule and the one with teeth. How a structure
13//! travels between a caller and a callee is the target's answer rather than C's, so the walk to
14//! the IR flattens a C type into a [`Shape`] and asks here what form it takes. Every psABI rule
15//! is behind [`Call`] and nothing outside this crate matches on an architecture to find one.
16//!
17//! # Status
18//!
19//! Triple parsing and the basic data model are real, which is what `rucc --print-config`
20//! reports, and so is the argument classification of every psABI in
21//! `spec/12-abi-and-runtime.md` sections 12.2 to 12.5. x86-64's register file is written down,
22//! in [`x86_64`], along with what each of the two conventions over it does with each register,
23//! what each of its machine instructions does with its operands, and which instructions a frame
24//! is made of, which is [`FrameInsts`]. AArch64's and RISC-V's arrive with their backends.
25//! Machine models land in `M6`.
26//!
27//! This crate is tier 3 in `spec/18-package-layout.md` section 18.5: its Rust API is
28//! explicitly unstable and will change without a major version bump.
29
30#![doc(html_root_url = "https://docs.rs/rucc-target/0.8.0")]
31
32use std::fmt;
33use std::str::FromStr;
34
35use rucc_base::float::Format;
36
37mod abi;
38mod branch;
39mod frame;
40mod operand;
41mod regs;
42pub mod x86_64;
43
44pub use crate::abi::{Arg, Call, Kind, Pass, Piece, Scalar, Shape, Slot};
45pub use crate::branch::BranchInsts;
46pub use crate::frame::{ClassMoves, FrameInsts};
47pub use crate::operand::{Constraint, OperandDesc, Role};
48pub use crate::regs::{CallRegs, ClassInfo, PhysReg, Places, RegClass, RegFile, Where};
49
50/// A target architecture.
51#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
52// Deliberately not `#[non_exhaustive]`. Adding a variant here has to break every
53// match that needs to change, in this workspace and in anyone else's code. That is
54// the property `spec/10-backend.md` section 10.8 is claiming when it says adding a
55// target is a data change: the compiler tells you every place the data is read.
56pub enum Arch {
57 /// x86-64, the first target and the one `M3` brings up.
58 X86_64,
59 /// AArch64, the second target, `M6`.
60 Aarch64,
61 /// 64-bit RISC-V. `spec/10-backend.md` calls this the middle-end canary, because it has
62 /// no condition codes and no complex addressing modes, so anything the middle end got
63 /// away with on x86-64 shows up here.
64 Riscv64,
65}
66
67impl Arch {
68 /// Pointer width in bits.
69 pub const fn pointer_width(self) -> u32 {
70 match self {
71 Arch::X86_64 | Arch::Aarch64 | Arch::Riscv64 => 64,
72 }
73 }
74
75 /// Whether the target is little-endian.
76 pub const fn is_little_endian(self) -> bool {
77 match self {
78 Arch::X86_64 | Arch::Aarch64 | Arch::Riscv64 => true,
79 }
80 }
81
82 /// The name as it appears in a triple.
83 pub const fn as_str(self) -> &'static str {
84 match self {
85 Arch::X86_64 => "x86_64",
86 Arch::Aarch64 => "aarch64",
87 Arch::Riscv64 => "riscv64",
88 }
89 }
90}
91
92/// The operating system a target runs on.
93#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
94// Deliberately not `#[non_exhaustive]`. Adding a variant here has to break every
95// match that needs to change, in this workspace and in anyone else's code. That is
96// the property `spec/10-backend.md` section 10.8 is claiming when it says adding a
97// target is a data change: the compiler tells you every place the data is read.
98pub enum Os {
99 /// Linux, hosted or freestanding.
100 Linux,
101 /// Apple platforms. `spec/12-abi-and-runtime.md` section 12.3 lists the four places
102 /// Apple diverges from AAPCS64, and every one of them is a real bug if missed.
103 Darwin,
104 /// Windows.
105 Windows,
106 /// No operating system, which is what `-ffreestanding` kernel work looks like.
107 None,
108}
109
110impl Os {
111 /// The name as it appears in a triple.
112 pub const fn as_str(self) -> &'static str {
113 match self {
114 Os::Linux => "linux",
115 Os::Darwin => "darwin",
116 Os::Windows => "windows",
117 Os::None => "none",
118 }
119 }
120
121 /// The object file format this operating system uses.
122 pub const fn object_format(self) -> ObjectFormat {
123 match self {
124 Os::Linux | Os::None => ObjectFormat::Elf,
125 Os::Darwin => ObjectFormat::MachO,
126 Os::Windows => ObjectFormat::Coff,
127 }
128 }
129}
130
131/// The C runtime and ABI variant.
132#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
133// Deliberately not `#[non_exhaustive]`. Adding a variant here has to break every
134// match that needs to change, in this workspace and in anyone else's code. That is
135// the property `spec/10-backend.md` section 10.8 is claiming when it says adding a
136// target is a data change: the compiler tells you every place the data is read.
137pub enum Env {
138 /// The default for the operating system.
139 None,
140 /// glibc.
141 Gnu,
142 /// musl.
143 Musl,
144 /// The MSVC ABI.
145 Msvc,
146}
147
148impl Env {
149 /// The name as it appears in a triple, if it appears at all.
150 pub const fn as_str(self) -> &'static str {
151 match self {
152 Env::None => "none",
153 Env::Gnu => "gnu",
154 Env::Musl => "musl",
155 Env::Msvc => "msvc",
156 }
157 }
158}
159
160/// The object file format to emit.
161#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
162// Deliberately not `#[non_exhaustive]`. Adding a variant here has to break every
163// match that needs to change, in this workspace and in anyone else's code. That is
164// the property `spec/10-backend.md` section 10.8 is claiming when it says adding a
165// target is a data change: the compiler tells you every place the data is read.
166pub enum ObjectFormat {
167 /// ELF.
168 Elf,
169 /// Mach-O.
170 MachO,
171 /// COFF.
172 Coff,
173}
174
175impl ObjectFormat {
176 /// The name used in diagnostics and in `--print-config`.
177 pub const fn as_str(self) -> &'static str {
178 match self {
179 ObjectFormat::Elf => "elf",
180 ObjectFormat::MachO => "macho",
181 ObjectFormat::Coff => "coff",
182 }
183 }
184}
185
186/// A target triple.
187///
188/// We accept the LLVM-style `arch-vendor-os-env` form because that is what build systems
189/// pass, and we normalise it to the three fields we actually branch on. The vendor field is
190/// parsed and discarded: no decision in the compiler depends on it, and keeping it would
191/// invite one.
192#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
193pub struct Triple {
194 /// The architecture.
195 pub arch: Arch,
196 /// The operating system.
197 pub os: Os,
198 /// The runtime and ABI variant.
199 pub env: Env,
200}
201
202impl Triple {
203 /// A triple from its three parts.
204 pub const fn new(arch: Arch, os: Os, env: Env) -> Self {
205 Self { arch, os, env }
206 }
207
208 /// The triple of the machine this compiler is running on.
209 ///
210 /// Used as the default target, which is what makes `rucc hello.c` work with no flags.
211 /// Unknown host combinations are not an error here: they are reported by the driver,
212 /// where there is somewhere to report them to.
213 pub fn host() -> Option<Self> {
214 let arch = match std::env::consts::ARCH {
215 "x86_64" => Arch::X86_64,
216 "aarch64" => Arch::Aarch64,
217 "riscv64" => Arch::Riscv64,
218 _ => return None,
219 };
220 // Which libc this is matters, and `std::env::consts` does not say. A compiler built on
221 // Alpine and defaulting to `x86_64-unknown-linux-gnu` describes a machine it is not
222 // running on: musl and glibc disagree about `int_fast16_t` among other things, and a
223 // header that is written out of the predefined type names picks the disagreement up.
224 // The libc rucc itself was linked against is the best evidence available about the one
225 // the code it compiles will be linked against, and it is right on every machine where
226 // rucc was built for the machine it runs on.
227 let linux = if cfg!(target_env = "musl") { Env::Musl } else { Env::Gnu };
228 let (os, env) = match std::env::consts::OS {
229 "linux" => (Os::Linux, linux),
230 "macos" => (Os::Darwin, Env::None),
231 "windows" => (Os::Windows, Env::Msvc),
232 _ => return None,
233 };
234 Some(Self::new(arch, os, env))
235 }
236}
237
238impl fmt::Display for Triple {
239 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240 // Always four fields, always the same spelling, because this string ends up in
241 // `--print-config` output that people diff.
242 write!(f, "{}-unknown-{}-{}", self.arch.as_str(), self.os.as_str(), self.env.as_str())
243 }
244}
245
246/// Why a triple failed to parse.
247#[derive(Debug, Clone, PartialEq, Eq)]
248pub struct ParseTripleError {
249 /// The triple as given.
250 pub input: String,
251 /// What specifically was not recognised.
252 pub reason: &'static str,
253}
254
255impl fmt::Display for ParseTripleError {
256 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257 write!(f, "unsupported target triple `{}`: {}", self.input, self.reason)
258 }
259}
260
261impl std::error::Error for ParseTripleError {}
262
263impl FromStr for Triple {
264 type Err = ParseTripleError;
265
266 fn from_str(s: &str) -> Result<Self, Self::Err> {
267 let err = |reason| ParseTripleError { input: s.to_owned(), reason };
268 let mut parts = s.split('-');
269
270 let arch = match parts.next() {
271 Some("x86_64" | "amd64") => Arch::X86_64,
272 Some("aarch64" | "arm64") => Arch::Aarch64,
273 Some("riscv64") => Arch::Riscv64,
274 _ => return Err(err("unknown architecture")),
275 };
276
277 // The vendor field is optional in practice. `x86_64-linux-gnu` and
278 // `x86_64-unknown-linux-gnu` both occur in the wild and mean the same thing, so the
279 // remaining fields are matched by content rather than by position.
280 let rest: Vec<&str> = parts.collect();
281 let mut os = None;
282 let mut env = None;
283 for part in &rest {
284 match *part {
285 "linux" => os = Some(Os::Linux),
286 "darwin" | "macos" | "macosx" | "ios" => os = Some(Os::Darwin),
287 "windows" | "win32" => os = Some(Os::Windows),
288 // `none` is the one token that means different things in the two positions.
289 // In `x86_64-unknown-none-elf` it is the operating system; in
290 // `aarch64-apple-darwin-none` it is the environment. Which one it is depends
291 // on whether an operating system has already been seen, and that rule is what
292 // makes `Display` round-trip through `FromStr`.
293 "none" if os.is_none() => os = Some(Os::None),
294 "none" => env = Some(Env::None),
295 "elf" => os = os.or(Some(Os::None)),
296 "gnu" | "gnueabi" | "gnueabihf" => env = Some(Env::Gnu),
297 "musl" | "musleabi" | "musleabihf" => env = Some(Env::Musl),
298 "msvc" => env = Some(Env::Msvc),
299 _ => {}
300 }
301 }
302
303 let os = os.ok_or_else(|| err("unknown operating system"))?;
304 let env = env.unwrap_or(match os {
305 Os::Linux => Env::Gnu,
306 Os::Windows => Env::Msvc,
307 Os::Darwin | Os::None => Env::None,
308 });
309 Ok(Self::new(arch, os, env))
310 }
311}
312
313/// The facts about a target that the compiler reads instead of hard-coding.
314///
315/// This is the whole of what a pass is allowed to know about where its output will run.
316/// It grows, and every field added here is one fewer `#[cfg]` somewhere it should not be.
317#[derive(Debug, Clone, PartialEq, Eq)]
318#[non_exhaustive]
319pub struct TargetInfo {
320 /// The triple this describes.
321 pub triple: Triple,
322 /// Width of a pointer in bits.
323 pub pointer_width: u32,
324 /// Whether bytes are ordered little end first.
325 pub little_endian: bool,
326 /// Whether a bare `char` is signed.
327 ///
328 /// Signed on x86-64 and unsigned on AArch64 Linux, which is the classic source of code
329 /// that works on one and not the other, so it is data rather than an assumption.
330 pub char_is_signed: bool,
331 /// Width of `long` in bits. This is the field that separates the LP64 world from
332 /// Windows LLP64.
333 pub long_width: u32,
334 /// Width of `long double` in bits: 80 bits of x87 stored in 128 on SysV x86-64,
335 /// 64 on Apple platforms, 64 on Windows.
336 pub long_double_width: u32,
337 /// The format `long double` actually is, which the width does not say.
338 ///
339 /// It is 128 bits wide on SysV x86-64 and on AArch64 Linux and the two are not the same
340 /// type: one is the x87 eighty bit format padded out to sixteen bytes and the other is
341 /// true quad precision with a hundred and thirteen bits of significand. Anything that
342 /// converts a constant or folds one has to know which, and the width alone cannot say.
343 pub long_double_format: Format,
344 /// The format `_Float64x` is, which is the widest format the target has short of a software
345 /// one.
346 ///
347 /// It follows the architecture and not the operating system, which is what makes it worth a
348 /// field of its own next to `long double`. Apple and Windows define `long double` as a
349 /// `double` and neither of them takes `_Float64x` down with it: the type has to be wider
350 /// than a `_Float64`, so it is the x87 eighty bit format on x86-64 and quad precision on
351 /// AArch64 and RISC-V wherever it is written.
352 pub float64x_format: Format,
353 /// Width of `wchar_t` in bits, which decides what a wide literal is encoded in.
354 ///
355 /// It is 16 on Windows, so a wide string there is UTF-16 and a character outside the basic
356 /// plane takes two elements, and 32 everywhere else, where a wide string is UTF-32 and no
357 /// character takes more than one.
358 pub wchar_width: u32,
359 /// Whether `wchar_t` is signed.
360 ///
361 /// x86-64 Linux makes it a signed `int` and AArch64 Linux makes it an `unsigned int`,
362 /// following the psABI's rule for plain `char`, so `L'\xffffffff'` is minus one on one of
363 /// them and four billion on the other.
364 pub wchar_is_signed: bool,
365 /// The granule a `_BitInt` wider than 64 bits is laid out in, in bits.
366 ///
367 /// Above 64 bits the psABIs stop treating a `_BitInt` like a standard integer type and
368 /// start treating it like an array of these, so its size is rounded up to a multiple of
369 /// this and its alignment is this. It is 64 on x86-64 and RISC-V and 128 on AArch64, which
370 /// is why `_BitInt(65)` is sixteen bytes aligned to eight on one and sixteen bytes aligned
371 /// to sixteen on the other. Measured with clang 18 on x86-64 Linux and clang on AArch64
372 /// Darwin rather than read off the documents.
373 pub bit_int_granule: u32,
374 /// The widest access, in bits, this machine performs atomically without taking a lock.
375 ///
376 /// It is what `__atomic_always_lock_free` and `__atomic_is_lock_free` answer from, and it is
377 /// a claim about what this compiler emits rather than about what the processor is capable of.
378 /// Sixty four on every target here. x86-64 does sixteen bytes atomically with `cmpxchg16b`,
379 /// which is not in the baseline the psABI names and which nothing in this compiler writes, and
380 /// AArch64 does the same with its pair instructions, which nothing writes either. A target
381 /// that answered yes for sixteen bytes and then called a library that has to take a lock for
382 /// them would have two answers to one question, and the wrong one is the one in the header.
383 pub lock_free_width: u32,
384 /// The object format to emit.
385 pub object_format: ObjectFormat,
386 /// What `__builtin_va_list` is, which is the type every `va_list` in every header is a
387 /// typedef of.
388 pub va_list: VaList,
389 /// The registers the machine has, which is [`RegFile::EMPTY`] for an architecture nothing
390 /// has described yet.
391 pub regs: &'static RegFile,
392 /// Which registers the calling convention gives which job, or `None` while the
393 /// architecture has no register file to name them out of.
394 pub call_regs: Option<&'static CallRegs>,
395}
396
397/// The type a target's `__builtin_va_list` is.
398///
399/// A variable argument list is the one place a psABI dictates a C type rather than how a type
400/// travels, and the four answers below are not four spellings of one thing: `sizeof(va_list)` is
401/// eight bytes on Apple's AArch64 and thirty two on Linux's, and on SysV x86-64 a `va_list` is an
402/// array, so a `va_list` passed to a function is passed as a pointer and one assigned to another
403/// is a constraint violation rather than a copy. Code in the wild depends on all of that.
404#[derive(Debug, Clone, Copy, PartialEq, Eq)]
405// Deliberately not `#[non_exhaustive]`, for the reason [`Arch`] is not: a fifth answer here is
406// a fifth type to build, and every place that builds one should stop compiling until it does.
407pub enum VaList {
408 /// `char *`, which is what a target whose arguments are all passed in one place needs: the
409 /// address of the next argument and nothing else. Apple's AArch64 and both Windows targets.
410 CharPointer,
411 /// `void *`, which is the RISC-V psABI's spelling of the same thing.
412 VoidPointer,
413 /// `struct __va_list_tag { unsigned gp_offset, fp_offset; void *overflow_arg_area,
414 /// *reg_save_area; } [1]`, the SysV x86-64 one. Arguments arrive in two register files and
415 /// on the stack, so the list is a cursor into each, and the array of one is what makes
416 /// passing it to `vfprintf` pass its address.
417 SysV,
418 /// `struct __va_list { void *__stack, *__gr_top, *__vr_top; int __gr_offs, __vr_offs; }`,
419 /// the AAPCS64 one. The same idea as SysV's, counting down from the top of each save area
420 /// rather than up from the bottom, and not an array.
421 Aapcs,
422}
423
424impl VaList {
425 /// The name used in `--print-config`.
426 #[must_use]
427 pub const fn as_str(self) -> &'static str {
428 match self {
429 VaList::CharPointer => "char-pointer",
430 VaList::VoidPointer => "void-pointer",
431 VaList::SysV => "sysv",
432 VaList::Aapcs => "aapcs",
433 }
434 }
435}
436
437impl TargetInfo {
438 /// The description of `triple`.
439 pub fn new(triple: Triple) -> Self {
440 let char_is_signed = match (triple.arch, triple.os) {
441 // The AArch64 and RISC-V psABIs make plain `char` unsigned, and x86-64 SysV
442 // makes it signed. Apple and Windows both override that back to signed on
443 // AArch64, which is the kind of divergence that only ever surfaces as a bug
444 // report from someone whose lexer compares a `char` against a negative value.
445 (Arch::Aarch64 | Arch::Riscv64, Os::Linux | Os::None) => false,
446 _ => true,
447 };
448 let long_width = match triple.os {
449 // Windows is LLP64: `long` stays 32 bits on a 64-bit target.
450 Os::Windows => 32,
451 _ => triple.arch.pointer_width(),
452 };
453 let long_double_width = match triple.os {
454 // Apple defines `long double` as `double`, per spec/12-abi-and-runtime.md
455 // section 12.3, and Windows does the same. On the SysV targets it is a distinct
456 // type: 80 bits of x87 stored in 128 on x86-64, and true quad precision on
457 // AArch64 and RISC-V.
458 Os::Darwin | Os::Windows => 64,
459 Os::Linux | Os::None => 128,
460 };
461 let long_double_format = match (triple.arch, long_double_width) {
462 (_, 64) => Format::Double,
463 // The one place two targets agree on the width and disagree on the type.
464 (Arch::X86_64, _) => Format::X87Extended,
465 (Arch::Aarch64 | Arch::Riscv64, _) => Format::Quad,
466 };
467 let float64x_format = match triple.arch {
468 Arch::X86_64 => Format::X87Extended,
469 Arch::Aarch64 | Arch::Riscv64 => Format::Quad,
470 };
471 let bit_int_granule = match triple.arch {
472 Arch::Aarch64 => 128,
473 Arch::X86_64 | Arch::Riscv64 => 64,
474 };
475 // Windows makes `wchar_t` 16 bits so that a wide string is UTF-16, and AArch64 Linux
476 // makes it unsigned the way it makes plain `char` unsigned. Neither follows from
477 // anything else here, which is why both are their own field.
478 let wchar_width = if triple.os == Os::Windows { 16 } else { 32 };
479 let wchar_is_signed = !matches!(
480 (triple.arch, triple.os),
481 (_, Os::Windows) | (Arch::Aarch64, Os::Linux | Os::None)
482 );
483 let va_list = match (triple.arch, triple.os) {
484 // Windows passes every argument in one place and spills the register ones next to
485 // the stack ones, so the list is an address, and Apple does the same on AArch64.
486 (_, Os::Windows) | (Arch::Aarch64, Os::Darwin) => VaList::CharPointer,
487 (Arch::X86_64, _) => VaList::SysV,
488 (Arch::Aarch64, _) => VaList::Aapcs,
489 (Arch::Riscv64, _) => VaList::VoidPointer,
490 };
491 // AArch64 and RISC-V have register files and this crate has not written them down yet.
492 // They arrive with the backends that need them, in M6 and M7.
493 let regs = match triple.arch {
494 Arch::X86_64 => &x86_64::REGS,
495 Arch::Aarch64 | Arch::Riscv64 => &RegFile::EMPTY,
496 };
497 let call_regs = match (triple.arch, triple.os) {
498 (Arch::X86_64, Os::Windows) => Some(&x86_64::WIN64),
499 // Apple's x86-64 follows SysV, and its divergences from it are on AArch64.
500 (Arch::X86_64, _) => Some(&x86_64::SYSV),
501 (Arch::Aarch64 | Arch::Riscv64, _) => None,
502 };
503 Self {
504 triple,
505 pointer_width: triple.arch.pointer_width(),
506 little_endian: triple.arch.is_little_endian(),
507 char_is_signed,
508 long_width,
509 long_double_width,
510 long_double_format,
511 float64x_format,
512 wchar_width,
513 wchar_is_signed,
514 bit_int_granule,
515 // Eight bytes on all three, for the reason the field gives: it is the widest access
516 // this compiler writes an instruction for, and every one of these machines has a wider
517 // one that nothing here reaches.
518 lock_free_width: 64,
519 object_format: triple.os.object_format(),
520 va_list,
521 regs,
522 call_regs,
523 }
524 }
525
526 /// The largest an object may be on this target, in bytes.
527 ///
528 /// `PTRDIFF_MAX`, which is what C 6.5.6 needs it to be: subtracting two pointers into one
529 /// object has to have an answer, and the answer has a `ptrdiff_t` to fit in. So an object
530 /// of exactly this many bytes is allowed and one byte more is not, which is the line GCC
531 /// draws too. It is the only size limit in the compiler and every layout question that has
532 /// one asks here rather than at whatever its own arithmetic happens to overflow at.
533 #[must_use]
534 pub const fn max_object_size(&self) -> u64 {
535 (1u64 << (self.pointer_width - 1)) - 1
536 }
537}
538
539#[cfg(test)]
540mod tests {
541 use super::*;
542
543 #[test]
544 fn parses_a_four_field_triple() {
545 let t: Triple = "x86_64-unknown-linux-gnu".parse().unwrap();
546 assert_eq!(t, Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
547 }
548
549 #[test]
550 fn parses_a_triple_with_no_vendor() {
551 let t: Triple = "aarch64-linux-musl".parse().unwrap();
552 assert_eq!(t, Triple::new(Arch::Aarch64, Os::Linux, Env::Musl));
553 }
554
555 #[test]
556 fn accepts_the_common_aliases() {
557 let a: Triple = "arm64-apple-darwin".parse().unwrap();
558 let b: Triple = "aarch64-apple-darwin".parse().unwrap();
559 assert_eq!(a, b);
560 assert_eq!(a.env, Env::None);
561 }
562
563 #[test]
564 fn fills_in_the_default_environment() {
565 let t: Triple = "x86_64-unknown-linux".parse().unwrap();
566 assert_eq!(t.env, Env::Gnu);
567 let w: Triple = "x86_64-pc-windows".parse().unwrap();
568 assert_eq!(w.env, Env::Msvc);
569 }
570
571 #[test]
572 fn rejects_what_it_does_not_support() {
573 let e = "sparc64-unknown-linux-gnu".parse::<Triple>().unwrap_err();
574 assert_eq!(e.reason, "unknown architecture");
575 let e = "x86_64-unknown-plan9".parse::<Triple>().unwrap_err();
576 assert_eq!(e.reason, "unknown operating system");
577 }
578
579 #[test]
580 fn displays_in_a_normalised_form() {
581 let t: Triple = "amd64-linux-gnu".parse().unwrap();
582 assert_eq!(t.to_string(), "x86_64-unknown-linux-gnu");
583 }
584
585 #[test]
586 fn display_round_trips_through_parse() {
587 for s in [
588 "x86_64-unknown-linux-gnu",
589 "aarch64-unknown-darwin-none",
590 "riscv64-unknown-linux-musl",
591 ] {
592 let t: Triple = s.parse().unwrap();
593 assert_eq!(t.to_string().parse::<Triple>().unwrap(), t);
594 }
595 }
596
597 #[test]
598 fn char_signedness_follows_the_psabi() {
599 let x86 = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
600 let arm = TargetInfo::new("aarch64-unknown-linux-gnu".parse().unwrap());
601 let mac = TargetInfo::new("aarch64-apple-darwin".parse().unwrap());
602 assert!(x86.char_is_signed);
603 assert!(!arm.char_is_signed);
604 assert!(mac.char_is_signed, "Apple overrides AAPCS64 back to a signed char");
605 }
606
607 #[test]
608 fn windows_is_llp64() {
609 let win = TargetInfo::new("x86_64-pc-windows-msvc".parse().unwrap());
610 assert_eq!(win.pointer_width, 64);
611 assert_eq!(win.long_width, 32);
612 }
613
614 #[test]
615 fn the_largest_object_is_ptrdiff_max() {
616 // Half the address space less one, which is what a pointer subtraction across the whole
617 // of one object has to fit in. gcc 16 on x86-64 prints this same number when it refuses
618 // an array, and takes an object of exactly this many bytes.
619 for triple in ["x86_64-unknown-linux-gnu", "aarch64-apple-darwin", "x86_64-pc-windows-msvc"]
620 {
621 let target = TargetInfo::new(triple.parse().unwrap());
622 assert_eq!(target.max_object_size(), 9_223_372_036_854_775_807, "{triple}");
623 }
624 }
625
626 #[test]
627 fn apple_long_double_is_double() {
628 let mac = TargetInfo::new("aarch64-apple-darwin".parse().unwrap());
629 assert_eq!(mac.long_double_width, 64);
630 assert_eq!(mac.long_double_format, Format::Double);
631 let linux = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
632 assert_eq!(linux.long_double_width, 128);
633 }
634
635 #[test]
636 fn wchar_t_divides_the_targets_in_two_directions_at_once() {
637 // Windows narrows it to sixteen bits, which makes a wide string UTF-16 there and
638 // UTF-32 everywhere else, and AArch64 Linux makes it unsigned without narrowing it.
639 let windows = TargetInfo::new("x86_64-pc-windows-msvc".parse().unwrap());
640 assert_eq!((windows.wchar_width, windows.wchar_is_signed), (16, false));
641 let arm = TargetInfo::new("aarch64-unknown-linux-gnu".parse().unwrap());
642 assert_eq!((arm.wchar_width, arm.wchar_is_signed), (32, false));
643 let linux = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
644 assert_eq!((linux.wchar_width, linux.wchar_is_signed), (32, true));
645 // Apple keeps it signed on the same processor where Linux does not, in the same way it
646 // keeps plain `char` signed there.
647 let mac = TargetInfo::new("aarch64-apple-darwin".parse().unwrap());
648 assert_eq!((mac.wchar_width, mac.wchar_is_signed), (32, true));
649 }
650
651 #[test]
652 fn va_list_is_the_psabis_type_and_not_one_type_with_four_spellings() {
653 let linux = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
654 assert_eq!(linux.va_list, VaList::SysV);
655 // x86-64 Darwin follows SysV here, and AArch64 Darwin does not follow AAPCS64.
656 let mac = TargetInfo::new("x86_64-apple-darwin".parse().unwrap());
657 assert_eq!(mac.va_list, VaList::SysV);
658 let arm_mac = TargetInfo::new("aarch64-apple-darwin".parse().unwrap());
659 assert_eq!(arm_mac.va_list, VaList::CharPointer);
660 let arm = TargetInfo::new("aarch64-unknown-linux-gnu".parse().unwrap());
661 assert_eq!(arm.va_list, VaList::Aapcs);
662 // Windows passes everything one way on both processors, so both get the simple one.
663 let win = TargetInfo::new("x86_64-pc-windows-msvc".parse().unwrap());
664 assert_eq!(win.va_list, VaList::CharPointer);
665 let arm_win = TargetInfo::new("aarch64-pc-windows-msvc".parse().unwrap());
666 assert_eq!(arm_win.va_list, VaList::CharPointer);
667 let riscv = TargetInfo::new("riscv64-unknown-linux-gnu".parse().unwrap());
668 assert_eq!(riscv.va_list, VaList::VoidPointer);
669 }
670
671 #[test]
672 fn two_targets_agree_on_the_width_of_long_double_and_not_on_the_type() {
673 // Sixteen bytes on both, and a different number in them: the x87 format has sixty four
674 // bits of significand and quad precision has a hundred and thirteen, so a constant
675 // converted for one is the wrong bits for the other.
676 let x86 = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
677 let arm = TargetInfo::new("aarch64-unknown-linux-gnu".parse().unwrap());
678 assert_eq!(x86.long_double_width, arm.long_double_width);
679 assert_eq!(x86.long_double_format, Format::X87Extended);
680 assert_eq!(arm.long_double_format, Format::Quad);
681 assert_eq!(x86.long_double_format.precision(), 64);
682 assert_eq!(arm.long_double_format.precision(), 113);
683 // Windows keeps the name and drops the type, the way Apple does.
684 let windows = TargetInfo::new("x86_64-pc-windows-msvc".parse().unwrap());
685 assert_eq!(windows.long_double_format, Format::Double);
686 }
687
688 #[test]
689 fn float64x_follows_the_processor_where_long_double_follows_the_operating_system() {
690 // `_Float64x` is the widest format the hardware has, and no ABI takes it away the way
691 // Apple and Windows take `long double` away. So the two fields say the same thing on
692 // Linux and disagree everywhere else, which is the whole reason there are two of them.
693 let x86 = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
694 assert_eq!(x86.float64x_format, Format::X87Extended);
695 let arm = TargetInfo::new("aarch64-unknown-linux-gnu".parse().unwrap());
696 assert_eq!(arm.float64x_format, Format::Quad);
697 let riscv = TargetInfo::new("riscv64-unknown-linux-gnu".parse().unwrap());
698 assert_eq!(riscv.float64x_format, Format::Quad);
699
700 let mac = TargetInfo::new("aarch64-apple-darwin".parse().unwrap());
701 assert_eq!(mac.long_double_format, Format::Double);
702 assert_eq!(mac.float64x_format, Format::Quad);
703 let windows = TargetInfo::new("x86_64-pc-windows-msvc".parse().unwrap());
704 assert_eq!(windows.long_double_format, Format::Double);
705 assert_eq!(windows.float64x_format, Format::X87Extended);
706 }
707
708 #[test]
709 fn the_object_format_follows_the_operating_system() {
710 assert_eq!(Os::Linux.object_format(), ObjectFormat::Elf);
711 assert_eq!(Os::Darwin.object_format(), ObjectFormat::MachO);
712 assert_eq!(Os::Windows.object_format(), ObjectFormat::Coff);
713 }
714
715 #[test]
716 fn a_target_carries_its_registers_and_says_so_when_it_has_none() {
717 let of = |triple: &str| TargetInfo::new(triple.parse().unwrap());
718 let linux = of("x86_64-unknown-linux-gnu");
719 assert_eq!(linux.regs.reg_named("rdi"), Some((x86_64::GPR, x86_64::RDI)));
720 assert_eq!(linux.call_regs.map(|regs| regs.int_args[0]), Some(x86_64::RDI));
721 // Apple's x86-64 is SysV and Windows is the one that is not.
722 let apple = of("x86_64-apple-darwin");
723 assert_eq!(apple.call_regs.map(|regs| regs.int_args[0]), Some(x86_64::RDI));
724 let windows = of("x86_64-pc-windows-msvc");
725 assert_eq!(windows.regs.len(x86_64::GPR), 16);
726 assert_eq!(windows.call_regs.map(|regs| regs.int_args[0]), Some(x86_64::RCX));
727 // Not described yet, and saying nothing is the answer rather than saying x86-64's.
728 let arm = of("aarch64-unknown-linux-gnu");
729 assert!(arm.regs.is_empty());
730 assert!(arm.call_regs.is_none());
731 }
732
733 #[test]
734 fn the_host_triple_is_one_we_support() {
735 // Every host in spec/15-testing.md section 15.7 must be recognised, and CI runs on
736 // all three, so a failure here means a host we claim support for stopped resolving.
737 let host = Triple::host().expect("the host must be a supported target");
738 assert_eq!(host.to_string().parse::<Triple>().unwrap(), host);
739 }
740}