diff --git a/tests/auxiliary/minicore.rs b/tests/auxiliary/minicore.rs index a68552175c318..663c0abaae80c 100644 --- a/tests/auxiliary/minicore.rs +++ b/tests/auxiliary/minicore.rs @@ -17,9 +17,64 @@ #![feature(no_core, lang_items, rustc_attrs, decl_macro, naked_functions, f16, f128)] #![allow(unused, improper_ctypes_definitions, internal_features)] #![feature(asm_experimental_arch)] +#![feature(intrinsics)] #![no_std] #![no_core] +/// Vendored from the 'cfg_if' crate + +macro_rules! cfg_if { + // match if/else chains with a final `else` + ( + $( + if #[cfg( $i_meta:meta )] { $( $i_tokens:tt )* } + ) else+ + else { $( $e_tokens:tt )* } + ) => { + cfg_if! { + @__items () ; + $( + (( $i_meta ) ( $( $i_tokens )* )) , + )+ + (() ( $( $e_tokens )* )) , + } + }; + + // Internal and recursive macro to emit all the items + // + // Collects all the previous cfgs in a list at the beginning, so they can be + // negated. After the semicolon is all the remaining items. + (@__items ( $( $_:meta , )* ) ; ) => {}; + ( + @__items ( $( $no:meta , )* ) ; + (( $( $yes:meta )? ) ( $( $tokens:tt )* )) , + $( $rest:tt , )* + ) => { + // Emit all items within one block, applying an appropriate #[cfg]. The + // #[cfg] will require all `$yes` matchers specified and must also negate + // all previous matchers. + #[cfg(all( + $( $yes , )? + not(any( $( $no ),* )) + ))] + cfg_if! { @__identity $( $tokens )* } + + // Recurse to emit all other items in `$rest`, and when we do so add all + // our `$yes` matchers to the list of `$no` matchers as future emissions + // will have to negate everything we just matched as well. + cfg_if! { + @__items ( $( $no , )* $( $yes , )? ) ; + $( $rest , )* + } + }; + + // Internal macro to make __apply work out right for different match types, + // because of how macros match/expand stuff. + (@__identity $( $tokens:tt )* ) => { + $( $tokens )* + }; +} + // `core` has some exotic `marker_impls!` macro for handling the with-generics cases, but for our // purposes, just use a simple macro_rules macro. macro_rules! impl_marker_trait { @@ -101,6 +156,7 @@ macro_rules! concat { /* compiler built-in */ }; } + #[rustc_builtin_macro] #[macro_export] macro_rules! stringify { @@ -108,3 +164,62 @@ macro_rules! stringify { /* compiler built-in */ }; } + +#[macro_export] +macro_rules! panic { + ($msg:literal) => { + $crate::panic(&$msg) + }; +} + +#[rustc_intrinsic] +#[rustc_intrinsic_const_stable_indirect] +#[rustc_intrinsic_must_be_overridden] +pub const fn size_of() -> usize { + loop {} +} + +#[rustc_intrinsic] +#[rustc_intrinsic_must_be_overridden] +pub const fn abort() -> ! { + loop {} +} + +#[lang = "panic"] +#[rustc_const_panic_str] +const fn panic(_expr: &&'static str) -> ! { + abort(); +} + +#[lang = "eq"] +pub trait PartialEq { + fn eq(&self, other: &Rhs) -> bool; + fn ne(&self, other: &Rhs) -> bool { + !self.eq(other) + } +} + +impl PartialEq for usize { + fn eq(&self, other: &usize) -> bool { + (*self) == (*other) + } +} + +impl PartialEq for bool { + fn eq(&self, other: &bool) -> bool { + (*self) == (*other) + } +} + +#[lang = "not"] +pub trait Not { + type Output; + fn not(self) -> Self::Output; +} + +impl Not for bool { + type Output = bool; + fn not(self) -> Self { + !self + } +} diff --git a/tests/run-make/core-ffi-typecheck-clang/rmake.rs b/tests/run-make/core-ffi-typecheck-clang/rmake.rs new file mode 100644 index 0000000000000..b0d500b1bb0fd --- /dev/null +++ b/tests/run-make/core-ffi-typecheck-clang/rmake.rs @@ -0,0 +1,194 @@ +//@ needs-force-clang-based-tests +// This test checks that the clang defines for each target allign with the core ffi types defined in +// mod.rs. Therefore each rust target is queried and the clang defines for each target are read and +// compared to the core sizes to verify all types and sizes allign at buildtime. +// +// If this test fails because Rust adds a target that Clang does not support, this target should be +// added to SKIPPED_TARGETS. + +use run_make_support::{clang, regex, rfs, rustc, serde_json}; +use serde_json::Value; + +// It is not possible to run the Rust test-suite on these targets. +const SKIPPED_TARGETS: &[&str] = &[ + "xtensa-esp32-espidf", + "xtensa-esp32-none-elf", + "xtensa-esp32s2-espidf", + "xtensa-esp32s2-none-elf", + "xtensa-esp32s3-espidf", + "xtensa-esp32s3-none-elf", + "csky-unknown-linux-gnuabiv2", + "csky-unknown-linux-gnuabiv2hf", +]; + +const MAPPED_TARGETS: &[(&str, &str)] = &[ + ("armv5te-unknown-linux-uclibcgnueabi", "armv5te-unknown-linux"), + ("mips-unknown-linux-uclibc", "mips-unknown-linux"), + ("mipsel-unknown-linux-uclibc", "mips-unknown-linux"), + ("powerpc-unknown-linux-gnuspe", "powerpc-unknown-linux-gnu"), + ("powerpc-unknown-linux-muslspe", "powerpc-unknown-linux-musl"), + ("x86_64-unknown-l4re-uclibc", "x86_64-unknown-l4re"), +]; + +fn main() { + let minicore_path = run_make_support::source_root().join("tests/auxiliary/minicore.rs"); + + preprocess_core_ffi(); + + let targets_json = + rustc().arg("--print").arg("all-target-specs-json").arg("-Z").arg("unstable-options").run(); + let targets_json_str = + String::from_utf8(targets_json.stdout().to_vec()).expect("error not a string"); + + let j: Value = serde_json::from_str(&targets_json_str).unwrap(); + for (target, v) in j.as_object().unwrap() { + let llvm_target = &v["llvm-target"].as_str().unwrap(); + + if SKIPPED_TARGETS.iter().any(|&to_skip_target| target == to_skip_target) { + continue; + } + + // Create a new variable to hold either the mapped target or original llvm_target + let target_to_use = MAPPED_TARGETS + .iter() + .find(|&&(from, _)| from == *llvm_target) + .map(|&(_, to)| to) + .unwrap_or(llvm_target); + + // Run Clang's preprocessor for the relevant target, printing default macro definitions. + let clang_output = + clang().args(&["-E", "-dM", "-x", "c", "/dev/null", "-target", &target_to_use]).run(); + + let defines = String::from_utf8(clang_output.stdout()).expect("Invalid UTF-8"); + + let minicore_content = rfs::read_to_string(&minicore_path); + let mut rmake_content = format!( + r#" + #![no_std] + #![no_core] + #![feature(link_cfg)] + #![allow(unused)] + #![crate_type = "rlib"] + + /* begin minicore content */ + {minicore_content} + /* end minicore content */ + + #[path = "processed_mod.rs"] + mod ffi; + #[path = "tests.rs"] + mod tests; + "# + ); + + rmake_content.push_str(&format!( + " + const CLANG_C_CHAR_SIZE: usize = {}; + const CLANG_C_CHAR_SIGNED: bool = {}; + const CLANG_C_SHORT_SIZE: usize = {}; + const CLANG_C_INT_SIZE: usize = {}; + const CLANG_C_LONG_SIZE: usize = {}; + const CLANG_C_LONGLONG_SIZE: usize = {}; + const CLANG_C_FLOAT_SIZE: usize = {}; + const CLANG_C_DOUBLE_SIZE: usize = {}; + const CLANG_C_SIZE_T_SIZE: usize = {}; + const CLANG_C_PTRDIFF_T_SIZE: usize = {}; + ", + parse_size(&defines, "CHAR"), + char_is_signed(&defines), + parse_size(&defines, "SHORT"), + parse_size(&defines, "INT"), + parse_size(&defines, "LONG"), + parse_size(&defines, "LONG_LONG"), + parse_size(&defines, "FLOAT"), + parse_size(&defines, "DOUBLE"), + parse_size(&defines, "SIZE_T"), + parse_size(&defines, "PTRDIFF_T"), + )); + + // Generate a target-specific rmake file. + // If type misalignments occur, + // generated rmake file name used to identify the failing target. + let file_name = format!("{}_rmake.rs", target.replace("-", "_").replace(".", "_")); + + // Attempt to build the test file for the relevant target. + // Tests use constant evaluation, so running is not necessary. + rfs::write(&file_name, rmake_content); + let rustc_output = rustc() + .arg("-Zunstable-options") + .arg("--emit=metadata") + .arg("--target") + .arg(target) + .arg("-o-") + .arg(&file_name) + .run(); + rfs::remove_file(&file_name); + if !rustc_output.status().success() { + panic!("Failed for target {}", target); + } + } + + // Cleanup + rfs::remove_file("processed_mod.rs"); +} + +// Helper to parse size from clang defines +fn parse_size(defines: &str, type_name: &str) -> usize { + let search_pattern = format!("__SIZEOF_{}__ ", type_name.to_uppercase()); + for line in defines.lines() { + if line.contains(&search_pattern) { + if let Some(size_str) = line.split_whitespace().last() { + return size_str.parse().unwrap_or(0); + } + } + } + + // Only allow CHAR to default to 1 + if type_name.to_uppercase() == "CHAR" { + return 1; + } + + panic!("Could not find size definition for type: {}", type_name); +} + +fn char_is_signed(defines: &str) -> bool { + !defines.lines().any(|line| line.contains("__CHAR_UNSIGNED__")) +} + +/// Parse core/ffi/mod.rs to retrieve only necessary macros and type defines +fn preprocess_core_ffi() { + let mod_path = run_make_support::source_root().join("library/core/src/ffi/mod.rs"); + let mut content = rfs::read_to_string(&mod_path); + + //remove stability features #![unstable] + let mut re = regex::Regex::new(r"#!?\[(un)?stable[^]]*?\]").unwrap(); + content = re.replace_all(&content, "").to_string(); + + //remove doc features #[doc...] + re = regex::Regex::new(r"#\[doc[^]]*?\]").unwrap(); + content = re.replace_all(&content, "").to_string(); + + //remove lang feature #[lang...] + re = regex::Regex::new(r"#\[lang[^]]*?\]").unwrap(); + content = re.replace_all(&content, "").to_string(); + + //remove non inline modules + re = regex::Regex::new(r".*mod.*;").unwrap(); + content = re.replace_all(&content, "").to_string(); + + //remove use + re = regex::Regex::new(r".*use.*;").unwrap(); + content = re.replace_all(&content, "").to_string(); + + //remove fn fmt {...} + re = regex::Regex::new(r"(?s)fn fmt.*?\{.*?\}").unwrap(); + content = re.replace_all(&content, "").to_string(); + + //rmv impl fmt {...} + re = regex::Regex::new(r"(?s)impl fmt::Debug for.*?\{.*?\}").unwrap(); + content = re.replace_all(&content, "").to_string(); + + let file_name = "processed_mod.rs"; + + rfs::write(&file_name, content); +} diff --git a/tests/run-make/core-ffi-typecheck-clang/tests.rs b/tests/run-make/core-ffi-typecheck-clang/tests.rs new file mode 100644 index 0000000000000..9dc44201ac766 --- /dev/null +++ b/tests/run-make/core-ffi-typecheck-clang/tests.rs @@ -0,0 +1,189 @@ +/// This file compares the size and signedness of Rust `c_*` types to those from Clang, +/// based on the `CLANG_C_*` constants. Comparisons are done at compile time so this +/// does not need to be executed. +use super::*; // `super` will include everything from the common template + +trait Signed { + const SIGNED: bool; +} + +// Verify Rust's 'c_long' is correct. +cfg_if! { + if #[cfg(all(target_arch = "aarch64", target_abi = "ilp32"))] { + // FIXME: long is not long enough on aarch64 ilp32, should be 8, defaulting to 4 + const XFAIL_C_LONG_SIZE: usize = 4; + pub const TEST_C_LONG_SIZE: () = if size_of::() != XFAIL_C_LONG_SIZE { + panic!("mismatched c_long size, target_abi: ilp32"); + }; + } + else if #[cfg(all(target_arch = "aarch64", target_os = "uefi"))] { + // FIXME: c_long misallignment llvm target is aarch64-unknown-windows, + // discrepencies between Rust target configuration and LLVM alias. + const XFAIL_C_LONG_SIZE: usize = 8; + pub const TEST_C_LONG_SIZE: () = if size_of::() != XFAIL_C_LONG_SIZE { + panic!("mismatched c_long size, target_os: uefi"); + }; + } + else if #[cfg(all(target_arch = "x86_64", target_os = "uefi"))] { + // FIXME: c_long misallignment llvm target is x86_64-unknown-windows, + // discrepencies between Rust target configuration and LLVM alias. + const XFAIL_C_LONG_SIZE: usize = 8; + pub const TEST_C_LONG_SIZE: () = if size_of::() != XFAIL_C_LONG_SIZE { + panic!("mismatched c_long size, target_os: uefi"); + }; + } + else { + // Default test + pub const TEST_C_LONG_SIZE: () = if size_of::() != CLANG_C_LONG_SIZE { + panic!("wrong c_long size"); + }; + } +} + +// Verify Rust's 'c_char' has correct sign. +cfg_if! { + if #[cfg(target_arch = "msp430")] { + // FIXME: c_char signedness misallignment on msp430, should be signed on CLANG + const XFAIL_C_CHAR_SIGNED: bool = false; + pub const TEST_C_CHAR_UNSIGNED: () = if ffi::c_char::SIGNED != XFAIL_C_CHAR_SIGNED { + panic!("mismatched c_char signed, target_arch: msp430"); + }; + } + else if #[cfg(all(target_arch = "aarch64", target_os = "uefi"))] { + // FIXME: c_char signedness misallignment llvm target is aarch64-unknown-windows, + // discrepencies between Rust target configuration and LLVM alias. + const XFAIL_C_CHAR_SIGNED: bool = false; + pub const TEST_C_CHAR_UNSIGNED: () = if ffi::c_char::SIGNED != XFAIL_C_CHAR_SIGNED { + panic!("mismatched c_char signed, target_os: uefi"); + }; + } + else { + pub const TEST_C_CHAR_UNSIGNED: () = if ffi::c_char::SIGNED != CLANG_C_CHAR_SIGNED { + panic!("mismatched c_char sign"); + }; + } +} + +// Verify Rust's 'c_double' is correct. +cfg_if! { + if #[cfg(target_arch = "avr")] { + // FIXME: double is not short enough on avr-unknown-gnu-atmega328 (should be 4 bytes) + const XFAIL_C_DOUBLE_SIZE: usize = 8; + pub const TEST_C_DOUBLE_SIZE: () = if size_of::() != XFAIL_C_DOUBLE_SIZE { + panic!("wrong c_double size, target_arch: avr"); + }; + } + else { + pub const TEST_C_DOUBLE_SIZE: () = if size_of::() != CLANG_C_DOUBLE_SIZE { + panic!("wrong c_double size"); + }; + } +} + +// Verify Rust's 'c_size_t' is correct +cfg_if! { + if #[cfg(all(target_arch = "aarch64", target_abi = "ilp32"))] { + // FIXME: size_t is not short enough on aarch64 ilp32, should be 4, defaulting to 8 + const XFAIL_C_SIZE_T_SIZE: usize = 4; + pub const TEST_C_SIZE_T_SIZE: () = if size_of::() != XFAIL_C_SIZE_T_SIZE { + panic!("wrong c_size_t size, target_arch: aarch64, target_abi: ilp32"); + }; + } + else { + pub const TEST_C_SIZE_T_SIZE: () = if size_of::() != CLANG_C_SIZE_T_SIZE { + panic!("wrong c_size_t size"); + }; + } +} + +// Verify Rust's 'c_ssize_t' is correct +cfg_if! { + if #[cfg(all(target_arch = "aarch64", target_abi = "ilp32"))] { + // FIXME: ssize_t is not short enough on aarch64 ilp32, should be 4, defaulting to 8 + const XFAIL_C_SSIZE_T_SIZE: usize = 4; + pub const TEST_C_SSIZE_T_SIZE: () = if size_of::() != XFAIL_C_SSIZE_T_SIZE { + panic!("wrong c_ssize_t size, target_arch: aarch64, target_abi: ilp32"); + }; + } + else { + pub const TEST_C_SSIZE_T_SIZE: () = if size_of::() != CLANG_C_SIZE_T_SIZE { + panic!("wrong c_size_t size"); + }; + } +} + +// Verify Rust's 'c_ptrdiff_t' is correct +cfg_if! { + if #[cfg(all(target_arch = "aarch64", target_abi = "ilp32"))] { + // FIXME: c_ptrdiff_t is not short enough on aarch64 ilp32, should be 4, defaulting to 8 + const XFAIL_C_PTRDIFF_T_SIZE: usize = 4; + pub const TEST_C_PTRDIFF_T_SIZE: () = + if size_of::() != XFAIL_C_PTRDIFF_T_SIZE { + panic!("wrong c_ssize_t size, target_arch: aarch64, target_abi: ilp32"); + }; + } + else { + pub const TEST_C_PTRDIFF_T_SIZE: () = + if size_of::() != CLANG_C_PTRDIFF_T_SIZE { + panic!("wrong c_size_t size"); + }; + } +} + +impl Signed for i8 { + const SIGNED: bool = true; +} + +impl Signed for u8 { + const SIGNED: bool = false; +} + +//c_char size +pub const TEST_C_CHAR_SIZE: () = if size_of::() != CLANG_C_CHAR_SIZE { + panic!("wrong c_char size"); +}; + +//c_int size +pub const TEST_C_INT_SIZE: () = if size_of::() != CLANG_C_INT_SIZE { + panic!("mismatched c_int size"); +}; + +//c_short size +pub const TEST_C_SHORT_SIZE: () = if size_of::() != CLANG_C_SHORT_SIZE { + panic!("wrong c_short size"); +}; + +//c_longlong size +pub const TEST_C_LONGLONG_SIZE: () = if size_of::() != CLANG_C_LONGLONG_SIZE { + panic!("wrong c_longlong size"); +}; + +//c_float size +pub const TEST_C_FLOAT_SIZE: () = if size_of::() != CLANG_C_FLOAT_SIZE { + panic!("wrong c_float size"); +}; + +//c_schar size +pub const TEST_C_SCHAR_SIZE: () = if size_of::() != CLANG_C_CHAR_SIZE { + panic!("wrong c_schar size"); +}; + +//c_uchar size +pub const TEST_C_UCHAR_SIZE: () = if size_of::() != CLANG_C_CHAR_SIZE { + panic!("wrong c_uchar size"); +}; + +//c_uint size +pub const TEST_C_UINT_SIZE: () = if size_of::() != CLANG_C_INT_SIZE { + panic!("mismatched c_uint size"); +}; + +//c_ushort size +pub const TEST_C_USHORT_SIZE: () = if size_of::() != CLANG_C_SHORT_SIZE { + panic!("wrong c_ushort size"); +}; + +//c_ulonglong size +pub const TEST_C_ULONGLONG_SIZE: () = if size_of::() != CLANG_C_LONGLONG_SIZE { + panic!("wrong c_ulonglong size"); +};