From 5dc5fda40c6b67e7fec18d4b784b4d087fbb2c18 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Fri, 19 Apr 2024 11:48:51 -0400 Subject: [PATCH] Add a feature to compile intrinsics that are missing on the system for testing --- Cargo.toml | 8 +- build.rs | 290 ++++++++++++++++++++++++++++--------------- testcrate/Cargo.toml | 3 +- 3 files changed, 201 insertions(+), 100 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 267f1b950..eb6ca470e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ cc = { optional = true, version = "1.0" } panic-handler = { path = 'crates/panic-handler' } [features] -default = ["compiler-builtins"] +default = ["compiler-builtins", "complete-system-rt"] # Enable compilation of C code in compiler-rt, filling in some more optimized # implementations and also filling in unimplemented intrinsics @@ -77,6 +77,12 @@ public-test-deps = [] # it during linking. weak-intrinsics = [] +# On some systems, there are no symbols available in their vendored compiler-rt +# that we would want to use for testing, e.g. MacOS does not provide `f128` +# intrinsics. Enabling this feature builds missing symbols from C so we have +# something to test against. +complete-system-rt = ["cc"] + [[example]] name = "intrinsics" required-features = ["compiler-builtins"] diff --git a/build.rs b/build.rs index ee9444d1f..b4ed30157 100644 --- a/build.rs +++ b/build.rs @@ -1,5 +1,8 @@ use std::{collections::BTreeMap, env, sync::atomic::Ordering}; +#[cfg(all(feature = "c", feature = "complete-system-rt"))] +build_error!("The 'c' and 'complete-system-rt' features are not compatible"); + fn main() { println!("cargo:rerun-if-changed=build.rs"); @@ -53,14 +56,13 @@ fn main() { // mangling names though we assume that we're also in test mode so we don't // build anything and we rely on the upstream implementation of compiler-rt // functions - if !cfg!(feature = "mangled-names") && cfg!(feature = "c") { - // Don't use a C compiler for these targets: - // - // * nvptx - everything is bitcode, not compatible with mixed C/Rust - if !target.contains("nvptx") { - #[cfg(feature = "c")] - c::compile(&llvm_target, &target); - } + // + // We also need a C compiler if we are building extra intrinsics for testing. + if (!cfg!(feature = "mangled-names") && cfg!(feature = "c")) + || cfg!(feature = "complete-system-rt") + { + #[cfg(any(feature = "c", feature = "complete-system-rt"))] + c::compile(&llvm_target, &target); } // To compile intrinsics.rs for thumb targets, where there is no libc @@ -145,7 +147,7 @@ fn generate_aarch64_outlined_atomics() { std::fs::write(dst, buf).unwrap(); } -#[cfg(feature = "c")] +#[cfg(any(feature = "c", feature = "complete-system-rt"))] mod c { extern crate cc; @@ -156,7 +158,7 @@ mod c { use std::path::{Path, PathBuf}; struct Sources { - // SYMBOL -> PATH TO SOURCE + /// SYMBOL -> PATH TO SOURCE map: BTreeMap<&'static str, &'static str>, } @@ -194,32 +196,133 @@ mod c { } } + struct Target { + arch: String, + env: String, + os: String, + vendor: String, + } + /// Compile intrinsics from the compiler-rt C source code - pub fn compile(llvm_target: &[&str], target: &String) { - let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); - let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap(); - let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); - let target_vendor = env::var("CARGO_CFG_TARGET_VENDOR").unwrap(); + pub fn compile(llvm_target: &[&str], target_str: &str) { + // Don't use a C compiler for these targets: + // + // * nvptx - everything is bitcode, not compatible with mixed C/Rust + if target_str.contains("nvptx") { + println!("cargo:warning=nvptx target; skipping C build"); + return; + } + + let target = Target { + arch: env::var("CARGO_CFG_TARGET_ARCH").unwrap(), + env: env::var("CARGO_CFG_TARGET_ENV").unwrap(), + os: env::var("CARGO_CFG_TARGET_OS").unwrap(), + vendor: env::var("CARGO_CFG_TARGET_VENDOR").unwrap(), + }; + let sys_missing_only = cfg!(feature = "complete-system-rt"); let mut consider_float_intrinsics = true; - let cfg = &mut cc::Build::new(); + + // These platforms have all intrinsics we want to test against, so we don't + // need to build anything else. + if sys_missing_only && [""].contains(&target.arch.as_str()) { + return; + } + + let mut cfg = create_c_build_config( + &target, + target_str, + sys_missing_only, + &mut consider_float_intrinsics, + ); + let mut sources = Sources::new(); + + if sys_missing_only { + extend_sources_sys_missing_only(&mut sources, &target, consider_float_intrinsics); + } else { + extend_sources_full( + &mut sources, + llvm_target, + &target, + consider_float_intrinsics, + ); + } + + // When compiling the C code we require the user to tell us where the + // source code is, and this is largely done so when we're compiling as + // part of rust-lang/rust we can use the same llvm-project repository as + // rust-lang/rust. + let root = match env::var_os("RUST_COMPILER_RT_ROOT") { + Some(s) => PathBuf::from(s), + None => { + println!("cargo:warning=Defaulting to RUST_COMPILER_RT_ROOT at ./compiler-rt"); + PathBuf::from("./compiler-rt") + } + }; + if !root.exists() { + panic!("RUST_COMPILER_RT_ROOT={} does not exist", root.display()); + } + + // Support deterministic builds by remapping the __FILE__ prefix if the + // compiler supports it. This fixes the nondeterminism caused by the + // use of that macro in lib/builtins/int_util.h in compiler-rt. + cfg.flag_if_supported(&format!("-ffile-prefix-map={}=.", root.display())); + + // Include out-of-line atomics for aarch64, which are all generated by supplying different + // sets of flags to the same source file. + // Note: Out-of-line aarch64 atomics are not supported by the msvc toolchain (#430). + let src_dir = root.join("lib/builtins"); + if target.arch == "aarch64" && target.env != "msvc" { + // See below for why we're building these as separate libraries. + build_aarch64_out_of_line_atomics_libraries(&src_dir, &mut cfg); + + // Some run-time CPU feature detection is necessary, as well. + let cpu_model_src = if src_dir.join("cpu_model.c").exists() { + "cpu_model.c" + } else { + "cpu_model/aarch64.c" + }; + sources.extend(&[("__aarch64_have_lse_atomics", cpu_model_src)]); + } + + let mut added_sources = HashSet::new(); + for (sym, src) in sources.map.iter() { + let src = src_dir.join(src); + if added_sources.insert(src.clone()) { + cfg.file(&src); + println!("cargo:rerun-if-changed={}", src.display()); + } + println!("cargo:rustc-cfg={}=\"optimized-c\"", sym); + } + + cfg.compile("libcompiler-rt.a"); + } + + /// Set up configuration for the C libraries + fn create_c_build_config( + target: &Target, + target_str: &str, + sys_missing_only: bool, + consider_float_intrinsics: &mut bool, + ) -> cc::Build { + let mut cfg = cc::Build::new(); // AArch64 GCCs exit with an error condition when they encounter any kind of floating point // code if the `nofp` and/or `nosimd` compiler flags have been set. // // Therefore, evaluate if those flags are present and set a boolean that causes any // compiler-rt intrinsics that contain floating point source to be excluded for this target. - if target_arch == "aarch64" { - let cflags_key = String::from("CFLAGS_") + &(target.to_owned().replace("-", "_")); + if target.arch == "aarch64" { + let cflags_key = format!("CFLAGS_{}", target_str.replace("-", "_")); if let Ok(cflags_value) = env::var(cflags_key) { if cflags_value.contains("+nofp") || cflags_value.contains("+nosimd") { - consider_float_intrinsics = false; + *consider_float_intrinsics = false; } } } cfg.warnings(false); - if target_env == "msvc" { + if target.env == "msvc" { // Don't pull in extra libraries on MSVC cfg.flag("/Zl"); @@ -248,7 +351,7 @@ mod c { // at odds with compiling with `-ffreestanding`, as the header // may be incompatible or not present. Create a minimal stub // header to use instead. - if target_os == "uefi" { + if target.os == "uefi" { let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); let include_dir = out_dir.join("include"); if !include_dir.exists() { @@ -258,7 +361,53 @@ mod c { cfg.flag(&format!("-I{}", include_dir.to_str().unwrap())); } - let mut sources = Sources::new(); + if target.os == "android" { + // Work around a bug in the NDK headers (fixed in + // https://r.android.com/2038949 which will be released in a future + // NDK version) by providing a definition of LONG_BIT. + cfg.define("LONG_BIT", "(8 * sizeof(long))"); + } + + if target.vendor == "apple" && sys_missing_only { + cfg.define("CRT_HAS_TF_MODE", "1"); + cfg.define("CRT_HAS_IEEE_TF", "1"); + cfg.define("CRT_HAS_F128", "1"); + cfg.define("CRT_HAS_128BIT", "1"); + // A hacky way to get something with the correct f128 ABI (uint128_t size and + // alignment but passed with the float CC) on platforms where it isn't defined. + cfg.define("tf_float", "double __attribute__((vector_size(16)))"); + } + + cfg + } + + /// Add functions that are implemented in Rust's `compiler-rt` and exist in LLVM `compiiler-rt` + /// sources, but are not built/available on the host system. + /// + /// This is only needed for testing. + fn extend_sources_sys_missing_only( + sources: &mut Sources, + target: &Target, + consider_float_intrinsics: bool, + ) { + if target.vendor == "apple" && consider_float_intrinsics { + sources.extend(&[ + ("__addtf3", "addtf3.c"), + ("__multf3", "multf3.c"), + ("__subtf3", "subtf3.c"), + ("__divtf3", "divtf3.c"), + ]); + } + } + + /// Add functions that are missing from Rust's `compiler-rt`, or that we would like to + /// replace with a more performant version. + fn extend_sources_full( + sources: &mut Sources, + llvm_target: &[&str], + target: &Target, + consider_float_intrinsics: bool, + ) { sources.extend(&[ ("__absvdi2", "absvdi2.c"), ("__absvsi2", "absvsi2.c"), @@ -308,10 +457,10 @@ mod c { // On iOS and 32-bit OSX these are all just empty intrinsics, no need to // include them. - if target_os != "ios" - && target_os != "watchos" - && target_os != "tvos" - && (target_vendor != "apple" || target_arch != "x86") + if target.os != "ios" + && target.os != "watchos" + && target.os != "tvos" + && (target.vendor != "apple" || target.arch != "x86") { sources.extend(&[ ("__absvti2", "absvti2.c"), @@ -333,7 +482,7 @@ mod c { } } - if target_vendor == "apple" { + if target.vendor == "apple" { sources.extend(&[ ("atomic_flag_clear", "atomic_flag_clear.c"), ("atomic_flag_clear_explicit", "atomic_flag_clear_explicit.c"), @@ -347,8 +496,8 @@ mod c { ]); } - if target_env != "msvc" { - if target_arch == "x86" { + if target.env != "msvc" { + if target.arch == "x86" { sources.extend(&[ ("__ashldi3", "i386/ashldi3.S"), ("__ashrdi3", "i386/ashrdi3.S"), @@ -362,11 +511,11 @@ mod c { } } - if target_arch == "arm" - && target_os != "ios" - && target_os != "watchos" - && target_os != "tvos" - && target_env != "msvc" + if target.arch == "arm" + && target.os != "ios" + && target.os != "watchos" + && target.os != "tvos" + && target.env != "msvc" { sources.extend(&[ ("__aeabi_div0", "arm/aeabi_div0.c"), @@ -389,7 +538,7 @@ mod c { ("__umodsi3", "arm/umodsi3.S"), ]); - if target_os == "freebsd" { + if target.os == "freebsd" { sources.extend(&[("__clear_cache", "clear_cache.c")]); } @@ -461,7 +610,7 @@ mod c { ]); } - if (target_arch == "aarch64" || target_arch == "arm64ec") && consider_float_intrinsics { + if (target.arch == "aarch64" || target.arch == "arm64ec") && consider_float_intrinsics { sources.extend(&[ ("__extenddftf2", "extenddftf2.c"), ("__extendsftf2", "extendsftf2.c"), @@ -477,25 +626,21 @@ mod c { ("__floatunsitf", "floatunsitf.c"), ("__trunctfdf2", "trunctfdf2.c"), ("__trunctfsf2", "trunctfsf2.c"), - ("__addtf3", "addtf3.c"), - ("__multf3", "multf3.c"), - ("__subtf3", "subtf3.c"), - ("__divtf3", "divtf3.c"), ("__powitf2", "powitf2.c"), ("__fe_getround", "fp_mode.c"), ("__fe_raise_inexact", "fp_mode.c"), ]); - if target_os != "windows" { + if target.os != "windows" { sources.extend(&[("__multc3", "multc3.c")]); } } - if target_arch == "mips" || target_arch == "riscv32" || target_arch == "riscv64" { + if target.arch == "mips" || target.arch == "riscv32" || target.arch == "riscv64" { sources.extend(&[("__bswapsi2", "bswapsi2.c")]); } - if target_arch == "mips64" { + if target.arch == "mips64" { sources.extend(&[ ("__extenddftf2", "extenddftf2.c"), ("__netf2", "comparetf2.c"), @@ -509,7 +654,7 @@ mod c { ]); } - if target_arch == "loongarch64" { + if target.arch == "loongarch64" { sources.extend(&[ ("__extenddftf2", "extenddftf2.c"), ("__netf2", "comparetf2.c"), @@ -524,7 +669,7 @@ mod c { } // Remove the assembly implementations that won't compile for the target - if llvm_target[0] == "thumbv6m" || llvm_target[0] == "thumbv8m.base" || target_os == "uefi" + if llvm_target[0] == "thumbv6m" || llvm_target[0] == "thumbv8m.base" || target.os == "uefi" { let mut to_remove = Vec::new(); for (k, v) in sources.map.iter() { @@ -543,65 +688,14 @@ mod c { } // Android uses emulated TLS so we need a runtime support function. - if target_os == "android" { + if target.os == "android" { sources.extend(&[("__emutls_get_address", "emutls.c")]); - - // Work around a bug in the NDK headers (fixed in - // https://r.android.com/2038949 which will be released in a future - // NDK version) by providing a definition of LONG_BIT. - cfg.define("LONG_BIT", "(8 * sizeof(long))"); } // OpenHarmony also uses emulated TLS. - if target_env == "ohos" { + if target.env == "ohos" { sources.extend(&[("__emutls_get_address", "emutls.c")]); } - - // When compiling the C code we require the user to tell us where the - // source code is, and this is largely done so when we're compiling as - // part of rust-lang/rust we can use the same llvm-project repository as - // rust-lang/rust. - let root = match env::var_os("RUST_COMPILER_RT_ROOT") { - Some(s) => PathBuf::from(s), - None => panic!("RUST_COMPILER_RT_ROOT is not set"), - }; - if !root.exists() { - panic!("RUST_COMPILER_RT_ROOT={} does not exist", root.display()); - } - - // Support deterministic builds by remapping the __FILE__ prefix if the - // compiler supports it. This fixes the nondeterminism caused by the - // use of that macro in lib/builtins/int_util.h in compiler-rt. - cfg.flag_if_supported(&format!("-ffile-prefix-map={}=.", root.display())); - - // Include out-of-line atomics for aarch64, which are all generated by supplying different - // sets of flags to the same source file. - // Note: Out-of-line aarch64 atomics are not supported by the msvc toolchain (#430). - let src_dir = root.join("lib/builtins"); - if target_arch == "aarch64" && target_env != "msvc" { - // See below for why we're building these as separate libraries. - build_aarch64_out_of_line_atomics_libraries(&src_dir, cfg); - - // Some run-time CPU feature detection is necessary, as well. - let cpu_model_src = if src_dir.join("cpu_model.c").exists() { - "cpu_model.c" - } else { - "cpu_model/aarch64.c" - }; - sources.extend(&[("__aarch64_have_lse_atomics", cpu_model_src)]); - } - - let mut added_sources = HashSet::new(); - for (sym, src) in sources.map.iter() { - let src = src_dir.join(src); - if added_sources.insert(src.clone()) { - cfg.file(&src); - println!("cargo:rerun-if-changed={}", src.display()); - } - println!("cargo:rustc-cfg={}=\"optimized-c\"", sym); - } - - cfg.compile("libcompiler-rt.a"); } fn build_aarch64_out_of_line_atomics_libraries(builtins_dir: &Path, cfg: &mut cc::Build) { diff --git a/testcrate/Cargo.toml b/testcrate/Cargo.toml index 762d3293b..6326cb2dc 100644 --- a/testcrate/Cargo.toml +++ b/testcrate/Cargo.toml @@ -25,8 +25,9 @@ utest-cortex-m-qemu = { default-features = false, git = "https://github.com/japa utest-macros = { git = "https://github.com/japaric/utest" } [features] -default = ["mangled-names"] +default = ["mangled-names", "complete-system-rt"] c = ["compiler_builtins/c"] no-asm = ["compiler_builtins/no-asm"] mem = ["compiler_builtins/mem"] mangled-names = ["compiler_builtins/mangled-names"] +complete-system-rt = ["compiler_builtins/complete-system-rt"]