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

Commit 6b9889a

Browse files
committed
Add a test against musl libm
Check our functions against `musl-math-sys`. This is similar to the existing musl tests that go through binary serialization, but works on more platforms.
1 parent 4045107 commit 6b9889a

File tree

2 files changed

+158
-0
lines changed

2 files changed

+158
-0
lines changed

crates/libm-test/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ musl-bitwise-tests = ["rand"]
1414
[dependencies]
1515
libm = { path = "../.." }
1616
libm-macros = { path = "../libm-macros" }
17+
paste = "1.0.15"
18+
19+
# We can't build musl on MSVC or wasm
20+
[target.'cfg(not(any(target_env = "msvc", target_family = "wasm", target_feature = "thumb-mode")))'.dependencies]
21+
musl-math-sys = { path = "../musl-math-sys" }
22+
23+
[dev-dependencies]
24+
rand = "0.8.5"
25+
rand_chacha = "0.3.1"
1726

1827
[build-dependencies]
1928
rand = { version = "0.8.5", optional = true }
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
//! Compare our implementations with the result of musl functions, as provided by `musl-math-sys`.
2+
//!
3+
//! Currently this only tests randomized inputs. In the future this may be improved to test edge
4+
//! cases or run exhaustive tests.
5+
//!
6+
//! Note that musl functions do not always provide 0.5ULP rounding, so our functions can do better
7+
//! than these results.
8+
9+
// Targets that we can't compile musl for
10+
#![cfg(not(any(target_env = "msvc", target_family = "wasm")))]
11+
// These wind up with stack overflows
12+
#![cfg(not(all(target_family = "windows", target_env = "gnu")))]
13+
// FIXME(#309): LE PPC crashes calling the musl version of some of these and are disabled. It
14+
// seems like a qemu bug but should be investigated further at some point. See
15+
// <https://github.com/rust-lang/libm/issues/309>.
16+
#![cfg(not(all(target_arch = "powerpc64", target_endian = "little")))]
17+
18+
use std::sync::LazyLock;
19+
20+
use libm_test::gen::CachedInput;
21+
use libm_test::{CheckOutput, GenerateInput, TupleCall};
22+
use musl_math_sys as musl;
23+
use rand::{Rng, SeedableRng};
24+
use rand_chacha::ChaCha8Rng;
25+
26+
const SEED: [u8; 32] = *b"3.141592653589793238462643383279";
27+
28+
const NTESTS: usize = {
29+
let mut ntests = if cfg!(optimizations_enabled) {
30+
5000
31+
} else {
32+
500
33+
};
34+
35+
// Tests can be pretty slow on non-64-bit targets and, for some reason, ppc.
36+
if !cfg!(target_pointer_width = "64") || cfg!(target_arch = "powerpc64") {
37+
ntests /= 5;
38+
}
39+
40+
ntests
41+
};
42+
43+
/// ULP allowed to differ from musl (note that musl itself may not be accurate).
44+
const ALLOWED_ULP: u32 = 2;
45+
46+
/// Certain functions have different allowed ULP (consider these xfail).
47+
///
48+
/// Currently this includes:
49+
/// - gamma functions that have higher errors
50+
/// - 32-bit functions fall back to a less precise algorithm.
51+
const ULP_OVERRIDES: &[(&str, u32)] = &[
52+
#[cfg(x86_no_sse)]
53+
("asinhf", 6),
54+
("lgamma", 6),
55+
("lgamma_r", 6),
56+
("lgammaf", 6),
57+
("lgammaf_r", 6),
58+
("tanh", 4),
59+
("tgamma", 8),
60+
#[cfg(not(target_pointer_width = "64"))]
61+
("exp10", 4),
62+
#[cfg(not(target_pointer_width = "64"))]
63+
("exp10f", 4),
64+
];
65+
66+
/// Tested inputs.
67+
static TEST_CASES: LazyLock<CachedInput> = LazyLock::new(|| make_test_cases(NTESTS));
68+
69+
/// The first argument to `jn` and `jnf` is the number of iterations. Make this a reasonable
70+
/// value so tests don't run forever.
71+
static TEST_CASES_JN: LazyLock<CachedInput> = LazyLock::new(|| {
72+
// It is easy to overflow the stack with these in debug mode
73+
let iterations = if cfg!(optimizations_enabled) && cfg!(target_pointer_width = "64") {
74+
0xffff
75+
} else if cfg!(windows) {
76+
0x00ff
77+
} else {
78+
0x0fff
79+
};
80+
81+
let mut cases = (&*TEST_CASES).clone();
82+
for case in cases.inputs_i32.iter_mut() {
83+
case.0 = iterations;
84+
}
85+
for case in cases.inputs_i32.iter_mut() {
86+
case.0 = iterations;
87+
}
88+
cases
89+
});
90+
91+
fn make_test_cases(ntests: usize) -> CachedInput {
92+
let mut rng = ChaCha8Rng::from_seed(SEED);
93+
94+
let inputs_i32 = (0..ntests).map(|_| rng.gen::<(i32, i32, i32)>()).collect();
95+
let inputs_f32 = (0..ntests).map(|_| rng.gen::<(f32, f32, f32)>()).collect();
96+
let inputs_f64 = (0..ntests).map(|_| rng.gen::<(f64, f64, f64)>()).collect();
97+
98+
CachedInput {
99+
inputs_f32,
100+
inputs_f64,
101+
inputs_i32,
102+
}
103+
}
104+
105+
macro_rules! musl_rand_tests {
106+
(
107+
fn_name: $fn_name:ident,
108+
CFn: $CFn:ty,
109+
CArgs: $CArgs:ty,
110+
CRet: $CRet:ty,
111+
RustFn: $RustFn:ty,
112+
RustArgs: $RustArgs:ty,
113+
RustRet: $RustRet:ty,
114+
attrs: [$($meta:meta)*]
115+
) => { paste::paste! {
116+
#[test]
117+
$(#[$meta])*
118+
fn [< musl_random_ $fn_name >]() {
119+
let fname = stringify!($fn_name);
120+
let inputs = if fname == "jn" || fname == "jnf" {
121+
&TEST_CASES_JN
122+
} else {
123+
&TEST_CASES
124+
};
125+
126+
let ulp = match ULP_OVERRIDES.iter().find(|(name, _val)| name == &fname) {
127+
Some((_name, val)) => *val,
128+
None => ALLOWED_ULP,
129+
};
130+
131+
let cases = <CachedInput as GenerateInput<$RustArgs>>::get_cases(inputs);
132+
for input in cases {
133+
let mres = input.call(musl::$fn_name as $CFn);
134+
let cres = input.call(libm::$fn_name as $RustFn);
135+
136+
mres.validate(cres, input, ulp);
137+
}
138+
}
139+
} };
140+
}
141+
142+
libm_macros::for_each_function! {
143+
callback: musl_rand_tests,
144+
skip: [],
145+
attributes: [
146+
#[cfg_attr(x86_no_sse, ignore)] // FIXME(correctness): wrong result on i586
147+
[exp10, exp10f, exp2, exp2f]
148+
],
149+
}

0 commit comments

Comments
 (0)