Merge pull request rust-lang/libm#349 from tgross35/better-typing
Migrate types from a macro to a trait
This commit is contained in:
commit
d715142b4d
14 changed files with 741 additions and 374 deletions
|
|
@ -8,6 +8,7 @@ publish = false
|
|||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
heck = "0.5.0"
|
||||
proc-macro2 = "1.0.88"
|
||||
quote = "1.0.37"
|
||||
syn = { version = "2.0.79", features = ["full", "extra-traits", "visit-mut"] }
|
||||
|
|
|
|||
132
library/compiler-builtins/libm/crates/libm-macros/src/enums.rs
Normal file
132
library/compiler-builtins/libm/crates/libm-macros/src/enums.rs
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
use heck::ToUpperCamelCase;
|
||||
use proc_macro2 as pm2;
|
||||
use proc_macro2::{Ident, Span};
|
||||
use quote::quote;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{Fields, ItemEnum, Variant};
|
||||
|
||||
use crate::{ALL_FUNCTIONS_FLAT, base_name};
|
||||
|
||||
/// Implement `#[function_enum]`, see documentation in `lib.rs`.
|
||||
pub fn function_enum(
|
||||
mut item: ItemEnum,
|
||||
attributes: pm2::TokenStream,
|
||||
) -> syn::Result<pm2::TokenStream> {
|
||||
expect_empty_enum(&item)?;
|
||||
let attr_span = attributes.span();
|
||||
let mut attr = attributes.into_iter();
|
||||
|
||||
// Attribute should be the identifier of the `BaseName` enum.
|
||||
let Some(tt) = attr.next() else {
|
||||
return Err(syn::Error::new(attr_span, "expected one attribute"));
|
||||
};
|
||||
|
||||
let pm2::TokenTree::Ident(base_enum) = tt else {
|
||||
return Err(syn::Error::new(tt.span(), "expected an identifier"));
|
||||
};
|
||||
|
||||
if let Some(tt) = attr.next() {
|
||||
return Err(syn::Error::new(tt.span(), "unexpected token after identifier"));
|
||||
}
|
||||
|
||||
let enum_name = &item.ident;
|
||||
let mut as_str_arms = Vec::new();
|
||||
let mut base_arms = Vec::new();
|
||||
|
||||
for func in ALL_FUNCTIONS_FLAT.iter() {
|
||||
let fn_name = func.name;
|
||||
let ident = Ident::new(&fn_name.to_upper_camel_case(), Span::call_site());
|
||||
let bname_ident = Ident::new(&base_name(fn_name).to_upper_camel_case(), Span::call_site());
|
||||
|
||||
// Match arm for `fn as_str(self)` matcher
|
||||
as_str_arms.push(quote! { Self::#ident => #fn_name });
|
||||
|
||||
// Match arm for `fn base_name(self)` matcher
|
||||
base_arms.push(quote! { Self::#ident => #base_enum::#bname_ident });
|
||||
|
||||
let variant =
|
||||
Variant { attrs: Vec::new(), ident, fields: Fields::Unit, discriminant: None };
|
||||
|
||||
item.variants.push(variant);
|
||||
}
|
||||
|
||||
let res = quote! {
|
||||
// Instantiate the enum
|
||||
#item
|
||||
|
||||
impl #enum_name {
|
||||
/// The stringified version of this function name.
|
||||
const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
#( #as_str_arms , )*
|
||||
}
|
||||
}
|
||||
|
||||
/// The base name enum for this function.
|
||||
const fn base_name(self) -> #base_enum {
|
||||
match self {
|
||||
#( #base_arms, )*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Implement `#[base_name_enum]`, see documentation in `lib.rs`.
|
||||
pub fn base_name_enum(
|
||||
mut item: ItemEnum,
|
||||
attributes: pm2::TokenStream,
|
||||
) -> syn::Result<pm2::TokenStream> {
|
||||
expect_empty_enum(&item)?;
|
||||
if !attributes.is_empty() {
|
||||
let sp = attributes.span();
|
||||
return Err(syn::Error::new(sp.span(), "no attributes expected"));
|
||||
}
|
||||
|
||||
let mut base_names: Vec<_> =
|
||||
ALL_FUNCTIONS_FLAT.iter().map(|func| base_name(func.name)).collect();
|
||||
base_names.sort_unstable();
|
||||
base_names.dedup();
|
||||
|
||||
let item_name = &item.ident;
|
||||
let mut as_str_arms = Vec::new();
|
||||
|
||||
for base_name in base_names {
|
||||
let ident = Ident::new(&base_name.to_upper_camel_case(), Span::call_site());
|
||||
|
||||
// Match arm for `fn as_str(self)` matcher
|
||||
as_str_arms.push(quote! { Self::#ident => #base_name });
|
||||
|
||||
let variant =
|
||||
Variant { attrs: Vec::new(), ident, fields: Fields::Unit, discriminant: None };
|
||||
|
||||
item.variants.push(variant);
|
||||
}
|
||||
|
||||
let res = quote! {
|
||||
// Instantiate the enum
|
||||
#item
|
||||
|
||||
impl #item_name {
|
||||
/// The stringified version of this base name.
|
||||
const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
#( #as_str_arms ),*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Verify that an enum is empty, otherwise return an error
|
||||
fn expect_empty_enum(item: &ItemEnum) -> syn::Result<()> {
|
||||
if !item.variants.is_empty() {
|
||||
Err(syn::Error::new(item.variants.span(), "expected an empty enum"))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,20 @@
|
|||
mod enums;
|
||||
mod parse;
|
||||
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use parse::{Invocation, StructuredInput};
|
||||
use proc_macro as pm;
|
||||
use proc_macro2::{self as pm2, Span};
|
||||
use quote::{ToTokens, quote};
|
||||
use syn::Ident;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::visit_mut::VisitMut;
|
||||
use syn::{Ident, ItemEnum};
|
||||
|
||||
const ALL_FUNCTIONS: &[(Signature, Option<Signature>, &[&str])] = &[
|
||||
const ALL_FUNCTIONS: &[(Ty, Signature, Option<Signature>, &[&str])] = &[
|
||||
(
|
||||
// `fn(f32) -> f32`
|
||||
Ty::F32,
|
||||
Signature { args: &[Ty::F32], returns: &[Ty::F32] },
|
||||
None,
|
||||
&[
|
||||
|
|
@ -22,6 +26,7 @@ const ALL_FUNCTIONS: &[(Signature, Option<Signature>, &[&str])] = &[
|
|||
),
|
||||
(
|
||||
// `(f64) -> f64`
|
||||
Ty::F64,
|
||||
Signature { args: &[Ty::F64], returns: &[Ty::F64] },
|
||||
None,
|
||||
&[
|
||||
|
|
@ -33,6 +38,7 @@ const ALL_FUNCTIONS: &[(Signature, Option<Signature>, &[&str])] = &[
|
|||
),
|
||||
(
|
||||
// `(f32, f32) -> f32`
|
||||
Ty::F32,
|
||||
Signature { args: &[Ty::F32, Ty::F32], returns: &[Ty::F32] },
|
||||
None,
|
||||
&[
|
||||
|
|
@ -50,6 +56,7 @@ const ALL_FUNCTIONS: &[(Signature, Option<Signature>, &[&str])] = &[
|
|||
),
|
||||
(
|
||||
// `(f64, f64) -> f64`
|
||||
Ty::F64,
|
||||
Signature { args: &[Ty::F64, Ty::F64], returns: &[Ty::F64] },
|
||||
None,
|
||||
&[
|
||||
|
|
@ -67,102 +74,120 @@ const ALL_FUNCTIONS: &[(Signature, Option<Signature>, &[&str])] = &[
|
|||
),
|
||||
(
|
||||
// `(f32, f32, f32) -> f32`
|
||||
Ty::F32,
|
||||
Signature { args: &[Ty::F32, Ty::F32, Ty::F32], returns: &[Ty::F32] },
|
||||
None,
|
||||
&["fmaf"],
|
||||
),
|
||||
(
|
||||
// `(f64, f64, f64) -> f64`
|
||||
Ty::F64,
|
||||
Signature { args: &[Ty::F64, Ty::F64, Ty::F64], returns: &[Ty::F64] },
|
||||
None,
|
||||
&["fma"],
|
||||
),
|
||||
(
|
||||
// `(f32) -> i32`
|
||||
Ty::F32,
|
||||
Signature { args: &[Ty::F32], returns: &[Ty::I32] },
|
||||
None,
|
||||
&["ilogbf"],
|
||||
),
|
||||
(
|
||||
// `(f64) -> i32`
|
||||
Ty::F64,
|
||||
Signature { args: &[Ty::F64], returns: &[Ty::I32] },
|
||||
None,
|
||||
&["ilogb"],
|
||||
),
|
||||
(
|
||||
// `(i32, f32) -> f32`
|
||||
Ty::F32,
|
||||
Signature { args: &[Ty::I32, Ty::F32], returns: &[Ty::F32] },
|
||||
None,
|
||||
&["jnf"],
|
||||
),
|
||||
(
|
||||
// `(i32, f64) -> f64`
|
||||
Ty::F64,
|
||||
Signature { args: &[Ty::I32, Ty::F64], returns: &[Ty::F64] },
|
||||
None,
|
||||
&["jn"],
|
||||
),
|
||||
(
|
||||
// `(f32, i32) -> f32`
|
||||
Ty::F32,
|
||||
Signature { args: &[Ty::F32, Ty::I32], returns: &[Ty::F32] },
|
||||
None,
|
||||
&["scalbnf", "ldexpf"],
|
||||
),
|
||||
(
|
||||
// `(f64, i64) -> f64`
|
||||
Ty::F64,
|
||||
Signature { args: &[Ty::F64, Ty::I32], returns: &[Ty::F64] },
|
||||
None,
|
||||
&["scalbn", "ldexp"],
|
||||
),
|
||||
(
|
||||
// `(f32, &mut f32) -> f32` as `(f32) -> (f32, f32)`
|
||||
Ty::F32,
|
||||
Signature { args: &[Ty::F32], returns: &[Ty::F32, Ty::F32] },
|
||||
Some(Signature { args: &[Ty::F32, Ty::MutF32], returns: &[Ty::F32] }),
|
||||
&["modff"],
|
||||
),
|
||||
(
|
||||
// `(f64, &mut f64) -> f64` as `(f64) -> (f64, f64)`
|
||||
Ty::F64,
|
||||
Signature { args: &[Ty::F64], returns: &[Ty::F64, Ty::F64] },
|
||||
Some(Signature { args: &[Ty::F64, Ty::MutF64], returns: &[Ty::F64] }),
|
||||
&["modf"],
|
||||
),
|
||||
(
|
||||
// `(f32, &mut c_int) -> f32` as `(f32) -> (f32, i32)`
|
||||
Ty::F32,
|
||||
Signature { args: &[Ty::F32], returns: &[Ty::F32, Ty::I32] },
|
||||
Some(Signature { args: &[Ty::F32, Ty::MutCInt], returns: &[Ty::F32] }),
|
||||
&["frexpf", "lgammaf_r"],
|
||||
),
|
||||
(
|
||||
// `(f64, &mut c_int) -> f64` as `(f64) -> (f64, i32)`
|
||||
Ty::F64,
|
||||
Signature { args: &[Ty::F64], returns: &[Ty::F64, Ty::I32] },
|
||||
Some(Signature { args: &[Ty::F64, Ty::MutCInt], returns: &[Ty::F64] }),
|
||||
&["frexp", "lgamma_r"],
|
||||
),
|
||||
(
|
||||
// `(f32, f32, &mut c_int) -> f32` as `(f32, f32) -> (f32, i32)`
|
||||
Ty::F32,
|
||||
Signature { args: &[Ty::F32, Ty::F32], returns: &[Ty::F32, Ty::I32] },
|
||||
Some(Signature { args: &[Ty::F32, Ty::F32, Ty::MutCInt], returns: &[Ty::F32] }),
|
||||
&["remquof"],
|
||||
),
|
||||
(
|
||||
// `(f64, f64, &mut c_int) -> f64` as `(f64, f64) -> (f64, i32)`
|
||||
Ty::F64,
|
||||
Signature { args: &[Ty::F64, Ty::F64], returns: &[Ty::F64, Ty::I32] },
|
||||
Some(Signature { args: &[Ty::F64, Ty::F64, Ty::MutCInt], returns: &[Ty::F64] }),
|
||||
&["remquo"],
|
||||
),
|
||||
(
|
||||
// `(f32, &mut f32, &mut f32)` as `(f32) -> (f32, f32)`
|
||||
Ty::F32,
|
||||
Signature { args: &[Ty::F32], returns: &[Ty::F32, Ty::F32] },
|
||||
Some(Signature { args: &[Ty::F32, Ty::MutF32, Ty::MutF32], returns: &[] }),
|
||||
&["sincosf"],
|
||||
),
|
||||
(
|
||||
// `(f64, &mut f64, &mut f64)` as `(f64) -> (f64, f64)`
|
||||
Ty::F64,
|
||||
Signature { args: &[Ty::F64], returns: &[Ty::F64, Ty::F64] },
|
||||
Some(Signature { args: &[Ty::F64, Ty::MutF64, Ty::MutF64], returns: &[] }),
|
||||
&["sincos"],
|
||||
),
|
||||
];
|
||||
|
||||
const KNOWN_TYPES: &[&str] = &["FTy", "CFn", "CArgs", "CRet", "RustFn", "RustArgs", "RustRet"];
|
||||
|
||||
/// A type used in a function signature.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
|
@ -190,12 +215,12 @@ impl ToTokens for Ty {
|
|||
Ty::F128 => quote! { f128 },
|
||||
Ty::I32 => quote! { i32 },
|
||||
Ty::CInt => quote! { ::core::ffi::c_int },
|
||||
Ty::MutF16 => quote! { &mut f16 },
|
||||
Ty::MutF32 => quote! { &mut f32 },
|
||||
Ty::MutF64 => quote! { &mut f64 },
|
||||
Ty::MutF128 => quote! { &mut f128 },
|
||||
Ty::MutI32 => quote! { &mut i32 },
|
||||
Ty::MutCInt => quote! { &mut core::ffi::c_int },
|
||||
Ty::MutF16 => quote! { &'a mut f16 },
|
||||
Ty::MutF32 => quote! { &'a mut f32 },
|
||||
Ty::MutF64 => quote! { &'a mut f64 },
|
||||
Ty::MutF128 => quote! { &'a mut f128 },
|
||||
Ty::MutI32 => quote! { &'a mut i32 },
|
||||
Ty::MutCInt => quote! { &'a mut core::ffi::c_int },
|
||||
};
|
||||
|
||||
tokens.extend(ts);
|
||||
|
|
@ -213,6 +238,7 @@ struct Signature {
|
|||
#[derive(Debug, Clone)]
|
||||
struct FunctionInfo {
|
||||
name: &'static str,
|
||||
base_fty: Ty,
|
||||
/// Function signature for C implementations
|
||||
c_sig: Signature,
|
||||
/// Function signature for Rust implementations
|
||||
|
|
@ -223,10 +249,11 @@ struct FunctionInfo {
|
|||
static ALL_FUNCTIONS_FLAT: LazyLock<Vec<FunctionInfo>> = LazyLock::new(|| {
|
||||
let mut ret = Vec::new();
|
||||
|
||||
for (rust_sig, c_sig, names) in ALL_FUNCTIONS {
|
||||
for (base_fty, rust_sig, c_sig, names) in ALL_FUNCTIONS {
|
||||
for name in *names {
|
||||
let api = FunctionInfo {
|
||||
name,
|
||||
base_fty: *base_fty,
|
||||
rust_sig: rust_sig.clone(),
|
||||
c_sig: c_sig.clone().unwrap_or_else(|| rust_sig.clone()),
|
||||
};
|
||||
|
|
@ -238,6 +265,37 @@ static ALL_FUNCTIONS_FLAT: LazyLock<Vec<FunctionInfo>> = LazyLock::new(|| {
|
|||
ret
|
||||
});
|
||||
|
||||
/// Populate an enum with a variant representing function. Names are in upper camel case.
|
||||
///
|
||||
/// Applied to an empty enum. Expects one attribute `#[function_enum(BaseName)]` that provides
|
||||
/// the name of the `BaseName` enum.
|
||||
#[proc_macro_attribute]
|
||||
pub fn function_enum(attributes: pm::TokenStream, tokens: pm::TokenStream) -> pm::TokenStream {
|
||||
let item = syn::parse_macro_input!(tokens as ItemEnum);
|
||||
let res = enums::function_enum(item, attributes.into());
|
||||
|
||||
match res {
|
||||
Ok(ts) => ts,
|
||||
Err(e) => e.into_compile_error(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Create an enum representing all possible base names, with names in upper camel case.
|
||||
///
|
||||
/// Applied to an empty enum.
|
||||
#[proc_macro_attribute]
|
||||
pub fn base_name_enum(attributes: pm::TokenStream, tokens: pm::TokenStream) -> pm::TokenStream {
|
||||
let item = syn::parse_macro_input!(tokens as ItemEnum);
|
||||
let res = enums::base_name_enum(item, attributes.into());
|
||||
|
||||
match res {
|
||||
Ok(ts) => ts,
|
||||
Err(e) => e.into_compile_error(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Do something for each function present in this crate.
|
||||
///
|
||||
/// Takes a callback macro and invokes it multiple times, once for each function that
|
||||
|
|
@ -258,6 +316,8 @@ static ALL_FUNCTIONS_FLAT: LazyLock<Vec<FunctionInfo>> = LazyLock::new(|| {
|
|||
/// (
|
||||
/// // Name of that function
|
||||
/// fn_name: $fn_name:ident,
|
||||
/// // The basic float type for this function (e.g. `f32`, `f64`)
|
||||
/// FTy: $FTy:ty,
|
||||
/// // Function signature of the C version (e.g. `fn(f32, &mut f32) -> f32`)
|
||||
/// CFn: $CFn:ty,
|
||||
/// // A tuple representing the C version's arguments (e.g. `(f32, &mut f32)`)
|
||||
|
|
@ -279,17 +339,16 @@ static ALL_FUNCTIONS_FLAT: LazyLock<Vec<FunctionInfo>> = LazyLock::new(|| {
|
|||
/// ) => { };
|
||||
/// }
|
||||
///
|
||||
/// // All fields except for `callback` are optional.
|
||||
/// libm_macros::for_each_function! {
|
||||
/// // The macro to invoke as a callback
|
||||
/// callback: callback_macro,
|
||||
/// // Which types to include either as a list (`[CFn, RustFn, RustArgs]`) or "all"
|
||||
/// emit_types: all,
|
||||
/// // Functions to skip, i.e. `callback` shouldn't be called at all for these.
|
||||
/// //
|
||||
/// // This is an optional field.
|
||||
/// skip: [sin, cos],
|
||||
/// // Attributes passed as `attrs` for specific functions. For example, here the invocation
|
||||
/// // with `sinf` and that with `cosf` will both get `meta1` and `meta2`, but no others will.
|
||||
/// //
|
||||
/// // This is an optional field.
|
||||
/// attributes: [
|
||||
/// #[meta1]
|
||||
/// #[meta2]
|
||||
|
|
@ -297,8 +356,6 @@ static ALL_FUNCTIONS_FLAT: LazyLock<Vec<FunctionInfo>> = LazyLock::new(|| {
|
|||
/// ],
|
||||
/// // Any tokens that should be passed directly to all invocations of the callback. This can
|
||||
/// // be used to pass local variables or other things the macro needs access to.
|
||||
/// //
|
||||
/// // This is an optional field.
|
||||
/// extra: [foo],
|
||||
/// // Similar to `extra`, but allow providing a pattern for only specific functions. Uses
|
||||
/// // a simplified match-like syntax.
|
||||
|
|
@ -313,7 +370,7 @@ pub fn for_each_function(tokens: pm::TokenStream) -> pm::TokenStream {
|
|||
let input = syn::parse_macro_input!(tokens as Invocation);
|
||||
|
||||
let res = StructuredInput::from_fields(input)
|
||||
.and_then(|s_in| validate(&s_in).map(|fn_list| (s_in, fn_list)))
|
||||
.and_then(|mut s_in| validate(&mut s_in).map(|fn_list| (s_in, fn_list)))
|
||||
.and_then(|(s_in, fn_list)| expand(s_in, &fn_list));
|
||||
|
||||
match res {
|
||||
|
|
@ -325,7 +382,7 @@ pub fn for_each_function(tokens: pm::TokenStream) -> pm::TokenStream {
|
|||
/// Check for any input that is structurally correct but has other problems.
|
||||
///
|
||||
/// Returns the list of function names that we should expand for.
|
||||
fn validate(input: &StructuredInput) -> syn::Result<Vec<&'static FunctionInfo>> {
|
||||
fn validate(input: &mut StructuredInput) -> syn::Result<Vec<&'static FunctionInfo>> {
|
||||
// Collect lists of all functions that are provied as macro inputs in various fields (only,
|
||||
// skip, attributes).
|
||||
let attr_mentions = input
|
||||
|
|
@ -376,6 +433,43 @@ fn validate(input: &StructuredInput) -> syn::Result<Vec<&'static FunctionInfo>>
|
|||
fn_list.push(func);
|
||||
}
|
||||
|
||||
// Types that the user would like us to provide in the macro
|
||||
let mut add_all_types = false;
|
||||
for ty in &input.emit_types {
|
||||
let ty_name = ty.to_string();
|
||||
if ty_name == "all" {
|
||||
add_all_types = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check that all requested types are valid
|
||||
if !KNOWN_TYPES.contains(&ty_name.as_str()) {
|
||||
let e = syn::Error::new(
|
||||
ty_name.span(),
|
||||
format!("unrecognized type identifier `{ty_name}`"),
|
||||
);
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
if add_all_types {
|
||||
// Ensure that if `all` was specified that nothing else was
|
||||
if input.emit_types.len() > 1 {
|
||||
let e = syn::Error::new(
|
||||
input.emit_types_span.unwrap(),
|
||||
"if `all` is specified, no other type identifiers may be given",
|
||||
);
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
// ...and then add all types
|
||||
input.emit_types.clear();
|
||||
for ty in KNOWN_TYPES {
|
||||
let ident = Ident::new(ty, Span::call_site());
|
||||
input.emit_types.push(ident);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(map) = &input.fn_extra {
|
||||
if !map.keys().any(|key| key == "_") {
|
||||
// No default provided; make sure every expected function is covered
|
||||
|
|
@ -451,20 +545,31 @@ fn expand(input: StructuredInput, fn_list: &[&FunctionInfo]) -> syn::Result<pm2:
|
|||
None => pm2::TokenStream::new(),
|
||||
};
|
||||
|
||||
let base_fty = func.base_fty;
|
||||
let c_args = &func.c_sig.args;
|
||||
let c_ret = &func.c_sig.returns;
|
||||
let rust_args = &func.rust_sig.args;
|
||||
let rust_ret = &func.rust_sig.returns;
|
||||
|
||||
let mut ty_fields = Vec::new();
|
||||
for ty in &input.emit_types {
|
||||
let field = match ty.to_string().as_str() {
|
||||
"FTy" => quote! { FTy: #base_fty, },
|
||||
"CFn" => quote! { CFn: fn( #(#c_args),* ,) -> ( #(#c_ret),* ), },
|
||||
"CArgs" => quote! { CArgs: ( #(#c_args),* ,), },
|
||||
"CRet" => quote! { CRet: ( #(#c_ret),* ), },
|
||||
"RustFn" => quote! { RustFn: fn( #(#rust_args),* ,) -> ( #(#rust_ret),* ), },
|
||||
"RustArgs" => quote! { RustArgs: ( #(#rust_args),* ,), },
|
||||
"RustRet" => quote! { RustRet: ( #(#rust_ret),* ), },
|
||||
_ => unreachable!("checked in validation"),
|
||||
};
|
||||
ty_fields.push(field);
|
||||
}
|
||||
|
||||
let new = quote! {
|
||||
#callback! {
|
||||
fn_name: #fn_name,
|
||||
CFn: fn( #(#c_args),* ,) -> ( #(#c_ret),* ),
|
||||
CArgs: ( #(#c_args),* ,),
|
||||
CRet: ( #(#c_ret),* ),
|
||||
RustFn: fn( #(#rust_args),* ,) -> ( #(#rust_ret),* ),
|
||||
RustArgs: ( #(#rust_args),* ,),
|
||||
RustRet: ( #(#rust_ret),* ),
|
||||
#( #ty_fields )*
|
||||
#meta_field
|
||||
#extra_field
|
||||
#fn_extra_field
|
||||
|
|
@ -488,24 +593,7 @@ struct MacroReplace {
|
|||
|
||||
impl MacroReplace {
|
||||
fn new(name: &'static str) -> Self {
|
||||
// Keep this in sync with `libm_test::canonical_name`
|
||||
let known_mappings = &[
|
||||
("erff", "erf"),
|
||||
("erf", "erf"),
|
||||
("lgammaf_r", "lgamma_r"),
|
||||
("modff", "modf"),
|
||||
("modf", "modf"),
|
||||
];
|
||||
|
||||
let norm_name = match known_mappings.iter().find(|known| known.0 == name) {
|
||||
Some(found) => found.1,
|
||||
None => name
|
||||
.strip_suffix("f")
|
||||
.or_else(|| name.strip_suffix("f16"))
|
||||
.or_else(|| name.strip_suffix("f128"))
|
||||
.unwrap_or(name),
|
||||
};
|
||||
|
||||
let norm_name = base_name(name);
|
||||
Self { fn_name: name, norm_name: norm_name.to_owned(), error: None }
|
||||
}
|
||||
|
||||
|
|
@ -539,3 +627,24 @@ impl VisitMut for MacroReplace {
|
|||
syn::visit_mut::visit_ident_mut(self, i);
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the unsuffixed name of a function.
|
||||
fn base_name(name: &str) -> &str {
|
||||
// Keep this in sync with `libm_test::base_name`
|
||||
let known_mappings = &[
|
||||
("erff", "erf"),
|
||||
("erf", "erf"),
|
||||
("lgammaf_r", "lgamma_r"),
|
||||
("modff", "modf"),
|
||||
("modf", "modf"),
|
||||
];
|
||||
|
||||
match known_mappings.iter().find(|known| known.0 == name) {
|
||||
Some(found) => found.1,
|
||||
None => name
|
||||
.strip_suffix("f")
|
||||
.or_else(|| name.strip_suffix("f16"))
|
||||
.or_else(|| name.strip_suffix("f128"))
|
||||
.unwrap_or(name),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use quote::ToTokens;
|
|||
use syn::parse::{Parse, ParseStream, Parser};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::token::Comma;
|
||||
use syn::token::{self, Comma};
|
||||
use syn::{Arm, Attribute, Expr, ExprMatch, Ident, Meta, Token, bracketed};
|
||||
|
||||
/// The input to our macro; just a list of `field: value` items.
|
||||
|
|
@ -39,6 +39,9 @@ impl Parse for Mapping {
|
|||
pub struct StructuredInput {
|
||||
/// Macro to invoke once per function
|
||||
pub callback: Ident,
|
||||
/// Whether or not to provide `CFn` `CArgs` `RustFn` etc. This is really only needed
|
||||
/// once for crate to set up the main trait.
|
||||
pub emit_types: Vec<Ident>,
|
||||
/// Skip these functions
|
||||
pub skip: Vec<Ident>,
|
||||
/// Invoke only for these functions
|
||||
|
|
@ -50,6 +53,7 @@ pub struct StructuredInput {
|
|||
/// Per-function extra expressions to pass to the macro
|
||||
pub fn_extra: Option<BTreeMap<Ident, Expr>>,
|
||||
// For diagnostics
|
||||
pub emit_types_span: Option<Span>,
|
||||
pub only_span: Option<Span>,
|
||||
pub fn_extra_span: Option<Span>,
|
||||
}
|
||||
|
|
@ -58,6 +62,7 @@ impl StructuredInput {
|
|||
pub fn from_fields(input: Invocation) -> syn::Result<Self> {
|
||||
let mut map: Vec<_> = input.fields.into_iter().collect();
|
||||
let cb_expr = expect_field(&mut map, "callback")?;
|
||||
let emit_types_expr = expect_field(&mut map, "emit_types").ok();
|
||||
let skip_expr = expect_field(&mut map, "skip").ok();
|
||||
let only_expr = expect_field(&mut map, "only").ok();
|
||||
let attr_expr = expect_field(&mut map, "attributes").ok();
|
||||
|
|
@ -71,6 +76,12 @@ impl StructuredInput {
|
|||
))?;
|
||||
}
|
||||
|
||||
let emit_types_span = emit_types_expr.as_ref().map(|expr| expr.span());
|
||||
let emit_types = match emit_types_expr {
|
||||
Some(expr) => Parser::parse2(parse_ident_or_array, expr.into_token_stream())?,
|
||||
None => Vec::new(),
|
||||
};
|
||||
|
||||
let skip = match skip_expr {
|
||||
Some(expr) => Parser::parse2(parse_ident_array, expr.into_token_stream())?,
|
||||
None => Vec::new(),
|
||||
|
|
@ -103,6 +114,7 @@ impl StructuredInput {
|
|||
|
||||
Ok(Self {
|
||||
callback: expect_ident(cb_expr)?,
|
||||
emit_types,
|
||||
skip,
|
||||
only,
|
||||
only_span,
|
||||
|
|
@ -110,6 +122,7 @@ impl StructuredInput {
|
|||
extra,
|
||||
fn_extra,
|
||||
fn_extra_span,
|
||||
emit_types_span,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -183,6 +196,15 @@ fn expect_ident(expr: Expr) -> syn::Result<Ident> {
|
|||
syn::parse2(expr.into_token_stream())
|
||||
}
|
||||
|
||||
/// Parse either a single identifier (`foo`) or an array of identifiers (`[foo, bar, baz]`).
|
||||
fn parse_ident_or_array(input: ParseStream) -> syn::Result<Vec<Ident>> {
|
||||
if !input.peek(token::Bracket) {
|
||||
return Ok(vec![input.parse()?]);
|
||||
}
|
||||
|
||||
parse_ident_array(input)
|
||||
}
|
||||
|
||||
/// Parse an array of expressions.
|
||||
fn parse_expr_array(input: ParseStream) -> syn::Result<Vec<Expr>> {
|
||||
let content;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
macro_rules! basic {
|
||||
(
|
||||
fn_name: $fn_name:ident,
|
||||
FTy: $FTy:ty,
|
||||
CFn: $CFn:ty,
|
||||
CArgs: $CArgs:ty,
|
||||
CRet: $CRet:ty,
|
||||
|
|
@ -17,9 +18,9 @@ macro_rules! basic {
|
|||
$(#[$meta])*
|
||||
mod $fn_name {
|
||||
#[allow(unused)]
|
||||
type CFnTy = $CFn;
|
||||
// type CArgsTy<'_> = $CArgs;
|
||||
// type CRetTy<'_> = $CRet;
|
||||
type FTy= $FTy;
|
||||
#[allow(unused)]
|
||||
type CFnTy<'a> = $CFn;
|
||||
#[allow(unused)]
|
||||
type RustFnTy = $RustFn;
|
||||
#[allow(unused)]
|
||||
|
|
@ -39,6 +40,7 @@ macro_rules! basic {
|
|||
mod test_basic {
|
||||
libm_macros::for_each_function! {
|
||||
callback: basic,
|
||||
emit_types: all,
|
||||
skip: [sin, cos],
|
||||
attributes: [
|
||||
// just some random attributes
|
||||
|
|
@ -58,25 +60,8 @@ mod test_basic {
|
|||
macro_rules! basic_no_extra {
|
||||
(
|
||||
fn_name: $fn_name:ident,
|
||||
CFn: $CFn:ty,
|
||||
CArgs: $CArgs:ty,
|
||||
CRet: $CRet:ty,
|
||||
RustFn: $RustFn:ty,
|
||||
RustArgs: $RustArgs:ty,
|
||||
RustRet: $RustRet:ty,
|
||||
) => {
|
||||
mod $fn_name {
|
||||
#[allow(unused)]
|
||||
type CFnTy = $CFn;
|
||||
// type CArgsTy<'_> = $CArgs;
|
||||
// type CRetTy<'_> = $CRet;
|
||||
#[allow(unused)]
|
||||
type RustFnTy = $RustFn;
|
||||
#[allow(unused)]
|
||||
type RustArgsTy = $RustArgs;
|
||||
#[allow(unused)]
|
||||
type RustRetTy = $RustRet;
|
||||
}
|
||||
mod $fn_name {}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -94,3 +79,26 @@ mod test_only {
|
|||
only: [sin, sinf],
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! specified_types {
|
||||
(
|
||||
fn_name: $fn_name:ident,
|
||||
RustFn: $RustFn:ty,
|
||||
RustArgs: $RustArgs:ty,
|
||||
) => {
|
||||
mod $fn_name {
|
||||
#[allow(unused)]
|
||||
type RustFnTy = $RustFn;
|
||||
#[allow(unused)]
|
||||
type RustArgsTy = $RustArgs;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
mod test_emit_types {
|
||||
// Test that we can specify a couple types to emit
|
||||
libm_macros::for_each_function! {
|
||||
callback: specified_types,
|
||||
emit_types: [RustFn, RustArgs],
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
#[libm_macros::function_enum(BaseName)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Function {}
|
||||
|
||||
#[libm_macros::base_name_enum]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum BaseName {}
|
||||
|
||||
#[test]
|
||||
fn as_str() {
|
||||
assert_eq!(Function::Sin.as_str(), "sin");
|
||||
assert_eq!(Function::Sinf.as_str(), "sinf");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basename() {
|
||||
assert_eq!(Function::Sin.base_name(), BaseName::Sin);
|
||||
assert_eq!(Function::Sinf.base_name(), BaseName::Sin);
|
||||
}
|
||||
|
|
@ -2,74 +2,105 @@ use std::hint::black_box;
|
|||
use std::time::Duration;
|
||||
|
||||
use criterion::{Criterion, criterion_main};
|
||||
use libm_test::gen::random;
|
||||
use libm_test::{CheckBasis, CheckCtx, TupleCall};
|
||||
use libm_test::gen::{CachedInput, random};
|
||||
use libm_test::{CheckBasis, CheckCtx, GenerateInput, MathOp, TupleCall};
|
||||
|
||||
/// Benchmark with this many items to get a variety
|
||||
const BENCH_ITER_ITEMS: usize = if cfg!(feature = "short-benchmarks") { 50 } else { 500 };
|
||||
|
||||
/// Extra parameters we only care about if we are benchmarking against musl.
|
||||
#[allow(dead_code)]
|
||||
struct MuslExtra<F> {
|
||||
musl_fn: Option<F>,
|
||||
skip_on_i586: bool,
|
||||
}
|
||||
|
||||
macro_rules! musl_rand_benches {
|
||||
(
|
||||
fn_name: $fn_name:ident,
|
||||
CFn: $CFn:ty,
|
||||
CArgs: $CArgs:ty,
|
||||
CRet: $CRet:ty,
|
||||
RustFn: $RustFn:ty,
|
||||
RustArgs: $RustArgs:ty,
|
||||
RustRet: $RustRet:ty,
|
||||
fn_extra: $skip_on_i586:expr,
|
||||
) => {
|
||||
paste::paste! {
|
||||
fn [< musl_bench_ $fn_name >](c: &mut Criterion) {
|
||||
let fn_name = stringify!($fn_name);
|
||||
type Op = libm_test::op::$fn_name::Routine;
|
||||
|
||||
let ulp = libm_test::musl_allowed_ulp(fn_name);
|
||||
let ctx = CheckCtx::new(ulp, fn_name, CheckBasis::Musl);
|
||||
let benchvec: Vec<_> = random::get_test_cases::<$RustArgs>(&ctx)
|
||||
.take(BENCH_ITER_ITEMS)
|
||||
.collect();
|
||||
|
||||
// Perform a sanity check that we are benchmarking the same thing
|
||||
// Don't test against musl if it is not available
|
||||
#[cfg(feature = "build-musl")]
|
||||
for input in benchvec.iter().copied() {
|
||||
use anyhow::Context;
|
||||
use libm_test::{CheckBasis, CheckCtx, CheckOutput};
|
||||
let musl_extra = MuslExtra {
|
||||
musl_fn: Some(musl_math_sys::$fn_name as <Op as MathOp>::CFn),
|
||||
skip_on_i586: $skip_on_i586
|
||||
};
|
||||
|
||||
if cfg!(x86_no_sse) && $skip_on_i586 {
|
||||
break;
|
||||
}
|
||||
#[cfg(not(feature = "build-musl"))]
|
||||
let musl_extra = MuslExtra {
|
||||
musl_fn: None,
|
||||
skip_on_i586: $skip_on_i586
|
||||
};
|
||||
|
||||
let musl_res = input.call(musl_math_sys::$fn_name as $CFn);
|
||||
let crate_res = input.call(libm::$fn_name as $RustFn);
|
||||
|
||||
let ctx = CheckCtx::new(ulp, fn_name, CheckBasis::Musl);
|
||||
crate_res.validate(musl_res, input, &ctx).context(fn_name).unwrap();
|
||||
}
|
||||
|
||||
/* Function pointers are black boxed to avoid inlining in the benchmark loop */
|
||||
|
||||
let mut group = c.benchmark_group(fn_name);
|
||||
group.bench_function("crate", |b| b.iter(|| {
|
||||
let f = black_box(libm::$fn_name as $RustFn);
|
||||
for input in benchvec.iter().copied() {
|
||||
input.call(f);
|
||||
}
|
||||
}));
|
||||
|
||||
// Don't test against musl if it is not available
|
||||
#[cfg(feature = "build-musl")]
|
||||
group.bench_function("musl", |b| b.iter(|| {
|
||||
let f = black_box(musl_math_sys::$fn_name as $CFn);
|
||||
for input in benchvec.iter().copied() {
|
||||
input.call(f);
|
||||
}
|
||||
}));
|
||||
bench_one::<Op>(c, musl_extra);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn bench_one<Op>(c: &mut Criterion, musl_extra: MuslExtra<Op::CFn>)
|
||||
where
|
||||
Op: MathOp,
|
||||
CachedInput: GenerateInput<Op::RustArgs>,
|
||||
{
|
||||
let name = Op::NAME_STR;
|
||||
|
||||
let ulp = libm_test::musl_allowed_ulp(name);
|
||||
let ctx = CheckCtx::new(ulp, name, CheckBasis::Musl);
|
||||
let benchvec: Vec<_> =
|
||||
random::get_test_cases::<Op::RustArgs>(&ctx).take(BENCH_ITER_ITEMS).collect();
|
||||
|
||||
// Perform a sanity check that we are benchmarking the same thing
|
||||
// Don't test against musl if it is not available
|
||||
#[cfg(feature = "build-musl")]
|
||||
for input in benchvec.iter().copied() {
|
||||
use anyhow::Context;
|
||||
use libm_test::CheckOutput;
|
||||
|
||||
if cfg!(x86_no_sse) && musl_extra.skip_on_i586 {
|
||||
break;
|
||||
}
|
||||
|
||||
let musl_res = input.call(musl_extra.musl_fn.unwrap());
|
||||
let crate_res = input.call(Op::ROUTINE);
|
||||
|
||||
crate_res.validate(musl_res, input, &ctx).context(name).unwrap();
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "build-musl"))]
|
||||
let _ = musl_extra; // silence unused warnings
|
||||
|
||||
/* Option pointers are black boxed to avoid inlining in the benchmark loop */
|
||||
|
||||
let mut group = c.benchmark_group(name);
|
||||
group.bench_function("crate", |b| {
|
||||
b.iter(|| {
|
||||
let f = black_box(Op::ROUTINE);
|
||||
for input in benchvec.iter().copied() {
|
||||
input.call(f);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Don't test against musl if it is not available
|
||||
#[cfg(feature = "build-musl")]
|
||||
{
|
||||
let musl_fn = musl_extra.musl_fn.unwrap();
|
||||
group.bench_function("musl", |b| {
|
||||
b.iter(|| {
|
||||
let f = black_box(musl_fn);
|
||||
for input in benchvec.iter().copied() {
|
||||
input.call(f);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
libm_macros::for_each_function! {
|
||||
callback: musl_rand_benches,
|
||||
skip: [],
|
||||
|
|
@ -83,12 +114,6 @@ libm_macros::for_each_function! {
|
|||
macro_rules! run_callback {
|
||||
(
|
||||
fn_name: $fn_name:ident,
|
||||
CFn: $_CFn:ty,
|
||||
CArgs: $_CArgs:ty,
|
||||
CRet: $_CRet:ty,
|
||||
RustFn: $_RustFn:ty,
|
||||
RustArgs: $_RustArgs:ty,
|
||||
RustRet: $_RustRet:ty,
|
||||
extra: [$criterion:ident],
|
||||
) => {
|
||||
paste::paste! {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
pub mod gen;
|
||||
#[cfg(feature = "test-multiprecision")]
|
||||
pub mod mpfloat;
|
||||
pub mod op;
|
||||
mod precision;
|
||||
mod test_traits;
|
||||
|
||||
pub use libm::support::{Float, Int};
|
||||
pub use op::{BaseName, MathOp, Name};
|
||||
pub use precision::{MaybeOverride, SpecialCase, multiprec_allowed_ulp, musl_allowed_ulp};
|
||||
pub use test_traits::{CheckBasis, CheckCtx, CheckOutput, GenerateInput, Hex, TupleCall};
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ pub use rug::Float as MpFloat;
|
|||
use rug::float::Round::Nearest;
|
||||
use rug::ops::{PowAssignRound, RemAssignRound};
|
||||
|
||||
use crate::Float;
|
||||
use crate::{Float, MathOp};
|
||||
|
||||
/// Create a multiple-precision float with the correct number of bits for a concrete float type.
|
||||
fn new_mpfloat<F: Float>() -> MpFloat {
|
||||
|
|
@ -29,23 +29,19 @@ where
|
|||
|
||||
/// Structures that represent a float operation.
|
||||
///
|
||||
/// The struct itself should hold any context that can be reused among calls to `run` (allocated
|
||||
/// `MpFloat`s).
|
||||
pub trait MpOp {
|
||||
/// Inputs to the operation (concrete float types).
|
||||
type Input;
|
||||
|
||||
/// Outputs from the operation (concrete float types).
|
||||
type Output;
|
||||
pub trait MpOp: MathOp {
|
||||
/// The struct itself should hold any context that can be reused among calls to `run` (allocated
|
||||
/// `MpFloat`s).
|
||||
type MpTy;
|
||||
|
||||
/// Create a new instance.
|
||||
fn new() -> Self;
|
||||
fn new_mp() -> Self::MpTy;
|
||||
|
||||
/// Perform the operation.
|
||||
///
|
||||
/// Usually this means assigning inputs to cached floats, performing the operation, applying
|
||||
/// subnormal approximation, and converting the result back to concrete values.
|
||||
fn run(&mut self, input: Self::Input) -> Self::Output;
|
||||
fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet;
|
||||
}
|
||||
|
||||
/// Implement `MpOp` for functions with a single return value.
|
||||
|
|
@ -53,32 +49,21 @@ macro_rules! impl_mp_op {
|
|||
// Matcher for unary functions
|
||||
(
|
||||
fn_name: $fn_name:ident,
|
||||
CFn: $CFn:ty,
|
||||
CArgs: $CArgs:ty,
|
||||
CRet: $CRet:ty,
|
||||
RustFn: fn($fty:ty,) -> $_ret:ty,
|
||||
RustArgs: $RustArgs:ty,
|
||||
RustRet: $RustRet:ty,
|
||||
RustFn: fn($_fty:ty,) -> $_ret:ty,
|
||||
fn_extra: $fn_name_normalized:expr,
|
||||
) => {
|
||||
paste::paste! {
|
||||
pub mod $fn_name {
|
||||
use super::*;
|
||||
pub struct Operation(MpFloat);
|
||||
impl MpOp for crate::op::$fn_name::Routine {
|
||||
type MpTy = MpFloat;
|
||||
|
||||
impl MpOp for Operation {
|
||||
type Input = $RustArgs;
|
||||
type Output = $RustRet;
|
||||
fn new_mp() -> Self::MpTy {
|
||||
new_mpfloat::<Self::FTy>()
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
Self(new_mpfloat::<$fty>())
|
||||
}
|
||||
|
||||
fn run(&mut self, input: Self::Input) -> Self::Output {
|
||||
self.0.assign(input.0);
|
||||
let ord = self.0.[< $fn_name_normalized _round >](Nearest);
|
||||
prep_retval::<Self::Output>(&mut self.0, ord)
|
||||
}
|
||||
fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
|
||||
this.assign(input.0);
|
||||
let ord = this.[< $fn_name_normalized _round >](Nearest);
|
||||
prep_retval::<Self::RustRet>(this, ord)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -86,33 +71,22 @@ macro_rules! impl_mp_op {
|
|||
// Matcher for binary functions
|
||||
(
|
||||
fn_name: $fn_name:ident,
|
||||
CFn: $CFn:ty,
|
||||
CArgs: $CArgs:ty,
|
||||
CRet: $CRet:ty,
|
||||
RustFn: fn($fty:ty, $_fty2:ty,) -> $_ret:ty,
|
||||
RustArgs: $RustArgs:ty,
|
||||
RustRet: $RustRet:ty,
|
||||
RustFn: fn($_fty:ty, $_fty2:ty,) -> $_ret:ty,
|
||||
fn_extra: $fn_name_normalized:expr,
|
||||
) => {
|
||||
paste::paste! {
|
||||
pub mod $fn_name {
|
||||
use super::*;
|
||||
pub struct Operation(MpFloat, MpFloat);
|
||||
impl MpOp for crate::op::$fn_name::Routine {
|
||||
type MpTy = (MpFloat, MpFloat);
|
||||
|
||||
impl MpOp for Operation {
|
||||
type Input = $RustArgs;
|
||||
type Output = $RustRet;
|
||||
fn new_mp() -> Self::MpTy {
|
||||
(new_mpfloat::<Self::FTy>(), new_mpfloat::<Self::FTy>())
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
Self(new_mpfloat::<$fty>(), new_mpfloat::<$fty>())
|
||||
}
|
||||
|
||||
fn run(&mut self, input: Self::Input) -> Self::Output {
|
||||
self.0.assign(input.0);
|
||||
self.1.assign(input.1);
|
||||
let ord = self.0.[< $fn_name_normalized _round >](&self.1, Nearest);
|
||||
prep_retval::<Self::Output>(&mut self.0, ord)
|
||||
}
|
||||
fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
|
||||
this.0.assign(input.0);
|
||||
this.1.assign(input.1);
|
||||
let ord = this.0.[< $fn_name_normalized _round >](&this.1, Nearest);
|
||||
prep_retval::<Self::RustRet>(&mut this.0, ord)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -120,34 +94,27 @@ macro_rules! impl_mp_op {
|
|||
// Matcher for ternary functions
|
||||
(
|
||||
fn_name: $fn_name:ident,
|
||||
CFn: $CFn:ty,
|
||||
CArgs: $CArgs:ty,
|
||||
CRet: $CRet:ty,
|
||||
RustFn: fn($fty:ty, $_fty2:ty, $_fty3:ty,) -> $_ret:ty,
|
||||
RustArgs: $RustArgs:ty,
|
||||
RustRet: $RustRet:ty,
|
||||
RustFn: fn($_fty:ty, $_fty2:ty, $_fty3:ty,) -> $_ret:ty,
|
||||
fn_extra: $fn_name_normalized:expr,
|
||||
) => {
|
||||
paste::paste! {
|
||||
pub mod $fn_name {
|
||||
use super::*;
|
||||
pub struct Operation(MpFloat, MpFloat, MpFloat);
|
||||
impl MpOp for crate::op::$fn_name::Routine {
|
||||
type MpTy = (MpFloat, MpFloat, MpFloat);
|
||||
|
||||
impl MpOp for Operation {
|
||||
type Input = $RustArgs;
|
||||
type Output = $RustRet;
|
||||
fn new_mp() -> Self::MpTy {
|
||||
(
|
||||
new_mpfloat::<Self::FTy>(),
|
||||
new_mpfloat::<Self::FTy>(),
|
||||
new_mpfloat::<Self::FTy>(),
|
||||
)
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
Self(new_mpfloat::<$fty>(), new_mpfloat::<$fty>(), new_mpfloat::<$fty>())
|
||||
}
|
||||
|
||||
fn run(&mut self, input: Self::Input) -> Self::Output {
|
||||
self.0.assign(input.0);
|
||||
self.1.assign(input.1);
|
||||
self.2.assign(input.2);
|
||||
let ord = self.0.[< $fn_name_normalized _round >](&self.1, &self.2, Nearest);
|
||||
prep_retval::<Self::Output>(&mut self.0, ord)
|
||||
}
|
||||
fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
|
||||
this.0.assign(input.0);
|
||||
this.1.assign(input.1);
|
||||
this.2.assign(input.2);
|
||||
let ord = this.0.[< $fn_name_normalized _round >](&this.1, &this.2, Nearest);
|
||||
prep_retval::<Self::RustRet>(&mut this.0, ord)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -156,6 +123,7 @@ macro_rules! impl_mp_op {
|
|||
|
||||
libm_macros::for_each_function! {
|
||||
callback: impl_mp_op,
|
||||
emit_types: [RustFn],
|
||||
skip: [
|
||||
// Most of these need a manual implementation
|
||||
fabs, ceil, copysign, floor, rint, round, trunc,
|
||||
|
|
@ -186,29 +154,23 @@ macro_rules! impl_no_round {
|
|||
($($fn_name:ident, $rug_name:ident;)*) => {
|
||||
paste::paste! {
|
||||
// Implement for both f32 and f64
|
||||
$( impl_no_round!{ @inner_unary [< $fn_name f >], (f32,), $rug_name } )*
|
||||
$( impl_no_round!{ @inner_unary $fn_name, (f64,), $rug_name } )*
|
||||
$( impl_no_round!{ @inner_unary [< $fn_name f >], $rug_name } )*
|
||||
$( impl_no_round!{ @inner_unary $fn_name, $rug_name } )*
|
||||
}
|
||||
};
|
||||
|
||||
(@inner_unary $fn_name:ident, ($fty:ty,), $rug_name:ident) => {
|
||||
pub mod $fn_name {
|
||||
use super::*;
|
||||
pub struct Operation(MpFloat);
|
||||
(@inner_unary $fn_name:ident, $rug_name:ident) => {
|
||||
impl MpOp for crate::op::$fn_name::Routine {
|
||||
type MpTy = MpFloat;
|
||||
|
||||
impl MpOp for Operation {
|
||||
type Input = ($fty,);
|
||||
type Output = $fty;
|
||||
fn new_mp() -> Self::MpTy {
|
||||
new_mpfloat::<Self::FTy>()
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
Self(new_mpfloat::<$fty>())
|
||||
}
|
||||
|
||||
fn run(&mut self, input: Self::Input) -> Self::Output {
|
||||
self.0.assign(input.0);
|
||||
self.0.$rug_name();
|
||||
prep_retval::<Self::Output>(&mut self.0, Ordering::Equal)
|
||||
}
|
||||
fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
|
||||
this.assign(input.0);
|
||||
this.$rug_name();
|
||||
prep_retval::<Self::RustRet>(this, Ordering::Equal)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -227,132 +189,81 @@ impl_no_round! {
|
|||
macro_rules! impl_op_for_ty {
|
||||
($fty:ty, $suffix:literal) => {
|
||||
paste::paste! {
|
||||
pub mod [<copysign $suffix>] {
|
||||
use super::*;
|
||||
pub struct Operation(MpFloat, MpFloat);
|
||||
impl MpOp for crate::op::[<copysign $suffix>]::Routine {
|
||||
type MpTy = (MpFloat, MpFloat);
|
||||
|
||||
impl MpOp for Operation {
|
||||
type Input = ($fty, $fty);
|
||||
type Output = $fty;
|
||||
fn new_mp() -> Self::MpTy {
|
||||
(new_mpfloat::<Self::FTy>(), new_mpfloat::<Self::FTy>())
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
Self(new_mpfloat::<$fty>(), new_mpfloat::<$fty>())
|
||||
}
|
||||
|
||||
fn run(&mut self, input: Self::Input) -> Self::Output {
|
||||
self.0.assign(input.0);
|
||||
self.1.assign(input.1);
|
||||
self.0.copysign_mut(&self.1);
|
||||
prep_retval::<Self::Output>(&mut self.0, Ordering::Equal)
|
||||
}
|
||||
fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
|
||||
this.0.assign(input.0);
|
||||
this.1.assign(input.1);
|
||||
this.0.copysign_mut(&this.1);
|
||||
prep_retval::<Self::RustRet>(&mut this.0, Ordering::Equal)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod [<pow $suffix>] {
|
||||
use super::*;
|
||||
pub struct Operation(MpFloat, MpFloat);
|
||||
impl MpOp for crate::op::[<pow $suffix>]::Routine {
|
||||
type MpTy = (MpFloat, MpFloat);
|
||||
|
||||
impl MpOp for Operation {
|
||||
type Input = ($fty, $fty);
|
||||
type Output = $fty;
|
||||
fn new_mp() -> Self::MpTy {
|
||||
(new_mpfloat::<Self::FTy>(), new_mpfloat::<Self::FTy>())
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
Self(new_mpfloat::<$fty>(), new_mpfloat::<$fty>())
|
||||
}
|
||||
|
||||
fn run(&mut self, input: Self::Input) -> Self::Output {
|
||||
self.0.assign(input.0);
|
||||
self.1.assign(input.1);
|
||||
let ord = self.0.pow_assign_round(&self.1, Nearest);
|
||||
prep_retval::<Self::Output>(&mut self.0, ord)
|
||||
}
|
||||
fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
|
||||
this.0.assign(input.0);
|
||||
this.1.assign(input.1);
|
||||
let ord = this.0.pow_assign_round(&this.1, Nearest);
|
||||
prep_retval::<Self::RustRet>(&mut this.0, ord)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod [<fmod $suffix>] {
|
||||
use super::*;
|
||||
pub struct Operation(MpFloat, MpFloat);
|
||||
impl MpOp for crate::op::[<fmod $suffix>]::Routine {
|
||||
type MpTy = (MpFloat, MpFloat);
|
||||
|
||||
impl MpOp for Operation {
|
||||
type Input = ($fty, $fty);
|
||||
type Output = $fty;
|
||||
fn new_mp() -> Self::MpTy {
|
||||
(new_mpfloat::<Self::FTy>(), new_mpfloat::<Self::FTy>())
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
Self(new_mpfloat::<$fty>(), new_mpfloat::<$fty>())
|
||||
}
|
||||
|
||||
fn run(&mut self, input: Self::Input) -> Self::Output {
|
||||
self.0.assign(input.0);
|
||||
self.1.assign(input.1);
|
||||
let ord = self.0.rem_assign_round(&self.1, Nearest);
|
||||
prep_retval::<Self::Output>(&mut self.0, ord)
|
||||
}
|
||||
fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
|
||||
this.0.assign(input.0);
|
||||
this.1.assign(input.1);
|
||||
let ord = this.0.rem_assign_round(&this.1, Nearest);
|
||||
prep_retval::<Self::RustRet>(&mut this.0, ord)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod [<lgamma_r $suffix>] {
|
||||
use super::*;
|
||||
pub struct Operation(MpFloat);
|
||||
impl MpOp for crate::op::[<jn $suffix>]::Routine {
|
||||
type MpTy = (i32, MpFloat);
|
||||
|
||||
impl MpOp for Operation {
|
||||
type Input = ($fty,);
|
||||
type Output = ($fty, i32);
|
||||
fn new_mp() -> Self::MpTy {
|
||||
(0, new_mpfloat::<Self::FTy>())
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
Self(new_mpfloat::<$fty>())
|
||||
}
|
||||
|
||||
fn run(&mut self, input: Self::Input) -> Self::Output {
|
||||
self.0.assign(input.0);
|
||||
let (sign, ord) = self.0.ln_abs_gamma_round(Nearest);
|
||||
let ret = prep_retval::<$fty>(&mut self.0, ord);
|
||||
(ret, sign as i32)
|
||||
}
|
||||
fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
|
||||
this.0 = input.0;
|
||||
this.1.assign(input.1);
|
||||
let ord = this.1.jn_round(this.0, Nearest);
|
||||
prep_retval::<Self::FTy>(&mut this.1, ord)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod [<jn $suffix>] {
|
||||
use super::*;
|
||||
pub struct Operation(i32, MpFloat);
|
||||
impl MpOp for crate::op::[<sincos $suffix>]::Routine {
|
||||
type MpTy = (MpFloat, MpFloat);
|
||||
|
||||
impl MpOp for Operation {
|
||||
type Input = (i32, $fty);
|
||||
type Output = $fty;
|
||||
|
||||
fn new() -> Self {
|
||||
Self(0, new_mpfloat::<$fty>())
|
||||
}
|
||||
|
||||
fn run(&mut self, input: Self::Input) -> Self::Output {
|
||||
self.0 = input.0;
|
||||
self.1.assign(input.1);
|
||||
let ord = self.1.jn_round(self.0, Nearest);
|
||||
prep_retval::<$fty>(&mut self.1, ord)
|
||||
}
|
||||
fn new_mp() -> Self::MpTy {
|
||||
(new_mpfloat::<Self::FTy>(), new_mpfloat::<Self::FTy>())
|
||||
}
|
||||
}
|
||||
|
||||
pub mod [<sincos $suffix>] {
|
||||
use super::*;
|
||||
pub struct Operation(MpFloat, MpFloat);
|
||||
|
||||
impl MpOp for Operation {
|
||||
type Input = ($fty,);
|
||||
type Output = ($fty, $fty);
|
||||
|
||||
fn new() -> Self {
|
||||
Self(new_mpfloat::<$fty>(), new_mpfloat::<$fty>())
|
||||
}
|
||||
|
||||
fn run(&mut self, input: Self::Input) -> Self::Output {
|
||||
self.0.assign(input.0);
|
||||
self.1.assign(0.0);
|
||||
let (sord, cord) = self.0.sin_cos_round(&mut self.1, Nearest);
|
||||
(
|
||||
prep_retval::<$fty>(&mut self.0, sord),
|
||||
prep_retval::<$fty>(&mut self.1, cord)
|
||||
)
|
||||
}
|
||||
fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
|
||||
this.0.assign(input.0);
|
||||
this.1.assign(0.0);
|
||||
let (sord, cord) = this.0.sin_cos_round(&mut this.1, Nearest);
|
||||
(
|
||||
prep_retval::<Self::FTy>(&mut this.0, sord),
|
||||
prep_retval::<Self::FTy>(&mut this.1, cord)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -362,7 +273,33 @@ macro_rules! impl_op_for_ty {
|
|||
impl_op_for_ty!(f32, "f");
|
||||
impl_op_for_ty!(f64, "");
|
||||
|
||||
// Account for `lgamma_r` not having a simple `f` suffix
|
||||
pub mod lgammaf_r {
|
||||
pub use super::lgamma_rf::*;
|
||||
// `lgamma_r` is not a simple suffix so we can't use the above macro.
|
||||
impl MpOp for crate::op::lgamma_r::Routine {
|
||||
type MpTy = MpFloat;
|
||||
|
||||
fn new_mp() -> Self::MpTy {
|
||||
new_mpfloat::<Self::FTy>()
|
||||
}
|
||||
|
||||
fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
|
||||
this.assign(input.0);
|
||||
let (sign, ord) = this.ln_abs_gamma_round(Nearest);
|
||||
let ret = prep_retval::<Self::FTy>(this, ord);
|
||||
(ret, sign as i32)
|
||||
}
|
||||
}
|
||||
|
||||
impl MpOp for crate::op::lgammaf_r::Routine {
|
||||
type MpTy = MpFloat;
|
||||
|
||||
fn new_mp() -> Self::MpTy {
|
||||
new_mpfloat::<Self::FTy>()
|
||||
}
|
||||
|
||||
fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
|
||||
this.assign(input.0);
|
||||
let (sign, ord) = this.ln_abs_gamma_round(Nearest);
|
||||
let ret = prep_retval::<Self::FTy>(this, ord);
|
||||
(ret, sign as i32)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
111
library/compiler-builtins/libm/crates/libm-test/src/op.rs
Normal file
111
library/compiler-builtins/libm/crates/libm-test/src/op.rs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
//! Types representing individual functions.
|
||||
//!
|
||||
//! Each routine gets a module with its name, e.g. `mod sinf { /* ... */ }`. The module
|
||||
//! contains a unit struct `Routine` which implements `MathOp`.
|
||||
//!
|
||||
//! Basically everything could be called a "function" here, so we loosely use the following
|
||||
//! terminology:
|
||||
//!
|
||||
//! - "Function": the math operation that does not have an associated precision. E.g. `f(x) = e^x`,
|
||||
//! `f(x) = log(x)`.
|
||||
//! - "Routine": A code implementation of a math operation with a specific precision. E.g. `exp`,
|
||||
//! `expf`, `expl`, `log`, `logf`.
|
||||
//! - "Operation" / "Op": Something that relates a routine to a function or is otherwise higher
|
||||
//! level. `Op` is also used as the name for generic parameters since it is terse.
|
||||
|
||||
use crate::{CheckOutput, Float, TupleCall};
|
||||
|
||||
/// An enum representing each possible routine name.
|
||||
#[libm_macros::function_enum(BaseName)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Name {}
|
||||
|
||||
/// The name without any type specifier, e.g. `sin` and `sinf` both become `sin`.
|
||||
#[libm_macros::base_name_enum]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum BaseName {}
|
||||
|
||||
/// Attributes ascribed to a `libm` routine including signature, type information,
|
||||
/// and naming.
|
||||
pub trait MathOp {
|
||||
/// The float type used for this operation.
|
||||
type FTy: Float;
|
||||
|
||||
/// The function type representing the signature in a C library.
|
||||
type CFn: Copy;
|
||||
|
||||
/// Arguments passed to the C library function as a tuple. These may include `&mut` return
|
||||
/// values.
|
||||
type CArgs<'a>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
/// The type returned by C implementations.
|
||||
type CRet;
|
||||
|
||||
/// The signature of the Rust function as a `fn(...) -> ...` type.
|
||||
type RustFn: Copy;
|
||||
|
||||
/// Arguments passed to the Rust library function as a tuple.
|
||||
///
|
||||
/// The required `TupleCall` bounds ensure this type can be passed either to the C function or
|
||||
/// to the Rust function.
|
||||
type RustArgs: Copy
|
||||
+ TupleCall<Self::RustFn, Output = Self::RustRet>
|
||||
+ TupleCall<Self::CFn, Output = Self::RustRet>;
|
||||
|
||||
/// Type returned from the Rust function.
|
||||
type RustRet: CheckOutput<Self::RustArgs>;
|
||||
|
||||
/// The name of this function, including suffix (e.g. `sin`, `sinf`).
|
||||
const NAME: Name;
|
||||
|
||||
/// The name as a string.
|
||||
const NAME_STR: &'static str = Self::NAME.as_str();
|
||||
|
||||
/// The name of the function excluding the type suffix, e.g. `sin` and `sinf` are both `sin`.
|
||||
const BASE_NAME: BaseName = Self::NAME.base_name();
|
||||
|
||||
/// The function in `libm` which can be called.
|
||||
const ROUTINE: Self::RustFn;
|
||||
}
|
||||
|
||||
macro_rules! do_thing {
|
||||
// Matcher for unary functions
|
||||
(
|
||||
fn_name: $fn_name:ident,
|
||||
FTy: $FTy:ty,
|
||||
CFn: $CFn:ty,
|
||||
CArgs: $CArgs:ty,
|
||||
CRet: $CRet:ty,
|
||||
RustFn: $RustFn:ty,
|
||||
RustArgs: $RustArgs:ty,
|
||||
RustRet: $RustRet:ty,
|
||||
) => {
|
||||
paste::paste! {
|
||||
pub mod $fn_name {
|
||||
use super::*;
|
||||
pub struct Routine;
|
||||
|
||||
impl MathOp for Routine {
|
||||
type FTy = $FTy;
|
||||
type CFn = for<'a> $CFn;
|
||||
type CArgs<'a> = $CArgs where Self: 'a;
|
||||
type CRet = $CRet;
|
||||
type RustFn = $RustFn;
|
||||
type RustArgs = $RustArgs;
|
||||
type RustRet = $RustRet;
|
||||
|
||||
const NAME: Name = Name::[< $fn_name:camel >];
|
||||
const ROUTINE: Self::RustFn = libm::$fn_name;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
libm_macros::for_each_function! {
|
||||
callback: do_thing,
|
||||
emit_types: all,
|
||||
}
|
||||
|
|
@ -137,7 +137,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<T1, T2, T3> TupleCall<fn(T1, &mut T2, &mut T3)> for (T1,)
|
||||
impl<T1, T2, T3> TupleCall<for<'a> fn(T1, &'a mut T2, &'a mut T3)> for (T1,)
|
||||
where
|
||||
T1: fmt::Debug,
|
||||
T2: fmt::Debug + Default,
|
||||
|
|
@ -145,7 +145,7 @@ where
|
|||
{
|
||||
type Output = (T2, T3);
|
||||
|
||||
fn call(self, f: fn(T1, &mut T2, &mut T3)) -> Self::Output {
|
||||
fn call(self, f: for<'a> fn(T1, &'a mut T2, &'a mut T3)) -> Self::Output {
|
||||
let mut t2 = T2::default();
|
||||
let mut t3 = T3::default();
|
||||
f(self.0, &mut t2, &mut t3);
|
||||
|
|
|
|||
|
|
@ -22,12 +22,6 @@ const ALLOWED_SKIPS: &[&str] = &[
|
|||
macro_rules! callback {
|
||||
(
|
||||
fn_name: $name:ident,
|
||||
CFn: $_CFn:ty,
|
||||
CArgs: $_CArgs:ty,
|
||||
CRet: $_CRet:ty,
|
||||
RustFn: $_RustFn:ty,
|
||||
RustArgs: $_RustArgs:ty,
|
||||
RustRet: $_RustRet:ty,
|
||||
extra: [$push_to:ident],
|
||||
) => {
|
||||
$push_to.push(stringify!($name));
|
||||
|
|
|
|||
|
|
@ -9,42 +9,46 @@
|
|||
// There are some targets we can't build musl for
|
||||
#![cfg(feature = "build-musl")]
|
||||
|
||||
use libm_test::gen::random;
|
||||
use libm_test::{CheckBasis, CheckCtx, CheckOutput, TupleCall, musl_allowed_ulp};
|
||||
use musl_math_sys as musl;
|
||||
use libm_test::gen::{CachedInput, random};
|
||||
use libm_test::{
|
||||
CheckBasis, CheckCtx, CheckOutput, GenerateInput, MathOp, TupleCall, musl_allowed_ulp,
|
||||
};
|
||||
|
||||
macro_rules! musl_rand_tests {
|
||||
(
|
||||
fn_name: $fn_name:ident,
|
||||
CFn: $CFn:ty,
|
||||
CArgs: $CArgs:ty,
|
||||
CRet: $CRet:ty,
|
||||
RustFn: $RustFn:ty,
|
||||
RustArgs: $RustArgs:ty,
|
||||
RustRet: $RustRet:ty,
|
||||
attrs: [$($meta:meta)*]
|
||||
) => { paste::paste! {
|
||||
#[test]
|
||||
$(#[$meta])*
|
||||
fn [< musl_random_ $fn_name >]() {
|
||||
let fname = stringify!($fn_name);
|
||||
let ulp = musl_allowed_ulp(fname);
|
||||
let ctx = CheckCtx::new(ulp, fname, CheckBasis::Musl);
|
||||
let cases = random::get_test_cases::<$RustArgs>(&ctx);
|
||||
|
||||
for input in cases {
|
||||
let musl_res = input.call(musl::$fn_name as $CFn);
|
||||
let crate_res = input.call(libm::$fn_name as $RustFn);
|
||||
|
||||
crate_res.validate(musl_res, input, &ctx).unwrap();
|
||||
) => {
|
||||
paste::paste! {
|
||||
#[test]
|
||||
$(#[$meta])*
|
||||
fn [< musl_random_ $fn_name >]() {
|
||||
test_one::<libm_test::op::$fn_name::Routine>(musl_math_sys::$fn_name);
|
||||
}
|
||||
}
|
||||
} };
|
||||
};
|
||||
}
|
||||
|
||||
fn test_one<Op>(musl_fn: Op::CFn)
|
||||
where
|
||||
Op: MathOp,
|
||||
CachedInput: GenerateInput<Op::RustArgs>,
|
||||
{
|
||||
let name = Op::NAME_STR;
|
||||
let ulp = musl_allowed_ulp(name);
|
||||
let ctx = CheckCtx::new(ulp, name, CheckBasis::Musl);
|
||||
let cases = random::get_test_cases::<Op::RustArgs>(&ctx);
|
||||
|
||||
for input in cases {
|
||||
let musl_res = input.call(musl_fn);
|
||||
let crate_res = input.call(Op::ROUTINE);
|
||||
|
||||
crate_res.validate(musl_res, input, &ctx).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
libm_macros::for_each_function! {
|
||||
callback: musl_rand_tests,
|
||||
skip: [],
|
||||
attributes: [
|
||||
#[cfg_attr(x86_no_sse, ignore)] // FIXME(correctness): wrong result on i586
|
||||
[exp10, exp10f, exp2, exp2f, rint]
|
||||
|
|
|
|||
|
|
@ -2,45 +2,48 @@
|
|||
|
||||
#![cfg(feature = "test-multiprecision")]
|
||||
|
||||
use libm_test::gen::random;
|
||||
use libm_test::mpfloat::{self, MpOp};
|
||||
use libm_test::{CheckBasis, CheckCtx, CheckOutput, TupleCall, multiprec_allowed_ulp};
|
||||
use libm_test::gen::{CachedInput, random};
|
||||
use libm_test::mpfloat::MpOp;
|
||||
use libm_test::{
|
||||
CheckBasis, CheckCtx, CheckOutput, GenerateInput, MathOp, TupleCall, multiprec_allowed_ulp,
|
||||
};
|
||||
|
||||
/// Implement a test against MPFR with random inputs.
|
||||
macro_rules! multiprec_rand_tests {
|
||||
(
|
||||
fn_name: $fn_name:ident,
|
||||
CFn: $CFn:ty,
|
||||
CArgs: $CArgs:ty,
|
||||
CRet: $CRet:ty,
|
||||
RustFn: $RustFn:ty,
|
||||
RustArgs: $RustArgs:ty,
|
||||
RustRet: $RustRet:ty,
|
||||
attrs: [$($meta:meta)*]
|
||||
) => {
|
||||
paste::paste! {
|
||||
#[test]
|
||||
$(#[$meta])*
|
||||
fn [< multiprec_random_ $fn_name >]() {
|
||||
type MpOpTy = mpfloat::$fn_name::Operation;
|
||||
|
||||
let fname = stringify!($fn_name);
|
||||
let ulp = multiprec_allowed_ulp(fname);
|
||||
let mut mp_vals = MpOpTy::new();
|
||||
let ctx = CheckCtx::new(ulp, fname, CheckBasis::Mpfr);
|
||||
let cases = random::get_test_cases::<$RustArgs>(&ctx);
|
||||
|
||||
for input in cases {
|
||||
let mp_res = mp_vals.run(input);
|
||||
let crate_res = input.call(libm::$fn_name as $RustFn);
|
||||
|
||||
crate_res.validate(mp_res, input, &ctx).unwrap();
|
||||
}
|
||||
test_one::<libm_test::op::$fn_name::Routine>();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn test_one<Op>()
|
||||
where
|
||||
Op: MathOp + MpOp,
|
||||
CachedInput: GenerateInput<Op::RustArgs>,
|
||||
{
|
||||
let name = Op::NAME_STR;
|
||||
|
||||
let ulp = multiprec_allowed_ulp(name);
|
||||
let mut mp_vals = Op::new_mp();
|
||||
let ctx = CheckCtx::new(ulp, name, CheckBasis::Mpfr);
|
||||
let cases = random::get_test_cases::<Op::RustArgs>(&ctx);
|
||||
|
||||
for input in cases {
|
||||
let mp_res = Op::run(&mut mp_vals, input);
|
||||
let crate_res = input.call(Op::ROUTINE);
|
||||
|
||||
crate_res.validate(mp_res, input, &ctx).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
libm_macros::for_each_function! {
|
||||
callback: multiprec_rand_tests,
|
||||
attributes: [
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue