cc/target/
llvm.rs

1use std::borrow::Cow;
2
3use super::{generated, TargetInfo};
4
5impl TargetInfo<'_> {
6    /// The LLVM/Clang target triple.
7    ///
8    /// See <https://clang.llvm.org/docs/CrossCompilation.html#target-triple>.
9    ///
10    /// Rust and Clang don't really agree on target naming, so we first try to
11    /// find the matching trible based on `rustc`'s output, but if no such
12    /// triple exists, we attempt to construct the triple from scratch.
13    ///
14    /// NOTE: You should never need to match on this explicitly, use the
15    /// fields on [`TargetInfo`] instead.
16    pub(crate) fn llvm_target(
17        &self,
18        rustc_target: &str,
19        version: Option<&str>,
20    ) -> Cow<'static, str> {
21        if rustc_target == "armv7-apple-ios" {
22            // FIXME(madsmtm): Unnecessary once we bump MSRV to Rust 1.74
23            return Cow::Borrowed("armv7-apple-ios");
24        } else if self.os == "uefi" {
25            // Override the UEFI LLVM targets.
26            //
27            // The rustc mappings (as of 1.82) for the UEFI targets are:
28            // * i686-unknown-uefi -> i686-unknown-windows-gnu
29            // * x86_64-unknown-uefi -> x86_64-unknown-windows
30            // * aarch64-unknown-uefi -> aarch64-unknown-windows
31            //
32            // However, in cc-rs all the UEFI targets use
33            // -windows-gnu. This has been the case since 2021 [1].
34            // * i686-unknown-uefi -> i686-unknown-windows-gnu
35            // * x86_64-unknown-uefi -> x86_64-unknown-windows-gnu
36            // * aarch64-unknown-uefi -> aarch64-unknown-windows-gnu
37            //
38            // For now, override the UEFI mapping to keep the behavior
39            // of cc-rs unchanged.
40            //
41            // TODO: as discussed in [2], it may be possible to switch
42            // to new UEFI targets added to clang, and regardless it
43            // would be good to have consistency between rustc and
44            // cc-rs.
45            //
46            // [1]: https://github.com/rust-lang/cc-rs/pull/623
47            // [2]: https://github.com/rust-lang/cc-rs/pull/1264
48            return Cow::Owned(format!("{}-unknown-windows-gnu", self.full_arch));
49        }
50
51        // If no version is requested, let's take the triple directly from
52        // `rustc` (the logic below is not yet good enough for most targets).
53        //
54        // FIXME(madsmtm): This should ideally be removed.
55        if version.is_none() {
56            if let Ok(index) = generated::LLVM_TARGETS
57                .binary_search_by_key(&rustc_target, |(rustc_target, _)| rustc_target)
58            {
59                let (_, llvm_target) = &generated::LLVM_TARGETS[index];
60                return Cow::Borrowed(llvm_target);
61            }
62        }
63
64        // Otherwise, attempt to construct the triple from the target info.
65
66        let arch = match self.full_arch {
67            riscv32 if riscv32.starts_with("riscv32") => "riscv32",
68            riscv64 if riscv64.starts_with("riscv64") => "riscv64",
69            "aarch64" if self.vendor == "apple" => "arm64",
70            "armv7" if self.vendor == "sony" => "thumbv7a", // FIXME
71            arch => arch,
72        };
73        let vendor = match self.vendor {
74            "kmc" | "nintendo" => "unknown",
75            "unknown" if self.os == "android" => "linux",
76            "uwp" => "pc",
77            "espressif" => "",
78            _ if self.arch == "msp430" => "",
79            vendor => vendor,
80        };
81        let os = match self.os {
82            "macos" => "macosx",
83            "visionos" => "xros",
84            "uefi" => "windows",
85            "solid_asp3" | "horizon" | "teeos" | "nuttx" | "espidf" => "none",
86            "nto" => "unknown",    // FIXME
87            "trusty" => "unknown", // FIXME
88            os => os,
89        };
90        let version = version.unwrap_or("");
91        let env = match self.env {
92            "newlib" | "nto70" | "nto71" | "nto71_iosock" | "p1" | "p2" | "relibc" | "sgx"
93            | "uclibc" => "",
94            env => env,
95        };
96        let abi = match self.abi {
97            "sim" => "simulator",
98            "llvm" | "softfloat" | "uwp" | "vec-extabi" => "",
99            "ilp32" => "_ilp32",
100            "abi64" => "",
101            abi => abi,
102        };
103        Cow::Owned(match (vendor, env, abi) {
104            ("", "", "") => format!("{arch}-{os}{version}"),
105            ("", env, abi) => format!("{arch}-{os}{version}-{env}{abi}"),
106            (vendor, "", "") => format!("{arch}-{vendor}-{os}{version}"),
107            (vendor, env, abi) => format!("{arch}-{vendor}-{os}{version}-{env}{abi}"),
108        })
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use std::process::Command;
115
116    use crate::TargetInfo;
117
118    #[test]
119    fn test_old_ios_target() {
120        assert_eq!(
121            TargetInfo {
122                full_arch: "armv7",
123                arch: "armv7",
124                vendor: "apple",
125                os: "ios",
126                env: "",
127                abi: "",
128            }
129            .llvm_target("armv7-apple-ios", None),
130            "armv7-apple-ios"
131        );
132    }
133
134    #[test]
135    fn basic_llvm_triple_guessing() {
136        assert_eq!(
137            TargetInfo {
138                full_arch: "aarch64",
139                arch: "aarch64",
140                vendor: "unknown",
141                os: "linux",
142                env: "",
143                abi: "",
144            }
145            .llvm_target("invalid", None),
146            "aarch64-unknown-linux"
147        );
148        assert_eq!(
149            TargetInfo {
150                full_arch: "x86_64",
151                arch: "x86_64",
152                vendor: "unknown",
153                os: "linux",
154                env: "gnu",
155                abi: "",
156            }
157            .llvm_target("invalid", None),
158            "x86_64-unknown-linux-gnu"
159        );
160        assert_eq!(
161            TargetInfo {
162                full_arch: "x86_64",
163                arch: "x86_64",
164                vendor: "unknown",
165                os: "linux",
166                env: "gnu",
167                abi: "eabi",
168            }
169            .llvm_target("invalid", None),
170            "x86_64-unknown-linux-gnueabi"
171        );
172        assert_eq!(
173            TargetInfo {
174                full_arch: "x86_64",
175                arch: "x86_64",
176                vendor: "apple",
177                os: "macos",
178                env: "",
179                abi: "",
180            }
181            .llvm_target("invalid", None),
182            "x86_64-apple-macosx"
183        );
184    }
185
186    #[test]
187    fn llvm_version() {
188        assert_eq!(
189            TargetInfo {
190                full_arch: "aarch64",
191                arch: "aarch64",
192                vendor: "apple",
193                os: "ios",
194                env: "",
195                abi: "sim",
196            }
197            .llvm_target("aarch64-apple-ios-sim", Some("14.0")),
198            "arm64-apple-ios14.0-simulator"
199        );
200        assert_eq!(
201            TargetInfo {
202                full_arch: "aarch64",
203                arch: "aarch64",
204                vendor: "apple",
205                os: "visionos",
206                env: "",
207                abi: "",
208            }
209            .llvm_target("aarch64-apple-visionos", Some("2.0")),
210            "arm64-apple-xros2.0"
211        );
212        assert_eq!(
213            TargetInfo {
214                full_arch: "aarch64",
215                arch: "aarch64",
216                vendor: "apple",
217                os: "ios",
218                env: "",
219                abi: "macabi",
220            }
221            .llvm_target("aarch64-apple-ios-macabi", Some("13.1")),
222            "arm64-apple-ios13.1-macabi"
223        );
224    }
225
226    #[test]
227    fn uefi() {
228        assert_eq!(
229            TargetInfo {
230                full_arch: "i686",
231                arch: "x86",
232                vendor: "unknown",
233                os: "uefi",
234                env: "",
235                abi: "",
236            }
237            .llvm_target("i686-unknown-uefi", None),
238            "i686-unknown-windows-gnu"
239        );
240        assert_eq!(
241            TargetInfo {
242                full_arch: "x86_64",
243                arch: "x86_64",
244                vendor: "unknown",
245                os: "uefi",
246                env: "",
247                abi: "",
248            }
249            .llvm_target("x86_64-unknown-uefi", None),
250            "x86_64-unknown-windows-gnu"
251        );
252        assert_eq!(
253            TargetInfo {
254                full_arch: "aarch64",
255                arch: "aarch64",
256                vendor: "unknown",
257                os: "uefi",
258                env: "",
259                abi: "",
260            }
261            .llvm_target("aarch64-unknown-uefi", None),
262            "aarch64-unknown-windows-gnu"
263        );
264    }
265
266    #[test]
267    #[ignore = "not yet done"]
268    fn llvm_for_all_rustc_targets() {
269        let rustc = std::env::var("RUSTC").unwrap_or_else(|_| "rustc".to_string());
270
271        let target_list = Command::new(&rustc)
272            .arg("--print=target-list")
273            .output()
274            .unwrap()
275            .stdout;
276        let target_list = String::from_utf8(target_list).unwrap();
277
278        let mut has_failure = false;
279        for target in target_list.lines() {
280            let spec_json = Command::new(&rustc)
281                .arg("--target")
282                .arg(target)
283                .arg("-Zunstable-options")
284                .arg("--print=target-spec-json")
285                .env("RUSTC_BOOTSTRAP", "1") // Crimes
286                .output()
287                .unwrap()
288                .stdout;
289            let spec_json = String::from_utf8(spec_json).unwrap();
290
291            // JSON crimes
292            let expected = spec_json
293                .split_once("llvm-target\": \"")
294                .unwrap()
295                .1
296                .split_once("\"")
297                .unwrap()
298                .0;
299            let actual = TargetInfo::from_rustc_target(target)
300                .map(|target| target.llvm_target("invalid", None));
301
302            if Some(expected) != actual.as_deref().ok() {
303                eprintln!("failed comparing {target}:");
304                eprintln!("  expected: Ok({expected:?})");
305                eprintln!("    actual: {actual:?}");
306                eprintln!();
307                has_failure = true;
308            }
309        }
310
311        if has_failure {
312            panic!("failed comparing targets");
313        }
314    }
315}