From e77e85d7816d814dd936b58f08fd3984edbf37eb Mon Sep 17 00:00:00 2001 From: gnzlbg Date: Tue, 2 Jul 2019 09:06:36 +0200 Subject: [PATCH 1/8] Provide a cdylib with the libm C ABI --- Cargo.toml | 1 + crates/libm-cdylib/Cargo.toml | 20 ++++ crates/libm-cdylib/build.rs | 8 ++ crates/libm-cdylib/src/lib.rs | 153 +++++++++++++++++++++++++++ crates/libm-cdylib/src/macros.rs | 55 ++++++++++ crates/libm-cdylib/src/test_utils.rs | 108 +++++++++++++++++++ src/math/acos.rs | 2 +- 7 files changed, 346 insertions(+), 1 deletion(-) create mode 100644 crates/libm-cdylib/Cargo.toml create mode 100644 crates/libm-cdylib/build.rs create mode 100644 crates/libm-cdylib/src/lib.rs create mode 100644 crates/libm-cdylib/src/macros.rs create mode 100644 crates/libm-cdylib/src/test_utils.rs diff --git a/Cargo.toml b/Cargo.toml index bd1bb80ce..6b44cf75d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ checked = [] [workspace] members = [ "crates/compiler-builtins-smoke-test", + "crates/libm-cdylib", "crates/libm-bench", ] diff --git a/crates/libm-cdylib/Cargo.toml b/crates/libm-cdylib/Cargo.toml new file mode 100644 index 000000000..43f427d4a --- /dev/null +++ b/crates/libm-cdylib/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "libm-cdylib" +version = "0.1.0" +authors = ["Gonzalo Brito Gadeschi "] +edition = "2018" + +[features] +default = ['stable'] +stable = [] + +# Used checked array indexing instead of unchecked array indexing in this +# library. +checked = [] + +[lib] +name = "libm" +crate-type = ["cdylib"] + +[dev-dependencies] +paste = "0.1.5" \ No newline at end of file diff --git a/crates/libm-cdylib/build.rs b/crates/libm-cdylib/build.rs new file mode 100644 index 000000000..f138cbf2f --- /dev/null +++ b/crates/libm-cdylib/build.rs @@ -0,0 +1,8 @@ +use std::env; +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + let profile = env::var("PROFILE").unwrap_or(String::new()); + if profile == "release" { + println!("cargo:rustc-cfg=release_profile"); + } +} diff --git a/crates/libm-cdylib/src/lib.rs b/crates/libm-cdylib/src/lib.rs new file mode 100644 index 000000000..a10433f98 --- /dev/null +++ b/crates/libm-cdylib/src/lib.rs @@ -0,0 +1,153 @@ +#![allow(dead_code)] +#![cfg_attr(not(test), feature(core_intrinsics, lang_items))] +#![cfg_attr(not(test), no_std)] + +#[path = "../../../src/math/mod.rs"] +mod libm; + +#[macro_use] +mod macros; + +#[cfg(test)] +mod test_utils; + +#[cfg(not(test))] +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + unsafe { core::intrinsics::abort() } +} + +#[cfg(not(test))] +#[lang = "eh_personality"] +extern "C" fn eh_personality() {} + +// All functions to be exported by the C ABI. +// Includes a test input/output pair for testing. +// The test output will be used to override the +// result of the function, and the test input +// is used to call the overriden function from C. +// This is needed to make sure that we are linking +// against this libm during testing, and not the +// system's libm. +// +// +// FIXME: missing symbols: _memcpy, _memset, etc. +export! { + fn acos(x: f64) -> f64: (42.) -> 42.; + fn acosf(x: f32) -> f32: (42.) -> 42.; + fn acosh(x: f64) -> f64: (42.) -> 42.; + fn acoshf(x: f32) -> f32: (42.) -> 42.; + fn asin(x: f64) -> f64: (42.) -> 42.; + fn asinf(x: f32) -> f32: (42.) -> 42.; + fn asinh(x: f64) -> f64: (42.) -> 42.; + fn asinhf(x: f32) -> f32: (42.) -> 42.; + // fn atan(x: f64) -> f64: (42.) -> 42.; + fn atanf(x: f32) -> f32: (42.) -> 42.; + fn atanh(x: f64) -> f64: (42.) -> 42.; + fn atanhf(x: f32) -> f32: (42.) -> 42.; + fn cbrt(x: f64) -> f64: (42.) -> 42.; + fn cbrtf(x: f32) -> f32: (42.) -> 42.; + fn ceil(x: f64) -> f64: (42.) -> 42.; + fn ceilf(x: f32) -> f32: (42.) -> 42.; + fn copysign(x: f64, y: f64) -> f64: (42., 42.) -> 42.; + fn copysignf(x: f32, y: f32) -> f32: (42., 42.) -> 42.; + //fn cos(x: f64) -> f64: (42.) -> 42.; + //fn cosf(x: f32) -> f32: (42.) -> 42.; + fn cosh(x: f64) -> f64: (42.) -> 42.; + fn coshf(x: f32) -> f32: (42.) -> 42.; + fn erf(x: f64) -> f64: (42.) -> 42.; + fn erfc(x: f64) -> f64: (42.) -> 42.; + fn erff(x: f32) -> f32: (42.) -> 42.; + fn erfcf(x: f32) -> f32: (42.) -> 42.; + fn exp(x: f64) -> f64: (42.) -> 42.; + fn expf(x: f32) -> f32: (42.) -> 42.; + // FIXME: not in C: + // fn exp10(x: f64) -> f64: (42.) -> 42.; + // fn exp10f(x: f32) -> f32: (42.) -> 42.; + fn exp2(x: f64) -> f64: (42.) -> 42.; + fn exp2f(x: f32) -> f32: (42.) -> 42.; + fn expm1(x: f64) -> f64: (42.) -> 42.; + fn expm1f(x: f32) -> f32: (42.) -> 42.; + fn fabs(x: f64) -> f64: (42.) -> 42.; + fn fabsf(x: f32) -> f32: (42.) -> 42.; + fn fdim(x: f64, y: f64) -> f64: (42., 42.) -> 42.; + fn fdimf(x: f32, y: f32) -> f32: (42., 42.) -> 42.; + fn floor(x: f64) -> f64: (42.) -> 42.; + fn floorf(x: f32) -> f32: (42.) -> 42.; + fn fma(x: f64, y: f64, z: f64) -> f64: (42., 42., 42.) -> 42.; + fn fmaf(x: f32, y: f32, z: f32) -> f32: (42., 42., 42.) -> 42.; + fn fmax(x: f64, y: f64) -> f64: (42., 42.) -> 42.; + fn fmaxf(x: f32, y: f32) -> f32: (42., 42.) -> 42.; + fn fmin(x: f64, y: f64) -> f64: (42., 42.) -> 42.; + fn fminf(x: f32, y: f32) -> f32: (42., 42.) -> 42.; + fn fmod(x: f64, y: f64) -> f64: (42., 42.) -> 42.; + fn fmodf(x: f32, y: f32) -> f32: (42., 42.) -> 42.; + + // different ABI than in C + // fn frexp(x: f64) -> (f64, i32): (42.) -> (42., 42); + // fn frexpf(x: f32) -> (f32, i32): (42.) -> (42., 42); + + fn hypot(x: f64, y: f64) -> f64: (42., 42.) -> 42.; + fn hypotf(x: f32, y: f32) -> f32: (42., 42.) -> 42.; + fn ilogb(x: f64) -> i32: (42.) -> 42; + fn ilogbf(x: f32) -> i32: (42.) -> 42; + + // FIXME: fail to link: + // fn j0(x: f64) -> f64: (42.) -> 42.; + // fn j0f(x: f32) -> f32: (42.) -> 42.; + // fn j1(x: f64) -> f64: (42.) -> 42.; + // fn j1f(x: f32) -> f32: (42.) -> 42.; + // fn jn(n: i32, x: f64) -> f64: (42, 42.) -> 42.; + // fn jnf(n: i32, x: f32) -> f32: (42, 42.) -> 42.; + + fn ldexp(x: f64, n: i32) -> f64: (42, 42.) -> 42.; + fn ldexpf(x: f32, n: i32) -> f32: (42, 42.) -> 42.; + fn lgamma(x: f64) -> f64: (42.) -> 42.; + fn lgammaf(x: f32) -> f32: (42.) -> 42.; + + // different ABI + // fn lgamma_r(x: f64) -> (f64, i32): (42.) -> (42., 42); + // fn lgammaf_r(x: f32) -> (f32, i32): (42.) -> (42., 42); + + fn log(x: f64) -> f64: (42.) -> 42.; + fn logf(x: f32) -> f32: (42.) -> 42.; + fn log10(x: f64) -> f64: (42.) -> 42.; + fn log10f(x: f32) -> f32: (42.) -> 42.; + fn log1p(x: f64) -> f64: (42.) -> 42.; + fn log1pf(x: f32) -> f32: (42.) -> 42.; + fn log2(x: f64) -> f64: (42.) -> 42.; + fn log2f(x: f32) -> f32: (42.) -> 42.; + fn pow(x: f64, y: f64) -> f64: (42., 42.) -> 42.; + fn powf(x: f32, y: f32) -> f32: (42., 42.) -> 42.; + // fn modf(x: f64) -> (f64, f64): (42.) -> (42., 42.); + // fn modff(x: f32) -> (f32, f32): (42.) -> (42., 42.); + + // different ABI + // remquo + // remquof + + fn round(x: f64) -> f64: (42.) -> 42.; + fn roundf(x: f32) -> f32: (42.) -> 42.; + fn scalbn(x: f64, n: i32) -> f64: (42., 42) -> 42.; + fn scalbnf(x: f32, n: i32) -> f32: (42., 42) -> 42.; + + // different ABI + // fn sincos + // fn sincosf + + // fn sin(x: f64) -> f64: (42.) -> 42.; + // fn sinf(x: f32) -> f32: (42.) -> 42.; + + fn sinh(x: f64) -> f64: (42.) -> 42.; + fn sinhf(x: f32) -> f32: (42.) -> 42.; + fn sqrt(x: f64) -> f64: (42.) -> 42.; + fn sqrtf(x: f32) -> f32: (42.) -> 42.; + // fn tan(x: f64) -> f64: (42.) -> 42.; + // fn tanf(x: f32) -> f32: (42.) -> 42.; + fn tanh(x: f64) -> f64: (42.) -> 42.; + fn tanhf(x: f32) -> f32: (42.) -> 42.; + // fn tgamma(x: f64) -> f64: (42.) -> 42.; + // fn tgammaf(x: f32) -> f32: (42.) -> 42.; + fn trunc(x: f64) -> f64: (42.) -> 42.; + fn truncf(x: f32) -> f32: (42.) -> 42.; +} diff --git a/crates/libm-cdylib/src/macros.rs b/crates/libm-cdylib/src/macros.rs new file mode 100644 index 000000000..c5b234089 --- /dev/null +++ b/crates/libm-cdylib/src/macros.rs @@ -0,0 +1,55 @@ +macro_rules! export { + (fn $id:ident ($($arg:ident : $arg_ty:ty),* ) -> $ret_ty:ty: + ($($test_arg:expr),*) -> $test_ret:expr;) => { + #[no_mangle] + pub extern "C" fn $id($($arg: $arg_ty),*) -> $ret_ty { + #[cfg(link_test)] { + let _ = libm::$id($($arg),*); + $test_ret as _ + } + #[cfg(not(link_test))] { + libm::$id($($arg),*) + } + } + + #[cfg(test)] + paste::item! { + #[test] + fn [<$id _test>]() { + use crate::test_utils::*; + let (cret_t, c_format_s) = ctype_and_cformat(stringify!($ret_ty)); + let ctest = format!( + r#" + #include + #include + #include + int main() {{ + {cret_t} result = {id}({input}); + fprintf(stdout, "{c_format_s}", result); + return 0; + }} + "#, + id = stringify!($id), + input = [$(stringify!($test_arg)),*].join(","), + cret_t = cret_t, + c_format_s = c_format_s + ); + + let src = &format!("../../target/{}.c", stringify!($id)); + let bin = &format!("../../target/{}", stringify!($id)); + let src_path = std::path::Path::new(src); + let bin_path = std::path::Path::new(bin); + write_to_file(&src_path, &ctest); + compile_file(&src_path, &bin_path); + compile_lib(); + check(&bin_path, $test_ret as $ret_ty) + } + } + }; + ($(fn $id:ident ($($arg:ident : $arg_ty:ty),* ) -> $ret_ty:ty: + ($($test_arg:expr),*) -> $test_ret:expr;)*) => { + $( + export! { fn $id ($($arg : $arg_ty),* ) -> $ret_ty: ($($test_arg),*) -> $test_ret; } + )* + } +} diff --git a/crates/libm-cdylib/src/test_utils.rs b/crates/libm-cdylib/src/test_utils.rs new file mode 100644 index 000000000..bcb6eadb4 --- /dev/null +++ b/crates/libm-cdylib/src/test_utils.rs @@ -0,0 +1,108 @@ +use std::{fs, io, path::Path, process}; + +pub(crate) fn write_to_file(path: &Path, content: &str) { + use io::Write; + let mut file = fs::File::create(&path).unwrap(); + write!(file, "{}", content).unwrap(); +} + +pub(crate) fn compile_lib() { + let mut cmd = process::Command::new("cargo"); + cmd.arg("build"); + if cfg!(release_profile) { + cmd.arg("--release"); + } + cmd.env("RUSTFLAGS", "--cfg=link_test"); + handle_err("lib_build", &cmd.output().unwrap()); +} + +pub(crate) fn compile_file(src_path: &Path, bin_path: &Path) { + let mut cmd = process::Command::new("CC"); + cmd.arg("-fno-builtin") + .arg("-o") + .arg(bin_path) + .arg(src_path); + handle_err( + &format!("compile file: {}", src_path.display()), + &cmd.output().unwrap(), + ); +} + +pub(crate) fn check(path: &Path, expected: T) +where + T: PartialEq + std::fmt::Debug + std::str::FromStr, + ::Err: std::fmt::Debug, +{ + let mut cmd = process::Command::new(path); + + let libm_path = format!( + "../../target/{}/liblibm.", + if cfg!(release_profile) { + "release" + } else { + "debug" + }, + ); + + // Replace libm at runtime + if cfg!(target_os = "macos") { + // cmd.env("DYLD_PRINT_LIBRARIES", "1"); + // cmd.env("X", "1"); + cmd.env("DYLD_FORCE_FLAT_NAMESPACE", "1"); + cmd.env( + "DYLD_INSERT_LIBRARIES", + format!("{}.{}", libm_path, "dylib"), + ); + } else if cfg!(target_os = "linux") { + cmd.env("LD_PRELOAD", format!("{}.{}", libm_path, "so")) + } + let output = cmd.output().unwrap(); + handle_err(&format!("run file: {}", path.display()), &output); + let result = String::from_utf8(output.stdout.clone()) + .unwrap() + .parse::(); + + if result.is_err() { + panic!(format_output("check (parse failure)", &output)); + } + let result = result.unwrap(); + assert_eq!(result, expected, "{}", format_output("check", &output)); +} + +pub(crate) fn handle_err(step: &str, output: &process::Output) { + if !output.status.success() { + eprintln!("{}", format_output(step, output)); + panic!(); + } +} + +pub(crate) fn format_output( + step: &str, + process::Output { + status, + stdout, + stderr, + }: &process::Output, +) -> String { + let mut s = format!("\nFAILED[{}]: exit code {:?}\n", step, status.code()); + s += &format!( + "FAILED[{}]: stdout:\n\n{}\n\n", + step, + String::from_utf8(stdout.to_vec()).unwrap() + ); + s += &format!( + "FAILED[{}]: stderr:\n\n{}\n\n", + step, + String::from_utf8(stderr.to_vec()).unwrap() + ); + s +} + +pub(crate) fn ctype_and_cformat(x: &str) -> (&str, &str) { + match x { + "f32" => ("float", "%f"), + "f64" => ("double", "%f"), + "i32" => ("int32_t", "%d"), + _ => panic!("unknown type: {}", x), + } +} diff --git a/src/math/acos.rs b/src/math/acos.rs index d5e1f6865..9028e8b49 100644 --- a/src/math/acos.rs +++ b/src/math/acos.rs @@ -62,7 +62,7 @@ fn r(z: f64) -> f64 { /// Returns values in radians, in the range of 0 to pi. #[inline] #[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)] -pub fn acos(x: f64) -> f64 { +extern "C" pub fn acos(x: f64) -> f64 { let x1p_120f = f64::from_bits(0x3870000000000000); // 0x1p-120 === 2 ^ -120 let z: f64; let w: f64; From 69f6e5661a19c9b0281602281fcd6c8c73aa2d15 Mon Sep 17 00:00:00 2001 From: gnzlbg Date: Tue, 2 Jul 2019 16:38:58 +0200 Subject: [PATCH 2/8] Fix nitpicks --- crates/libm-cdylib/src/lib.rs | 35 +++++++++++++++------------- crates/libm-cdylib/src/macros.rs | 29 ++++++++++++++++++----- crates/libm-cdylib/src/test_utils.rs | 30 ++++++++++++++++++++---- src/math/acos.rs | 2 +- 4 files changed, 69 insertions(+), 27 deletions(-) diff --git a/crates/libm-cdylib/src/lib.rs b/crates/libm-cdylib/src/lib.rs index a10433f98..776c3f702 100644 --- a/crates/libm-cdylib/src/lib.rs +++ b/crates/libm-cdylib/src/lib.rs @@ -14,24 +14,22 @@ mod test_utils; #[cfg(not(test))] #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { + // TODO: just call libc::abort - fails to link + // unsafe { libc::abort() } unsafe { core::intrinsics::abort() } } +// TODO: there has to be a way to avoid this #[cfg(not(test))] #[lang = "eh_personality"] extern "C" fn eh_personality() {} -// All functions to be exported by the C ABI. -// Includes a test input/output pair for testing. -// The test output will be used to override the -// result of the function, and the test input -// is used to call the overriden function from C. -// This is needed to make sure that we are linking -// against this libm during testing, and not the -// system's libm. +// This macro exports the functions that are part of the C ABI. // -// -// FIXME: missing symbols: _memcpy, _memset, etc. +// It generates tests that replace the implementation of the +// function with a specific value, that then is used to check +// that the library is properly linked. + export! { fn acos(x: f64) -> f64: (42.) -> 42.; fn acosf(x: f32) -> f32: (42.) -> 42.; @@ -41,6 +39,7 @@ export! { fn asinf(x: f32) -> f32: (42.) -> 42.; fn asinh(x: f64) -> f64: (42.) -> 42.; fn asinhf(x: f32) -> f32: (42.) -> 42.; + // FIXME: fails to link. Missing symbol: _memcpy // fn atan(x: f64) -> f64: (42.) -> 42.; fn atanf(x: f32) -> f32: (42.) -> 42.; fn atanh(x: f64) -> f64: (42.) -> 42.; @@ -51,6 +50,7 @@ export! { fn ceilf(x: f32) -> f32: (42.) -> 42.; fn copysign(x: f64, y: f64) -> f64: (42., 42.) -> 42.; fn copysignf(x: f32, y: f32) -> f32: (42., 42.) -> 42.; + // FIXME: fails to link. Missing symbols //fn cos(x: f64) -> f64: (42.) -> 42.; //fn cosf(x: f32) -> f32: (42.) -> 42.; fn cosh(x: f64) -> f64: (42.) -> 42.; @@ -83,7 +83,7 @@ export! { fn fmod(x: f64, y: f64) -> f64: (42., 42.) -> 42.; fn fmodf(x: f32, y: f32) -> f32: (42., 42.) -> 42.; - // different ABI than in C + // TODO: different ABI than in C - need a more elaborate wrapper // fn frexp(x: f64) -> (f64, i32): (42.) -> (42., 42); // fn frexpf(x: f32) -> (f32, i32): (42.) -> (42., 42); @@ -92,7 +92,7 @@ export! { fn ilogb(x: f64) -> i32: (42.) -> 42; fn ilogbf(x: f32) -> i32: (42.) -> 42; - // FIXME: fail to link: + // FIXME: fails to link. Missing symbols // fn j0(x: f64) -> f64: (42.) -> 42.; // fn j0f(x: f32) -> f32: (42.) -> 42.; // fn j1(x: f64) -> f64: (42.) -> 42.; @@ -105,7 +105,7 @@ export! { fn lgamma(x: f64) -> f64: (42.) -> 42.; fn lgammaf(x: f32) -> f32: (42.) -> 42.; - // different ABI + // TODO: different ABI than in C - need a more elaborate wrapper // fn lgamma_r(x: f64) -> (f64, i32): (42.) -> (42., 42); // fn lgammaf_r(x: f32) -> (f32, i32): (42.) -> (42., 42); @@ -119,10 +119,10 @@ export! { fn log2f(x: f32) -> f32: (42.) -> 42.; fn pow(x: f64, y: f64) -> f64: (42., 42.) -> 42.; fn powf(x: f32, y: f32) -> f32: (42., 42.) -> 42.; + + // FIXME: different ABI than in C - need a more elaborate wrapper // fn modf(x: f64) -> (f64, f64): (42.) -> (42., 42.); // fn modff(x: f32) -> (f32, f32): (42.) -> (42., 42.); - - // different ABI // remquo // remquof @@ -131,10 +131,11 @@ export! { fn scalbn(x: f64, n: i32) -> f64: (42., 42) -> 42.; fn scalbnf(x: f32, n: i32) -> f32: (42., 42) -> 42.; - // different ABI + // FIXME: different ABI than in C - need a more elaborate wrapper // fn sincos // fn sincosf + // FIXME: missing symbols - fails to link // fn sin(x: f64) -> f64: (42.) -> 42.; // fn sinf(x: f32) -> f32: (42.) -> 42.; @@ -142,10 +143,12 @@ export! { fn sinhf(x: f32) -> f32: (42.) -> 42.; fn sqrt(x: f64) -> f64: (42.) -> 42.; fn sqrtf(x: f32) -> f32: (42.) -> 42.; + // FIXME: missing symbols - fails to link // fn tan(x: f64) -> f64: (42.) -> 42.; // fn tanf(x: f32) -> f32: (42.) -> 42.; fn tanh(x: f64) -> f64: (42.) -> 42.; fn tanhf(x: f32) -> f32: (42.) -> 42.; + // FIXME: missing symbols - fails to link // fn tgamma(x: f64) -> f64: (42.) -> 42.; // fn tgammaf(x: f32) -> f32: (42.) -> 42.; fn trunc(x: f64) -> f64: (42.) -> 42.; diff --git a/crates/libm-cdylib/src/macros.rs b/crates/libm-cdylib/src/macros.rs index c5b234089..264c954b4 100644 --- a/crates/libm-cdylib/src/macros.rs +++ b/crates/libm-cdylib/src/macros.rs @@ -3,21 +3,33 @@ macro_rules! export { ($($test_arg:expr),*) -> $test_ret:expr;) => { #[no_mangle] pub extern "C" fn $id($($arg: $arg_ty),*) -> $ret_ty { + // This just forwards the call to the appropriate function of the + // libm crate. + #[cfg(not(link_test))] { + libm::$id($($arg),*) + } + // When generating the linking tests, we return a specific incorrect + // value. This lets us tell this libm from the system's one appart: #[cfg(link_test)] { + // TODO: as part of the rountrip, we probably want to verify + // that the argument values are the unique ones provided. let _ = libm::$id($($arg),*); $test_ret as _ } - #[cfg(not(link_test))] { - libm::$id($($arg),*) - } } #[cfg(test)] paste::item! { + // These tests check that the library links properly. #[test] - fn [<$id _test>]() { + fn [<$id _link_test>]() { use crate::test_utils::*; - let (cret_t, c_format_s) = ctype_and_cformat(stringify!($ret_ty)); + + // Generate a small C program that calls the C API from + // . This program prints the result into an appropriate + // type, that is then printed to stdout. + let (cret_t, c_format_s) + = ctype_and_printf_format_specifier(stringify!($ret_ty)); let ctest = format!( r#" #include @@ -40,8 +52,13 @@ macro_rules! export { let src_path = std::path::Path::new(src); let bin_path = std::path::Path::new(bin); write_to_file(&src_path, &ctest); + + // We now compile the C program into an executable, make sure + // that the libm-cdylib has been generated (and generate it if + // it isn't), and then we run the program, override the libm + // dynamically, and verify the result. compile_file(&src_path, &bin_path); - compile_lib(); + compile_cdylib(); check(&bin_path, $test_ret as $ret_ty) } } diff --git a/crates/libm-cdylib/src/test_utils.rs b/crates/libm-cdylib/src/test_utils.rs index bcb6eadb4..83c394dba 100644 --- a/crates/libm-cdylib/src/test_utils.rs +++ b/crates/libm-cdylib/src/test_utils.rs @@ -1,12 +1,18 @@ use std::{fs, io, path::Path, process}; +/// Writes the `content` string to a file at `path`. pub(crate) fn write_to_file(path: &Path, content: &str) { use io::Write; let mut file = fs::File::create(&path).unwrap(); write!(file, "{}", content).unwrap(); } -pub(crate) fn compile_lib() { +/// Compiles the libm-cdylib library as a C library. +/// +/// This just compiles it once, all other times it just +/// succeeds. We compile it with --cfg link_test to +/// enable the tests. +pub(crate) fn compile_cdylib() { let mut cmd = process::Command::new("cargo"); cmd.arg("build"); if cfg!(release_profile) { @@ -16,8 +22,16 @@ pub(crate) fn compile_lib() { handle_err("lib_build", &cmd.output().unwrap()); } +/// Compiles the test C program with source at `src_path` into +/// an executable at `bin_path`. pub(crate) fn compile_file(src_path: &Path, bin_path: &Path) { let mut cmd = process::Command::new("CC"); + // We disable the usage of builtin functions, e.g., from libm. + // This should ideally produce a link failure if libm is not dynamically + // linked. + // + // On MacOSX libSystem is linked (for printf, etc.) and it links libSystem_m + // transitively, so this doesn't help =/ cmd.arg("-fno-builtin") .arg("-o") .arg(bin_path) @@ -28,6 +42,7 @@ pub(crate) fn compile_file(src_path: &Path, bin_path: &Path) { ); } +/// Run the program and verify that it prints the expected value. pub(crate) fn check(path: &Path, expected: T) where T: PartialEq + std::fmt::Debug + std::str::FromStr, @@ -35,8 +50,9 @@ where { let mut cmd = process::Command::new(path); + // Find the cdylib - we just support standard locations for now. let libm_path = format!( - "../../target/{}/liblibm.", + "../../target/{}/liblibm", if cfg!(release_profile) { "release" } else { @@ -46,6 +62,7 @@ where // Replace libm at runtime if cfg!(target_os = "macos") { + // for debugging: // cmd.env("DYLD_PRINT_LIBRARIES", "1"); // cmd.env("X", "1"); cmd.env("DYLD_FORCE_FLAT_NAMESPACE", "1"); @@ -54,10 +71,12 @@ where format!("{}.{}", libm_path, "dylib"), ); } else if cfg!(target_os = "linux") { - cmd.env("LD_PRELOAD", format!("{}.{}", libm_path, "so")) + cmd.env("LD_PRELOAD", format!("{}.{}", libm_path, "so")); } + // Run the binary: let output = cmd.output().unwrap(); handle_err(&format!("run file: {}", path.display()), &output); + // Parse the result: let result = String::from_utf8(output.stdout.clone()) .unwrap() .parse::(); @@ -65,6 +84,7 @@ where if result.is_err() { panic!(format_output("check (parse failure)", &output)); } + // Check the result: let result = result.unwrap(); assert_eq!(result, expected, "{}", format_output("check", &output)); } @@ -98,7 +118,9 @@ pub(crate) fn format_output( s } -pub(crate) fn ctype_and_cformat(x: &str) -> (&str, &str) { +/// For a given Rust type `x`, this prints the name of the type in C, +/// as well as the printf format specifier used to print values of that type. +pub(crate) fn ctype_and_printf_format_specifier(x: &str) -> (&str, &str) { match x { "f32" => ("float", "%f"), "f64" => ("double", "%f"), diff --git a/src/math/acos.rs b/src/math/acos.rs index 9028e8b49..d5e1f6865 100644 --- a/src/math/acos.rs +++ b/src/math/acos.rs @@ -62,7 +62,7 @@ fn r(z: f64) -> f64 { /// Returns values in radians, in the range of 0 to pi. #[inline] #[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)] -extern "C" pub fn acos(x: f64) -> f64 { +pub fn acos(x: f64) -> f64 { let x1p_120f = f64::from_bits(0x3870000000000000); // 0x1p-120 === 2 ^ -120 let z: f64; let w: f64; From 4db1c4fcaec2737ef8fbd2d3e19d9ef1fe8ea6d5 Mon Sep 17 00:00:00 2001 From: gnzlbg Date: Tue, 2 Jul 2019 17:02:27 +0200 Subject: [PATCH 3/8] Document formatting specifiers --- crates/libm-cdylib/src/test_utils.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/libm-cdylib/src/test_utils.rs b/crates/libm-cdylib/src/test_utils.rs index 83c394dba..274e1d434 100644 --- a/crates/libm-cdylib/src/test_utils.rs +++ b/crates/libm-cdylib/src/test_utils.rs @@ -122,6 +122,15 @@ pub(crate) fn format_output( /// as well as the printf format specifier used to print values of that type. pub(crate) fn ctype_and_printf_format_specifier(x: &str) -> (&str, &str) { match x { + // Note: fprintf has no format specifier for floats, `%f`, converts + // floats into a double, and prints that. + // + // For the linking tests, precision doesn't really matter. The only + // thing that's tested is whether our implementation was properly called + // or not. This is done by making our functions return an incorrect + // magic value, different from the correct result. So as long as this is + // precise enough for us to be able to parse `42.0` from stdout as + // 42_f32/f64, everything works. "f32" => ("float", "%f"), "f64" => ("double", "%f"), "i32" => ("int32_t", "%d"), From a549e378b32b7588fcd552c2bbb1575865a75cc2 Mon Sep 17 00:00:00 2001 From: gnzlbg Date: Tue, 2 Jul 2019 17:07:19 +0200 Subject: [PATCH 4/8] Run libm-cdylib tests; add OSX test jobs --- azure-pipelines.yml | 17 +++++++++++++++++ ci/run.sh | 8 ++++++++ 2 files changed, 25 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c89346c73..1b9b75a3d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -40,6 +40,23 @@ jobs: x86_64: TARGET: x86_64-unknown-linux-gnu + - job: OSX + pool: + vmImage: macos-10.13 + steps: + - template: ci/azure-install-rust.yml + - bash: | + rustup target add $TARGET + sh ./ci/run.sh $TARGET + strategy: + matrix: + i686-apple-darwin: + TARGET: i686-apple-darwin + TOOLCHAIN: nightly + x86_64-apple-darwin: + TARGET: x86_64-apple-darwin + TOOLCHAIN: nightly + - job: wasm pool: vmImage: ubuntu-16.04 diff --git a/ci/run.sh b/ci/run.sh index 37ffb8793..b51f22461 100755 --- a/ci/run.sh +++ b/ci/run.sh @@ -13,3 +13,11 @@ $CMD --release --features 'stable' $CMD --features 'stable checked musl-reference-tests' $CMD --release --features 'stable checked musl-reference-tests' + +if [ "$TARGET" = "x86_64-unknown-linux-gnu" ] || [ "${TARGET}" = "x86_64-apple-darwin" ]; then + ( + cd crates/libm-cdylib + cargo test + cargo test --release + ) +fi From 164560a84c91e6fb162c95d1ea688a6791b200e3 Mon Sep 17 00:00:00 2001 From: gnzlbg Date: Tue, 2 Jul 2019 17:18:49 +0200 Subject: [PATCH 5/8] Disable musl reference tests on OSX --- ci/run.sh | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/ci/run.sh b/ci/run.sh index b51f22461..3af4e9982 100755 --- a/ci/run.sh +++ b/ci/run.sh @@ -8,11 +8,17 @@ CMD="cargo test --all --no-default-features --target $TARGET" $CMD $CMD --release -$CMD --features 'stable' -$CMD --release --features 'stable' +$CMD --features "stable" +$CMD --release --features "stable" -$CMD --features 'stable checked musl-reference-tests' -$CMD --release --features 'stable checked musl-reference-tests' +TEST_MUSL="musl-reference-tests" +if [ "$TARGET" = "x86_64-apple-darwin" ] || [ "$TARGET" = "i686-apple-darwin" ] ; then + # FIXME: disable musl-reference-tests on OSX + export TEST_MUSL="" +fi + +$CMD --features "stable checked" +$CMD --release --features "stable checked ${TEST_MUSL}" if [ "$TARGET" = "x86_64-unknown-linux-gnu" ] || [ "${TARGET}" = "x86_64-apple-darwin" ]; then ( From 53eab6a900f9a79edb627a9ee589c0852b59f92f Mon Sep 17 00:00:00 2001 From: gnzlbg Date: Tue, 2 Jul 2019 17:32:26 +0200 Subject: [PATCH 6/8] Be a bit more robust around path handling; support CARGO_TARGET_DIR --- ci/run.sh | 4 +++ crates/libm-cdylib/src/macros.rs | 8 +++--- crates/libm-cdylib/src/test_utils.rs | 41 ++++++++++++++++++---------- 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/ci/run.sh b/ci/run.sh index 3af4e9982..5588b0db1 100755 --- a/ci/run.sh +++ b/ci/run.sh @@ -1,8 +1,12 @@ #!/usr/bin/env sh set -ex + TARGET=$1 +export RUST_BACKTRACE=1 +export RUST_TEST_THREADS=1 + CMD="cargo test --all --no-default-features --target $TARGET" $CMD diff --git a/crates/libm-cdylib/src/macros.rs b/crates/libm-cdylib/src/macros.rs index 264c954b4..1c229ddc3 100644 --- a/crates/libm-cdylib/src/macros.rs +++ b/crates/libm-cdylib/src/macros.rs @@ -47,10 +47,10 @@ macro_rules! export { c_format_s = c_format_s ); - let src = &format!("../../target/{}.c", stringify!($id)); - let bin = &format!("../../target/{}", stringify!($id)); - let src_path = std::path::Path::new(src); - let bin_path = std::path::Path::new(bin); + let target_dir = target_dir(); + eprintln!("target dir: {}", target_dir.display()); + let src_path = target_dir.clone().join(format!("{}.c", stringify!($id))); + let bin_path = target_dir.clone().join(format!("{}", stringify!($id))); write_to_file(&src_path, &ctest); // We now compile the C program into an executable, make sure diff --git a/crates/libm-cdylib/src/test_utils.rs b/crates/libm-cdylib/src/test_utils.rs index 274e1d434..a9ba105cd 100644 --- a/crates/libm-cdylib/src/test_utils.rs +++ b/crates/libm-cdylib/src/test_utils.rs @@ -25,7 +25,16 @@ pub(crate) fn compile_cdylib() { /// Compiles the test C program with source at `src_path` into /// an executable at `bin_path`. pub(crate) fn compile_file(src_path: &Path, bin_path: &Path) { - let mut cmd = process::Command::new("CC"); + let cc = if std::env::var("CC").is_ok() { + std::env::var("CC").unwrap().to_string() + } else if cfg!(target_os = "linux") { + "gcc".to_string() + } else if cfg!(target_os = "macos") { + "clang".to_string() + } else { + panic!("unknown platform - Ccompiler not found") + }; + let mut cmd = process::Command::new(&cc); // We disable the usage of builtin functions, e.g., from libm. // This should ideally produce a link failure if libm is not dynamically // linked. @@ -51,27 +60,23 @@ where let mut cmd = process::Command::new(path); // Find the cdylib - we just support standard locations for now. - let libm_path = format!( - "../../target/{}/liblibm", - if cfg!(release_profile) { - "release" - } else { - "debug" - }, - ); + let libm_path = target_dir().join(if cfg!(release_profile) { + "release" + } else { + "debug" + }); // Replace libm at runtime if cfg!(target_os = "macos") { + let lib_path = libm_path.join("liblibm.dylib"); // for debugging: // cmd.env("DYLD_PRINT_LIBRARIES", "1"); // cmd.env("X", "1"); cmd.env("DYLD_FORCE_FLAT_NAMESPACE", "1"); - cmd.env( - "DYLD_INSERT_LIBRARIES", - format!("{}.{}", libm_path, "dylib"), - ); + cmd.env("DYLD_INSERT_LIBRARIES", lib_path.display().to_string()); } else if cfg!(target_os = "linux") { - cmd.env("LD_PRELOAD", format!("{}.{}", libm_path, "so")); + let lib_path = libm_path.join("liblibm.so"); + cmd.env("LD_PRELOAD", lib_path.display().to_string()); } // Run the binary: let output = cmd.output().unwrap(); @@ -137,3 +142,11 @@ pub(crate) fn ctype_and_printf_format_specifier(x: &str) -> (&str, &str) { _ => panic!("unknown type: {}", x), } } + +pub(crate) fn target_dir() -> std::path::PathBuf { + if let Ok(dir) = std::env::var("CARGO_TARGET_DIR") { + std::path::PathBuf::from(&dir) + } else { + Path::new("../../target").into() + } +} From 7fa7c152e72929ceb09aed14e4de857b8f11eeda Mon Sep 17 00:00:00 2001 From: gnzlbg Date: Tue, 2 Jul 2019 22:10:23 +0200 Subject: [PATCH 7/8] Properly link the library --- Cargo.toml | 2 +- azure-pipelines.yml | 5 ++- ci/run.sh | 14 ++++--- crates/libm-cdylib/build.rs | 9 +++++ crates/libm-cdylib/src/lib.rs | 7 ++++ crates/libm-cdylib/src/macros.rs | 8 ++-- crates/libm-cdylib/src/test_utils.rs | 57 +++++++++++++++++++--------- 7 files changed, 73 insertions(+), 29 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6b44cf75d..042b6e65b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,8 +26,8 @@ checked = [] [workspace] members = [ "crates/compiler-builtins-smoke-test", - "crates/libm-cdylib", "crates/libm-bench", + "crates/libm-cdylib", ] [dev-dependencies] diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1b9b75a3d..4ccc59f2a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -37,8 +37,11 @@ jobs: TARGET: powerpc64-unknown-linux-gnu powerpc64le: TARGET: powerpc64le-unknown-linux-gnu - x86_64: + x86_64_stable: TARGET: x86_64-unknown-linux-gnu + x86_64_nightly: + TARGET: x86_64-unknown-linux-gnu + TOOLCHAIN: nightly - job: OSX pool: diff --git a/ci/run.sh b/ci/run.sh index 5588b0db1..f0016a793 100755 --- a/ci/run.sh +++ b/ci/run.sh @@ -24,10 +24,12 @@ fi $CMD --features "stable checked" $CMD --release --features "stable checked ${TEST_MUSL}" -if [ "$TARGET" = "x86_64-unknown-linux-gnu" ] || [ "${TARGET}" = "x86_64-apple-darwin" ]; then - ( - cd crates/libm-cdylib - cargo test - cargo test --release - ) +if rustc --version | grep "nightly" ; then + if [ "$TARGET" = "x86_64-unknown-linux-gnu" ] || [ "${TARGET}" = "x86_64-apple-darwin" ]; then + ( + cd crates/libm-cdylib + cargo test + cargo test --release + ) + fi fi diff --git a/crates/libm-cdylib/build.rs b/crates/libm-cdylib/build.rs index f138cbf2f..0031e8a9f 100644 --- a/crates/libm-cdylib/build.rs +++ b/crates/libm-cdylib/build.rs @@ -5,4 +5,13 @@ fn main() { if profile == "release" { println!("cargo:rustc-cfg=release_profile"); } + let nightly = { + let mut cmd = std::process::Command::new("rustc"); + cmd.arg("--version"); + let output = String::from_utf8(cmd.output().unwrap().stdout).unwrap(); + output.contains("nightly") + }; + if nightly { + println!("cargo:rustc-cfg=unstable_rust"); + } } diff --git a/crates/libm-cdylib/src/lib.rs b/crates/libm-cdylib/src/lib.rs index 776c3f702..7223a770c 100644 --- a/crates/libm-cdylib/src/lib.rs +++ b/crates/libm-cdylib/src/lib.rs @@ -1,3 +1,10 @@ +#![cfg( + // The tests are only enabled on x86 32/64-bit linux/macos: + all(unstable_rust, + any(target_os = "linux", target_os = "macos"), + any(target_arch = "x86", target_arch = "x86_64") + ) +)] #![allow(dead_code)] #![cfg_attr(not(test), feature(core_intrinsics, lang_items))] #![cfg_attr(not(test), no_std)] diff --git a/crates/libm-cdylib/src/macros.rs b/crates/libm-cdylib/src/macros.rs index 1c229ddc3..091fec7e7 100644 --- a/crates/libm-cdylib/src/macros.rs +++ b/crates/libm-cdylib/src/macros.rs @@ -25,6 +25,9 @@ macro_rules! export { fn [<$id _link_test>]() { use crate::test_utils::*; + // This re-compiles the dynamic library: + compile_cdylib(); + // Generate a small C program that calls the C API from // . This program prints the result into an appropriate // type, that is then printed to stdout. @@ -55,10 +58,9 @@ macro_rules! export { // We now compile the C program into an executable, make sure // that the libm-cdylib has been generated (and generate it if - // it isn't), and then we run the program, override the libm - // dynamically, and verify the result. + // it isn't), and then we run the program, override the libm, + // and verify the result. compile_file(&src_path, &bin_path); - compile_cdylib(); check(&bin_path, $test_ret as $ret_ty) } } diff --git a/crates/libm-cdylib/src/test_utils.rs b/crates/libm-cdylib/src/test_utils.rs index a9ba105cd..36cc524b4 100644 --- a/crates/libm-cdylib/src/test_utils.rs +++ b/crates/libm-cdylib/src/test_utils.rs @@ -45,6 +45,21 @@ pub(crate) fn compile_file(src_path: &Path, bin_path: &Path) { .arg("-o") .arg(bin_path) .arg(src_path); + + // Link our libm + let lib_path = cdylib_dir(); + { + let mut ls = process::Command::new("ls"); + ls.arg(lib_path.clone()); + let output = ls.output().unwrap(); + let output = String::from_utf8(output.stdout).unwrap(); + println!("ls\n{}]n", output); + } + cmd.arg(format!("-L{}", lib_path.display())); + cmd.arg("-llibm"); + + eprintln!("compile cmd: {:?}", cmd); + handle_err( &format!("compile file: {}", src_path.display()), &cmd.output().unwrap(), @@ -59,25 +74,12 @@ where { let mut cmd = process::Command::new(path); - // Find the cdylib - we just support standard locations for now. - let libm_path = target_dir().join(if cfg!(release_profile) { - "release" - } else { - "debug" - }); - - // Replace libm at runtime - if cfg!(target_os = "macos") { - let lib_path = libm_path.join("liblibm.dylib"); - // for debugging: - // cmd.env("DYLD_PRINT_LIBRARIES", "1"); - // cmd.env("X", "1"); - cmd.env("DYLD_FORCE_FLAT_NAMESPACE", "1"); - cmd.env("DYLD_INSERT_LIBRARIES", lib_path.display().to_string()); - } else if cfg!(target_os = "linux") { - let lib_path = libm_path.join("liblibm.so"); - cmd.env("LD_PRELOAD", lib_path.display().to_string()); + if cfg!(target_os = "linux") { + let ld_library_path = std::env::var("LD_LIBRARY_PATH").unwrap_or_default(); + let ld_library_path = format!("{}:{}", cdylib_dir().display(), ld_library_path); + cmd.env("LD_LIBRARY_PATH", ld_library_path); } + // Run the binary: let output = cmd.output().unwrap(); handle_err(&format!("run file: {}", path.display()), &output); @@ -150,3 +152,22 @@ pub(crate) fn target_dir() -> std::path::PathBuf { Path::new("../../target").into() } } + +pub(crate) fn cdylib_dir() -> std::path::PathBuf { + target_dir().join(if cfg!(release_profile) { + "release" + } else { + "debug" + }) +} + +pub(crate) fn cdylib_path() -> std::path::PathBuf { + let libm_path = cdylib_dir(); + if cfg!(target_os = "macos") { + libm_path.join("liblibm.dylib") + } else if cfg!(target_os = "linux") { + libm_path.join("liblibm.so") + } else { + panic!("unsupported target_os") + } +} From b20e63ebb9485d6359563b97ad80a57e33397601 Mon Sep 17 00:00:00 2001 From: gnzlbg Date: Wed, 3 Jul 2019 12:25:44 +0200 Subject: [PATCH 8/8] Remove debug code --- crates/libm-cdylib/src/macros.rs | 1 - crates/libm-cdylib/src/test_utils.rs | 12 +----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/crates/libm-cdylib/src/macros.rs b/crates/libm-cdylib/src/macros.rs index 091fec7e7..4cbc23fdd 100644 --- a/crates/libm-cdylib/src/macros.rs +++ b/crates/libm-cdylib/src/macros.rs @@ -51,7 +51,6 @@ macro_rules! export { ); let target_dir = target_dir(); - eprintln!("target dir: {}", target_dir.display()); let src_path = target_dir.clone().join(format!("{}.c", stringify!($id))); let bin_path = target_dir.clone().join(format!("{}", stringify!($id))); write_to_file(&src_path, &ctest); diff --git a/crates/libm-cdylib/src/test_utils.rs b/crates/libm-cdylib/src/test_utils.rs index 36cc524b4..146ebf805 100644 --- a/crates/libm-cdylib/src/test_utils.rs +++ b/crates/libm-cdylib/src/test_utils.rs @@ -48,18 +48,9 @@ pub(crate) fn compile_file(src_path: &Path, bin_path: &Path) { // Link our libm let lib_path = cdylib_dir(); - { - let mut ls = process::Command::new("ls"); - ls.arg(lib_path.clone()); - let output = ls.output().unwrap(); - let output = String::from_utf8(output.stdout).unwrap(); - println!("ls\n{}]n", output); - } cmd.arg(format!("-L{}", lib_path.display())); cmd.arg("-llibm"); - eprintln!("compile cmd: {:?}", cmd); - handle_err( &format!("compile file: {}", src_path.display()), &cmd.output().unwrap(), @@ -98,8 +89,7 @@ where pub(crate) fn handle_err(step: &str, output: &process::Output) { if !output.status.success() { - eprintln!("{}", format_output(step, output)); - panic!(); + panic!("{}", format_output(step, output)); } }