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

Use rustdoc JSON for API list, add functions that were missing #384

Merged
merged 3 commits into from
Jan 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ jobs:
run: ./ci/download-musl.sh
shell: bash

- name: Verify API list
if: matrix.os == 'ubuntu-24.04'
run: python3 etc/update-api-list.py --check

# Non-linux tests just use our raw script
- name: Run locally
if: matrix.os != 'ubuntu-24.04' || contains(matrix.target, 'wasm')
Expand Down
1 change: 1 addition & 0 deletions ci/run-docker.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ run() {
docker run \
--rm \
--user "$(id -u):$(id -g)" \
-e CI \
-e RUSTFLAGS \
-e CARGO_HOME=/cargo \
-e CARGO_TARGET_DIR=/target \
Expand Down
16 changes: 8 additions & 8 deletions crates/libm-macros/src/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ const ALL_OPERATIONS_NESTED: &[(FloatTy, Signature, Option<Signature>, &[&str])]
None,
&[
"acosf", "acoshf", "asinf", "asinhf", "atanf", "atanhf", "cbrtf", "ceilf", "cosf",
"coshf", "erff", "exp10f", "exp2f", "expf", "expm1f", "fabsf", "floorf", "j0f", "j1f",
"lgammaf", "log10f", "log1pf", "log2f", "logf", "rintf", "roundf", "sinf", "sinhf",
"sqrtf", "tanf", "tanhf", "tgammaf", "truncf",
"coshf", "erff", "erfcf", "exp10f", "exp2f", "expf", "expm1f", "fabsf", "floorf",
"j0f", "j1f", "lgammaf", "log10f", "log1pf", "log2f", "logf", "rintf", "roundf",
"sinf", "sinhf", "sqrtf", "tanf", "tanhf", "tgammaf", "truncf", "y0f", "y1f",
],
),
(
Expand All @@ -23,9 +23,9 @@ const ALL_OPERATIONS_NESTED: &[(FloatTy, Signature, Option<Signature>, &[&str])]
None,
&[
"acos", "acosh", "asin", "asinh", "atan", "atanh", "cbrt", "ceil", "cos", "cosh",
"erf", "exp10", "exp2", "exp", "expm1", "fabs", "floor", "j0", "j1", "lgamma", "log10",
"log1p", "log2", "log", "rint", "round", "sin", "sinh", "sqrt", "tan", "tanh",
"tgamma", "trunc",
"erf", "erfc", "exp10", "exp2", "exp", "expm1", "fabs", "floor", "j0", "j1", "lgamma",
"log10", "log1p", "log2", "log", "rint", "round", "sin", "sinh", "sqrt", "tan", "tanh",
"tgamma", "trunc", "y0", "y1",
],
),
(
Expand Down Expand Up @@ -97,14 +97,14 @@ const ALL_OPERATIONS_NESTED: &[(FloatTy, Signature, Option<Signature>, &[&str])]
FloatTy::F32,
Signature { args: &[Ty::I32, Ty::F32], returns: &[Ty::F32] },
None,
&["jnf"],
&["jnf", "ynf"],
),
(
// `(i32, f64) -> f64`
FloatTy::F64,
Signature { args: &[Ty::I32, Ty::F64], returns: &[Ty::F64] },
None,
&["jn"],
&["jn", "yn"],
),
(
// `(f32, i32) -> f32`
Expand Down
34 changes: 0 additions & 34 deletions crates/libm-test/build.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,8 @@
use std::fmt::Write;
use std::fs;

#[path = "../../configure.rs"]
mod configure;
use configure::Config;

fn main() {
let cfg = Config::from_env();

list_all_tests(&cfg);

configure::emit_test_config(&cfg);
}

/// Create a list of all source files in an array. This can be used for making sure that
/// all functions are tested or otherwise covered in some way.
// FIXME: it would probably be better to use rustdoc JSON output to get public functions.
fn list_all_tests(cfg: &Config) {
let math_src = cfg.manifest_dir.join("../../src/math");

let mut files = fs::read_dir(math_src)
.unwrap()
.map(|f| f.unwrap().path())
.filter(|entry| entry.is_file())
.map(|f| f.file_stem().unwrap().to_str().unwrap().to_owned())
.collect::<Vec<_>>();
files.sort();

let mut s = "pub const ALL_FUNCTIONS: &[&str] = &[".to_owned();
for f in files {
if f == "mod" {
// skip mod.rs
continue;
}
write!(s, "\"{f}\",").unwrap();
}
write!(s, "];").unwrap();

let outfile = cfg.out_dir.join("all_files.rs");
fs::write(outfile, s).unwrap();
}
3 changes: 3 additions & 0 deletions crates/libm-test/src/domain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ impl_has_domain! {
cos => TRIG;
cosh => UNBOUNDED;
erf => UNBOUNDED;
erfc => UNBOUNDED;
exp => UNBOUNDED;
exp10 => UNBOUNDED;
exp2 => UNBOUNDED;
Expand All @@ -173,6 +174,8 @@ impl_has_domain! {
tanh => UNBOUNDED;
tgamma => GAMMA;
trunc => UNBOUNDED;
y0 => UNBOUNDED;
y1 => UNBOUNDED;
}

/* Manual implementations, these functions don't follow `foo`->`foof` naming */
Expand Down
6 changes: 5 additions & 1 deletion crates/libm-test/src/gen/random.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ pub fn get_test_cases<RustArgs>(ctx: &CheckCtx) -> impl Iterator<Item = RustArgs
where
CachedInput: GenerateInput<RustArgs>,
{
let inputs = if ctx.base_name == BaseName::Jn { &TEST_CASES_JN } else { &TEST_CASES };
let inputs = if ctx.base_name == BaseName::Jn || ctx.base_name == BaseName::Yn {
&TEST_CASES_JN
} else {
&TEST_CASES
};
inputs.get_cases()
}
12 changes: 9 additions & 3 deletions crates/libm-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ pub use test_traits::{CheckOutput, GenerateInput, Hex, TupleCall};
/// propagate.
pub type TestResult<T = (), E = anyhow::Error> = Result<T, E>;

// List of all files present in libm's source
include!(concat!(env!("OUT_DIR"), "/all_files.rs"));

/// True if `EMULATED` is set and nonempty. Used to determine how many iterations to run.
pub const fn emulated() -> bool {
match option_env!("EMULATED") {
Expand All @@ -34,3 +31,12 @@ pub const fn emulated() -> bool {
Some(_) => true,
}
}

/// True if `CI` is set and nonempty.
pub const fn ci() -> bool {
match option_env!("CI") {
Some(s) if s.is_empty() => false,
None => false,
Some(_) => true,
}
}
17 changes: 16 additions & 1 deletion crates/libm-test/src/mpfloat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ libm_macros::for_each_function! {
fabsf, ceilf, copysignf, floorf, rintf, roundf, truncf,
fmod, fmodf, frexp, frexpf, ilogb, ilogbf, jn, jnf, ldexp, ldexpf,
lgamma_r, lgammaf_r, modf, modff, nextafter, nextafterf, pow,powf,
remquo, remquof, scalbn, scalbnf, sincos, sincosf,
remquo, remquof, scalbn, scalbnf, sincos, sincosf, yn, ynf,
],
fn_extra: match MACRO_FN_NAME {
// Remap function names that are different between mpfr and libm
Expand Down Expand Up @@ -266,6 +266,21 @@ macro_rules! impl_op_for_ty {
)
}
}

impl MpOp for crate::op::[<yn $suffix>]::Routine {
type MpTy = (i32, MpFloat);

fn new_mp() -> Self::MpTy {
(0, new_mpfloat::<Self::FTy>())
}

fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
this.0 = input.0;
this.1.assign(input.1);
let ord = this.1.yn_round(this.0, Nearest);
prep_retval::<Self::FTy>(&mut this.1, ord)
}
}
}
};
}
Expand Down
14 changes: 6 additions & 8 deletions crates/libm-test/src/precision.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,9 @@ pub fn default_ulp(ctx: &CheckCtx) -> u32 {
// Overrides that apply to either basis
// FMA is expected to be infinite precision.
(_, Id::Fma | Id::Fmaf) => 0,
(_, Id::J0 | Id::J0f | Id::J1 | Id::J1f) => {
// Results seem very target-dependent
if cfg!(target_arch = "x86_64") { 4000 } else { 800_000 }
}
(_, Id::Jn | Id::Jnf) => 1000,
(_, Id::J0 | Id::J0f | Id::J1 | Id::J1f | Id::Y0 | Id::Y0f | Id::Y1 | Id::Y1f) => 800_000,
(_, Id::Jn | Id::Jnf | Id::Yn | Id::Ynf) => 1000,
(_, Id::Erfc | Id::Erfcf) => 4,

// Overrides for musl
#[cfg(x86_no_sse)]
Expand Down Expand Up @@ -297,7 +295,7 @@ impl MaybeOverride<(i32, f32)> for SpecialCase {
(Musl, _) => bessel_prec_dropoff(input, ulp, ctx),

// We return +0.0, MPFR returns -0.0
(Mpfr, BaseName::Jn)
(Mpfr, BaseName::Jn | BaseName::Yn)
if input.1 == f32::NEG_INFINITY && actual == F::ZERO && expected == F::ZERO =>
{
XFAIL
Expand All @@ -319,7 +317,7 @@ impl MaybeOverride<(i32, f64)> for SpecialCase {
(Musl, _) => bessel_prec_dropoff(input, ulp, ctx),

// We return +0.0, MPFR returns -0.0
(Mpfr, BaseName::Jn)
(Mpfr, BaseName::Jn | BaseName::Yn)
if input.1 == f64::NEG_INFINITY && actual == F::ZERO && expected == F::ZERO =>
{
XFAIL
Expand All @@ -336,7 +334,7 @@ fn bessel_prec_dropoff<F: Float>(
ulp: &mut u32,
ctx: &CheckCtx,
) -> Option<TestResult> {
if ctx.base_name == BaseName::Jn {
if ctx.base_name == BaseName::Jn || ctx.base_name == BaseName::Yn {
if input.0 > 4000 {
return XFAIL;
} else if input.0 > 2000 {
Expand Down
70 changes: 38 additions & 32 deletions crates/libm-test/tests/check_coverage.rs
Original file line number Diff line number Diff line change
@@ -1,54 +1,60 @@
//! Ensure that `for_each_function!` isn't missing any symbols.

/// Files in `src/` that do not export a testable symbol.
const ALLOWED_SKIPS: &[&str] = &[
// Not a generic test function
"fenv",
// Nonpublic functions
"expo2",
"k_cos",
"k_cosf",
"k_expo2",
"k_expo2f",
"k_sin",
"k_sinf",
"k_tan",
"k_tanf",
"rem_pio2",
"rem_pio2_large",
"rem_pio2f",
];
use std::collections::HashSet;
use std::env;
use std::path::Path;
use std::process::Command;

macro_rules! callback {
(
fn_name: $name:ident,
extra: [$push_to:ident],
extra: [$set:ident],
) => {
$push_to.push(stringify!($name));
let name = stringify!($name);
let new = $set.insert(name);
assert!(new, "duplicate function `{name}` in `ALL_OPERATIONS`");
};
}

#[test]
fn test_for_each_function_all_included() {
let mut included = Vec::new();
let mut missing = Vec::new();
let all_functions: HashSet<_> = include_str!("../../../etc/function-list.txt")
.lines()
.filter(|line| !line.starts_with("#"))
.collect();

let mut tested = HashSet::new();

libm_macros::for_each_function! {
callback: callback,
extra: [included],
extra: [tested],
};

for f in libm_test::ALL_FUNCTIONS {
if !included.contains(f) && !ALLOWED_SKIPS.contains(f) {
missing.push(f)
}
}

if !missing.is_empty() {
let untested = all_functions.difference(&tested);
if untested.clone().next().is_some() {
panic!(
"missing tests for the following: {missing:#?} \
"missing tests for the following: {untested:#?} \
\nmake sure any new functions are entered in \
`ALL_FUNCTIONS` (in `libm-macros`)."
`ALL_OPERATIONS` (in `libm-macros`)."
);
}
assert_eq!(all_functions, tested);
}

#[test]
fn ensure_list_updated() {
if libm_test::ci() {
// Most CI tests run in Docker where we don't have Python or Rustdoc, so it's easiest
// to just run the python file directly when it is available.
eprintln!("skipping test; CI runs the python file directly");
return;
}

let res = Command::new("python3")
.arg(Path::new(env!("CARGO_MANIFEST_DIR")).join("../../etc/update-api-list.py"))
.arg("--check")
.status()
.unwrap();

assert!(res.success(), "May need to run `./etc/update-api-list.py`");
}
4 changes: 3 additions & 1 deletion crates/libm-test/tests/multiprecision.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ libm_macros::for_each_function! {
attributes: [
// Also an assertion failure on i686: at `MPFR_ASSERTN (! mpfr_erangeflag_p ())`
#[ignore = "large values are infeasible in MPFR"]
[jn, jnf],
[jn, jnf, yn, ynf],
],
skip: [
// FIXME: MPFR tests needed
Expand Down Expand Up @@ -157,6 +157,8 @@ libm_macros::for_each_function! {
remquof,
scalbn,
scalbnf,
yn,
ynf,

// FIXME: MPFR tests needed
frexp,
Expand Down
1 change: 1 addition & 0 deletions crates/musl-math-sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,5 +282,6 @@ functions! {
musl_y0f: y0f(a: f32) -> f32;
musl_y1: y1(a: f64) -> f64;
musl_y1f: y1f(a: f32) -> f32;
musl_yn: yn(a: c_int, b: f64) -> f64;
musl_ynf: ynf(a: c_int, b: f32) -> f32;
}
Loading