From 4296e03224d7af5ad30507a9e18d46638832613f Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 16 Aug 2026 23:52:48 +0200 Subject: [PATCH 1/2] move Target consistency check into separate file --- compiler/rustc_target/src/spec/consistency.rs | 697 ++++++++++++++++++ compiler/rustc_target/src/spec/mod.rs | 689 +---------------- 2 files changed, 699 insertions(+), 687 deletions(-) create mode 100644 compiler/rustc_target/src/spec/consistency.rs diff --git a/compiler/rustc_target/src/spec/consistency.rs b/compiler/rustc_target/src/spec/consistency.rs new file mode 100644 index 0000000000000..5483f905bfdb6 --- /dev/null +++ b/compiler/rustc_target/src/spec/consistency.rs @@ -0,0 +1,697 @@ +use core::result::Result; + +use rustc_abi::Endian; +use rustc_data_structures::fx::FxHashSet; + +use crate::spec::{ + Arch, Cc, CfgAbi, Env, FloatAbi, LinkerFlavor, Lld, LlvmAbi, Os, RelocModel, RustcAbi, Target, + TargetKind, +}; + +impl Target { + /// Check some basic consistency of the current target. For JSON targets we are less strict; + /// some of these checks are more guidelines than strict rules. + pub(super) fn check_consistency(&self, kind: TargetKind) -> Result<(), String> { + macro_rules! check { + ($b:expr, $($msg:tt)*) => { + if !$b { + return Err(format!($($msg)*)); + } + } + } + macro_rules! check_eq { + ($left:expr, $right:expr, $($msg:tt)*) => { + if ($left) != ($right) { + return Err(format!($($msg)*)); + } + } + } + macro_rules! check_ne { + ($left:expr, $right:expr, $($msg:tt)*) => { + if ($left) == ($right) { + return Err(format!($($msg)*)); + } + } + } + macro_rules! check_matches { + ($left:expr, $right:pat, $($msg:tt)*) => { + if !matches!($left, $right) { + return Err(format!($($msg)*)); + } + } + } + + check_eq!( + self.is_like_darwin, + self.vendor == "apple", + "`is_like_darwin` must be set if and only if `vendor` is `apple`" + ); + check_eq!( + self.is_like_solaris, + matches!(self.os, Os::Solaris | Os::Illumos), + "`is_like_solaris` must be set if and only if `os` is `solaris` or `illumos`" + ); + check_eq!( + self.is_like_gpu, + self.arch == Arch::Nvptx64 || self.arch == Arch::AmdGpu, + "`is_like_gpu` must be set if and only if `target` is `nvptx64` or `amdgcn`" + ); + check_eq!( + self.is_like_windows, + matches!(self.os, Os::Windows | Os::Uefi | Os::Cygwin), + "`is_like_windows` must be set if and only if `os` is `windows`, `uefi` or `cygwin`" + ); + check_eq!( + self.is_like_wasm, + matches!(self.arch, Arch::Wasm32 | Arch::Wasm64), + "`is_like_wasm` must be set if and only if `arch` is `wasm32` or `wasm64`" + ); + if self.is_like_msvc { + check!(self.is_like_windows, "if `is_like_msvc` is set, `is_like_windows` must be set"); + } + if self.os == Os::Emscripten { + check!(self.is_like_wasm, "the `emcscripten` os only makes sense on wasm-like targets"); + } + + // Check that default linker flavor is compatible with some other key properties. + check_eq!( + self.is_like_darwin, + matches!(self.linker_flavor, LinkerFlavor::Darwin(..)), + "`linker_flavor` must be `darwin` if and only if `is_like_darwin` is set" + ); + check_eq!( + self.is_like_msvc, + matches!(self.linker_flavor, LinkerFlavor::Msvc(..)), + "`linker_flavor` must be `msvc` if and only if `is_like_msvc` is set" + ); + check_eq!( + self.is_like_wasm && self.os != Os::Emscripten, + matches!(self.linker_flavor, LinkerFlavor::WasmLld(..)), + "`linker_flavor` must be `wasm-lld` if and only if `is_like_wasm` is set and the `os` is not `emscripten`", + ); + check_eq!( + self.os == Os::Emscripten, + matches!(self.linker_flavor, LinkerFlavor::EmCc), + "`linker_flavor` must be `em-cc` if and only if `os` is `emscripten`" + ); + check_eq!( + self.arch == Arch::Bpf, + matches!(self.linker_flavor, LinkerFlavor::Bpf), + "`linker_flavor` must be `bpf` if and only if `arch` is `bpf`" + ); + + for args in [ + &self.pre_link_args, + &self.late_link_args, + &self.late_link_args_dynamic, + &self.late_link_args_static, + &self.post_link_args, + ] { + for (&flavor, flavor_args) in args { + check!( + !flavor_args.is_empty() || self.arch == Arch::Avr, + "linker flavor args must not be empty" + ); + // Check that flavors mentioned in link args are compatible with the default flavor. + match self.linker_flavor { + LinkerFlavor::Gnu(..) => { + check_matches!( + flavor, + LinkerFlavor::Gnu(..), + "mixing GNU and non-GNU linker flavors" + ); + } + LinkerFlavor::Darwin(..) => { + check_matches!( + flavor, + LinkerFlavor::Darwin(..), + "mixing Darwin and non-Darwin linker flavors" + ) + } + LinkerFlavor::WasmLld(..) => { + check_matches!( + flavor, + LinkerFlavor::WasmLld(..), + "mixing wasm and non-wasm linker flavors" + ) + } + LinkerFlavor::Unix(..) => { + check_matches!( + flavor, + LinkerFlavor::Unix(..), + "mixing unix and non-unix linker flavors" + ); + } + LinkerFlavor::Msvc(..) => { + check_matches!( + flavor, + LinkerFlavor::Msvc(..), + "mixing MSVC and non-MSVC linker flavors" + ); + } + LinkerFlavor::EmCc | LinkerFlavor::Bpf | LinkerFlavor::Llbc => { + check_eq!(flavor, self.linker_flavor, "mixing different linker flavors") + } + } + + // Check that link args for cc and non-cc versions of flavors are consistent. + let check_noncc = |noncc_flavor| -> Result<(), String> { + if let Some(noncc_args) = args.get(&noncc_flavor) { + for arg in flavor_args { + if let Some(suffix) = arg.strip_prefix("-Wl,") { + check!( + noncc_args.iter().any(|a| a == suffix), + " link args for cc and non-cc versions of flavors are not consistent" + ); + } + } + } + Ok(()) + }; + + match self.linker_flavor { + LinkerFlavor::Gnu(Cc::Yes, lld) => check_noncc(LinkerFlavor::Gnu(Cc::No, lld))?, + LinkerFlavor::WasmLld(Cc::Yes) => check_noncc(LinkerFlavor::WasmLld(Cc::No))?, + LinkerFlavor::Unix(Cc::Yes) => check_noncc(LinkerFlavor::Unix(Cc::No))?, + _ => {} + } + } + + // Check that link args for lld and non-lld versions of flavors are consistent. + for cc in [Cc::No, Cc::Yes] { + check_eq!( + args.get(&LinkerFlavor::Gnu(cc, Lld::No)), + args.get(&LinkerFlavor::Gnu(cc, Lld::Yes)), + "link args for lld and non-lld versions of flavors are not consistent", + ); + check_eq!( + args.get(&LinkerFlavor::Darwin(cc, Lld::No)), + args.get(&LinkerFlavor::Darwin(cc, Lld::Yes)), + "link args for lld and non-lld versions of flavors are not consistent", + ); + } + check_eq!( + args.get(&LinkerFlavor::Msvc(Lld::No)), + args.get(&LinkerFlavor::Msvc(Lld::Yes)), + "link args for lld and non-lld versions of flavors are not consistent", + ); + } + + if self.link_self_contained.is_disabled() { + check!( + self.pre_link_objects_self_contained.is_empty() + && self.post_link_objects_self_contained.is_empty(), + "if `link_self_contained` is disabled, then `pre_link_objects_self_contained` and `post_link_objects_self_contained` must be empty", + ); + } + + // If your target really needs to deviate from the rules below, + // except it and document the reasons. + // Keep the default "unknown" vendor instead. + check_ne!(self.vendor, "", "`vendor` cannot be empty"); + if let Os::Other(s) = &self.os { + check!(!s.is_empty(), "`os` cannot be empty"); + } + if !self.can_use_os_unknown() { + // Keep the default "none" for bare metal targets instead. + check_ne!( + self.os, + Os::Unknown, + "`unknown` os can only be used on particular targets; use `none` for bare-metal targets" + ); + } + + // Check dynamic linking stuff. + // We skip this for JSON targets since otherwise, our default values would fail this test. + // These checks are not critical for correctness, but more like default guidelines. + // FIXME (https://github.com/rust-lang/rust/issues/133459): do we want to change the JSON + // target defaults so that they pass these checks? + if kind == TargetKind::Builtin { + // BPF: when targeting user space vms (like rbpf), those can load dynamic libraries. + // hexagon: when targeting QuRT, that OS can load dynamic libraries. + // wasm{32,64}: dynamic linking is inherent in the definition of the VM. + if self.os == Os::None + && !matches!(self.arch, Arch::Bpf | Arch::Hexagon | Arch::Wasm32 | Arch::Wasm64) + { + check!( + !self.dynamic_linking, + "dynamic linking is not supported on this OS/architecture" + ); + } + if self.only_cdylib + || self.crt_static_allows_dylibs + || !self.late_link_args_dynamic.is_empty() + { + check!( + self.dynamic_linking, + "dynamic linking must be allowed when `only_cdylib` or `crt_static_allows_dylibs` or `late_link_args_dynamic` are set" + ); + } + // Apparently PIC was slow on wasm at some point, see comments in wasm_base.rs + if self.dynamic_linking && !self.is_like_wasm { + check_eq!( + self.relocation_model, + RelocModel::Pic, + "targets that support dynamic linking must use the `pic` relocation model" + ); + } + if self.position_independent_executables { + check_eq!( + self.relocation_model, + RelocModel::Pic, + "targets that support position-independent executables must use the `pic` relocation model" + ); + } + // The UEFI targets do not support dynamic linking but still require PIC (#101377). + if self.relocation_model == RelocModel::Pic && self.os != Os::Uefi { + check!( + self.dynamic_linking || self.position_independent_executables, + "when the relocation model is `pic`, the target must support dynamic linking or use position-independent executables. \ + Set the relocation model to `static` to avoid this requirement" + ); + } + if self.static_position_independent_executables { + check!( + self.position_independent_executables, + "if `static_position_independent_executables` is set, then `position_independent_executables` must be set" + ); + } + if self.position_independent_executables { + check!( + self.executables, + "if `position_independent_executables` is set then `executables` must be set" + ); + } + } + + // Check crt static stuff + if self.crt_static_default || self.crt_static_allows_dylibs { + check!( + self.crt_static_respected, + "static CRT can be enabled but `crt_static_respected` is not set" + ); + } + + // Ensure built-in targets don't use the `Other` variants. + if kind == TargetKind::Builtin { + check!( + !matches!(self.arch, Arch::Other(_)), + "`Arch::Other` is only meant for JSON targets" + ); + check!(!matches!(self.os, Os::Other(_)), "`Os::Other` is only meant for JSON targets"); + check!( + !matches!(self.env, Env::Other(_)), + "`Env::Other` is only meant for JSON targets" + ); + check!( + !matches!(self.cfg_abi, CfgAbi::Other(_)), + "`CfgAbi::Other` is only meant for JSON targets" + ); + check!( + !matches!(self.llvm_abiname, LlvmAbi::Other(_)), + "`LlvmAbi::Other` is only meant for JSON targets" + ); + } + + // Check ABI flag consistency, for the architectures where we have proper ABI treatment. + // To ensure targets are trated consistently, please consult with the team before allowing + // new cases. + match self.arch { + Arch::X86 => { + check!( + self.llvm_abiname == LlvmAbi::Unspecified, + "`llvm_abiname` is unused on x86-32" + ); + check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on x86-32"); + check_matches!( + (&self.rustc_abi, &self.cfg_abi), + // FIXME: we do not currently set a target_abi for softfloat targets here, + // but we probably should, so we already allow it. + ( + Some(RustcAbi::Softfloat), + CfgAbi::SoftFloat | CfgAbi::Unspecified | CfgAbi::Other(_) + ) | ( + Some(RustcAbi::X86Sse2) | None, + CfgAbi::Uwp + | CfgAbi::Llvm + | CfgAbi::Sim + | CfgAbi::Unspecified + | CfgAbi::Other(_) + ), + "invalid x86-32 Rust-specific ABI and `cfg(target_abi)` combination:\n\ + Rust-specific ABI: {:?}\n\ + cfg(target_abi): {}", + self.rustc_abi, + self.cfg_abi, + ); + } + Arch::X86_64 => { + check!( + self.llvm_abiname == LlvmAbi::Unspecified, + "`llvm_abiname` is unused on x86-64" + ); + check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on x86-64"); + // FIXME: we do not currently set a target_abi for softfloat targets here, but we + // probably should, so we already allow it. + // FIXME: Ensure that target_abi = "x32" correlates with actually using that ABI. + // Do any of the others need a similar check? + check_matches!( + (&self.rustc_abi, &self.cfg_abi), + ( + Some(RustcAbi::Softfloat), + CfgAbi::SoftFloat | CfgAbi::Unspecified | CfgAbi::Other(_) + ) | ( + None, + CfgAbi::X32 + | CfgAbi::Llvm + | CfgAbi::Fortanix + | CfgAbi::Uwp + | CfgAbi::MacAbi + | CfgAbi::Sim + | CfgAbi::Unspecified + | CfgAbi::Other(_) + ), + "invalid x86-64 Rust-specific ABI and `cfg(target_abi)` combination:\n\ + Rust-specific ABI: {:?}\n\ + cfg(target_abi): {}", + self.rustc_abi, + self.cfg_abi, + ); + } + Arch::RiscV32 => { + check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on RISC-V"); + check!(self.rustc_abi.is_none(), "`rustc_abi` is unused on RISC-V"); + check_matches!( + (&self.llvm_abiname, &self.cfg_abi), + (LlvmAbi::Ilp32, CfgAbi::Unspecified | CfgAbi::Other(_)) + | (LlvmAbi::Ilp32f, CfgAbi::Unspecified | CfgAbi::Other(_)) + | (LlvmAbi::Ilp32d, CfgAbi::Unspecified | CfgAbi::Other(_)) + | (LlvmAbi::Ilp32e, CfgAbi::Ilp32e), + "invalid RISC-V ABI name and `cfg(target_abi)` combination:\n\ + ABI name: {}\n\ + cfg(target_abi): {}", + self.llvm_abiname, + self.cfg_abi, + ); + } + Arch::RiscV64 => { + // Note that the `lp64e` is still unstable as it's not (yet) part of the ELF psABI. + check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on RISC-V"); + check!(self.rustc_abi.is_none(), "`rustc_abi` is unused on RISC-V"); + check_matches!( + (&self.llvm_abiname, &self.cfg_abi), + (LlvmAbi::Lp64, CfgAbi::Unspecified | CfgAbi::Other(_)) + | (LlvmAbi::Lp64f, CfgAbi::Unspecified | CfgAbi::Other(_)) + | (LlvmAbi::Lp64d, CfgAbi::Unspecified | CfgAbi::Other(_)) + | (LlvmAbi::Lp64e, CfgAbi::Unspecified | CfgAbi::Other(_)), + "invalid RISC-V ABI name and `cfg(target_abi)` combination:\n\ + ABI name: {}\n\ + cfg(target_abi): {}", + self.llvm_abiname, + self.cfg_abi, + ); + } + Arch::Arm => { + check!( + self.llvm_abiname == LlvmAbi::Unspecified, + "`llvm_abiname` is unused on ARM" + ); + check!(self.rustc_abi.is_none(), "`rustc_abi` is unused on ARM"); + check_matches!( + (&self.llvm_floatabi, &self.cfg_abi), + ( + Some(FloatAbi::Hard), + CfgAbi::EabiHf | CfgAbi::Uwp | CfgAbi::Unspecified | CfgAbi::Other(_) + ) | (Some(FloatAbi::Soft), CfgAbi::Eabi), + "Invalid combination of float ABI and `cfg(target_abi)` for ARM target\n\ + float ABI: {:?}\n\ + cfg(target_abi): {}", + self.llvm_floatabi, + self.cfg_abi, + ) + } + Arch::AArch64 => { + check_matches!( + self.llvm_abiname, + LlvmAbi::Unspecified | LlvmAbi::Pauthtest, + "invalid llvm ABI for aarch64" + ); + check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on aarch64"); + // FIXME: Ensure that target_abi = "ilp32" correlates with actually using that ABI. + // Do any of the others need a similar check? + check_matches!( + (&self.rustc_abi, &self.cfg_abi), + (Some(RustcAbi::Softfloat), CfgAbi::SoftFloat) + | ( + None, + CfgAbi::Ilp32 + | CfgAbi::Llvm + | CfgAbi::MacAbi + | CfgAbi::Pauthtest + | CfgAbi::Sim + | CfgAbi::Uwp + | CfgAbi::Unspecified + | CfgAbi::Other(_) + ), + "invalid aarch64 Rust-specific ABI and `cfg(target_abi)` combination:\n\ + Rust-specific ABI: {:?}\n\ + cfg(target_abi): {}", + self.rustc_abi, + self.cfg_abi, + ); + } + Arch::PowerPC => { + check!( + self.llvm_abiname == LlvmAbi::Unspecified, + "`llvm_abiname` is unused on PowerPC" + ); + check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on PowerPC"); + check_matches!( + (&self.rustc_abi, &self.cfg_abi), + (Some(RustcAbi::PowerPcSpe), CfgAbi::Spe) + | (None, CfgAbi::Unspecified | CfgAbi::Other(_)), + "invalid PowerPC Rust-specific ABI and `cfg(target_abi)` combination:\n\ + Rust-specific ABI: {:?}\n\ + cfg(target_abi): {}", + self.rustc_abi, + self.cfg_abi, + ); + } + Arch::PowerPC64 => { + check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on PowerPC64"); + check!(self.rustc_abi.is_none(), "`rustc_abi` is unused on PowerPC64"); + // PowerPC64 targets that are not AIX must set their ABI to either ELFv1 or ELFv2 + if self.os == Os::Aix { + // FIXME: Check that `target_abi` matches the actually configured ABI + // (vec-default vs vec-ext). + check_matches!( + (&self.llvm_abiname, &self.cfg_abi), + (LlvmAbi::Unspecified, CfgAbi::VecDefault | CfgAbi::VecExtAbi), + "invalid PowerPC64 AIX ABI name and `cfg(target_abi)` combination:\n\ + ABI name: {}\n\ + cfg(target_abi): {}", + self.llvm_abiname, + self.cfg_abi, + ); + } else if self.endian == Endian::Big { + check_matches!( + (&self.llvm_abiname, &self.cfg_abi), + (LlvmAbi::ElfV1, CfgAbi::ElfV1) | (LlvmAbi::ElfV2, CfgAbi::ElfV2), + "invalid PowerPC64 big-endian ABI name and `cfg(target_abi)` combination:\n\ + ABI name: {}\n\ + cfg(target_abi): {}", + self.llvm_abiname, + self.cfg_abi, + ); + } else { + check_matches!( + (&self.llvm_abiname, &self.cfg_abi), + (LlvmAbi::ElfV2, CfgAbi::ElfV2), + "invalid PowerPC64 little-endian ABI name and `cfg(target_abi)` combination:\n\ + ABI name: {}\n\ + cfg(target_abi): {}", + self.llvm_abiname, + self.cfg_abi, + ); + } + } + Arch::S390x => { + check!( + self.llvm_abiname == LlvmAbi::Unspecified, + "`llvm_abiname` is unused on s390x" + ); + check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on s390x"); + check_matches!( + (&self.rustc_abi, &self.cfg_abi), + (Some(RustcAbi::Softfloat), CfgAbi::SoftFloat) + | (None, CfgAbi::Unspecified | CfgAbi::Other(_)), + "invalid s390x Rust-specific ABI and `cfg(target_abi)` combination:\n\ + Rust-specific ABI: {:?}\n\ + cfg(target_abi): {}", + self.rustc_abi, + self.cfg_abi, + ); + } + Arch::LoongArch32 => { + check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on LoongArch"); + check!(self.rustc_abi.is_none(), "`rustc_abi` is unused on LoongArch"); + check_matches!( + (&self.llvm_abiname, &self.cfg_abi), + (LlvmAbi::Ilp32s, CfgAbi::SoftFloat) + | (LlvmAbi::Ilp32f, CfgAbi::Unspecified | CfgAbi::Other(_)) + | (LlvmAbi::Ilp32d, CfgAbi::Unspecified | CfgAbi::Other(_)), + "invalid LoongArch ABI name and `cfg(target_abi)` combination:\n\ + ABI name: {}\n\ + cfg(target_abi): {}", + self.llvm_abiname, + self.cfg_abi, + ); + } + Arch::LoongArch64 => { + check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on LoongArch"); + check!(self.rustc_abi.is_none(), "`rustc_abi` is unused on LoongArch"); + check_matches!( + (&self.llvm_abiname, &self.cfg_abi), + (LlvmAbi::Lp64s, CfgAbi::SoftFloat) + | (LlvmAbi::Lp64f, CfgAbi::Unspecified | CfgAbi::Other(_)) + | (LlvmAbi::Lp64d, CfgAbi::Unspecified | CfgAbi::Other(_)), + "invalid LoongArch ABI name and `cfg(target_abi)` combination:\n\ + ABI name: {}\n\ + cfg(target_abi): {}", + self.llvm_abiname, + self.cfg_abi, + ); + } + Arch::Mips | Arch::Mips32r6 => { + check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on MIPS"); + check!(self.rustc_abi.is_none(), "`rustc_abi` is unused on MIPS"); + check_matches!( + (&self.llvm_abiname, &self.cfg_abi), + (LlvmAbi::O32, CfgAbi::Unspecified | CfgAbi::Other(_)), + "invalid MIPS ABI name and `cfg(target_abi)` combination:\n\ + ABI name: {}\n\ + cfg(target_abi): {}", + self.llvm_abiname, + self.cfg_abi, + ); + } + Arch::Mips64 | Arch::Mips64r6 => { + check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on MIPS"); + check!(self.rustc_abi.is_none(), "`rustc_abi` is unused on MIPS"); + check_matches!( + (&self.llvm_abiname, &self.cfg_abi), + // No in-tree targets use "n32" but at least for now we let out-of-tree targets + // experiment with that. + (LlvmAbi::N64, CfgAbi::Abi64) + | (LlvmAbi::N32, CfgAbi::Unspecified | CfgAbi::Other(_)), + "invalid MIPS ABI name and `cfg(target_abi)` combination:\n\ + ABI name: {}\n\ + cfg(target_abi): {}", + self.llvm_abiname, + self.cfg_abi, + ); + } + Arch::CSky => { + check!( + self.llvm_abiname == LlvmAbi::Unspecified, + "`llvm_abiname` is unused on CSky" + ); + check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on CSky"); + check!(self.rustc_abi.is_none(), "`rustc_abi` is unused on CSky"); + // FIXME: Check that `target_abi` matches the actually configured ABI (v2 vs v2hf). + check_matches!( + self.cfg_abi, + CfgAbi::AbiV2 | CfgAbi::AbiV2Hf, + "invalid `target_abi` for CSky" + ); + } + Arch::Wasm32 | Arch::Wasm64 => { + check!( + self.llvm_abiname == LlvmAbi::Unspecified, + "`llvm_abiname` is unused on wasm" + ); + check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on wasm"); + check!(self.rustc_abi.is_none(), "`rustc_abi` is unused on wasm"); + check_matches!( + self.cfg_abi, + CfgAbi::Unspecified | CfgAbi::Other(_), + "invalid `target_abi` for wasm" + ); + } + ref arch => { + check!(self.rustc_abi.is_none(), "`rustc_abi` is unused on {arch}"); + // Ensure consistency among built-in targets, but give JSON targets the opportunity + // to experiment with these. + if kind == TargetKind::Builtin { + check!( + self.llvm_abiname == LlvmAbi::Unspecified, + "`llvm_abiname` is unused on {arch}" + ); + check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on {arch}"); + check_matches!( + self.cfg_abi, + CfgAbi::Unspecified | CfgAbi::Other(_), + "`target_abi` is unused on {arch}" + ); + } + } + } + + // Check that the target cpu constraints make sense. + if self.need_explicit_cpu { + check!( + self.requires_consistent_cpu, + "if `need_explicit_cpu` is set, then `requires_consistent_cpu` must be set" + ); + } + + // Check that the given target-features string makes some basic sense. + if !self.features.is_empty() { + let mut features_enabled = FxHashSet::default(); + let mut features_disabled = FxHashSet::default(); + for feat in self.features.split(',') { + if let Some(feat) = feat.strip_prefix("+") { + features_enabled.insert(feat); + if features_disabled.contains(feat) { + return Err(format!( + "target feature `{feat}` is both enabled and disabled" + )); + } + } else if let Some(feat) = feat.strip_prefix("-") { + features_disabled.insert(feat); + if features_enabled.contains(feat) { + return Err(format!( + "target feature `{feat}` is both enabled and disabled" + )); + } + } else { + return Err(format!( + "target feature `{feat}` is invalid, must start with `+` or `-`" + )); + } + } + // Check that we don't mis-set any of the ABI-relevant features. + let abi_feature_constraints = self.abi_required_features(); + for feat in abi_feature_constraints.required { + // The feature might be enabled by default so we can't *require* it to show up. + // But it must not be *disabled*. + if features_disabled.contains(feat) { + return Err(format!( + "target feature `{feat}` is required by the ABI but gets disabled in target spec" + )); + } + } + for feat in abi_feature_constraints.incompatible { + // The feature might be disabled by default so we can't *require* it to show up. + // But it must not be *enabled*. + if features_enabled.contains(feat) { + return Err(format!( + "target feature `{feat}` is incompatible with the ABI but gets enabled in target spec" + )); + } + } + } + + Ok(()) + } +} diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index 97c8db0780733..0a4d178980aff 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -51,7 +51,7 @@ use rustc_abi::{ Align, CVariadicStatus, CanonAbi, Endian, ExternAbi, Integer, Size, TargetDataLayout, TargetDataLayoutError, }; -use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; +use rustc_data_structures::fx::FxIndexSet; use rustc_error_messages::{DiagArgValue, IntoDiagArg, into_diag_arg_using_display}; use rustc_macros::{BlobDecodable, Decodable, Encodable, StableHash}; use rustc_span::{Symbol, kw, sym}; @@ -65,6 +65,7 @@ pub mod crt_objects; mod abi_map; mod base; +mod consistency; mod json; mod tuple; @@ -3015,692 +3016,6 @@ impl Target { self.max_atomic_width.unwrap_or_else(|| self.pointer_width.into()) } - /// Check some basic consistency of the current target. For JSON targets we are less strict; - /// some of these checks are more guidelines than strict rules. - fn check_consistency(&self, kind: TargetKind) -> Result<(), String> { - macro_rules! check { - ($b:expr, $($msg:tt)*) => { - if !$b { - return Err(format!($($msg)*)); - } - } - } - macro_rules! check_eq { - ($left:expr, $right:expr, $($msg:tt)*) => { - if ($left) != ($right) { - return Err(format!($($msg)*)); - } - } - } - macro_rules! check_ne { - ($left:expr, $right:expr, $($msg:tt)*) => { - if ($left) == ($right) { - return Err(format!($($msg)*)); - } - } - } - macro_rules! check_matches { - ($left:expr, $right:pat, $($msg:tt)*) => { - if !matches!($left, $right) { - return Err(format!($($msg)*)); - } - } - } - - check_eq!( - self.is_like_darwin, - self.vendor == "apple", - "`is_like_darwin` must be set if and only if `vendor` is `apple`" - ); - check_eq!( - self.is_like_solaris, - matches!(self.os, Os::Solaris | Os::Illumos), - "`is_like_solaris` must be set if and only if `os` is `solaris` or `illumos`" - ); - check_eq!( - self.is_like_gpu, - self.arch == Arch::Nvptx64 || self.arch == Arch::AmdGpu, - "`is_like_gpu` must be set if and only if `target` is `nvptx64` or `amdgcn`" - ); - check_eq!( - self.is_like_windows, - matches!(self.os, Os::Windows | Os::Uefi | Os::Cygwin), - "`is_like_windows` must be set if and only if `os` is `windows`, `uefi` or `cygwin`" - ); - check_eq!( - self.is_like_wasm, - matches!(self.arch, Arch::Wasm32 | Arch::Wasm64), - "`is_like_wasm` must be set if and only if `arch` is `wasm32` or `wasm64`" - ); - if self.is_like_msvc { - check!(self.is_like_windows, "if `is_like_msvc` is set, `is_like_windows` must be set"); - } - if self.os == Os::Emscripten { - check!(self.is_like_wasm, "the `emcscripten` os only makes sense on wasm-like targets"); - } - - // Check that default linker flavor is compatible with some other key properties. - check_eq!( - self.is_like_darwin, - matches!(self.linker_flavor, LinkerFlavor::Darwin(..)), - "`linker_flavor` must be `darwin` if and only if `is_like_darwin` is set" - ); - check_eq!( - self.is_like_msvc, - matches!(self.linker_flavor, LinkerFlavor::Msvc(..)), - "`linker_flavor` must be `msvc` if and only if `is_like_msvc` is set" - ); - check_eq!( - self.is_like_wasm && self.os != Os::Emscripten, - matches!(self.linker_flavor, LinkerFlavor::WasmLld(..)), - "`linker_flavor` must be `wasm-lld` if and only if `is_like_wasm` is set and the `os` is not `emscripten`", - ); - check_eq!( - self.os == Os::Emscripten, - matches!(self.linker_flavor, LinkerFlavor::EmCc), - "`linker_flavor` must be `em-cc` if and only if `os` is `emscripten`" - ); - check_eq!( - self.arch == Arch::Bpf, - matches!(self.linker_flavor, LinkerFlavor::Bpf), - "`linker_flavor` must be `bpf` if and only if `arch` is `bpf`" - ); - - for args in [ - &self.pre_link_args, - &self.late_link_args, - &self.late_link_args_dynamic, - &self.late_link_args_static, - &self.post_link_args, - ] { - for (&flavor, flavor_args) in args { - check!( - !flavor_args.is_empty() || self.arch == Arch::Avr, - "linker flavor args must not be empty" - ); - // Check that flavors mentioned in link args are compatible with the default flavor. - match self.linker_flavor { - LinkerFlavor::Gnu(..) => { - check_matches!( - flavor, - LinkerFlavor::Gnu(..), - "mixing GNU and non-GNU linker flavors" - ); - } - LinkerFlavor::Darwin(..) => { - check_matches!( - flavor, - LinkerFlavor::Darwin(..), - "mixing Darwin and non-Darwin linker flavors" - ) - } - LinkerFlavor::WasmLld(..) => { - check_matches!( - flavor, - LinkerFlavor::WasmLld(..), - "mixing wasm and non-wasm linker flavors" - ) - } - LinkerFlavor::Unix(..) => { - check_matches!( - flavor, - LinkerFlavor::Unix(..), - "mixing unix and non-unix linker flavors" - ); - } - LinkerFlavor::Msvc(..) => { - check_matches!( - flavor, - LinkerFlavor::Msvc(..), - "mixing MSVC and non-MSVC linker flavors" - ); - } - LinkerFlavor::EmCc | LinkerFlavor::Bpf | LinkerFlavor::Llbc => { - check_eq!(flavor, self.linker_flavor, "mixing different linker flavors") - } - } - - // Check that link args for cc and non-cc versions of flavors are consistent. - let check_noncc = |noncc_flavor| -> Result<(), String> { - if let Some(noncc_args) = args.get(&noncc_flavor) { - for arg in flavor_args { - if let Some(suffix) = arg.strip_prefix("-Wl,") { - check!( - noncc_args.iter().any(|a| a == suffix), - " link args for cc and non-cc versions of flavors are not consistent" - ); - } - } - } - Ok(()) - }; - - match self.linker_flavor { - LinkerFlavor::Gnu(Cc::Yes, lld) => check_noncc(LinkerFlavor::Gnu(Cc::No, lld))?, - LinkerFlavor::WasmLld(Cc::Yes) => check_noncc(LinkerFlavor::WasmLld(Cc::No))?, - LinkerFlavor::Unix(Cc::Yes) => check_noncc(LinkerFlavor::Unix(Cc::No))?, - _ => {} - } - } - - // Check that link args for lld and non-lld versions of flavors are consistent. - for cc in [Cc::No, Cc::Yes] { - check_eq!( - args.get(&LinkerFlavor::Gnu(cc, Lld::No)), - args.get(&LinkerFlavor::Gnu(cc, Lld::Yes)), - "link args for lld and non-lld versions of flavors are not consistent", - ); - check_eq!( - args.get(&LinkerFlavor::Darwin(cc, Lld::No)), - args.get(&LinkerFlavor::Darwin(cc, Lld::Yes)), - "link args for lld and non-lld versions of flavors are not consistent", - ); - } - check_eq!( - args.get(&LinkerFlavor::Msvc(Lld::No)), - args.get(&LinkerFlavor::Msvc(Lld::Yes)), - "link args for lld and non-lld versions of flavors are not consistent", - ); - } - - if self.link_self_contained.is_disabled() { - check!( - self.pre_link_objects_self_contained.is_empty() - && self.post_link_objects_self_contained.is_empty(), - "if `link_self_contained` is disabled, then `pre_link_objects_self_contained` and `post_link_objects_self_contained` must be empty", - ); - } - - // If your target really needs to deviate from the rules below, - // except it and document the reasons. - // Keep the default "unknown" vendor instead. - check_ne!(self.vendor, "", "`vendor` cannot be empty"); - if let Os::Other(s) = &self.os { - check!(!s.is_empty(), "`os` cannot be empty"); - } - if !self.can_use_os_unknown() { - // Keep the default "none" for bare metal targets instead. - check_ne!( - self.os, - Os::Unknown, - "`unknown` os can only be used on particular targets; use `none` for bare-metal targets" - ); - } - - // Check dynamic linking stuff. - // We skip this for JSON targets since otherwise, our default values would fail this test. - // These checks are not critical for correctness, but more like default guidelines. - // FIXME (https://github.com/rust-lang/rust/issues/133459): do we want to change the JSON - // target defaults so that they pass these checks? - if kind == TargetKind::Builtin { - // BPF: when targeting user space vms (like rbpf), those can load dynamic libraries. - // hexagon: when targeting QuRT, that OS can load dynamic libraries. - // wasm{32,64}: dynamic linking is inherent in the definition of the VM. - if self.os == Os::None - && !matches!(self.arch, Arch::Bpf | Arch::Hexagon | Arch::Wasm32 | Arch::Wasm64) - { - check!( - !self.dynamic_linking, - "dynamic linking is not supported on this OS/architecture" - ); - } - if self.only_cdylib - || self.crt_static_allows_dylibs - || !self.late_link_args_dynamic.is_empty() - { - check!( - self.dynamic_linking, - "dynamic linking must be allowed when `only_cdylib` or `crt_static_allows_dylibs` or `late_link_args_dynamic` are set" - ); - } - // Apparently PIC was slow on wasm at some point, see comments in wasm_base.rs - if self.dynamic_linking && !self.is_like_wasm { - check_eq!( - self.relocation_model, - RelocModel::Pic, - "targets that support dynamic linking must use the `pic` relocation model" - ); - } - if self.position_independent_executables { - check_eq!( - self.relocation_model, - RelocModel::Pic, - "targets that support position-independent executables must use the `pic` relocation model" - ); - } - // The UEFI targets do not support dynamic linking but still require PIC (#101377). - if self.relocation_model == RelocModel::Pic && self.os != Os::Uefi { - check!( - self.dynamic_linking || self.position_independent_executables, - "when the relocation model is `pic`, the target must support dynamic linking or use position-independent executables. \ - Set the relocation model to `static` to avoid this requirement" - ); - } - if self.static_position_independent_executables { - check!( - self.position_independent_executables, - "if `static_position_independent_executables` is set, then `position_independent_executables` must be set" - ); - } - if self.position_independent_executables { - check!( - self.executables, - "if `position_independent_executables` is set then `executables` must be set" - ); - } - } - - // Check crt static stuff - if self.crt_static_default || self.crt_static_allows_dylibs { - check!( - self.crt_static_respected, - "static CRT can be enabled but `crt_static_respected` is not set" - ); - } - - // Ensure built-in targets don't use the `Other` variants. - if kind == TargetKind::Builtin { - check!( - !matches!(self.arch, Arch::Other(_)), - "`Arch::Other` is only meant for JSON targets" - ); - check!(!matches!(self.os, Os::Other(_)), "`Os::Other` is only meant for JSON targets"); - check!( - !matches!(self.env, Env::Other(_)), - "`Env::Other` is only meant for JSON targets" - ); - check!( - !matches!(self.cfg_abi, CfgAbi::Other(_)), - "`CfgAbi::Other` is only meant for JSON targets" - ); - check!( - !matches!(self.llvm_abiname, LlvmAbi::Other(_)), - "`LlvmAbi::Other` is only meant for JSON targets" - ); - } - - // Check ABI flag consistency, for the architectures where we have proper ABI treatment. - // To ensure targets are trated consistently, please consult with the team before allowing - // new cases. - match self.arch { - Arch::X86 => { - check!( - self.llvm_abiname == LlvmAbi::Unspecified, - "`llvm_abiname` is unused on x86-32" - ); - check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on x86-32"); - check_matches!( - (&self.rustc_abi, &self.cfg_abi), - // FIXME: we do not currently set a target_abi for softfloat targets here, - // but we probably should, so we already allow it. - ( - Some(RustcAbi::Softfloat), - CfgAbi::SoftFloat | CfgAbi::Unspecified | CfgAbi::Other(_) - ) | ( - Some(RustcAbi::X86Sse2) | None, - CfgAbi::Uwp - | CfgAbi::Llvm - | CfgAbi::Sim - | CfgAbi::Unspecified - | CfgAbi::Other(_) - ), - "invalid x86-32 Rust-specific ABI and `cfg(target_abi)` combination:\n\ - Rust-specific ABI: {:?}\n\ - cfg(target_abi): {}", - self.rustc_abi, - self.cfg_abi, - ); - } - Arch::X86_64 => { - check!( - self.llvm_abiname == LlvmAbi::Unspecified, - "`llvm_abiname` is unused on x86-64" - ); - check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on x86-64"); - // FIXME: we do not currently set a target_abi for softfloat targets here, but we - // probably should, so we already allow it. - // FIXME: Ensure that target_abi = "x32" correlates with actually using that ABI. - // Do any of the others need a similar check? - check_matches!( - (&self.rustc_abi, &self.cfg_abi), - ( - Some(RustcAbi::Softfloat), - CfgAbi::SoftFloat | CfgAbi::Unspecified | CfgAbi::Other(_) - ) | ( - None, - CfgAbi::X32 - | CfgAbi::Llvm - | CfgAbi::Fortanix - | CfgAbi::Uwp - | CfgAbi::MacAbi - | CfgAbi::Sim - | CfgAbi::Unspecified - | CfgAbi::Other(_) - ), - "invalid x86-64 Rust-specific ABI and `cfg(target_abi)` combination:\n\ - Rust-specific ABI: {:?}\n\ - cfg(target_abi): {}", - self.rustc_abi, - self.cfg_abi, - ); - } - Arch::RiscV32 => { - check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on RISC-V"); - check!(self.rustc_abi.is_none(), "`rustc_abi` is unused on RISC-V"); - check_matches!( - (&self.llvm_abiname, &self.cfg_abi), - (LlvmAbi::Ilp32, CfgAbi::Unspecified | CfgAbi::Other(_)) - | (LlvmAbi::Ilp32f, CfgAbi::Unspecified | CfgAbi::Other(_)) - | (LlvmAbi::Ilp32d, CfgAbi::Unspecified | CfgAbi::Other(_)) - | (LlvmAbi::Ilp32e, CfgAbi::Ilp32e), - "invalid RISC-V ABI name and `cfg(target_abi)` combination:\n\ - ABI name: {}\n\ - cfg(target_abi): {}", - self.llvm_abiname, - self.cfg_abi, - ); - } - Arch::RiscV64 => { - // Note that the `lp64e` is still unstable as it's not (yet) part of the ELF psABI. - check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on RISC-V"); - check!(self.rustc_abi.is_none(), "`rustc_abi` is unused on RISC-V"); - check_matches!( - (&self.llvm_abiname, &self.cfg_abi), - (LlvmAbi::Lp64, CfgAbi::Unspecified | CfgAbi::Other(_)) - | (LlvmAbi::Lp64f, CfgAbi::Unspecified | CfgAbi::Other(_)) - | (LlvmAbi::Lp64d, CfgAbi::Unspecified | CfgAbi::Other(_)) - | (LlvmAbi::Lp64e, CfgAbi::Unspecified | CfgAbi::Other(_)), - "invalid RISC-V ABI name and `cfg(target_abi)` combination:\n\ - ABI name: {}\n\ - cfg(target_abi): {}", - self.llvm_abiname, - self.cfg_abi, - ); - } - Arch::Arm => { - check!( - self.llvm_abiname == LlvmAbi::Unspecified, - "`llvm_abiname` is unused on ARM" - ); - check!(self.rustc_abi.is_none(), "`rustc_abi` is unused on ARM"); - check_matches!( - (&self.llvm_floatabi, &self.cfg_abi), - ( - Some(FloatAbi::Hard), - CfgAbi::EabiHf | CfgAbi::Uwp | CfgAbi::Unspecified | CfgAbi::Other(_) - ) | (Some(FloatAbi::Soft), CfgAbi::Eabi), - "Invalid combination of float ABI and `cfg(target_abi)` for ARM target\n\ - float ABI: {:?}\n\ - cfg(target_abi): {}", - self.llvm_floatabi, - self.cfg_abi, - ) - } - Arch::AArch64 => { - check_matches!( - self.llvm_abiname, - LlvmAbi::Unspecified | LlvmAbi::Pauthtest, - "invalid llvm ABI for aarch64" - ); - check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on aarch64"); - // FIXME: Ensure that target_abi = "ilp32" correlates with actually using that ABI. - // Do any of the others need a similar check? - check_matches!( - (&self.rustc_abi, &self.cfg_abi), - (Some(RustcAbi::Softfloat), CfgAbi::SoftFloat) - | ( - None, - CfgAbi::Ilp32 - | CfgAbi::Llvm - | CfgAbi::MacAbi - | CfgAbi::Pauthtest - | CfgAbi::Sim - | CfgAbi::Uwp - | CfgAbi::Unspecified - | CfgAbi::Other(_) - ), - "invalid aarch64 Rust-specific ABI and `cfg(target_abi)` combination:\n\ - Rust-specific ABI: {:?}\n\ - cfg(target_abi): {}", - self.rustc_abi, - self.cfg_abi, - ); - } - Arch::PowerPC => { - check!( - self.llvm_abiname == LlvmAbi::Unspecified, - "`llvm_abiname` is unused on PowerPC" - ); - check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on PowerPC"); - check_matches!( - (&self.rustc_abi, &self.cfg_abi), - (Some(RustcAbi::PowerPcSpe), CfgAbi::Spe) - | (None, CfgAbi::Unspecified | CfgAbi::Other(_)), - "invalid PowerPC Rust-specific ABI and `cfg(target_abi)` combination:\n\ - Rust-specific ABI: {:?}\n\ - cfg(target_abi): {}", - self.rustc_abi, - self.cfg_abi, - ); - } - Arch::PowerPC64 => { - check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on PowerPC64"); - check!(self.rustc_abi.is_none(), "`rustc_abi` is unused on PowerPC64"); - // PowerPC64 targets that are not AIX must set their ABI to either ELFv1 or ELFv2 - if self.os == Os::Aix { - // FIXME: Check that `target_abi` matches the actually configured ABI - // (vec-default vs vec-ext). - check_matches!( - (&self.llvm_abiname, &self.cfg_abi), - (LlvmAbi::Unspecified, CfgAbi::VecDefault | CfgAbi::VecExtAbi), - "invalid PowerPC64 AIX ABI name and `cfg(target_abi)` combination:\n\ - ABI name: {}\n\ - cfg(target_abi): {}", - self.llvm_abiname, - self.cfg_abi, - ); - } else if self.endian == Endian::Big { - check_matches!( - (&self.llvm_abiname, &self.cfg_abi), - (LlvmAbi::ElfV1, CfgAbi::ElfV1) | (LlvmAbi::ElfV2, CfgAbi::ElfV2), - "invalid PowerPC64 big-endian ABI name and `cfg(target_abi)` combination:\n\ - ABI name: {}\n\ - cfg(target_abi): {}", - self.llvm_abiname, - self.cfg_abi, - ); - } else { - check_matches!( - (&self.llvm_abiname, &self.cfg_abi), - (LlvmAbi::ElfV2, CfgAbi::ElfV2), - "invalid PowerPC64 little-endian ABI name and `cfg(target_abi)` combination:\n\ - ABI name: {}\n\ - cfg(target_abi): {}", - self.llvm_abiname, - self.cfg_abi, - ); - } - } - Arch::S390x => { - check!( - self.llvm_abiname == LlvmAbi::Unspecified, - "`llvm_abiname` is unused on s390x" - ); - check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on s390x"); - check_matches!( - (&self.rustc_abi, &self.cfg_abi), - (Some(RustcAbi::Softfloat), CfgAbi::SoftFloat) - | (None, CfgAbi::Unspecified | CfgAbi::Other(_)), - "invalid s390x Rust-specific ABI and `cfg(target_abi)` combination:\n\ - Rust-specific ABI: {:?}\n\ - cfg(target_abi): {}", - self.rustc_abi, - self.cfg_abi, - ); - } - Arch::LoongArch32 => { - check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on LoongArch"); - check!(self.rustc_abi.is_none(), "`rustc_abi` is unused on LoongArch"); - check_matches!( - (&self.llvm_abiname, &self.cfg_abi), - (LlvmAbi::Ilp32s, CfgAbi::SoftFloat) - | (LlvmAbi::Ilp32f, CfgAbi::Unspecified | CfgAbi::Other(_)) - | (LlvmAbi::Ilp32d, CfgAbi::Unspecified | CfgAbi::Other(_)), - "invalid LoongArch ABI name and `cfg(target_abi)` combination:\n\ - ABI name: {}\n\ - cfg(target_abi): {}", - self.llvm_abiname, - self.cfg_abi, - ); - } - Arch::LoongArch64 => { - check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on LoongArch"); - check!(self.rustc_abi.is_none(), "`rustc_abi` is unused on LoongArch"); - check_matches!( - (&self.llvm_abiname, &self.cfg_abi), - (LlvmAbi::Lp64s, CfgAbi::SoftFloat) - | (LlvmAbi::Lp64f, CfgAbi::Unspecified | CfgAbi::Other(_)) - | (LlvmAbi::Lp64d, CfgAbi::Unspecified | CfgAbi::Other(_)), - "invalid LoongArch ABI name and `cfg(target_abi)` combination:\n\ - ABI name: {}\n\ - cfg(target_abi): {}", - self.llvm_abiname, - self.cfg_abi, - ); - } - Arch::Mips | Arch::Mips32r6 => { - check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on MIPS"); - check!(self.rustc_abi.is_none(), "`rustc_abi` is unused on MIPS"); - check_matches!( - (&self.llvm_abiname, &self.cfg_abi), - (LlvmAbi::O32, CfgAbi::Unspecified | CfgAbi::Other(_)), - "invalid MIPS ABI name and `cfg(target_abi)` combination:\n\ - ABI name: {}\n\ - cfg(target_abi): {}", - self.llvm_abiname, - self.cfg_abi, - ); - } - Arch::Mips64 | Arch::Mips64r6 => { - check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on MIPS"); - check!(self.rustc_abi.is_none(), "`rustc_abi` is unused on MIPS"); - check_matches!( - (&self.llvm_abiname, &self.cfg_abi), - // No in-tree targets use "n32" but at least for now we let out-of-tree targets - // experiment with that. - (LlvmAbi::N64, CfgAbi::Abi64) - | (LlvmAbi::N32, CfgAbi::Unspecified | CfgAbi::Other(_)), - "invalid MIPS ABI name and `cfg(target_abi)` combination:\n\ - ABI name: {}\n\ - cfg(target_abi): {}", - self.llvm_abiname, - self.cfg_abi, - ); - } - Arch::CSky => { - check!( - self.llvm_abiname == LlvmAbi::Unspecified, - "`llvm_abiname` is unused on CSky" - ); - check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on CSky"); - check!(self.rustc_abi.is_none(), "`rustc_abi` is unused on CSky"); - // FIXME: Check that `target_abi` matches the actually configured ABI (v2 vs v2hf). - check_matches!( - self.cfg_abi, - CfgAbi::AbiV2 | CfgAbi::AbiV2Hf, - "invalid `target_abi` for CSky" - ); - } - Arch::Wasm32 | Arch::Wasm64 => { - check!( - self.llvm_abiname == LlvmAbi::Unspecified, - "`llvm_abiname` is unused on wasm" - ); - check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on wasm"); - check!(self.rustc_abi.is_none(), "`rustc_abi` is unused on wasm"); - check_matches!( - self.cfg_abi, - CfgAbi::Unspecified | CfgAbi::Other(_), - "invalid `target_abi` for wasm" - ); - } - ref arch => { - check!(self.rustc_abi.is_none(), "`rustc_abi` is unused on {arch}"); - // Ensure consistency among built-in targets, but give JSON targets the opportunity - // to experiment with these. - if kind == TargetKind::Builtin { - check!( - self.llvm_abiname == LlvmAbi::Unspecified, - "`llvm_abiname` is unused on {arch}" - ); - check!(self.llvm_floatabi.is_none(), "`llvm_floatabi` is unused on {arch}"); - check_matches!( - self.cfg_abi, - CfgAbi::Unspecified | CfgAbi::Other(_), - "`target_abi` is unused on {arch}" - ); - } - } - } - - // Check that the target cpu constraints make sense. - if self.need_explicit_cpu { - check!( - self.requires_consistent_cpu, - "if `need_explicit_cpu` is set, then `requires_consistent_cpu` must be set" - ); - } - - // Check that the given target-features string makes some basic sense. - if !self.features.is_empty() { - let mut features_enabled = FxHashSet::default(); - let mut features_disabled = FxHashSet::default(); - for feat in self.features.split(',') { - if let Some(feat) = feat.strip_prefix("+") { - features_enabled.insert(feat); - if features_disabled.contains(feat) { - return Err(format!( - "target feature `{feat}` is both enabled and disabled" - )); - } - } else if let Some(feat) = feat.strip_prefix("-") { - features_disabled.insert(feat); - if features_enabled.contains(feat) { - return Err(format!( - "target feature `{feat}` is both enabled and disabled" - )); - } - } else { - return Err(format!( - "target feature `{feat}` is invalid, must start with `+` or `-`" - )); - } - } - // Check that we don't mis-set any of the ABI-relevant features. - let abi_feature_constraints = self.abi_required_features(); - for feat in abi_feature_constraints.required { - // The feature might be enabled by default so we can't *require* it to show up. - // But it must not be *disabled*. - if features_disabled.contains(feat) { - return Err(format!( - "target feature `{feat}` is required by the ABI but gets disabled in target spec" - )); - } - } - for feat in abi_feature_constraints.incompatible { - // The feature might be disabled by default so we can't *require* it to show up. - // But it must not be *enabled*. - if features_enabled.contains(feat) { - return Err(format!( - "target feature `{feat}` is incompatible with the ABI but gets enabled in target spec" - )); - } - } - } - - Ok(()) - } - /// Test target self-consistency and JSON encoding/decoding roundtrip. #[cfg(test)] fn test_target(mut self) { From 41e670dbbd0da014c9ee422ef4fcdeac3b31a9b2 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 16 Aug 2026 23:55:29 +0200 Subject: [PATCH 2/2] ping me when target ABI checks are being changed --- triagebot.toml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/triagebot.toml b/triagebot.toml index 763c668fe7881..96ad4dd0009fe 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -1303,12 +1303,20 @@ cc = ["@TaKO8Ki", "@JonathanBrouwer"] message = "This PR changes rustc_public" cc = ["@oli-obk", "@celinval", "@ouz-a", "@makai410"] -[mentions."compiler/rustc_target/src/spec"] +[mentions."compiler/rustc_target/src/spec/targets"] message = """ These commits modify **compiler targets**. (See the [Target Tier Policy](https://doc.rust-lang.org/nightly/rustc/target-tier-policy.html).) """ +[mentions."compiler/rustc_target/src/spec/consistency.rs"] +message = "Target consistency checks are being changed" +cc = ["@RalfJung"] + +[mentions."compiler/rustc_target/src/target_features.rs"] +message = "Target features are being changed; ensure all ABI effects are being accounted for" +cc = ["@RalfJung"] + [mentions."src/doc/style-guide"] cc = ["@rust-lang/style"]