Skip to main content

rucc_tuple/
tuple.rs

1//! The tuple itself: ten fields, one canonical spelling, and a parser that refuses to guess.
2
3use core::fmt;
4use core::str::FromStr;
5
6use crate::{Abi, Arch, DataModel, Endian, Env, Error, ObjectFormat, Os, SubArch, Version};
7
8/// Everything about a target that changes the bytes the compiler emits.
9///
10/// The membership rule, from `spec/cross-compile/03-target-model.md` section 3.2: a fact belongs here if it
11/// changes how a function is called or how a struct is laid out. Everything else, the CPU
12/// model, the optimization level, the instruction set extensions, is a flag and lives outside.
13/// The rule is what makes the tuple usable as a cache key, and the cache is what makes the
14/// distribution in `spec/cross-compile/13-distribution.md` fit in the size budget, so this is load bearing
15/// rather than tidy.
16///
17/// Construct with [`TargetTuple::new`] or by parsing. The fields are private because six of the
18/// ten are derived from the other four, and a struct literal would let a caller build a
19/// combination that does not exist, such as Windows with an ELF object format.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
21pub struct TargetTuple {
22    arch: Arch,
23    sub_arch: SubArch,
24    endian: Endian,
25    data_model: DataModel,
26    os: Os,
27    os_version: Option<Version>,
28    env: Env,
29    env_version: Option<Version>,
30    abi: Abi,
31    object_format: ObjectFormat,
32}
33
34/// The parts a caller supplies, with the rest derived.
35///
36/// A builder rather than ten arguments, because eight of the ten calls in this workspace supply
37/// three of them and a function with seven defaulted parameters is a function nobody calls
38/// correctly.
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub struct TupleBuilder {
41    arch: Arch,
42    sub_arch: SubArch,
43    endian: Option<Endian>,
44    data_model: Option<DataModel>,
45    os: Os,
46    os_version: Option<Version>,
47    env: Option<Env>,
48    env_version: Option<Version>,
49    abi: Abi,
50}
51
52impl TupleBuilder {
53    /// Start from the two fields that have no default.
54    pub const fn new(arch: Arch, os: Os) -> Self {
55        TupleBuilder {
56            arch,
57            sub_arch: SubArch::None,
58            endian: None,
59            data_model: None,
60            os,
61            os_version: None,
62            env: None,
63            env_version: None,
64            abi: Abi::Default,
65        }
66    }
67
68    /// Set the baseline within the architecture family.
69    pub const fn sub_arch(mut self, sub_arch: SubArch) -> Self {
70        self.sub_arch = sub_arch;
71        self
72    }
73
74    /// Set the byte order, overriding the architecture's default.
75    pub const fn endian(mut self, endian: Endian) -> Self {
76        self.endian = Some(endian);
77        self
78    }
79
80    /// Set the data model, overriding the one derived from the architecture and OS. The only
81    /// target in the matrix that needs this is `x86_64-linux-gnux32`.
82    pub const fn data_model(mut self, data_model: DataModel) -> Self {
83        self.data_model = Some(data_model);
84        self
85    }
86
87    /// Set the OS version, which is a deployment target on Darwin and a preview number on WASI.
88    pub const fn os_version(mut self, version: Version) -> Self {
89        self.os_version = Some(version);
90        self
91    }
92
93    /// Set the environment, overriding the OS default.
94    pub const fn env(mut self, env: Env) -> Self {
95        self.env = Some(env);
96        self
97    }
98
99    /// Set the environment version, which is a glibc version or an Android API level.
100    pub const fn env_version(mut self, version: Version) -> Self {
101        self.env_version = Some(version);
102        self
103    }
104
105    /// Set the float ABI.
106    pub const fn abi(mut self, abi: Abi) -> Self {
107        self.abi = abi;
108        self
109    }
110
111    /// Derive the remaining fields and check the combination describes a machine.
112    ///
113    /// # Errors
114    ///
115    /// Returns the specific mismatch, never a generic failure. A caller that cannot say which
116    /// component the user got wrong produces a diagnostic the user cannot act on.
117    pub fn build(self) -> Result<TargetTuple, Error> {
118        let arch = self.arch;
119        let sub_arch = self.sub_arch;
120        if !sub_arch.belongs_to(arch) {
121            return Err(Error::SubArchMismatch { arch, sub_arch });
122        }
123
124        let endian = self.endian.unwrap_or(arch.default_endian());
125        if endian != arch.default_endian() && !arch.has_both_endians() {
126            return Err(Error::EndianUnsupported { arch, endian });
127        }
128
129        let os = self.os;
130        let env = self.env.unwrap_or(os.default_env());
131        if !env_is_valid_for(os, env) {
132            return Err(Error::EnvMismatch { os, env });
133        }
134
135        let abi = self.abi;
136        if !abi.is_valid_for(arch) {
137            return Err(Error::AbiMismatch { arch, abi });
138        }
139
140        if self.os_version.is_some() && !os.takes_version() {
141            return Err(Error::VersionNotAccepted { component: "operating system" });
142        }
143        if self.env_version.is_some() && !env.is_libc() {
144            return Err(Error::VersionNotAccepted { component: "environment" });
145        }
146
147        let data_model = self.data_model.unwrap_or_else(|| default_data_model(arch, os, env));
148        let object_format = os.object_format().unwrap_or(match arch {
149            Arch::Wasm32 => ObjectFormat::Wasm,
150            _ => ObjectFormat::Elf,
151        });
152
153        Ok(TargetTuple {
154            arch,
155            sub_arch,
156            endian,
157            data_model,
158            os,
159            os_version: self.os_version,
160            env,
161            env_version: self.env_version,
162            abi,
163            object_format,
164        })
165    }
166}
167
168/// Whether an OS and an environment go together.
169///
170/// Two rules rather than a table of pairs. A libc is selectable only on Linux, which is the one
171/// system where the user genuinely picks one. The Darwin ABI variants are legal only on Darwin.
172fn env_is_valid_for(os: Os, env: Env) -> bool {
173    match env {
174        Env::None => true,
175        Env::Gnu => matches!(os, Os::Linux | Os::Windows),
176        Env::Musl => matches!(os, Os::Linux),
177        Env::Android => matches!(os, Os::Linux),
178        Env::Msvc => matches!(os, Os::Windows),
179        Env::Simulator | Env::MacAbi => os.is_darwin(),
180    }
181}
182
183/// The widths of `int`, `long` and a pointer for a target that did not name them.
184///
185/// Windows is the whole reason this is not just a function of the architecture. A 64-bit
186/// Windows target has 64-bit pointers and a 32-bit `long`, and a compiler that reads the
187/// pointer width off the architecture and assumes `long` matches it produces structures whose
188/// layout disagrees with every Windows header.
189fn default_data_model(arch: Arch, os: Os, _env: Env) -> DataModel {
190    match arch {
191        Arch::X86 | Arch::Arm | Arch::Riscv32 | Arch::Wasm32 => DataModel::Ilp32,
192        _ => {
193            if matches!(os, Os::Windows) {
194                DataModel::Llp64
195            } else {
196                DataModel::Lp64
197            }
198        }
199    }
200}
201
202impl TargetTuple {
203    /// The common case: an architecture, an OS, and the OS's default environment.
204    ///
205    /// # Errors
206    ///
207    /// Returns the mismatch if the pair does not describe a machine.
208    pub fn new(arch: Arch, os: Os) -> Result<Self, Error> {
209        TupleBuilder::new(arch, os).build()
210    }
211
212    /// Start a builder for a target that needs more than an architecture and an OS.
213    pub const fn builder(arch: Arch, os: Os) -> TupleBuilder {
214        TupleBuilder::new(arch, os)
215    }
216
217    /// The instruction set family.
218    pub const fn arch(self) -> Arch {
219        self.arch
220    }
221
222    /// The baseline within the family.
223    pub const fn sub_arch(self) -> SubArch {
224        self.sub_arch
225    }
226
227    /// The byte order.
228    pub const fn endian(self) -> Endian {
229        self.endian
230    }
231
232    /// The widths of `int`, `long` and a pointer.
233    pub const fn data_model(self) -> DataModel {
234        self.data_model
235    }
236
237    /// The operating system.
238    pub const fn os(self) -> Os {
239        self.os
240    }
241
242    /// The OS version, which is a deployment target on Darwin and a preview number on WASI.
243    pub const fn os_version(self) -> Option<Version> {
244        self.os_version
245    }
246
247    /// The environment, which is the C library on Linux and the ABI variant elsewhere.
248    pub const fn env(self) -> Env {
249        self.env
250    }
251
252    /// The environment version, which is a glibc version or an Android API level.
253    pub const fn env_version(self) -> Option<Version> {
254        self.env_version
255    }
256
257    /// The float ABI as the tuple named it, which is [`Abi::Default`] unless the user was
258    /// explicit. Call [`TargetTuple::resolved_abi`] to get the one code generation uses.
259    pub const fn abi(self) -> Abi {
260        self.abi
261    }
262
263    /// The float ABI with the target's default filled in.
264    pub const fn resolved_abi(self) -> Abi {
265        self.abi.resolve(self.arch, self.sub_arch, self.os, self.env)
266    }
267
268    /// The container the compiler writes for this target.
269    pub const fn object_format(self) -> ObjectFormat {
270        self.object_format
271    }
272
273    /// The width of a pointer in bits, read from the data model rather than from the
274    /// architecture.
275    pub const fn pointer_width(self) -> u32 {
276        self.data_model.pointer_width()
277    }
278
279    /// Whether bytes are stored least significant first.
280    pub const fn is_little_endian(self) -> bool {
281        matches!(self.endian, Endian::Little)
282    }
283
284    /// Whether plain `char` is signed.
285    ///
286    /// Signed on x86 and wasm, unsigned on ARM, AArch64, RISC-V, LoongArch, PowerPC and s390x.
287    /// This is the classic first cross compilation bug, because a corpus written and tested on
288    /// x86-64 contains code that assumes `char` holds negative values and it passes until the day
289    /// it runs on ARM.
290    ///
291    /// s390x is the row worth naming, because the obvious guess is wrong. It is a big-endian
292    /// mainframe architecture with a signed everything else, and its `char` is unsigned, which the
293    /// ELF ABI supplement says and which `__CHAR_UNSIGNED__` from a cross compiler confirms.
294    ///
295    /// The operating system overrides the architecture twice. Windows says signed everywhere
296    /// because the Microsoft ABI does, and Darwin says signed on AArch64 because Apple kept it
297    /// that way for source compatibility with the Intel Macs, against what AAPCS64 says. So
298    /// `aarch64-macos` and `aarch64-linux-gnu` are the same architecture with opposite answers,
299    /// which is the pair most likely to catch a corpus out.
300    pub const fn char_is_signed(self) -> bool {
301        match self.arch {
302            Arch::X86_64 | Arch::X86 | Arch::Wasm32 => true,
303            Arch::Arm | Arch::Aarch64 | Arch::Arm64Ec => {
304                matches!(self.os, Os::Windows) || self.os.is_darwin()
305            }
306            Arch::Riscv64 | Arch::Riscv32 | Arch::LoongArch64 | Arch::PowerPc64 | Arch::S390x => {
307                false
308            }
309        }
310    }
311
312    /// Whether symbols carry a leading underscore.
313    pub const fn leading_underscore(self) -> bool {
314        self.object_format.leading_underscore(self.data_model)
315    }
316
317    /// The leading component of the canonical spelling, which is the architecture with its
318    /// baseline and byte order folded in.
319    ///
320    /// The folding is not decoration. `powerpc64le` and `armv7` and `aarch64_be` are what every
321    /// other toolchain writes, and a tuple that spelled them as separate components would not
322    /// paste into anybody's build script.
323    pub fn arch_component(self) -> String {
324        let mut s = String::new();
325        match self.arch {
326            Arch::PowerPc64 => {
327                s.push_str("powerpc64");
328                if matches!(self.endian, Endian::Little) {
329                    s.push_str("le");
330                }
331            }
332            Arch::Arm => {
333                s.push_str("arm");
334                s.push_str(self.sub_arch.as_str());
335                if matches!(self.endian, Endian::Big) {
336                    s.push_str("eb");
337                }
338            }
339            Arch::Aarch64 => {
340                s.push_str("aarch64");
341                if matches!(self.endian, Endian::Big) {
342                    s.push_str("_be");
343                }
344            }
345            other => {
346                s.push_str(other.as_str());
347                if self.endian != other.default_endian() {
348                    s.push_str("eb");
349                }
350            }
351        }
352        s
353    }
354
355    /// The OS component of the canonical spelling, with its version.
356    pub fn os_component(self) -> String {
357        match (self.os, self.os_version) {
358            (Os::Wasi, Some(v)) => format!("wasip{}", v.major_part()),
359            (Os::Wasi, None) => "wasi".to_string(),
360            (os, Some(v)) => format!("{os}.{v}"),
361            (os, None) => os.as_str().to_string(),
362        }
363    }
364
365    /// The environment component of the canonical spelling, with its version and with the
366    /// suffixes GCC fuses into it put back.
367    ///
368    /// Empty when there is nothing to say, and the caller drops the separator in that case, so
369    /// `x86_64-none` has two components and `armv7m-none-eabi` has three.
370    pub fn env_component(self) -> String {
371        self.env_component_with(true)
372    }
373
374    /// The environment component, optionally without the dotted version.
375    ///
376    /// The version is dropped for the LLVM spelling because LLVM has no place to put it: an
377    /// Android API level is part of the environment name there and a glibc version simply is
378    /// not expressible. Emitting `gnu.2.28` into a `.ll` file would produce a triple that LLVM
379    /// parses as an unknown environment, which is worse than losing the pin, and the pin is
380    /// still in the canonical spelling that everything of ours reads.
381    fn env_component_with(self, dotted_version: bool) -> String {
382        let mut s = String::new();
383        match (self.env, self.env_version) {
384            (Env::Android, Some(v)) => {
385                s.push_str("android");
386                s.push_str(&v.major_part().to_string());
387            }
388            (env, Some(v)) if dotted_version => {
389                s.push_str(env.as_str());
390                s.push('.');
391                s.push_str(&v.to_string());
392            }
393            (env, _) => s.push_str(env.as_str()),
394        }
395
396        if matches!(self.arch, Arch::Arm) {
397            match self.resolved_abi() {
398                Abi::DoubleFloat => s.push_str("eabihf"),
399                _ => s.push_str("eabi"),
400            }
401        } else if matches!(self.data_model, DataModel::Ilp32On64) {
402            s.push_str("x32");
403        }
404        s
405    }
406
407    /// The canonical spelling. This is what `--print-target-triple` answers and what the cache
408    /// is keyed on.
409    ///
410    /// It has no vendor component. The vendor field in a GNU triple has carried no information
411    /// since the last vendor that mattered stopped shipping a Unix, and every tool that reads
412    /// one has to special case `unknown`, `pc`, `none` and `w64` to get past it. The parser
413    /// accepts a vendor so that pasted triples work; the canonical form does not write one.
414    pub fn to_canonical_string(self) -> String {
415        let env = self.env_component();
416        if env.is_empty() {
417            format!("{}-{}", self.arch_component(), self.os_component())
418        } else {
419            format!("{}-{}-{}", self.arch_component(), self.os_component(), env)
420        }
421    }
422
423    /// The LLVM spelling, with a vendor and with a three component OS version.
424    ///
425    /// This exists because the tuple has to leave the building. A `.ll` file, an object file's
426    /// target metadata, a `--target` handed to an external tool and a user pasting from a Clang
427    /// invocation all speak LLVM's dialect, and `--print-llvm-triple` is the flag that says what
428    /// it would be. `spec/cross-compile/12-driver.md` section 12.3 keeps the two flags separate rather than
429    /// picking one spelling and making half the users translate.
430    pub fn to_llvm_string(self) -> String {
431        let vendor = match self.os {
432            Os::MacOs | Os::IOs => "apple",
433            Os::Windows => match self.env {
434                Env::Gnu => "w64",
435                _ => "pc",
436            },
437            Os::Illumos => "pc",
438            _ => "unknown",
439        };
440
441        let os = match (self.os, self.os_version) {
442            (Os::MacOs, Some(v)) => format!("macosx{}", v.to_llvm_string()),
443            (Os::MacOs, None) => "macosx".to_string(),
444            (Os::IOs, Some(v)) => format!("ios{}", v.to_llvm_string()),
445            (Os::Wasi, Some(v)) if v.major_part() == 1 => "wasi".to_string(),
446            (Os::Wasi, Some(v)) => format!("wasip{}", v.major_part()),
447            (Os::None, _) => "none".to_string(),
448            (os, _) => os.as_str().to_string(),
449        };
450
451        let env = self.env_component_with(false);
452        if env.is_empty() {
453            format!("{}-{}-{}", self.arch_component(), vendor, os)
454        } else {
455            format!("{}-{}-{}-{}", self.arch_component(), vendor, os, env)
456        }
457    }
458}
459
460impl fmt::Display for TargetTuple {
461    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
462        f.write_str(&self.to_canonical_string())
463    }
464}
465
466impl FromStr for TargetTuple {
467    type Err = Error;
468
469    fn from_str(s: &str) -> Result<Self, Self::Err> {
470        crate::parse::parse(s)
471    }
472}