diff --git a/library/compiler-builtins/libm/crates/libm-macros/Cargo.toml b/library/compiler-builtins/libm/crates/libm-macros/Cargo.toml index 3da9d45a2b9d..c9defb1c5cbb 100644 --- a/library/compiler-builtins/libm/crates/libm-macros/Cargo.toml +++ b/library/compiler-builtins/libm/crates/libm-macros/Cargo.toml @@ -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"] } diff --git a/library/compiler-builtins/libm/crates/libm-macros/src/enums.rs b/library/compiler-builtins/libm/crates/libm-macros/src/enums.rs new file mode 100644 index 000000000000..d9017dff72bc --- /dev/null +++ b/library/compiler-builtins/libm/crates/libm-macros/src/enums.rs @@ -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 { + 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 { + 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(()) + } +} diff --git a/library/compiler-builtins/libm/crates/libm-macros/src/lib.rs b/library/compiler-builtins/libm/crates/libm-macros/src/lib.rs index 41d13035cf47..2db412e79a1d 100644 --- a/library/compiler-builtins/libm/crates/libm-macros/src/lib.rs +++ b/library/compiler-builtins/libm/crates/libm-macros/src/lib.rs @@ -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, &[&str])] = &[ +const ALL_FUNCTIONS: &[(Ty, Signature, Option, &[&str])] = &[ ( // `fn(f32) -> f32` + Ty::F32, Signature { args: &[Ty::F32], returns: &[Ty::F32] }, None, &[ @@ -22,6 +26,7 @@ const ALL_FUNCTIONS: &[(Signature, Option, &[&str])] = &[ ), ( // `(f64) -> f64` + Ty::F64, Signature { args: &[Ty::F64], returns: &[Ty::F64] }, None, &[ @@ -33,6 +38,7 @@ const ALL_FUNCTIONS: &[(Signature, Option, &[&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, &[&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, &[&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> = 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> = 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> = 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> = 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> = 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> { +fn validate(input: &mut StructuredInput) -> syn::Result> { // 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> 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::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), + } +} diff --git a/library/compiler-builtins/libm/crates/libm-macros/src/parse.rs b/library/compiler-builtins/libm/crates/libm-macros/src/parse.rs index ee9bd524bbef..369bbae2f4fa 100644 --- a/library/compiler-builtins/libm/crates/libm-macros/src/parse.rs +++ b/library/compiler-builtins/libm/crates/libm-macros/src/parse.rs @@ -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, /// Skip these functions pub skip: Vec, /// 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>, // For diagnostics + pub emit_types_span: Option, pub only_span: Option, pub fn_extra_span: Option, } @@ -58,6 +62,7 @@ impl StructuredInput { pub fn from_fields(input: Invocation) -> syn::Result { 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 { 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> { + 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> { let content; diff --git a/library/compiler-builtins/libm/crates/libm-macros/tests/basic.rs b/library/compiler-builtins/libm/crates/libm-macros/tests/basic.rs index 8f8c09f1b64a..2eaba04f4c67 100644 --- a/library/compiler-builtins/libm/crates/libm-macros/tests/basic.rs +++ b/library/compiler-builtins/libm/crates/libm-macros/tests/basic.rs @@ -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], + } +} diff --git a/library/compiler-builtins/libm/crates/libm-macros/tests/enum.rs b/library/compiler-builtins/libm/crates/libm-macros/tests/enum.rs new file mode 100644 index 000000000000..884b8d8d6518 --- /dev/null +++ b/library/compiler-builtins/libm/crates/libm-macros/tests/enum.rs @@ -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); +} diff --git a/library/compiler-builtins/libm/crates/libm-test/benches/random.rs b/library/compiler-builtins/libm/crates/libm-test/benches/random.rs index 6c9047c3cdb4..6f2305dd27eb 100644 --- a/library/compiler-builtins/libm/crates/libm-test/benches/random.rs +++ b/library/compiler-builtins/libm/crates/libm-test/benches/random.rs @@ -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 { + musl_fn: Option, + 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 ::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::(c, musl_extra); } } }; } +fn bench_one(c: &mut Criterion, musl_extra: MuslExtra) +where + Op: MathOp, + CachedInput: GenerateInput, +{ + 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::(&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! { diff --git a/library/compiler-builtins/libm/crates/libm-test/src/lib.rs b/library/compiler-builtins/libm/crates/libm-test/src/lib.rs index 56a872779d6c..e64ad6264085 100644 --- a/library/compiler-builtins/libm/crates/libm-test/src/lib.rs +++ b/library/compiler-builtins/libm/crates/libm-test/src/lib.rs @@ -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}; diff --git a/library/compiler-builtins/libm/crates/libm-test/src/mpfloat.rs b/library/compiler-builtins/libm/crates/libm-test/src/mpfloat.rs index 2e6fdae7f19d..507b077b37cd 100644 --- a/library/compiler-builtins/libm/crates/libm-test/src/mpfloat.rs +++ b/library/compiler-builtins/libm/crates/libm-test/src/mpfloat.rs @@ -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() -> 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::() + } - 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::(&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::(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::(), new_mpfloat::()) + } - 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::(&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::(&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::(), + new_mpfloat::(), + new_mpfloat::(), + ) + } - 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::(&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::(&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::() + } - 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::(&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::(this, Ordering::Equal) } } }; @@ -227,132 +189,81 @@ impl_no_round! { macro_rules! impl_op_for_ty { ($fty:ty, $suffix:literal) => { paste::paste! { - pub mod [] { - use super::*; - pub struct Operation(MpFloat, MpFloat); + impl MpOp for crate::op::[]::Routine { + type MpTy = (MpFloat, MpFloat); - impl MpOp for Operation { - type Input = ($fty, $fty); - type Output = $fty; + fn new_mp() -> Self::MpTy { + (new_mpfloat::(), new_mpfloat::()) + } - 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::(&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::(&mut this.0, Ordering::Equal) } } - pub mod [] { - use super::*; - pub struct Operation(MpFloat, MpFloat); + impl MpOp for crate::op::[]::Routine { + type MpTy = (MpFloat, MpFloat); - impl MpOp for Operation { - type Input = ($fty, $fty); - type Output = $fty; + fn new_mp() -> Self::MpTy { + (new_mpfloat::(), new_mpfloat::()) + } - 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::(&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::(&mut this.0, ord) } } - pub mod [] { - use super::*; - pub struct Operation(MpFloat, MpFloat); + impl MpOp for crate::op::[]::Routine { + type MpTy = (MpFloat, MpFloat); - impl MpOp for Operation { - type Input = ($fty, $fty); - type Output = $fty; + fn new_mp() -> Self::MpTy { + (new_mpfloat::(), new_mpfloat::()) + } - 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::(&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::(&mut this.0, ord) } } - pub mod [] { - use super::*; - pub struct Operation(MpFloat); + impl MpOp for crate::op::[]::Routine { + type MpTy = (i32, MpFloat); - impl MpOp for Operation { - type Input = ($fty,); - type Output = ($fty, i32); + fn new_mp() -> Self::MpTy { + (0, new_mpfloat::()) + } - 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::(&mut this.1, ord) } } - pub mod [] { - use super::*; - pub struct Operation(i32, MpFloat); + impl MpOp for crate::op::[]::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::(), new_mpfloat::()) } - } - pub mod [] { - 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::(&mut this.0, sord), + prep_retval::(&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::() + } + + 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::(this, ord); + (ret, sign as i32) + } +} + +impl MpOp for crate::op::lgammaf_r::Routine { + type MpTy = MpFloat; + + fn new_mp() -> Self::MpTy { + new_mpfloat::() + } + + 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::(this, ord); + (ret, sign as i32) + } } diff --git a/library/compiler-builtins/libm/crates/libm-test/src/op.rs b/library/compiler-builtins/libm/crates/libm-test/src/op.rs new file mode 100644 index 000000000000..fe0a08a28e51 --- /dev/null +++ b/library/compiler-builtins/libm/crates/libm-test/src/op.rs @@ -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 + + TupleCall; + + /// Type returned from the Rust function. + type RustRet: CheckOutput; + + /// 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, +} diff --git a/library/compiler-builtins/libm/crates/libm-test/src/test_traits.rs b/library/compiler-builtins/libm/crates/libm-test/src/test_traits.rs index e69e16d24a96..b9bec9a44795 100644 --- a/library/compiler-builtins/libm/crates/libm-test/src/test_traits.rs +++ b/library/compiler-builtins/libm/crates/libm-test/src/test_traits.rs @@ -137,7 +137,7 @@ where } } -impl TupleCall for (T1,) +impl TupleCall 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); diff --git a/library/compiler-builtins/libm/crates/libm-test/tests/check_coverage.rs b/library/compiler-builtins/libm/crates/libm-test/tests/check_coverage.rs index ef6d21fdbbad..b7988660e662 100644 --- a/library/compiler-builtins/libm/crates/libm-test/tests/check_coverage.rs +++ b/library/compiler-builtins/libm/crates/libm-test/tests/check_coverage.rs @@ -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)); diff --git a/library/compiler-builtins/libm/crates/libm-test/tests/compare_built_musl.rs b/library/compiler-builtins/libm/crates/libm-test/tests/compare_built_musl.rs index 5a118f7c209d..d4ba9e90010b 100644 --- a/library/compiler-builtins/libm/crates/libm-test/tests/compare_built_musl.rs +++ b/library/compiler-builtins/libm/crates/libm-test/tests/compare_built_musl.rs @@ -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::(musl_math_sys::$fn_name); } } - } }; + }; +} + +fn test_one(musl_fn: Op::CFn) +where + Op: MathOp, + CachedInput: GenerateInput, +{ + 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::(&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] diff --git a/library/compiler-builtins/libm/crates/libm-test/tests/multiprecision.rs b/library/compiler-builtins/libm/crates/libm-test/tests/multiprecision.rs index f8d94a1609e9..676ee86a0d00 100644 --- a/library/compiler-builtins/libm/crates/libm-test/tests/multiprecision.rs +++ b/library/compiler-builtins/libm/crates/libm-test/tests/multiprecision.rs @@ -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::(); } } }; } +fn test_one() +where + Op: MathOp + MpOp, + CachedInput: GenerateInput, +{ + 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::(&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: [