-
Notifications
You must be signed in to change notification settings - Fork 102
[WIP] Provide a cdylib with the libm C ABI #192
Conversation
#[cfg(not(test))] | ||
#[panic_handler] | ||
fn panic(_info: &core::panic::PanicInfo) -> ! { | ||
unsafe { core::intrinsics::abort() } |
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.
TODO: I should probably call the C abort function here.
|
||
#[cfg(not(test))] | ||
#[lang = "eh_personality"] | ||
extern "C" fn eh_personality() {} |
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.
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 comment
The 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 std
and have it provide these?
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.
Because libstd would pull the system's libm.
pub(crate) fn ctype_and_printf_format_specifier(x: &str) -> (&str, &str) { | ||
match x { | ||
"f32" => ("float", "%f"), | ||
"f64" => ("double", "%f"), |
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.
should this be %lf
?
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.
IIRC, %f prints doubles. There is no format string for printing floats AFAIK - %f converts floats into a double, and prints that.
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.
Would it make sense when %f
is a double anyway? maybe %.24f
for floats %.54f
for doubles makes more sense?
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.
Or even better maybe just %e
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.
For the linking tests it doesn't really matter. I'll add a comment about this.
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.
So I've added a comment explaining what this does right now.
If we wanted to verify the results of this lib, I think I would prefer doing so as part of the libm-test system. We could dynamically link libm as the system library, and just verify that the results of the C ABI match the results that are produced by the Rust library, or something like that.
The problem which checking the ABI here is that, if for some reason linking failed, or the symbol has a slightly different name, etc. the functions from the system library are invoked. So if we were to just simply verify the results, we can't tell whether its our libm or the system libm that's being invoked.
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.
Maybe there is a way to reliably prevent libm from being linked on Linux, while still being able to link, e.g., fprintf
and other parts of the standard library. On MacOSX, this is IMO not worth it - everything links libSystem.dylib
, which always dynamically links libm
.
Is there anyway we can fix the linking issue? By looking the at the online docs the following functions should be present in the Kernel
There are no sincos or exp10 functions in the Kernel, they are only present in the accelerate or simd framework as vectorized version. |
The problem is that some symbols are missing from the cdylib, like |
|
||
#[cfg(not(test))] | ||
#[lang = "eh_personality"] | ||
extern "C" fn eh_personality() {} |
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.
This has to do with libcore itself being built with panic=unwind, but why not just link to std
and have it provide these?
#![cfg_attr(not(test), no_std)] | ||
|
||
#[path = "../../../src/math/mod.rs"] | ||
mod libm; |
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.
// 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 comment
The 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 comment
The 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 comment
The 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 comment
The 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.
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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
This tests that the C ABI we provide is compatible with <math.h>
, which is what allows this library to be used from C.
We currently don't test it, and in fact, libm
currently doesn't provide a C compatible ABI, which is why some of the functions in the lib.rs
are commented out.
Simplest example is the libm
functions returning tuples by value (e.g. (i32, f32)
). The libm C ABI doesn't do that, and takes pointer arguments instead that are used as "out" parameters. So the cdylib is going to have to workaround those manually.
@@ -0,0 +1,143 @@ | |||
use std::{fs, io, path::Path, process}; |
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.
I personally feel like this file may be a bit over-the-top when testing, I'm not sure it's really all that necessary to be invoking Cargo from this crate's own test suite to test various C things?
If it's really this extensive then I would prefer this not live in this repository to help keep this repository manageable and not pull in a whole set of new maintenance.
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.
It isn't expensive on my 8 year old system - cargo
is invoked on every test, but the first invocation "locks", and once the first invocation completes, all of them just return "success - the library was already built".
I can use a lazy_static!
to just call cargo
once, but it felt like unnecessary complexity.
So I've cleaned this up, and CI is green. I've added two MacOSX build jobs, and a nightly x86_64-linux build job. For the time being, the tests are only run there. This is sub-optimal. Ideally, we would have a But these are architectural decision that have to be made depending on the goals of the library, so I've opened #195 to discuss that first. |
It seems like this is intended to build on #198 so I'm going to hold off on reviewing/merging until that's landed. |
This PR adds a new crate
libm-cdylib
that implements the<math.h>
API and that is ABI compatible with the platforms libm implementation.Since libm is usually dynamically linked, this allows replacing the libm implementation of any program in the platform with the Rust implementation, e.g., using
LD_PRELOAD
and similar APIs.The library is tested by just linking it to C programs that use
<cmath.h>
and checking that the library is called and produce expected "unique" results that differ from the system libraries. The C programs are linked to the system library, and then the math library is dynamically overriden.Right now this works properly on macos, I'll iterate on the linux part of this on CI.