Merge pull request rust-lang/libm#349 from tgross35/better-typing

Migrate types from a macro to a trait
This commit is contained in:
Trevor Gross 2024-11-02 16:44:59 -05:00 committed by GitHub
commit d715142b4d
14 changed files with 741 additions and 374 deletions

View file

@ -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"] }

View 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(())
}
}

View file

@ -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),
}
}

View file

@ -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;

View file

@ -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],
}
}

View file

@ -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);
}

View file

@ -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! {

View file

@ -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};

View file

@ -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)
}
}

View 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,
}

View file

@ -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);

View file

@ -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));

View file

@ -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]

View file

@ -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: [