From 1628e6039e538975bc4bc2a2a9971714fab0b742 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 23 Nov 2024 18:44:03 +0100 Subject: [PATCH] ensure JSON-defined targets are consistent --- compiler/rustc_target/src/spec/mod.rs | 304 +++++++++++++++++- .../rustc_target/src/spec/tests/tests_impl.rs | 182 ----------- tests/ui/check-cfg/my-awesome-platform.json | 1 + tests/ui/codegen/mismatched-data-layout.json | 3 +- .../ui/codegen/mismatched-data-layouts.stderr | 2 +- 5 files changed, 305 insertions(+), 187 deletions(-) delete mode 100644 compiler/rustc_target/src/spec/tests/tests_impl.rs diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index 321ab40403a37..49c7612b6a019 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -34,6 +34,9 @@ //! 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::assert_matches::assert_matches; use std::borrow::Cow; use std::collections::BTreeMap; use std::hash::{Hash, Hasher}; @@ -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() } )+ } @@ -2849,6 +2850,302 @@ impl Target { self.max_atomic_width.unwrap_or_else(|| self.pointer_width.into()) } + fn check_consistency(&self) -> 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"); + } + + // 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 `mscv` 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 + // 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 && self.os != "emscripten") { + 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 { + 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") + } + _ => {} + } + + 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().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 @@ -3456,6 +3753,7 @@ impl Target { key!(supports_xray, bool); base.update_from_cli(); + base.check_consistency()?; // 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 bd47d12ef9ff7..0000000000000 --- a/compiler/rustc_target/src/spec/tests/tests_impl.rs +++ /dev/null @@ -1,182 +0,0 @@ -use std::assert_matches::assert_matches; - -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); - } - - // 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 && self.os != "emscripten") { - 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") - } - _ => {} - } - } - - // 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/check-cfg/my-awesome-platform.json b/tests/ui/check-cfg/my-awesome-platform.json index 5e9ab8f1a2d00..36f77e88a47b7 100644 --- a/tests/ui/check-cfg/my-awesome-platform.json +++ b/tests/ui/check-cfg/my-awesome-platform.json @@ -8,5 +8,6 @@ "os": "ericos", "linker-flavor": "ld.lld", "linker": "rust-lld", + "relocation-model": "static", "executables": true } diff --git a/tests/ui/codegen/mismatched-data-layout.json b/tests/ui/codegen/mismatched-data-layout.json index 4cb0602dc75b5..071e7b62c8626 100644 --- a/tests/ui/codegen/mismatched-data-layout.json +++ b/tests/ui/codegen/mismatched-data-layout.json @@ -5,9 +5,10 @@ "target-endian": "little", "target-pointer-width": "64", "target-c-int-width": "32", - "os": "unknown", + "os": "none", "linker-flavor": "ld.lld", "linker": "rust-lld", + "relocation-model": "static", "executables": true } diff --git a/tests/ui/codegen/mismatched-data-layouts.stderr b/tests/ui/codegen/mismatched-data-layouts.stderr index 1fe242266dff7..781310cac03ee 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-6355763278957643006`, `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