Skip to content
This repository was archived by the owner on Apr 28, 2025. It is now read-only.

Commit e77e85d

Browse files
committed
Provide a cdylib with the libm C ABI
1 parent 885afa3 commit e77e85d

File tree

7 files changed

+346
-1
lines changed

7 files changed

+346
-1
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ checked = []
2626
[workspace]
2727
members = [
2828
"crates/compiler-builtins-smoke-test",
29+
"crates/libm-cdylib",
2930
"crates/libm-bench",
3031
]
3132

crates/libm-cdylib/Cargo.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[package]
2+
name = "libm-cdylib"
3+
version = "0.1.0"
4+
authors = ["Gonzalo Brito Gadeschi <gonzalobg88@gmail.com>"]
5+
edition = "2018"
6+
7+
[features]
8+
default = ['stable']
9+
stable = []
10+
11+
# Used checked array indexing instead of unchecked array indexing in this
12+
# library.
13+
checked = []
14+
15+
[lib]
16+
name = "libm"
17+
crate-type = ["cdylib"]
18+
19+
[dev-dependencies]
20+
paste = "0.1.5"

crates/libm-cdylib/build.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use std::env;
2+
fn main() {
3+
println!("cargo:rerun-if-changed=build.rs");
4+
let profile = env::var("PROFILE").unwrap_or(String::new());
5+
if profile == "release" {
6+
println!("cargo:rustc-cfg=release_profile");
7+
}
8+
}

crates/libm-cdylib/src/lib.rs

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
#![allow(dead_code)]
2+
#![cfg_attr(not(test), feature(core_intrinsics, lang_items))]
3+
#![cfg_attr(not(test), no_std)]
4+
5+
#[path = "../../../src/math/mod.rs"]
6+
mod libm;
7+
8+
#[macro_use]
9+
mod macros;
10+
11+
#[cfg(test)]
12+
mod test_utils;
13+
14+
#[cfg(not(test))]
15+
#[panic_handler]
16+
fn panic(_info: &core::panic::PanicInfo) -> ! {
17+
unsafe { core::intrinsics::abort() }
18+
}
19+
20+
#[cfg(not(test))]
21+
#[lang = "eh_personality"]
22+
extern "C" fn eh_personality() {}
23+
24+
// All functions to be exported by the C ABI.
25+
// Includes a test input/output pair for testing.
26+
// The test output will be used to override the
27+
// result of the function, and the test input
28+
// is used to call the overriden function from C.
29+
// This is needed to make sure that we are linking
30+
// against this libm during testing, and not the
31+
// system's libm.
32+
//
33+
//
34+
// FIXME: missing symbols: _memcpy, _memset, etc.
35+
export! {
36+
fn acos(x: f64) -> f64: (42.) -> 42.;
37+
fn acosf(x: f32) -> f32: (42.) -> 42.;
38+
fn acosh(x: f64) -> f64: (42.) -> 42.;
39+
fn acoshf(x: f32) -> f32: (42.) -> 42.;
40+
fn asin(x: f64) -> f64: (42.) -> 42.;
41+
fn asinf(x: f32) -> f32: (42.) -> 42.;
42+
fn asinh(x: f64) -> f64: (42.) -> 42.;
43+
fn asinhf(x: f32) -> f32: (42.) -> 42.;
44+
// fn atan(x: f64) -> f64: (42.) -> 42.;
45+
fn atanf(x: f32) -> f32: (42.) -> 42.;
46+
fn atanh(x: f64) -> f64: (42.) -> 42.;
47+
fn atanhf(x: f32) -> f32: (42.) -> 42.;
48+
fn cbrt(x: f64) -> f64: (42.) -> 42.;
49+
fn cbrtf(x: f32) -> f32: (42.) -> 42.;
50+
fn ceil(x: f64) -> f64: (42.) -> 42.;
51+
fn ceilf(x: f32) -> f32: (42.) -> 42.;
52+
fn copysign(x: f64, y: f64) -> f64: (42., 42.) -> 42.;
53+
fn copysignf(x: f32, y: f32) -> f32: (42., 42.) -> 42.;
54+
//fn cos(x: f64) -> f64: (42.) -> 42.;
55+
//fn cosf(x: f32) -> f32: (42.) -> 42.;
56+
fn cosh(x: f64) -> f64: (42.) -> 42.;
57+
fn coshf(x: f32) -> f32: (42.) -> 42.;
58+
fn erf(x: f64) -> f64: (42.) -> 42.;
59+
fn erfc(x: f64) -> f64: (42.) -> 42.;
60+
fn erff(x: f32) -> f32: (42.) -> 42.;
61+
fn erfcf(x: f32) -> f32: (42.) -> 42.;
62+
fn exp(x: f64) -> f64: (42.) -> 42.;
63+
fn expf(x: f32) -> f32: (42.) -> 42.;
64+
// FIXME: not in C:
65+
// fn exp10(x: f64) -> f64: (42.) -> 42.;
66+
// fn exp10f(x: f32) -> f32: (42.) -> 42.;
67+
fn exp2(x: f64) -> f64: (42.) -> 42.;
68+
fn exp2f(x: f32) -> f32: (42.) -> 42.;
69+
fn expm1(x: f64) -> f64: (42.) -> 42.;
70+
fn expm1f(x: f32) -> f32: (42.) -> 42.;
71+
fn fabs(x: f64) -> f64: (42.) -> 42.;
72+
fn fabsf(x: f32) -> f32: (42.) -> 42.;
73+
fn fdim(x: f64, y: f64) -> f64: (42., 42.) -> 42.;
74+
fn fdimf(x: f32, y: f32) -> f32: (42., 42.) -> 42.;
75+
fn floor(x: f64) -> f64: (42.) -> 42.;
76+
fn floorf(x: f32) -> f32: (42.) -> 42.;
77+
fn fma(x: f64, y: f64, z: f64) -> f64: (42., 42., 42.) -> 42.;
78+
fn fmaf(x: f32, y: f32, z: f32) -> f32: (42., 42., 42.) -> 42.;
79+
fn fmax(x: f64, y: f64) -> f64: (42., 42.) -> 42.;
80+
fn fmaxf(x: f32, y: f32) -> f32: (42., 42.) -> 42.;
81+
fn fmin(x: f64, y: f64) -> f64: (42., 42.) -> 42.;
82+
fn fminf(x: f32, y: f32) -> f32: (42., 42.) -> 42.;
83+
fn fmod(x: f64, y: f64) -> f64: (42., 42.) -> 42.;
84+
fn fmodf(x: f32, y: f32) -> f32: (42., 42.) -> 42.;
85+
86+
// different ABI than in C
87+
// fn frexp(x: f64) -> (f64, i32): (42.) -> (42., 42);
88+
// fn frexpf(x: f32) -> (f32, i32): (42.) -> (42., 42);
89+
90+
fn hypot(x: f64, y: f64) -> f64: (42., 42.) -> 42.;
91+
fn hypotf(x: f32, y: f32) -> f32: (42., 42.) -> 42.;
92+
fn ilogb(x: f64) -> i32: (42.) -> 42;
93+
fn ilogbf(x: f32) -> i32: (42.) -> 42;
94+
95+
// FIXME: fail to link:
96+
// fn j0(x: f64) -> f64: (42.) -> 42.;
97+
// fn j0f(x: f32) -> f32: (42.) -> 42.;
98+
// fn j1(x: f64) -> f64: (42.) -> 42.;
99+
// fn j1f(x: f32) -> f32: (42.) -> 42.;
100+
// fn jn(n: i32, x: f64) -> f64: (42, 42.) -> 42.;
101+
// fn jnf(n: i32, x: f32) -> f32: (42, 42.) -> 42.;
102+
103+
fn ldexp(x: f64, n: i32) -> f64: (42, 42.) -> 42.;
104+
fn ldexpf(x: f32, n: i32) -> f32: (42, 42.) -> 42.;
105+
fn lgamma(x: f64) -> f64: (42.) -> 42.;
106+
fn lgammaf(x: f32) -> f32: (42.) -> 42.;
107+
108+
// different ABI
109+
// fn lgamma_r(x: f64) -> (f64, i32): (42.) -> (42., 42);
110+
// fn lgammaf_r(x: f32) -> (f32, i32): (42.) -> (42., 42);
111+
112+
fn log(x: f64) -> f64: (42.) -> 42.;
113+
fn logf(x: f32) -> f32: (42.) -> 42.;
114+
fn log10(x: f64) -> f64: (42.) -> 42.;
115+
fn log10f(x: f32) -> f32: (42.) -> 42.;
116+
fn log1p(x: f64) -> f64: (42.) -> 42.;
117+
fn log1pf(x: f32) -> f32: (42.) -> 42.;
118+
fn log2(x: f64) -> f64: (42.) -> 42.;
119+
fn log2f(x: f32) -> f32: (42.) -> 42.;
120+
fn pow(x: f64, y: f64) -> f64: (42., 42.) -> 42.;
121+
fn powf(x: f32, y: f32) -> f32: (42., 42.) -> 42.;
122+
// fn modf(x: f64) -> (f64, f64): (42.) -> (42., 42.);
123+
// fn modff(x: f32) -> (f32, f32): (42.) -> (42., 42.);
124+
125+
// different ABI
126+
// remquo
127+
// remquof
128+
129+
fn round(x: f64) -> f64: (42.) -> 42.;
130+
fn roundf(x: f32) -> f32: (42.) -> 42.;
131+
fn scalbn(x: f64, n: i32) -> f64: (42., 42) -> 42.;
132+
fn scalbnf(x: f32, n: i32) -> f32: (42., 42) -> 42.;
133+
134+
// different ABI
135+
// fn sincos
136+
// fn sincosf
137+
138+
// fn sin(x: f64) -> f64: (42.) -> 42.;
139+
// fn sinf(x: f32) -> f32: (42.) -> 42.;
140+
141+
fn sinh(x: f64) -> f64: (42.) -> 42.;
142+
fn sinhf(x: f32) -> f32: (42.) -> 42.;
143+
fn sqrt(x: f64) -> f64: (42.) -> 42.;
144+
fn sqrtf(x: f32) -> f32: (42.) -> 42.;
145+
// fn tan(x: f64) -> f64: (42.) -> 42.;
146+
// fn tanf(x: f32) -> f32: (42.) -> 42.;
147+
fn tanh(x: f64) -> f64: (42.) -> 42.;
148+
fn tanhf(x: f32) -> f32: (42.) -> 42.;
149+
// fn tgamma(x: f64) -> f64: (42.) -> 42.;
150+
// fn tgammaf(x: f32) -> f32: (42.) -> 42.;
151+
fn trunc(x: f64) -> f64: (42.) -> 42.;
152+
fn truncf(x: f32) -> f32: (42.) -> 42.;
153+
}

crates/libm-cdylib/src/macros.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
macro_rules! export {
2+
(fn $id:ident ($($arg:ident : $arg_ty:ty),* ) -> $ret_ty:ty:
3+
($($test_arg:expr),*) -> $test_ret:expr;) => {
4+
#[no_mangle]
5+
pub extern "C" fn $id($($arg: $arg_ty),*) -> $ret_ty {
6+
#[cfg(link_test)] {
7+
let _ = libm::$id($($arg),*);
8+
$test_ret as _
9+
}
10+
#[cfg(not(link_test))] {
11+
libm::$id($($arg),*)
12+
}
13+
}
14+
15+
#[cfg(test)]
16+
paste::item! {
17+
#[test]
18+
fn [<$id _test>]() {
19+
use crate::test_utils::*;
20+
let (cret_t, c_format_s) = ctype_and_cformat(stringify!($ret_ty));
21+
let ctest = format!(
22+
r#"
23+
#include <math.h>
24+
#include <stdio.h>
25+
#include <stdint.h>
26+
int main() {{
27+
{cret_t} result = {id}({input});
28+
fprintf(stdout, "{c_format_s}", result);
29+
return 0;
30+
}}
31+
"#,
32+
id = stringify!($id),
33+
input = [$(stringify!($test_arg)),*].join(","),
34+
cret_t = cret_t,
35+
c_format_s = c_format_s
36+
);
37+
38+
let src = &format!("../../target/{}.c", stringify!($id));
39+
let bin = &format!("../../target/{}", stringify!($id));
40+
let src_path = std::path::Path::new(src);
41+
let bin_path = std::path::Path::new(bin);
42+
write_to_file(&src_path, &ctest);
43+
compile_file(&src_path, &bin_path);
44+
compile_lib();
45+
check(&bin_path, $test_ret as $ret_ty)
46+
}
47+
}
48+
};
49+
($(fn $id:ident ($($arg:ident : $arg_ty:ty),* ) -> $ret_ty:ty:
50+
($($test_arg:expr),*) -> $test_ret:expr;)*) => {
51+
$(
52+
export! { fn $id ($($arg : $arg_ty),* ) -> $ret_ty: ($($test_arg),*) -> $test_ret; }
53+
)*
54+
}
55+
}

crates/libm-cdylib/src/test_utils.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use std::{fs, io, path::Path, process};
2+
3+
pub(crate) fn write_to_file(path: &Path, content: &str) {
4+
use io::Write;
5+
let mut file = fs::File::create(&path).unwrap();
6+
write!(file, "{}", content).unwrap();
7+
}
8+
9+
pub(crate) fn compile_lib() {
10+
let mut cmd = process::Command::new("cargo");
11+
cmd.arg("build");
12+
if cfg!(release_profile) {
13+
cmd.arg("--release");
14+
}
15+
cmd.env("RUSTFLAGS", "--cfg=link_test");
16+
handle_err("lib_build", &cmd.output().unwrap());
17+
}
18+
19+
pub(crate) fn compile_file(src_path: &Path, bin_path: &Path) {
20+
let mut cmd = process::Command::new("CC");
21+
cmd.arg("-fno-builtin")
22+
.arg("-o")
23+
.arg(bin_path)
24+
.arg(src_path);
25+
handle_err(
26+
&format!("compile file: {}", src_path.display()),
27+
&cmd.output().unwrap(),
28+
);
29+
}
30+
31+
pub(crate) fn check<T>(path: &Path, expected: T)
32+
where
33+
T: PartialEq + std::fmt::Debug + std::str::FromStr,
34+
<T as std::str::FromStr>::Err: std::fmt::Debug,
35+
{
36+
let mut cmd = process::Command::new(path);
37+
38+
let libm_path = format!(
39+
"../../target/{}/liblibm.",
40+
if cfg!(release_profile) {
41+
"release"
42+
} else {
43+
"debug"
44+
},
45+
);
46+
47+
// Replace libm at runtime
48+
if cfg!(target_os = "macos") {
49+
// cmd.env("DYLD_PRINT_LIBRARIES", "1");
50+
// cmd.env("X", "1");
51+
cmd.env("DYLD_FORCE_FLAT_NAMESPACE", "1");
52+
cmd.env(
53+
"DYLD_INSERT_LIBRARIES",
54+
format!("{}.{}", libm_path, "dylib"),
55+
);
56+
} else if cfg!(target_os = "linux") {
57+
cmd.env("LD_PRELOAD", format!("{}.{}", libm_path, "so"))
58+
}
59+
let output = cmd.output().unwrap();
60+
handle_err(&format!("run file: {}", path.display()), &output);
61+
let result = String::from_utf8(output.stdout.clone())
62+
.unwrap()
63+
.parse::<T>();
64+
65+
if result.is_err() {
66+
panic!(format_output("check (parse failure)", &output));
67+
}
68+
let result = result.unwrap();
69+
assert_eq!(result, expected, "{}", format_output("check", &output));
70+
}
71+
72+
pub(crate) fn handle_err(step: &str, output: &process::Output) {
73+
if !output.status.success() {
74+
eprintln!("{}", format_output(step, output));
75+
panic!();
76+
}
77+
}
78+
79+
pub(crate) fn format_output(
80+
step: &str,
81+
process::Output {
82+
status,
83+
stdout,
84+
stderr,
85+
}: &process::Output,
86+
) -> String {
87+
let mut s = format!("\nFAILED[{}]: exit code {:?}\n", step, status.code());
88+
s += &format!(
89+
"FAILED[{}]: stdout:\n\n{}\n\n",
90+
step,
91+
String::from_utf8(stdout.to_vec()).unwrap()
92+
);
93+
s += &format!(
94+
"FAILED[{}]: stderr:\n\n{}\n\n",
95+
step,
96+
String::from_utf8(stderr.to_vec()).unwrap()
97+
);
98+
s
99+
}
100+
101+
pub(crate) fn ctype_and_cformat(x: &str) -> (&str, &str) {
102+
match x {
103+
"f32" => ("float", "%f"),
104+
"f64" => ("double", "%f"),
105+
"i32" => ("int32_t", "%d"),
106+
_ => panic!("unknown type: {}", x),
107+
}
108+
}

src/math/acos.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ fn r(z: f64) -> f64 {
6262
/// Returns values in radians, in the range of 0 to pi.
6363
#[inline]
6464
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
65-
pub fn acos(x: f64) -> f64 {
65+
extern "C" pub fn acos(x: f64) -> f64 {
6666
let x1p_120f = f64::from_bits(0x3870000000000000); // 0x1p-120 === 2 ^ -120
6767
let z: f64;
6868
let w: f64;

0 commit comments

Comments
 (0)