Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Prototype run-make test to check core::ffi::c_* types against clang #133944

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion library/core/src/ffi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ type_alias! { "c_longlong.md", c_longlong = i64; }
type_alias! { "c_ulonglong.md", c_ulonglong = u64; }

type_alias! { "c_float.md", c_float = f32; }
type_alias! { "c_double.md", c_double = f64; }
type_alias! { "c_double.md", c_double = c_double_definition::c_double; #[doc(cfg(all()))]}

/// Equivalent to C's `size_t` type, from `stddef.h` (or `cstddef` for C++).
///
Expand Down Expand Up @@ -205,6 +205,16 @@ mod c_long_definition {
}
}

mod c_double_definition {
cfg_if! {
if #[cfg(all(target_arch = "avr"))] {
pub type c_double = f32;
} else {
pub type c_double = f64;
}
}
}

// N.B., for LLVM to recognize the void pointer type and by extension
// functions like malloc(), we need to have it represented as i8* in
// LLVM bitcode. The enum used here ensures this and prevents misuse
Expand Down
124 changes: 124 additions & 0 deletions tests/auxiliary/minicore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,127 @@ macro_rules! stringify {
/* compiler built-in */
};
}

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 )*
};
}

#[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<T>() -> 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<Rhs: ?Sized = Self> {
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 = "bitxor"]
pub trait BitXor<Rhs = Self> {
type Output;
fn bitxor(self, rhs: Rhs) -> Self::Output;
}

impl BitXor for bool {
type Output = bool;
fn bitxor(self, rhs: bool) -> bool {
(self || rhs) && !(self && rhs)
}
}

#[lang = "not"]
pub trait Not {
type Output;
fn not(self) -> Self::Output;
}

impl Not for bool {
type Output = bool;
fn not(self) -> Self {
!self
}
}
163 changes: 163 additions & 0 deletions tests/run-make/core-ffi-typecheck/rmake.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
use run_make_support::{clang, regex, rfs, rustc};

const SKIPPED_TARGETS: &[&str] = &[
"riscv", //error: unknown target triple 'riscv32e-unknown-none-elf'
"wasm", //error: unknown target triple 'wasm32v1-none'
"xtensa", //error: unknown target triple 'xtensa-esp32-espidf'
];

fn main() {
let targets = get_target_list();

let minicore_path = run_make_support::source_root().join("tests/auxiliary/minicore.rs");

regex_mod();

for target in targets.lines() {
if SKIPPED_TARGETS.iter().any(|prefix| target.starts_with(prefix)) {
continue;
}

let clang_output =
clang().args(&["-E", "-dM", "-x", "c", "/dev/null", "-target", target]).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(intrinsics)]
#![feature(link_cfg)]
#![allow(unused)]
#![crate_type = "rlib"]
{}
#[path = "processed_mod.rs"]
mod ffi;
#[path = "tests.rs"]
mod tests;
"#,
minicore_content
);

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 = {};
",
parse_size(&defines, "CHAR"),
parse_signed(&defines, "CHAR"),
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"),
));

// Write to target-specific rmake file
let mut file_name = format!("{}_rmake.rs", target.replace("-", "_"));

if target.starts_with("thumbv8m") {
file_name = String::from("thumbv8m_rmake.rs");
}

rfs::create_file(&file_name);
rfs::write(&file_name, rmake_content);
let rustc_output = rustc()
.arg("-Zunstable-options")
.arg("--emit=metadata")
.arg("--target")
.arg(target)
.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");
}

fn get_target_list() -> String {
let completed_process = rustc().arg("--print").arg("target-list").run();
String::from_utf8(completed_process.stdout()).expect("error not a string")
}

// 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);
}

// Helper to parse signedness from clang defines
fn parse_signed(defines: &str, type_name: &str) -> bool {
match type_name.to_uppercase().as_str() {
"CHAR" => {
// Check if char is explicitly unsigned
!defines.lines().any(|line| line.contains("__CHAR_UNSIGNED__"))
}
_ => true,
}
}

// Parse core/ffi/mod.rs to retrieve only necessary macros and type defines
fn regex_mod() {
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 = format!("processed_mod.rs");

rfs::create_file(&file_name);
rfs::write(&file_name, content);
}
Loading
Loading