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

Commit f9f59e0

Browse files
committed
Refactor libm-test; add tests specifics for each type of API
1 parent f81edcc commit f9f59e0

File tree

13 files changed

+594
-365
lines changed

13 files changed

+594
-365
lines changed

crates/libm-analyze/src/lib.rs

+62-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
extern crate proc_macro;
66
use self::proc_macro::TokenStream;
77
use quote::quote;
8+
use std::collections::HashSet;
89
use syn::parse_macro_input;
910

1011
/// `input` contains a single identifier, corresponding to a user-defined macro.
@@ -13,22 +14,26 @@ use syn::parse_macro_input;
1314
/// See tests/analyze or below for the API.
1415
#[proc_macro]
1516
pub fn for_each_api(input: TokenStream) -> TokenStream {
17+
let input = parse_macro_input!(input as Input);
1618
let files = get_libm_files();
17-
let functions = get_functions(&files);
18-
let input = parse_macro_input!(input as syn::Ident);
19+
let functions = get_functions(&files, &input.ignored);
1920
let mut tokens = proc_macro2::TokenStream::new();
21+
let input_macro = input.macro_id;
2022
for function in functions {
2123
let id = function.ident;
24+
let api_kind = function.api_kind;
2225
let ret_ty = function.ret_ty;
2326
let arg_tys = function.arg_tys;
2427
let arg_ids = get_arg_ids(arg_tys.len());
2528
let t = quote! {
26-
#input! {
29+
#input_macro! {
2730
id: #id;
31+
api_kind: #api_kind;
2832
arg_tys: #(#arg_tys),*;
2933
arg_ids: #(#arg_ids),*;
3034
ret_ty: #ret_ty;
3135
}
36+
3237
};
3338
tokens.extend(t);
3439
}
@@ -78,6 +83,7 @@ fn get_libm_files() -> Vec<syn::File> {
7883
/// Function signature that will be expanded for the user macro.
7984
struct FnSig {
8085
ident: syn::Ident,
86+
api_kind: syn::Ident,
8187
c_abi: bool,
8288
ret_ty: Option<syn::Type>,
8389
arg_tys: Vec<syn::Type>,
@@ -101,7 +107,7 @@ macro_rules! syn_to_str {
101107

102108
/// Extracts all public functions from the libm files while
103109
/// doing some sanity checks on the function signatures.
104-
fn get_functions(files: &[syn::File]) -> Vec<FnSig> {
110+
fn get_functions(files: &[syn::File], ignored: &Option<HashSet<String>>) -> Vec<FnSig> {
105111
let mut error = false;
106112
let mut functions = Vec::new();
107113
// Traverse all files matching function items
@@ -122,10 +128,17 @@ fn get_functions(files: &[syn::File]) -> Vec<FnSig> {
122128
// Build a function signature while doing some sanity checks
123129
let mut fn_sig = FnSig {
124130
ident: ident.clone(),
131+
api_kind: to_api_kind(ident.clone()),
125132
c_abi: false,
126133
arg_tys: Vec::new(),
127134
ret_ty: None,
128135
};
136+
// Skip ignored functions:
137+
if let Some(ignored) = ignored {
138+
if ignored.contains(&fn_sig.name()) {
139+
continue;
140+
}
141+
}
129142
macro_rules! err {
130143
($msg:expr) => {{
131144
#[cfg(feature = "analyze")]
@@ -300,3 +313,48 @@ fn get_arg_ids(len: usize) -> Vec<syn::Ident> {
300313
}
301314
ids
302315
}
316+
317+
/// Returns the ApiKind enum variant for this function
318+
fn to_api_kind(id: syn::Ident) -> syn::Ident {
319+
let name = syn_to_str!(id);
320+
let first = name.chars().nth(0).unwrap();
321+
let first_upper = first.to_uppercase().nth(0).unwrap();
322+
let name = name.replacen(first, &first_upper.to_string(), 1);
323+
syn::Ident::new(&name, proc_macro2::Span::call_site())
324+
}
325+
326+
#[derive(Debug)]
327+
struct Input {
328+
macro_id: syn::Ident,
329+
ignored: Option<HashSet<String>>,
330+
}
331+
332+
impl syn::parse::Parse for Input {
333+
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
334+
let content;
335+
let macro_id: syn::Ident = input.parse()?;
336+
let lookahead = input.lookahead1();
337+
if lookahead.peek(syn::token::Paren) {
338+
let _paren_token = syn::parenthesized!(content in input);
339+
let ignored: syn::Lit = content.parse::<syn::Lit>()?;
340+
if let syn::Lit::Str(c) = ignored {
341+
let s = c.value();
342+
let mut hash_set = HashSet::<String>::new();
343+
for i in s.split(",") {
344+
hash_set.insert(i.to_string());
345+
}
346+
Ok(Input {
347+
macro_id: macro_id,
348+
ignored: Some(hash_set),
349+
})
350+
} else {
351+
Err(lookahead.error())
352+
}
353+
} else {
354+
Ok(Input {
355+
macro_id: macro_id,
356+
ignored: None,
357+
})
358+
}
359+
}
360+
}

crates/libm-analyze/tests/analyze.rs

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
macro_rules! nop {
55
(
66
id: $id:ident;
7+
api_kind: $api_kind:ident;
78
arg_tys: $($arg_tys:ty),*;
89
arg_ids: $($arg_ids:ident),*;
910
ret_ty: $ret_ty:ty;
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//! Tests that the proc-macro accepts macros with
2+
//! the following pattern:
3+
4+
macro_rules! nop {
5+
(
6+
id: $id:ident;
7+
api_kind: $api_kind:ident;
8+
arg_tys: $($arg_tys:ty),*;
9+
arg_ids: $($arg_ids:ident),*;
10+
ret_ty: $ret_ty:ty;
11+
) => {};
12+
}
13+
14+
libm_analyze::for_each_api!(nop("j1f,jn"));

crates/libm-bench/benches/bench.rs

+4-10
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
11
#![feature(test)]
22
extern crate test;
33

4-
use libm_test::{get_api_kind, ApiKind, Call};
4+
use libm_test::{ApiKind, CallFn};
55
use rand::Rng;
66
use test::Bencher;
77

88
macro_rules! bench_fn {
9-
// FIXME: jnf benches never terminate
10-
(
11-
id: jnf;
12-
arg_tys: $($arg_tys:ty),*;
13-
arg_ids: $($arg_ids:ident),*;
14-
ret_ty: $ret_ty:ty;
15-
) => {};
169
(
1710
id: $id:ident;
11+
api_kind: $api_kind:ident;
1812
arg_tys: $($arg_tys:ty),*;
1913
arg_ids: $($arg_ids:ident),*;
2014
ret_ty: $ret_ty:ty;
@@ -29,7 +23,7 @@ macro_rules! bench_fn {
2923
let mut rng = rand::thread_rng();
3024
let mut x: ( $($arg_tys,)+ ) = ( $(rng.gen::<$arg_tys>(),)+ );
3125

32-
if let ApiKind::Jx = get_api_kind!(fn: $id) {
26+
if let ApiKind::Jn = ApiKind::$api_kind {
3327
let ptr = &mut x as *mut _ as *mut i32;
3428
unsafe { ptr.write(ptr.read() & 0xffff) };
3529
}
@@ -39,4 +33,4 @@ macro_rules! bench_fn {
3933
};
4034
}
4135

42-
libm_analyze::for_each_api!(bench_fn);
36+
libm_analyze::for_each_api!(bench_fn(/*ignore:*/ "jnf"));

crates/libm-test/src/api_kind.rs

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
//! An enum with one variant per API
2+
3+
// FIXME: this should be auto-generated
4+
5+
/// Kind of libm API - used to handle generating tests
6+
/// for some functions slightly differently.
7+
#[derive(Copy, Clone, Debug, PartialEq)]
8+
pub enum ApiKind {
9+
Acos,
10+
Acosf,
11+
Acosh,
12+
Acoshf,
13+
Asin,
14+
Asinf,
15+
Asinh,
16+
Asinhf,
17+
Atan,
18+
Atan2,
19+
Atan2f,
20+
Atanf,
21+
Atanh,
22+
Atanhf,
23+
Cbrt,
24+
Cbrtf,
25+
Ceil,
26+
Ceilf,
27+
Copysign,
28+
Copysignf,
29+
Cos,
30+
Cosf,
31+
Cosh,
32+
Coshf,
33+
Erf,
34+
Erfc,
35+
Erfcf,
36+
Erff,
37+
Exp,
38+
Exp10,
39+
Exp10f,
40+
Exp2,
41+
Exp2f,
42+
Expf,
43+
Expm1,
44+
Expm1f,
45+
Fabs,
46+
Fabsf,
47+
Fdim,
48+
Fdimf,
49+
Floor,
50+
Floorf,
51+
Fma,
52+
Fmaf,
53+
Fmax,
54+
Fmaxf,
55+
Fmin,
56+
Fminf,
57+
Fmod,
58+
Fmodf,
59+
Hypot,
60+
Hypotf,
61+
Ilogb,
62+
Ilogbf,
63+
J0,
64+
J0f,
65+
J1,
66+
J1f,
67+
Jn,
68+
Ldexp,
69+
Ldexpf,
70+
Lgamma,
71+
Lgammaf,
72+
Log,
73+
Log10,
74+
Log10f,
75+
Log1p,
76+
Log1pf,
77+
Log2,
78+
Log2f,
79+
Logf,
80+
Modf,
81+
Modff,
82+
Nextafter,
83+
Nextafterf,
84+
Nexttoward,
85+
Nexttowardf,
86+
Pow,
87+
Powf,
88+
Remainder,
89+
Remainderf,
90+
Rint,
91+
Rintf,
92+
Round,
93+
Roundf,
94+
Scalbn,
95+
Scalbnf,
96+
Sin,
97+
SinCos,
98+
SinCosPi,
99+
SinCosPif,
100+
SinCosf,
101+
Sinf,
102+
Sinh,
103+
Sinhf,
104+
Sqrt,
105+
Sqrtf,
106+
Tan,
107+
Tanf,
108+
Tanh,
109+
Tanhf,
110+
Tgamma,
111+
Tgammaf,
112+
Trunc,
113+
Truncf,
114+
Y0,
115+
Y0f,
116+
Y1,
117+
Y1f,
118+
Yn,
119+
Ynf,
120+
}

crates/libm-test/src/call_fn.rs

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//! Trait to do function dispatch based on tuples:
2+
3+
/// This implements function dispatch for tuples of arguments used in the tests
4+
/// above, so that we can: (f32, 32).call(fn(f32, f32) -> f32) generically.
5+
///
6+
/// We need the input parameter F to support dispatching, e.g., (f32,f32) with
7+
/// functions that return both f32 or i32. Those are two different types, so we
8+
/// need to be parametric over them.
9+
pub trait CallFn<F> {
10+
type Ret;
11+
fn call(self, f: F) -> Self::Ret;
12+
}
13+
14+
macro_rules! impl_call {
15+
(($($arg_tys:ty),*) -> $ret_ty:ty: $self_:ident: $($xs:expr),*) => {
16+
impl CallFn<unsafe extern"C" fn($($arg_tys),*) -> $ret_ty> for ($($arg_tys,)+) {
17+
type Ret = $ret_ty;
18+
fn call(self, f: unsafe extern "C" fn($($arg_tys),*) -> $ret_ty) -> Self::Ret {
19+
let $self_ = self;
20+
unsafe { f($($xs),*) }
21+
}
22+
}
23+
};
24+
}
25+
26+
impl_call!((f32) -> f32: x: x.0);
27+
impl_call!((f64) -> f64: x: x.0);
28+
impl_call!((f64) -> i32: x: x.0);
29+
impl_call!((f32) -> i32: x: x.0);
30+
impl_call!((f32, f32) -> f32: x: x.0, x.1);
31+
impl_call!((f32, f64) -> f32: x: x.0, x.1);
32+
impl_call!((f64, f64) -> f64: x: x.0, x.1);
33+
impl_call!((f64, i32) -> f64: x: x.0, x.1);
34+
impl_call!((f32, i32) -> f32: x: x.0, x.1);
35+
impl_call!((i32, f64) -> f64: x: x.0, x.1);
36+
impl_call!((i32, f32) -> f32: x: x.0, x.1);
37+
impl_call!((f32, f32, f32) -> f32: x: x.0, x.1, x.2);
38+
impl_call!((f64, f64, f64) -> f64: x: x.0, x.1, x.2);

crates/libm-test/src/float_rng.rs

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//! Traits for generating ranges of floating-point values
2+
3+
pub trait Toward: Sized {
4+
/// Generates `len` minimal incremental steps from self to other.
5+
///
6+
/// Other is often never reached.
7+
fn toward(self, other: Self, len: usize) -> Vec<Self>;
8+
}
9+
10+
pub trait Distribute: Sized {
11+
/// Distributes `len` values in range `[self, other]`.
12+
fn distribute(self, other: Self, len: usize) -> Vec<Self>;
13+
}
14+
15+
macro_rules! impl_f {
16+
($float_ty:ident, $toward_fn:path) => {
17+
impl Toward for $float_ty {
18+
fn toward(self, other: Self, len: usize) -> Vec<Self> {
19+
let mut vec = Vec::with_capacity(len);
20+
let mut current = self;
21+
vec.push(self);
22+
for _ in 0..=len {
23+
current = $toward_fn(current, other as _);
24+
vec.push(self);
25+
if current.to_bits() == other.to_bits() {
26+
break;
27+
}
28+
}
29+
vec
30+
}
31+
}
32+
impl Distribute for $float_ty {
33+
fn distribute(self, other: Self, mut len: usize) -> Vec<Self> {
34+
let mut vec = Vec::with_capacity(len + 1);
35+
vec.push(self);
36+
// Bresenham's alg:
37+
let mut x = self;
38+
while len > 0 {
39+
x += (other - self) / (len as Self);
40+
len -= 1;
41+
vec.push(x);
42+
}
43+
vec
44+
}
45+
}
46+
};
47+
}
48+
impl_f!(f32, libm::nextafterf);
49+
impl_f!(f64, libm::nextafter);

0 commit comments

Comments
 (0)