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

Commit 119a182

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 9b3ec5e commit 119a182

File tree

2 files changed

+156
-0
lines changed

2 files changed

+156
-0
lines changed

crates/libm-test/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@ musl-bitwise-tests = ["rand"]
1313

1414
[dependencies]
1515
libm = { path = "../..", features = ["_internal-features"] }
16+
paste = "1.0.15"
17+
18+
# We can't build musl on MSVC or wasm
19+
[target.'cfg(not(any(target_env = "msvc", target_family = "wasm", target_feature = "thumb-mode")))'.dependencies]
20+
musl-math-sys = { path = "../musl-math-sys" }
21+
22+
[dev-dependencies]
23+
rand = "0.8.5"
24+
rand_chacha = "0.3.1"
1625

1726
[build-dependencies]
1827
rand = { version = "0.8.5", optional = true }
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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::ffi::c_int;
19+
use std::sync::LazyLock;
20+
21+
use libm_test::gen::CachedInput;
22+
use libm_test::{CheckOutput, GenerateInput, TupleCall};
23+
use musl_math_sys as musl;
24+
use rand::{Rng, SeedableRng};
25+
use rand_chacha::ChaCha8Rng;
26+
27+
const SEED: [u8; 32] = *b"3.141592653589793238462643383279";
28+
29+
const NTESTS: usize = {
30+
let mut ntests = if cfg!(optimizations_enabled) {
31+
5000
32+
} else {
33+
500
34+
};
35+
36+
// Tests can be pretty slow on non-64-bit targets and, for some reason, ppc.
37+
if !cfg!(target_pointer_width = "64") || cfg!(target_arch = "powerpc64") {
38+
ntests /= 5;
39+
}
40+
41+
ntests
42+
};
43+
44+
/// ULP allowed to differ from musl (note that musl itself may not be accurate).
45+
const ALLOWED_ULP: u32 = 2;
46+
47+
/// Certain functions have different allowed ULP (consider these xfail).
48+
///
49+
/// Currently this includes:
50+
/// - gamma functions that have higher errors
51+
/// - 32-bit functions fall back to a less precise algorithm.
52+
const ULP_OVERRIDES: &[(&str, u32)] = &[
53+
#[cfg(x86_no_sse)]
54+
("asinhf", 6),
55+
("lgamma", 6),
56+
("lgamma_r", 6),
57+
("lgammaf", 6),
58+
("lgammaf_r", 6),
59+
("tanh", 4),
60+
("tgamma", 8),
61+
#[cfg(not(target_pointer_width = "64"))]
62+
("exp10", 4),
63+
#[cfg(not(target_pointer_width = "64"))]
64+
("exp10f", 4),
65+
];
66+
67+
/// Tested inputs.
68+
static TEST_CASES: LazyLock<CachedInput> = LazyLock::new(|| make_test_cases(NTESTS));
69+
70+
/// The first argument to `jn` and `jnf` is the number of iterations. Make this a reasonable
71+
/// value so tests don't run forever.
72+
static TEST_CASES_JN: LazyLock<CachedInput> = LazyLock::new(|| {
73+
// It is easy to overflow the stack with these in debug mode
74+
let iterations = if cfg!(optimizations_enabled) && cfg!(target_pointer_width = "64") {
75+
0xffff
76+
} else if cfg!(windows) {
77+
0x00ff
78+
} else {
79+
0x0fff
80+
};
81+
82+
let mut cases = (&*TEST_CASES).clone();
83+
for case in cases.inputs_i32.iter_mut() {
84+
case.0 = iterations;
85+
}
86+
for case in cases.inputs_i32.iter_mut() {
87+
case.0 = iterations;
88+
}
89+
cases
90+
});
91+
92+
fn make_test_cases(ntests: usize) -> CachedInput {
93+
let mut rng = ChaCha8Rng::from_seed(SEED);
94+
95+
let inputs_i32 = (0..ntests).map(|_| rng.gen::<(i32, i32, i32)>()).collect();
96+
let inputs_f32 = (0..ntests).map(|_| rng.gen::<(f32, f32, f32)>()).collect();
97+
let inputs_f64 = (0..ntests).map(|_| rng.gen::<(f64, f64, f64)>()).collect();
98+
99+
CachedInput {
100+
inputs_f32,
101+
inputs_f64,
102+
inputs_i32,
103+
}
104+
}
105+
106+
macro_rules! musl_rand_tests {
107+
(@each_signature
108+
SysArgsTupleTy: $_sys_argty:ty,
109+
RustArgsTupleTy: $argty:ty,
110+
SysFnTy: $fnty_sys:ty,
111+
RustFnTy: $fnty_rust:ty,
112+
functions: [$( {
113+
attrs: [$($fn_meta:meta),*],
114+
fn_name: $name:ident,
115+
} ),*],
116+
) => { paste::paste! {
117+
$(
118+
#[test]
119+
$(#[$fn_meta])*
120+
fn [< musl_random_ $name >]() {
121+
let fname = stringify!($name);
122+
let inputs = if fname == "jn" || fname == "jnf" {
123+
&TEST_CASES_JN
124+
} else {
125+
&TEST_CASES
126+
};
127+
128+
let ulp = match ULP_OVERRIDES.iter().find(|(name, _val)| name == &fname) {
129+
Some((_name, val)) => *val,
130+
None => ALLOWED_ULP,
131+
};
132+
133+
let cases = <CachedInput as GenerateInput<$argty>>::get_cases(inputs);
134+
for input in cases {
135+
let mres = input.call(musl::$name as $fnty_sys);
136+
let cres = input.call(libm::$name as $fnty_rust);
137+
138+
mres.validate(cres, input, ulp);
139+
}
140+
}
141+
)*
142+
} };
143+
144+
(@all_items$($tt:tt)*) => {};
145+
}
146+
147+
libm::for_each_function!(musl_rand_tests);

0 commit comments

Comments
 (0)