diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index fead20ec7d1f9..1c7d473447acf 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -34,6 +34,8 @@ //! the target's settings, though `target-feature` and `link-args` will *add* //! to the list specified by the target, rather than replace. +// ignore-tidy-filelength + use std::borrow::Cow; use std::collections::BTreeMap; use std::hash::{Hash, Hasher}; @@ -42,6 +44,7 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use std::{fmt, io}; +use rustc_data_structures::fx::FxHashSet; use rustc_fs_util::try_canonicalize; use rustc_macros::{Decodable, Encodable, HashStable_Generic}; use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; @@ -1605,13 +1608,11 @@ macro_rules! supported_targets { #[cfg(test)] mod tests { - mod tests_impl; - // Cannot put this into a separate file without duplication, make an exception. $( #[test] // `#[test]` fn $module() { - tests_impl::test_target(crate::spec::targets::$module::target()); + crate::spec::targets::$module::target().test_target() } )+ } @@ -1998,6 +1999,14 @@ impl TargetWarnings { } } +/// For the [`Target::check_consistency`] function, determines whether the given target is a builtin or a JSON +/// target. +#[derive(Copy, Clone, Debug, PartialEq)] +enum TargetKind { + Json, + Builtin, +} + /// Everything `rustc` knows about how to compile for a specific target. /// /// Every field here must be specified, and has no default value. @@ -2846,6 +2855,357 @@ 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_osx, + self.vendor == "apple", + "`is_like_osx` must be set if and only if `vendor` is `apple`" + ); + check_eq!( + self.is_like_solaris, + self.os == "solaris" || self.os == "illumos", + "`is_like_solaris` must be set if and only if `os` is `solaris` or `illumos`" + ); + check_eq!( + self.is_like_windows, + self.os == "windows" || self.os == "uefi", + "`is_like_windows` must be set if and only if `os` is `windows` or `uefi`" + ); + check_eq!( + self.is_like_wasm, + self.arch == "wasm32" || self.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 == "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_osx, + matches!(self.linker_flavor, LinkerFlavor::Darwin(..)), + "`linker_flavor` must be `darwin` if and only if `is_like_osx` 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 != "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 == "emscripten", + matches!(self.linker_flavor, LinkerFlavor::EmCc), + "`linker_flavor` must be `em-cc` if and only if `os` is `emscripten`" + ); + check_eq!( + self.arch == "bpf", + matches!(self.linker_flavor, LinkerFlavor::Bpf), + "`linker_flavor` must be `bpf` if and only if `arch` is `bpf`" + ); + check_eq!( + self.arch == "nvptx64", + matches!(self.linker_flavor, LinkerFlavor::Ptx), + "`linker_flavor` must be `ptc` if and only if `arch` is `nvptx64`" + ); + + 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(), "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::Ptx + | 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"); + check_ne!(self.os, "", "`os` cannot be empty"); + if !self.can_use_os_unknown() { + // Keep the default "none" for bare metal targets instead. + check_ne!( + self.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 == "none" + && (self.arch != "bpf" + && self.arch != "hexagon" + && self.arch != "wasm32" + && self.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 != "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" + ); + } + + // Check that RISC-V targets always specify which ABI they use. + match &*self.arch { + "riscv32" => { + check_matches!( + &*self.llvm_abiname, + "ilp32" | "ilp32f" | "ilp32d" | "ilp32e", + "invalid RISC-V ABI name" + ); + } + "riscv64" => { + // Note that the `lp64e` is still unstable as it's not (yet) part of the ELF psABI. + check_matches!( + &*self.llvm_abiname, + "lp64" | "lp64f" | "lp64d" | "lp64q" | "lp64e", + "invalid RISC-V ABI name" + ); + } + _ => {} + } + + // 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 `-`" + )); + } + } + } + + Ok(()) + } + + /// Test target self-consistency and JSON encoding/decoding roundtrip. + #[cfg(test)] + fn test_target(mut self) { + let recycled_target = Target::from_json(self.to_json()).map(|(j, _)| j); + self.update_to_cli(); + self.check_consistency(TargetKind::Builtin).unwrap(); + assert_eq!(recycled_target, Ok(self)); + } + + // Add your target to the whitelist if it has `std` library + // and you certainly want "unknown" for the OS name. + fn can_use_os_unknown(&self) -> bool { + self.llvm_target == "wasm32-unknown-unknown" + || self.llvm_target == "wasm64-unknown-unknown" + || (self.env == "sgx" && self.vendor == "fortanix") + } + /// Loads a target descriptor from a JSON object. pub fn from_json(obj: Json) -> Result<(Target, TargetWarnings), String> { // While ugly, this code must remain this way to retain @@ -3452,6 +3812,7 @@ impl Target { key!(supports_xray, bool); base.update_from_cli(); + base.check_consistency(TargetKind::Json)?; // Each field should have been read using `Json::remove` so any keys remaining are unused. let remaining_keys = obj.keys(); diff --git a/compiler/rustc_target/src/spec/tests/tests_impl.rs b/compiler/rustc_target/src/spec/tests/tests_impl.rs deleted file mode 100644 index 3225cdc759d98..0000000000000 --- a/compiler/rustc_target/src/spec/tests/tests_impl.rs +++ /dev/null @@ -1,208 +0,0 @@ -use std::assert_matches::assert_matches; - -use rustc_data_structures::fx::FxHashSet; - -use super::super::*; - -// Test target self-consistency and JSON encoding/decoding roundtrip. -pub(super) fn test_target(mut target: Target) { - let recycled_target = Target::from_json(target.to_json()).map(|(j, _)| j); - target.update_to_cli(); - target.check_consistency(); - assert_eq!(recycled_target, Ok(target)); -} - -impl Target { - fn check_consistency(&self) { - assert_eq!(self.is_like_osx, self.vendor == "apple"); - assert_eq!(self.is_like_solaris, self.os == "solaris" || self.os == "illumos"); - assert_eq!(self.is_like_windows, self.os == "windows" || self.os == "uefi"); - assert_eq!(self.is_like_wasm, self.arch == "wasm32" || self.arch == "wasm64"); - if self.is_like_msvc { - assert!(self.is_like_windows); - } - if self.os == "emscripten" { - assert!(self.is_like_wasm); - } - - // Check that default linker flavor is compatible with some other key properties. - assert_eq!(self.is_like_osx, matches!(self.linker_flavor, LinkerFlavor::Darwin(..))); - assert_eq!(self.is_like_msvc, matches!(self.linker_flavor, LinkerFlavor::Msvc(..))); - assert_eq!( - self.is_like_wasm && self.os != "emscripten", - matches!(self.linker_flavor, LinkerFlavor::WasmLld(..)) - ); - assert_eq!(self.os == "emscripten", matches!(self.linker_flavor, LinkerFlavor::EmCc)); - assert_eq!(self.arch == "bpf", matches!(self.linker_flavor, LinkerFlavor::Bpf)); - assert_eq!(self.arch == "nvptx64", matches!(self.linker_flavor, LinkerFlavor::Ptx)); - - 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 { - assert!(!flavor_args.is_empty()); - // Check that flavors mentioned in link args are compatible with the default flavor. - match self.linker_flavor { - LinkerFlavor::Gnu(..) => { - assert_matches!(flavor, LinkerFlavor::Gnu(..)); - } - LinkerFlavor::Darwin(..) => { - assert_matches!(flavor, LinkerFlavor::Darwin(..)) - } - LinkerFlavor::WasmLld(..) => { - assert_matches!(flavor, LinkerFlavor::WasmLld(..)) - } - LinkerFlavor::Unix(..) => { - assert_matches!(flavor, LinkerFlavor::Unix(..)); - } - LinkerFlavor::Msvc(..) => { - assert_matches!(flavor, LinkerFlavor::Msvc(..)) - } - LinkerFlavor::EmCc - | LinkerFlavor::Bpf - | LinkerFlavor::Ptx - | LinkerFlavor::Llbc => { - assert_eq!(flavor, self.linker_flavor) - } - } - - // Check that link args for cc and non-cc versions of flavors are consistent. - let check_noncc = |noncc_flavor| { - if let Some(noncc_args) = args.get(&noncc_flavor) { - for arg in flavor_args { - if let Some(suffix) = arg.strip_prefix("-Wl,") { - assert!(noncc_args.iter().any(|a| a == suffix)); - } - } - } - }; - - 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] { - assert_eq!( - args.get(&LinkerFlavor::Gnu(cc, Lld::No)), - args.get(&LinkerFlavor::Gnu(cc, Lld::Yes)), - ); - assert_eq!( - args.get(&LinkerFlavor::Darwin(cc, Lld::No)), - args.get(&LinkerFlavor::Darwin(cc, Lld::Yes)), - ); - } - assert_eq!( - args.get(&LinkerFlavor::Msvc(Lld::No)), - args.get(&LinkerFlavor::Msvc(Lld::Yes)), - ); - } - - if self.link_self_contained.is_disabled() { - assert!( - self.pre_link_objects_self_contained.is_empty() - && self.post_link_objects_self_contained.is_empty() - ); - } - - // If your target really needs to deviate from the rules below, - // except it and document the reasons. - // Keep the default "unknown" vendor instead. - assert_ne!(self.vendor, ""); - assert_ne!(self.os, ""); - if !self.can_use_os_unknown() { - // Keep the default "none" for bare metal targets instead. - assert_ne!(self.os, "unknown"); - } - - // Check dynamic linking stuff - // 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 == "none" - && (self.arch != "bpf" - && self.arch != "hexagon" - && self.arch != "wasm32" - && self.arch != "wasm64") - { - assert!(!self.dynamic_linking); - } - if self.only_cdylib - || self.crt_static_allows_dylibs - || !self.late_link_args_dynamic.is_empty() - { - assert!(self.dynamic_linking); - } - // Apparently PIC was slow on wasm at some point, see comments in wasm_base.rs - if self.dynamic_linking && !self.is_like_wasm { - assert_eq!(self.relocation_model, RelocModel::Pic); - } - if self.position_independent_executables { - assert_eq!(self.relocation_model, RelocModel::Pic); - } - // The UEFI targets do not support dynamic linking but still require PIC (#101377). - if self.relocation_model == RelocModel::Pic && self.os != "uefi" { - assert!(self.dynamic_linking || self.position_independent_executables); - } - if self.static_position_independent_executables { - assert!(self.position_independent_executables); - } - if self.position_independent_executables { - assert!(self.executables); - } - - // Check crt static stuff - if self.crt_static_default || self.crt_static_allows_dylibs { - assert!(self.crt_static_respected); - } - - // Check that RISC-V targets always specify which ABI they use. - match &*self.arch { - "riscv32" => { - assert_matches!(&*self.llvm_abiname, "ilp32" | "ilp32f" | "ilp32d" | "ilp32e") - } - "riscv64" => { - // Note that the `lp64e` is still unstable as it's not (yet) part of the ELF psABI. - assert_matches!(&*self.llvm_abiname, "lp64" | "lp64f" | "lp64d" | "lp64q" | "lp64e") - } - _ => {} - } - - // 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) { - panic!("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) { - panic!("target feature `{feat}` is both enabled and disabled"); - } - } else { - panic!("target feature `{feat}` is invalid, must start with `+` or `-`"); - } - } - } - } - - // Add your target to the whitelist if it has `std` library - // and you certainly want "unknown" for the OS name. - fn can_use_os_unknown(&self) -> bool { - self.llvm_target == "wasm32-unknown-unknown" - || self.llvm_target == "wasm64-unknown-unknown" - || (self.env == "sgx" && self.vendor == "fortanix") - } -} diff --git a/tests/ui/codegen/mismatched-data-layout.json b/tests/ui/codegen/mismatched-data-layout.json index 4cb0602dc75b5..7adc883252474 100644 --- a/tests/ui/codegen/mismatched-data-layout.json +++ b/tests/ui/codegen/mismatched-data-layout.json @@ -5,7 +5,7 @@ "target-endian": "little", "target-pointer-width": "64", "target-c-int-width": "32", - "os": "unknown", + "os": "none", "linker-flavor": "ld.lld", "linker": "rust-lld", "executables": true diff --git a/tests/ui/codegen/mismatched-data-layouts.stderr b/tests/ui/codegen/mismatched-data-layouts.stderr index 1fe242266dff7..b7d5d82bee08c 100644 --- a/tests/ui/codegen/mismatched-data-layouts.stderr +++ b/tests/ui/codegen/mismatched-data-layouts.stderr @@ -1,4 +1,4 @@ -error: data-layout for target `mismatched-data-layout-7814813422914914169`, `normalized data layout`, differs from LLVM target's `x86_64-unknown-none-gnu` default layout, `normalized data layout` +error: data-layout for target `mismatched-data-layout-7193370089426056427`, `normalized data layout`, differs from LLVM target's `x86_64-unknown-none-gnu` default layout, `normalized data layout` error: aborting due to 1 previous error