-
Notifications
You must be signed in to change notification settings - Fork 102
[WIP] Provide a cdylib with the libm C ABI #192
Changes from all commits
e77e85d
69f6e56
4db1c4f
a549e37
164560a
53eab6a
7fa7c15
b20e63e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,35 @@ | ||
#!/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 | ||
$CMD --release | ||
|
||
$CMD --features 'stable' | ||
$CMD --release --features 'stable' | ||
$CMD --features "stable" | ||
$CMD --release --features "stable" | ||
|
||
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}" | ||
|
||
$CMD --features 'stable checked musl-reference-tests' | ||
$CMD --release --features 'stable checked musl-reference-tests' | ||
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
[package] | ||
name = "libm-cdylib" | ||
version = "0.1.0" | ||
authors = ["Gonzalo Brito Gadeschi <gonzalobg88@gmail.com>"] | ||
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" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
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"); | ||
} | ||
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"); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
#![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)] | ||
|
||
#[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) -> ! { | ||
// TODO: just call libc::abort - fails to link | ||
// unsafe { libc::abort() } | ||
unsafe { core::intrinsics::abort() } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO: I should probably call the C abort function here. |
||
} | ||
|
||
// TODO: there has to be a way to avoid this | ||
#[cfg(not(test))] | ||
#[lang = "eh_personality"] | ||
extern "C" fn eh_personality() {} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There has to be a better way that avoids this, but I exhausted all my google-fu trying to find it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This has to do with libcore itself being built with panic=unwind, but why not just link to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because libstd would pull the system's libm. |
||
|
||
// This macro exports the functions that are part of the C ABI. | ||
// | ||
// 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.; | ||
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.; | ||
// 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.; | ||
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.; | ||
// 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.; | ||
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.; | ||
|
||
// 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); | ||
|
||
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: 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.; | ||
// 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.; | ||
|
||
// 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); | ||
|
||
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.; | ||
|
||
// 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.); | ||
// 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.; | ||
|
||
// 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.; | ||
|
||
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.; | ||
// 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.; | ||
fn truncf(x: f32) -> f32: (42.) -> 42.; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we really want to maintain a cdylib in this repository then I would prefer that this list were automatically inferred instead of duplicated with the definitions in the main source code. If the duplication is going to happen here then I would prefer that this cdylib is provided in a different repository than this one. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will do. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm still going to have to generate some of these by hand though because of the API mismatches between this libm, and C's libm. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah even the original test harness does that, but it's not as explicit why it does it, I had to look up the musl code to understand what was going on in the test harness. |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
macro_rules! export { | ||
gnzlbg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
(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 { | ||
// This just forwards the call to the appropriate function of the | ||
// libm crate. | ||
#[cfg(not(link_test))] { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding a pretty complicated macro to test what already has a pretty complicated of tests seems like overkill? Why does the cdylib itself need to be tested when it's all already tested elsewhere? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This tests that the C ABI we provide is compatible with We currently don't test it, and in fact, Simplest example is the |
||
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(test)] | ||
paste::item! { | ||
// These tests check that the library links properly. | ||
#[test] | ||
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 | ||
// <math.h>. 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 <math.h> | ||
#include <stdio.h> | ||
#include <stdint.h> | ||
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 target_dir = target_dir(); | ||
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 | ||
// that the libm-cdylib has been generated (and generate it if | ||
// it isn't), and then we run the program, override the libm, | ||
// and verify the result. | ||
compile_file(&src_path, &bin_path); | ||
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; } | ||
)* | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this do
extern crate libm
instead?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would pull in std, see below.