• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: Apache-2.0 OR MIT
2 
3 // The rustc-cfg emitted by the build script are *not* public API.
4 
5 #![allow(clippy::match_same_arms)] // https://github.com/rust-lang/rust-clippy/issues/12044
6 
7 #[path = "version.rs"]
8 mod version;
9 use version::{rustc_version, Version};
10 
11 use std::{env, str};
12 
13 include!("no_atomic.rs");
14 
main()15 fn main() {
16     println!("cargo:rerun-if-changed=build.rs");
17     println!("cargo:rerun-if-changed=no_atomic.rs");
18     println!("cargo:rerun-if-changed=version.rs");
19 
20     #[cfg(feature = "unsafe-assume-single-core")]
21     println!("cargo:rustc-cfg=portable_atomic_unsafe_assume_single_core");
22     #[cfg(feature = "s-mode")]
23     println!("cargo:rustc-cfg=portable_atomic_s_mode");
24     #[cfg(feature = "force-amo")]
25     println!("cargo:rustc-cfg=portable_atomic_force_amo");
26     #[cfg(feature = "disable-fiq")]
27     println!("cargo:rustc-cfg=portable_atomic_disable_fiq");
28 
29     let target = &*env::var("TARGET").expect("TARGET not set");
30     let target_arch = &*env::var("CARGO_CFG_TARGET_ARCH").expect("CARGO_CFG_TARGET_ARCH not set");
31     let target_os = &*env::var("CARGO_CFG_TARGET_OS").expect("CARGO_CFG_TARGET_OS not set");
32 
33     let version = match rustc_version() {
34         Some(version) => version,
35         None => {
36             if env::var_os("PORTABLE_ATOMIC_DENY_WARNINGS").is_some() {
37                 panic!("unable to determine rustc version")
38             }
39             println!(
40                 "cargo:warning={}: unable to determine rustc version; assuming latest stable rustc (1.{})",
41                 env!("CARGO_PKG_NAME"),
42                 Version::LATEST.minor
43             );
44             Version::LATEST
45         }
46     };
47 
48     if version.minor >= 80 {
49         println!(
50             r#"cargo:rustc-check-cfg=cfg(target_feature,values("zaamo","zabha","experimental-zacas","fast-serialization","load-store-on-cond","distinct-ops","miscellaneous-extensions-3"))"#
51         );
52 
53         // Custom cfgs set by build script. Not public API.
54         // grep -F 'cargo:rustc-cfg=' build.rs | grep -Ev '^ *//' | sed -E 's/^.*cargo:rustc-cfg=//; s/(=\\)?".*$//' | LC_ALL=C sort -u | tr '\n' ',' | sed -E 's/,$/\n/'
55         println!(
56             "cargo:rustc-check-cfg=cfg(portable_atomic_disable_fiq,portable_atomic_force_amo,portable_atomic_ll_sc_rmw,portable_atomic_pre_llvm_15,portable_atomic_pre_llvm_16,portable_atomic_pre_llvm_18,portable_atomic_pre_llvm_19,portable_atomic_new_atomic_intrinsics,portable_atomic_no_asm,portable_atomic_no_asm_maybe_uninit,portable_atomic_no_atomic_64,portable_atomic_no_atomic_cas,portable_atomic_no_atomic_load_store,portable_atomic_no_atomic_min_max,portable_atomic_no_cfg_target_has_atomic,portable_atomic_no_cmpxchg16b_intrinsic,portable_atomic_no_cmpxchg16b_target_feature,portable_atomic_no_const_raw_ptr_deref,portable_atomic_no_const_transmute,portable_atomic_no_core_unwind_safe,portable_atomic_no_diagnostic_namespace,portable_atomic_no_stronger_failure_ordering,portable_atomic_no_track_caller,portable_atomic_no_unsafe_op_in_unsafe_fn,portable_atomic_s_mode,portable_atomic_sanitize_thread,portable_atomic_target_feature,portable_atomic_unsafe_assume_single_core,portable_atomic_unstable_asm,portable_atomic_unstable_asm_experimental_arch,portable_atomic_unstable_cfg_target_has_atomic,portable_atomic_unstable_isa_attribute)"
57         );
58         // TODO: handle multi-line target_feature_fallback
59         // grep -F 'target_feature_fallback("' build.rs | grep -Ev '^ *//' | sed -E 's/^.*target_feature_fallback\(//; s/",.*$/"/' | LC_ALL=C sort -u | tr '\n' ',' | sed -E 's/,$/\n/'
60         println!(
61             r#"cargo:rustc-check-cfg=cfg(portable_atomic_target_feature,values("cmpxchg16b","distinct-ops","experimental-zacas","fast-serialization","load-store-on-cond","lse","lse128","lse2","mclass","miscellaneous-extensions-3","quadword-atomics","rcpc3","v6","zaamo","zabha"))"#
62         );
63     }
64 
65     // https://github.com/rust-lang/rust/pull/123745 (includes https://github.com/rust-lang/cargo/pull/13560) merged in Rust 1.79 (nightly-2024-04-11).
66     if !version.probe(79, 2024, 4, 10) {
67         // HACK: If --target is specified, rustflags is not applied to the build
68         // script itself, so the build script will not be recompiled when rustflags
69         // is changed. That in itself is not a problem, but the old Cargo does
70         // not rerun the build script as well, which can be problematic.
71         // https://github.com/rust-lang/cargo/issues/13003
72         // This problem has been fixed in 1.79 so only older versions need a workaround.
73         println!("cargo:rerun-if-env-changed=CARGO_ENCODED_RUSTFLAGS");
74         println!("cargo:rerun-if-env-changed=RUSTFLAGS");
75         println!("cargo:rerun-if-env-changed=CARGO_BUILD_RUSTFLAGS");
76         let mut target_upper = target.replace(|c: char| c == '-' || c == '.', "_");
77         target_upper.make_ascii_uppercase();
78         println!("cargo:rerun-if-env-changed=CARGO_TARGET_{}_RUSTFLAGS", target_upper);
79     }
80 
81     // Note that cfgs are `no_`*, not `has_*`. This allows treating as the latest
82     // stable rustc is used when the build script doesn't run. This is useful
83     // for non-cargo build systems that don't run the build script.
84 
85     // atomic_min_max stabilized in Rust 1.45 (nightly-2020-05-30): https://github.com/rust-lang/rust/pull/72324
86     if !version.probe(45, 2020, 5, 29) {
87         println!("cargo:rustc-cfg=portable_atomic_no_atomic_min_max");
88     }
89     // track_caller stabilized in Rust 1.46 (nightly-2020-07-02): https://github.com/rust-lang/rust/pull/72445
90     if !version.probe(46, 2020, 7, 1) {
91         println!("cargo:rustc-cfg=portable_atomic_no_track_caller");
92     }
93     // unsafe_op_in_unsafe_fn stabilized in Rust 1.52 (nightly-2021-03-11): https://github.com/rust-lang/rust/pull/79208
94     if !version.probe(52, 2021, 3, 10) {
95         println!("cargo:rustc-cfg=portable_atomic_no_unsafe_op_in_unsafe_fn");
96     }
97     // const_transmute stabilized in Rust 1.56 (nightly-2021-07-29): https://github.com/rust-lang/rust/pull/85769
98     if !version.probe(56, 2021, 7, 28) {
99         println!("cargo:rustc-cfg=portable_atomic_no_const_transmute");
100     }
101     // https://github.com/rust-lang/rust/pull/84662 merged in Rust 1.56 (nightly-2021-08-02).
102     if !version.probe(56, 2021, 8, 1) {
103         println!("cargo:rustc-cfg=portable_atomic_no_core_unwind_safe");
104     }
105     // const_raw_ptr_deref stabilized in Rust 1.58 (nightly-2021-11-15): https://github.com/rust-lang/rust/pull/89551
106     if !version.probe(58, 2021, 11, 14) {
107         println!("cargo:rustc-cfg=portable_atomic_no_const_raw_ptr_deref");
108     }
109     // https://github.com/rust-lang/rust/pull/98383 merged in Rust 1.64 (nightly-2022-07-19).
110     if !version.probe(64, 2022, 7, 18) {
111         println!("cargo:rustc-cfg=portable_atomic_no_stronger_failure_ordering");
112     }
113     // https://github.com/rust-lang/rust/pull/114790 merged in nightly-2023-08-24
114     if !version.probe(74, 2023, 8, 23) {
115         println!("cargo:rustc-cfg=portable_atomic_no_asm_maybe_uninit");
116     }
117     // #[diagnostic] stabilized in Rust 1.78 (nightly-2024-03-09): https://github.com/rust-lang/rust/pull/119888
118     if !version.probe(78, 2024, 3, 8) {
119         println!("cargo:rustc-cfg=portable_atomic_no_diagnostic_namespace");
120     }
121 
122     // asm stabilized in Rust 1.59 (nightly-2021-12-16): https://github.com/rust-lang/rust/pull/91728
123     let no_asm = !version.probe(59, 2021, 12, 15);
124     if no_asm {
125         if version.nightly
126             && version.probe(46, 2020, 6, 20)
127             && ((target_arch != "x86" && target_arch != "x86_64") || version.llvm >= 10)
128             && is_allowed_feature("asm")
129         {
130             // This feature was added in Rust 1.45 (nightly-2020-05-20), but
131             // concat! in asm! requires Rust 1.46 (nightly-2020-06-21).
132             // x86 intel syntax requires LLVM 10 (since Rust 1.53, the minimum
133             // external LLVM version is 10+: https://github.com/rust-lang/rust/pull/83387).
134             // The part of this feature we use has not been changed since nightly-2020-06-21
135             // until it was stabilized in nightly-2021-12-16, so it can be safely enabled in
136             // nightly, which is older than nightly-2021-12-16.
137             println!("cargo:rustc-cfg=portable_atomic_unstable_asm");
138         }
139         println!("cargo:rustc-cfg=portable_atomic_no_asm");
140     }
141 
142     // feature(cfg_target_has_atomic) stabilized in Rust 1.60 (nightly-2022-02-11): https://github.com/rust-lang/rust/pull/93824
143     if !version.probe(60, 2022, 2, 10) {
144         if version.nightly
145             && version.probe(40, 2019, 10, 13)
146             && is_allowed_feature("cfg_target_has_atomic")
147         {
148             // This feature has not been changed since the change in Rust 1.40 (nightly-2019-10-14)
149             // until it was stabilized in nightly-2022-02-11, so it can be safely enabled in
150             // nightly, which is older than nightly-2022-02-11.
151             println!("cargo:rustc-cfg=portable_atomic_unstable_cfg_target_has_atomic");
152         } else {
153             println!("cargo:rustc-cfg=portable_atomic_no_cfg_target_has_atomic");
154             let target = &*convert_custom_linux_target(target);
155             if NO_ATOMIC_CAS.contains(&target) {
156                 println!("cargo:rustc-cfg=portable_atomic_no_atomic_cas");
157             }
158             if NO_ATOMIC_64.contains(&target) {
159                 println!("cargo:rustc-cfg=portable_atomic_no_atomic_64");
160             } else {
161                 // Otherwise, assuming `"max-atomic-width" == 64` or `"max-atomic-width" == 128`.
162             }
163         }
164     }
165     // We don't need to use convert_custom_linux_target here because all linux targets have atomics.
166     if NO_ATOMIC.contains(&target) {
167         println!("cargo:rustc-cfg=portable_atomic_no_atomic_load_store");
168     }
169 
170     if version.llvm < 19 {
171         println!("cargo:rustc-cfg=portable_atomic_pre_llvm_19");
172         if version.llvm < 18 {
173             println!("cargo:rustc-cfg=portable_atomic_pre_llvm_18");
174             if version.llvm < 16 {
175                 println!("cargo:rustc-cfg=portable_atomic_pre_llvm_16");
176                 if version.llvm < 15 {
177                     println!("cargo:rustc-cfg=portable_atomic_pre_llvm_15");
178                 }
179             }
180         }
181     }
182 
183     if version.nightly {
184         // `cfg(sanitize = "..")` is not stabilized.
185         let sanitize = env::var("CARGO_CFG_SANITIZE").unwrap_or_default();
186         if sanitize.contains("thread") {
187             // Most kinds of sanitizers are not compatible with asm
188             // (https://github.com/google/sanitizers/issues/192),
189             // but it seems that ThreadSanitizer is the only one that can cause
190             // false positives in our code.
191             println!("cargo:rustc-cfg=portable_atomic_sanitize_thread");
192         }
193 
194         // https://github.com/rust-lang/rust/pull/93868 merged in Rust 1.60 (nightly-2022-02-13).
195         // https://github.com/rust-lang/rust/pull/111331 merged in Rust 1.71 (nightly-2023-05-09).
196         if !no_asm
197             && (target_arch == "powerpc64" && version.probe(60, 2022, 2, 12)
198                 || target_arch == "s390x" && version.probe(71, 2023, 5, 8)
199                 || target_arch == "arm64ec")
200             && is_allowed_feature("asm_experimental_arch")
201         {
202             println!("cargo:rustc-cfg=portable_atomic_unstable_asm_experimental_arch");
203         }
204     }
205 
206     match target_arch {
207         "x86_64" => {
208             // cmpxchg16b_target_feature stabilized in Rust 1.69 (nightly-2023-03-01): https://github.com/rust-lang/rust/pull/106774
209             if !version.probe(69, 2023, 2, 28) {
210                 println!("cargo:rustc-cfg=portable_atomic_no_cmpxchg16b_target_feature");
211             }
212             // For Miri and ThreadSanitizer.
213             // https://github.com/rust-lang/rust/pull/109359 (includes https://github.com/rust-lang/stdarch/pull/1358) merged in Rust 1.70 (nightly-2023-03-24).
214             if version.nightly && !version.probe(70, 2023, 3, 23) {
215                 println!("cargo:rustc-cfg=portable_atomic_no_cmpxchg16b_intrinsic");
216             }
217 
218             // cmpxchg16b_target_feature stabilized in Rust 1.69.
219             if needs_target_feature_fallback(&version, Some(69)) {
220                 // x86_64 Apple targets always support CMPXCHG16B:
221                 // https://github.com/rust-lang/rust/blob/1.68.0/compiler/rustc_target/src/spec/x86_64_apple_darwin.rs#L8
222                 // https://github.com/rust-lang/rust/blob/1.68.0/compiler/rustc_target/src/spec/apple_base.rs#L69-L70
223                 // (Since Rust 1.78, Windows (except Windows 7) targets also enable CMPXCHG16B, but
224                 // this branch is only used on pre-1.69 that cmpxchg16b_target_feature is unstable.)
225                 // Script to get builtin targets that support CMPXCHG16B by default:
226                 // $ (for target in $(rustc --print target-list | grep -E '^x86_64'); do rustc --print cfg --target "${target}" | grep -Fq '"cmpxchg16b"' && printf '%s\n' "${target}"; done)
227                 let is_apple = env::var("CARGO_CFG_TARGET_VENDOR").unwrap_or_default() == "apple";
228                 let has_cmpxchg16b = is_apple;
229                 // LLVM recognizes this also as cx16 target feature: https://godbolt.org/z/KM3jz616j
230                 // However, it is unlikely that rustc will support that name, so we ignore it.
231                 target_feature_fallback("cmpxchg16b", has_cmpxchg16b);
232             }
233         }
234         "aarch64" | "arm64ec" => {
235             // For Miri and ThreadSanitizer.
236             // https://github.com/rust-lang/rust/pull/97423 merged in Rust 1.64 (nightly-2022-06-30).
237             if version.nightly && version.probe(64, 2022, 6, 29) {
238                 println!("cargo:rustc-cfg=portable_atomic_new_atomic_intrinsics");
239             }
240 
241             // target_feature "lse2"/"lse128"/"rcpc3" is unstable and available on rustc side since nightly-2024-08-30: https://github.com/rust-lang/rust/pull/128192
242             if !version.probe(82, 2024, 8, 29) || needs_target_feature_fallback(&version, None) {
243                 // FEAT_LSE2 doesn't imply FEAT_LSE. FEAT_LSE128 implies FEAT_LSE but not FEAT_LSE2.
244                 // AArch64 macOS always supports FEAT_LSE and FEAT_LSE2 because M1 is Armv8.4 with all features of Armv8.5 except FEAT_BTI:
245                 // https://github.com/llvm/llvm-project/blob/llvmorg-19.1.0/llvm/lib/Target/AArch64/AArch64Processors.td#L1203
246                 // https://github.com/llvm/llvm-project/blob/llvmorg-19.1.0/llvm/lib/Target/AArch64/AArch64Processors.td#L865
247                 // Script to get builtin targets that support FEAT_LSE/FEAT_LSE2 by default:
248                 // $ (for target in $(rustc --print target-list | grep -E '^aarch64|^arm64'); do rustc --print cfg --target "${target}" | grep -Fq '"lse"' && printf '%s\n' "${target}"; done)
249                 // $ (for target in $(rustc --print target-list | grep -E '^aarch64|^arm64'); do rustc --print cfg --target "${target}" | grep -Fq '"lse2"' && printf '%s\n' "${target}"; done)
250                 let is_macos = target_os == "macos";
251                 let mut has_lse = is_macos;
252                 target_feature_fallback("lse2", is_macos);
253                 has_lse |= target_feature_fallback("lse128", false);
254                 target_feature_fallback("rcpc3", false);
255                 // aarch64_target_feature stabilized in Rust 1.61.
256                 if needs_target_feature_fallback(&version, Some(61)) {
257                     target_feature_fallback("lse", has_lse);
258                 }
259             }
260 
261             // As of Apple M1/M1 Pro, on Apple hardware, CAS-loop-based RMW is much slower than
262             // LL/SC-loop-based RMW: https://github.com/taiki-e/portable-atomic/pull/89
263             let is_apple = env::var("CARGO_CFG_TARGET_VENDOR").unwrap_or_default() == "apple";
264             if is_apple || target_cpu().map_or(false, |cpu| cpu.starts_with("apple-")) {
265                 println!("cargo:rustc-cfg=portable_atomic_ll_sc_rmw");
266             }
267         }
268         "arm" => {
269             // For non-Linux/Android pre-v6 Arm (tier 3) with unsafe_assume_single_core enabled.
270             // feature(isa_attribute) stabilized in Rust 1.67 (nightly-2022-11-06): https://github.com/rust-lang/rust/pull/102458
271             if version.nightly && !version.probe(67, 2022, 11, 5) {
272                 println!("cargo:rustc-cfg=portable_atomic_unstable_isa_attribute");
273             }
274 
275             if needs_target_feature_fallback(&version, None) {
276                 // #[cfg(target_feature = "v7")] and others don't work on stable.
277                 // armv7-unknown-linux-gnueabihf
278                 //    ^^
279                 let mut subarch =
280                     strip_prefix(target, "arm").or_else(|| strip_prefix(target, "thumb")).unwrap();
281                 subarch = strip_prefix(subarch, "eb").unwrap_or(subarch); // ignore endianness
282                 subarch = subarch.split('-').next().unwrap(); // ignore vender/os/env
283                 subarch = subarch.split('.').next().unwrap(); // ignore .base/.main suffix
284                 let mut known = true;
285                 // See https://github.com/taiki-e/atomic-maybe-uninit/blob/HEAD/build.rs for details
286                 let mut mclass = false;
287                 match subarch {
288                     "v7" | "v7a" | "v7neon" | "v7s" | "v7k" | "v8" | "v8a" | "v9" | "v9a" => {} // aclass
289                     "v7r" | "v8r" | "v9r" => {} // rclass
290                     "v6m" | "v7em" | "v7m" | "v8m" => mclass = true,
291                     // arm-linux-androideabi is v5te
292                     // https://github.com/rust-lang/rust/blob/1.80.0/compiler/rustc_target/src/spec/targets/arm_linux_androideabi.rs#L18
293                     _ if target == "arm-linux-androideabi" => subarch = "v5te",
294                     // armeb-unknown-linux-gnueabi is v8 & aclass
295                     // https://github.com/rust-lang/rust/blob/1.80.0/compiler/rustc_target/src/spec/targets/armeb_unknown_linux_gnueabi.rs#L18
296                     _ if target == "armeb-unknown-linux-gnueabi" => subarch = "v8",
297                     // Legacy Arm architectures (pre-v7 except v6m) don't have *class target feature.
298                     "" => subarch = "v6",
299                     "v4t" | "v5te" | "v6" | "v6k" => {}
300                     _ => {
301                         known = false;
302                         if env::var_os("PORTABLE_ATOMIC_DENY_WARNINGS").is_some() {
303                             panic!("unrecognized Arm subarch: {}", target)
304                         }
305                         println!(
306                             "cargo:warning={}: unrecognized Arm subarch: {}",
307                             env!("CARGO_PKG_NAME"),
308                             target
309                         );
310                     }
311                 }
312                 let v6 = known
313                     && (subarch.starts_with("v6")
314                         || subarch.starts_with("v7")
315                         || subarch.starts_with("v8")
316                         || subarch.starts_with("v9"));
317                 target_feature_fallback("v6", v6);
318                 target_feature_fallback("mclass", mclass);
319             }
320         }
321         "riscv32" | "riscv64" => {
322             // As of rustc 1.80, target_feature "zaamo"/"zabha"/"zacas" is not available on rustc side:
323             // https://github.com/rust-lang/rust/blob/1.80.0/compiler/rustc_target/src/target_features.rs#L273
324             // zabha and zacas imply zaamo in GCC, but do not in LLVM (but enabling them without zaamo is not allowed).
325             // https://github.com/llvm/llvm-project/blob/llvmorg-19.1.0/llvm/lib/TargetParser/RISCVISAInfo.cpp#L772-L778
326             // https://github.com/gcc-mirror/gcc/blob/08693e29ec186fd7941d0b73d4d466388971fe2f/gcc/config/riscv/arch-canonicalize#L45-L46
327             if version.llvm >= 19 {
328                 // amo*.{b,h}
329                 // available since LLVM 19 https://github.com/llvm/llvm-project/commit/89f87c387627150d342722b79c78cea2311cddf7 / https://github.com/llvm/llvm-project/commit/6b7444964a8d028989beee554a1f5c61d16a1cac
330                 target_feature_fallback("zabha", false);
331             }
332             if version.llvm == 19 {
333                 // amocas.{w,d,q} (and amocas.{b,h} if zabha is also available)
334                 // available as experimental since LLVM 17 https://github.com/llvm/llvm-project/commit/29f630a1ddcbb03caa31b5002f0cbc105ff3a869
335                 // attempted to make non-experimental in LLVM 19 https://github.com/llvm/llvm-project/commit/95aab69c109adf29e183090c25dc95c773215746
336                 // but reverted in https://github.com/llvm/llvm-project/commit/70e7d26e560173c8b9db4c75ab4a3004cd5f021a
337                 // check == 19 instead of range 17..=19 because it is more experimental in LLVM 17/18.
338                 // check == 19 instead of >= 19 because "experimental-zacas" feature
339                 // may no longer exist when it is marked as non-experimental in LLVM 20.
340                 target_feature_fallback("experimental-zacas", false);
341             }
342             // amo*.{w,d}
343             target_feature_fallback("zaamo", false);
344         }
345         "powerpc64" => {
346             // target_feature "quadword-atomics" is unstable and available on rustc side since nightly-2024-09-28: https://github.com/rust-lang/rust/pull/130873
347             if !version.probe(83, 2024, 9, 27) || needs_target_feature_fallback(&version, None) {
348                 let target_endian =
349                     env::var("CARGO_CFG_TARGET_ENDIAN").expect("CARGO_CFG_TARGET_ENDIAN not set");
350                 // powerpc64le is pwr8+ by default https://github.com/llvm/llvm-project/blob/llvmorg-19.1.0/llvm/lib/Target/PowerPC/PPC.td#L702
351                 // See also https://github.com/rust-lang/rust/issues/59932
352                 let mut has_pwr8_features = target_endian == "little";
353                 // https://github.com/llvm/llvm-project/commit/549e118e93c666914a1045fde38a2cac33e1e445
354                 if let Some(cpu) = &target_cpu() {
355                     if let Some(mut cpu_version) = strip_prefix(cpu, "pwr") {
356                         cpu_version = strip_suffix(cpu_version, "x").unwrap_or(cpu_version); // for pwr5x and pwr6x
357                         if let Ok(cpu_version) = cpu_version.parse::<u32>() {
358                             has_pwr8_features = cpu_version >= 8;
359                         }
360                     } else {
361                         // https://github.com/llvm/llvm-project/blob/llvmorg-19.1.0/llvm/lib/Target/PowerPC/PPC.td#L702
362                         // https://github.com/llvm/llvm-project/blob/llvmorg-19.1.0/llvm/lib/Target/PowerPC/PPC.td#L483
363                         // On the minimum external LLVM version of the oldest rustc version which we can use asm_experimental_arch
364                         // on this target (see CI config for more), "future" is based on pwr10 features.
365                         // https://github.com/llvm/llvm-project/blob/llvmorg-12.0.0/llvm/lib/Target/PowerPC/PPC.td#L370
366                         has_pwr8_features = cpu == "ppc64le" || cpu == "future";
367                     }
368                 }
369                 // lqarx and stqcx.
370                 target_feature_fallback("quadword-atomics", has_pwr8_features);
371             }
372         }
373         "s390x" => {
374             // https://github.com/llvm/llvm-project/blob/llvmorg-19.1.0/llvm/lib/Target/SystemZ/SystemZFeatures.td
375             let mut arch9_features = false; // z196+
376             let mut arch13_features = false; // z15+
377             if let Some(cpu) = target_cpu() {
378                 // LLVM and GCC recognize the same names:
379                 // https://github.com/llvm/llvm-project/blob/llvmorg-19.1.0/llvm/lib/Target/SystemZ/SystemZProcessors.td
380                 // https://github.com/gcc-mirror/gcc/blob/releases/gcc-14.2.0/gcc/config/s390/s390.opt#L58-L125
381                 match &*cpu {
382                     "arch9" | "z196" | "arch10" | "zEC12" | "arch11" | "z13" | "arch12" | "z14" => {
383                         arch9_features = true;
384                     }
385                     "arch13" | "z15" | "arch14" | "z16" => {
386                         arch9_features = true;
387                         arch13_features = true;
388                     }
389                     _ => {}
390                 }
391             }
392             // As of rustc 1.80, target_feature "fast-serialization"/"load-store-on-cond"/"distinct-ops"/"miscellaneous-extensions-3" is not available on rustc side:
393             // https://github.com/rust-lang/rust/blob/1.80.0/compiler/rustc_target/src/target_features.rs
394             // bcr 14,0
395             target_feature_fallback("fast-serialization", arch9_features);
396             // {l,st}oc{,g}{,r}
397             target_feature_fallback("load-store-on-cond", arch9_features);
398             // {al,sl,n,o,x}{,g}rk
399             target_feature_fallback("distinct-ops", arch9_features);
400             // nand (nnr{,g}k), select (sel{,g}r), etc.
401             target_feature_fallback("miscellaneous-extensions-3", arch13_features);
402         }
403         _ => {}
404     }
405 }
406 
407 // HACK: Currently, it seems that the only way to handle unstable target
408 // features on the stable is to parse the `-C target-feature` in RUSTFLAGS.
409 //
410 // - #[cfg(target_feature = "unstable_target_feature")] doesn't work on stable.
411 // - CARGO_CFG_TARGET_FEATURE excludes unstable target features on stable.
412 //
413 // As mentioned in the [RFC2045], unstable target features are also passed to LLVM
414 // (e.g., https://godbolt.org/z/4rr7rMcfG), so this hack works properly on stable.
415 //
416 // [RFC2045]: https://rust-lang.github.io/rfcs/2045-target-feature.html#backend-compilation-options
needs_target_feature_fallback(version: &Version, stable: Option<u32>) -> bool417 fn needs_target_feature_fallback(version: &Version, stable: Option<u32>) -> bool {
418     match stable {
419         // In these cases, cfg(target_feature = "...") would work, so skip emitting our own fallback target_feature cfg.
420         _ if version.nightly => false,
421         Some(stabilized) if version.minor >= stabilized => false,
422         _ => true,
423     }
424 }
target_feature_fallback(name: &str, mut has_target_feature: bool) -> bool425 fn target_feature_fallback(name: &str, mut has_target_feature: bool) -> bool {
426     if let Some(rustflags) = env::var_os("CARGO_ENCODED_RUSTFLAGS") {
427         for mut flag in rustflags.to_string_lossy().split('\x1f') {
428             flag = strip_prefix(flag, "-C").unwrap_or(flag);
429             if let Some(flag) = strip_prefix(flag, "target-feature=") {
430                 for s in flag.split(',') {
431                     // TODO: Handles cases where a specific target feature
432                     // implicitly enables another target feature.
433                     match (s.as_bytes().first(), s.as_bytes().get(1..)) {
434                         (Some(b'+'), Some(f)) if f == name.as_bytes() => has_target_feature = true,
435                         (Some(b'-'), Some(f)) if f == name.as_bytes() => has_target_feature = false,
436                         _ => {}
437                     }
438                 }
439             }
440         }
441     }
442     if has_target_feature {
443         println!("cargo:rustc-cfg=portable_atomic_target_feature=\"{}\"", name);
444     }
445     has_target_feature
446 }
447 
target_cpu() -> Option<String>448 fn target_cpu() -> Option<String> {
449     let rustflags = env::var_os("CARGO_ENCODED_RUSTFLAGS")?;
450     let rustflags = rustflags.to_string_lossy();
451     let mut cpu = None;
452     for mut flag in rustflags.split('\x1f') {
453         flag = strip_prefix(flag, "-C").unwrap_or(flag);
454         if let Some(flag) = strip_prefix(flag, "target-cpu=") {
455             cpu = Some(flag);
456         }
457     }
458     cpu.map(str::to_owned)
459 }
460 
is_allowed_feature(name: &str) -> bool461 fn is_allowed_feature(name: &str) -> bool {
462     // https://github.com/dtolnay/thiserror/pull/248
463     if env::var_os("RUSTC_STAGE").is_some() {
464         return false;
465     }
466 
467     // allowed by default
468     let mut allowed = true;
469     if let Some(rustflags) = env::var_os("CARGO_ENCODED_RUSTFLAGS") {
470         for mut flag in rustflags.to_string_lossy().split('\x1f') {
471             flag = strip_prefix(flag, "-Z").unwrap_or(flag);
472             if let Some(flag) = strip_prefix(flag, "allow-features=") {
473                 // If it is specified multiple times, the last value will be preferred.
474                 allowed = flag.split(',').any(|allowed| allowed == name);
475             }
476         }
477     }
478     allowed
479 }
480 
481 // Adapted from https://github.com/crossbeam-rs/crossbeam/blob/crossbeam-utils-0.8.14/build-common.rs.
482 //
483 // The target triplets have the form of 'arch-vendor-system'.
484 //
485 // When building for Linux (e.g. the 'system' part is
486 // 'linux-something'), replace the vendor with 'unknown'
487 // so that mapping to rust standard targets happens correctly.
convert_custom_linux_target(target: &str) -> String488 fn convert_custom_linux_target(target: &str) -> String {
489     let mut parts: Vec<&str> = target.split('-').collect();
490     let system = parts.get(2);
491     if system == Some(&"linux") {
492         parts[1] = "unknown";
493     }
494     parts.join("-")
495 }
496 
497 // str::strip_prefix requires Rust 1.45
498 #[must_use]
strip_prefix<'a>(s: &'a str, pat: &str) -> Option<&'a str>499 fn strip_prefix<'a>(s: &'a str, pat: &str) -> Option<&'a str> {
500     if s.starts_with(pat) {
501         Some(&s[pat.len()..])
502     } else {
503         None
504     }
505 }
506 // str::strip_suffix requires Rust 1.45
507 #[must_use]
strip_suffix<'a>(s: &'a str, pat: &str) -> Option<&'a str>508 fn strip_suffix<'a>(s: &'a str, pat: &str) -> Option<&'a str> {
509     if s.ends_with(pat) {
510         Some(&s[..s.len() - pat.len()])
511     } else {
512         None
513     }
514 }
515