diff --git a/Cargo.toml b/Cargo.toml index 98a60bfe3..bfc11509e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ arch = [] # This tells the compiler to assume that a Nightly toolchain is being used and # that it should activate any useful Nightly things accordingly. -unstable = ["unstable-intrinsics"] +unstable = ["unstable-intrinsics", "unstable-float"] # Enable calls to functions in `core::intrinsics` unstable-intrinsics = [] @@ -29,6 +29,9 @@ unstable-intrinsics = [] # Make some internal things public for testing. unstable-test-support = [] +# Enable the nightly-only `f16` and `f128`. +unstable-float = [] + # Used to prevent using any intrinsics or arch-specific code. # # HACK: this is a negative feature which is generally a bad idea in Cargo, but diff --git a/build.rs b/build.rs index 001029236..9c9e0e723 100644 --- a/build.rs +++ b/build.rs @@ -1,6 +1,10 @@ use std::env; +mod configure; + fn main() { + let cfg = configure::Config::from_env(); + println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rustc-check-cfg=cfg(assert_no_panic)"); @@ -14,29 +18,5 @@ fn main() { } } - configure_intrinsics(); - configure_arch(); -} - -/// Simplify the feature logic for enabling intrinsics so code only needs to use -/// `cfg(intrinsics_enabled)`. -fn configure_intrinsics() { - println!("cargo:rustc-check-cfg=cfg(intrinsics_enabled)"); - - // Disabled by default; `unstable-intrinsics` enables again; `force-soft-floats` overrides - // to disable. - if cfg!(feature = "unstable-intrinsics") && !cfg!(feature = "force-soft-floats") { - println!("cargo:rustc-cfg=intrinsics_enabled"); - } -} - -/// Simplify the feature logic for enabling arch-specific features so code only needs to use -/// `cfg(arch_enabled)`. -fn configure_arch() { - println!("cargo:rustc-check-cfg=cfg(arch_enabled)"); - - // Enabled by default via the "arch" feature, `force-soft-floats` overrides to disable. - if cfg!(feature = "arch") && !cfg!(feature = "force-soft-floats") { - println!("cargo:rustc-cfg=arch_enabled"); - } + configure::emit_libm_config(&cfg); } diff --git a/ci/run.sh b/ci/run.sh index d89c8bdf0..2c65db1c5 100755 --- a/ci/run.sh +++ b/ci/run.sh @@ -6,6 +6,10 @@ export RUST_BACKTRACE="${RUST_BACKTRACE:-full}" # Needed for no-panic to correct detect a lack of panics export RUSTFLAGS="${RUSTFLAGS:-} -Ccodegen-units=1" +# MPFR tests consume a lot of stack and often overflow. Default to 20 MiB (10x +# the default) +export RUST_MIN_STACK="${RUST_MIN_STACK:-20971520}" + target="${1:-}" if [ -z "$target" ]; then @@ -57,7 +61,7 @@ case "$target" in *windows-gnu) extra_flags="$extra_flags --exclude libm-macros" ;; esac -# Make sure we can build with overriding features. We test the indibidual +# Make sure we can build with overriding features. We test the individual # features it controls separately. cargo check --no-default-features cargo check --features "force-soft-floats" @@ -69,6 +73,10 @@ if [ "${BUILD_ONLY:-}" = "1" ]; then echo "can't run tests on $target; skipping" else + # Check `force-soft-floats` again, this time including test crates. + cargo check --all --target "$target" $extra_flags \ + --features "force-soft-floats" --all-targets + cmd="cargo test --all --target $target $extra_flags" # stable by default diff --git a/configure.rs b/configure.rs new file mode 100644 index 000000000..389e86c33 --- /dev/null +++ b/configure.rs @@ -0,0 +1,168 @@ +// Configuration shared with both libm and libm-test + +use std::env; +use std::path::PathBuf; + +#[allow(dead_code)] +pub struct Config { + pub manifest_dir: PathBuf, + pub out_dir: PathBuf, + pub opt_level: u8, + pub target_arch: String, + pub target_env: String, + pub target_family: Option, + pub target_os: String, + pub target_string: String, + pub target_vendor: String, + pub target_features: Vec, +} + +impl Config { + pub fn from_env() -> Self { + let target_features = env::var("CARGO_CFG_TARGET_FEATURE") + .map(|feats| feats.split(',').map(ToOwned::to_owned).collect()) + .unwrap_or_default(); + + Self { + manifest_dir: PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()), + out_dir: PathBuf::from(env::var("OUT_DIR").unwrap()), + opt_level: env::var("OPT_LEVEL").unwrap().parse().unwrap(), + target_arch: env::var("CARGO_CFG_TARGET_ARCH").unwrap(), + target_env: env::var("CARGO_CFG_TARGET_ENV").unwrap(), + target_family: env::var("CARGO_CFG_TARGET_FAMILY").ok(), + target_os: env::var("CARGO_CFG_TARGET_OS").unwrap(), + target_string: env::var("TARGET").unwrap(), + target_vendor: env::var("CARGO_CFG_TARGET_VENDOR").unwrap(), + target_features, + } + } +} + +/// Libm gets most config options made available. +#[allow(dead_code)] +pub fn emit_libm_config(cfg: &Config) { + emit_intrinsics_cfg(); + emit_arch_cfg(); + emit_optimization_cfg(cfg); + emit_cfg_shorthands(cfg); + emit_f16_f128_cfg(cfg); +} + +/// Tests don't need most feature-related config. +#[allow(dead_code)] +pub fn emit_test_config(cfg: &Config) { + emit_optimization_cfg(cfg); + emit_cfg_shorthands(cfg); + emit_f16_f128_cfg(cfg); +} + +/// Simplify the feature logic for enabling intrinsics so code only needs to use +/// `cfg(intrinsics_enabled)`. +fn emit_intrinsics_cfg() { + println!("cargo:rustc-check-cfg=cfg(intrinsics_enabled)"); + + // Disabled by default; `unstable-intrinsics` enables again; `force-soft-floats` overrides + // to disable. + if cfg!(feature = "unstable-intrinsics") && !cfg!(feature = "force-soft-floats") { + println!("cargo:rustc-cfg=intrinsics_enabled"); + } +} + +/// Simplify the feature logic for enabling arch-specific features so code only needs to use +/// `cfg(arch_enabled)`. +fn emit_arch_cfg() { + println!("cargo:rustc-check-cfg=cfg(arch_enabled)"); + + // Enabled by default via the "arch" feature, `force-soft-floats` overrides to disable. + if cfg!(feature = "arch") && !cfg!(feature = "force-soft-floats") { + println!("cargo:rustc-cfg=arch_enabled"); + } +} + +/// Some tests are extremely slow. Emit a config option based on optimization level. +fn emit_optimization_cfg(cfg: &Config) { + println!("cargo:rustc-check-cfg=cfg(optimizations_enabled)"); + + if cfg.opt_level >= 2 { + println!("cargo:rustc-cfg=optimizations_enabled"); + } +} + +/// Provide an alias for common longer config combinations. +fn emit_cfg_shorthands(cfg: &Config) { + println!("cargo:rustc-check-cfg=cfg(x86_no_sse)"); + if cfg.target_arch == "x86" && !cfg.target_features.iter().any(|f| f == "sse") { + // Shorthand to detect i586 targets + println!("cargo:rustc-cfg=x86_no_sse"); + } +} + +/// Configure whether or not `f16` and `f128` support should be enabled. +fn emit_f16_f128_cfg(cfg: &Config) { + println!("cargo:rustc-check-cfg=cfg(f16_enabled)"); + println!("cargo:rustc-check-cfg=cfg(f128_enabled)"); + + // `unstable-float` enables these features. + if !cfg!(feature = "unstable-float") { + return; + } + + // Set whether or not `f16` and `f128` are supported at a basic level by LLVM. This only means + // that the backend will not crash when using these types and generates code that can be called + // without crashing (no infinite recursion). This does not mean that the platform doesn't have + // ABI or other bugs. + // + // We do this here rather than in `rust-lang/rust` because configuring via cargo features is + // not straightforward. + // + // Original source of this list: + // + let f16_enabled = match cfg.target_arch.as_str() { + // Unsupported + "arm64ec" => false, + // Selection failure + "s390x" => false, + // Infinite recursion + // FIXME(llvm): loongarch fixed by + "csky" => false, + "hexagon" => false, + "loongarch64" => false, + "mips" | "mips64" | "mips32r6" | "mips64r6" => false, + "powerpc" | "powerpc64" => false, + "sparc" | "sparc64" => false, + "wasm32" | "wasm64" => false, + // Most everything else works as of LLVM 19 + _ => true, + }; + + let f128_enabled = match cfg.target_arch.as_str() { + // Unsupported (libcall is not supported) + "amdgpu" => false, + // Unsupported + "arm64ec" => false, + // Selection failure + "mips64" | "mips64r6" => false, + // Selection failure + "nvptx64" => false, + // Selection failure + "powerpc64" if &cfg.target_os == "aix" => false, + // Selection failure + "sparc" => false, + // Most everything else works as of LLVM 19 + _ => true, + }; + + // If the feature is set, disable these types. + let disable_both = env::var_os("CARGO_FEATURE_NO_F16_F128").is_some(); + + println!("cargo:rustc-check-cfg=cfg(f16_enabled)"); + println!("cargo:rustc-check-cfg=cfg(f128_enabled)"); + + if f16_enabled && !disable_both { + println!("cargo:rustc-cfg=f16_enabled"); + } + + if f128_enabled && !disable_both { + println!("cargo:rustc-cfg=f128_enabled"); + } +} diff --git a/crates/compiler-builtins-smoke-test/Cargo.toml b/crates/compiler-builtins-smoke-test/Cargo.toml index e75c4f42b..82cfeecb9 100644 --- a/crates/compiler-builtins-smoke-test/Cargo.toml +++ b/crates/compiler-builtins-smoke-test/Cargo.toml @@ -21,5 +21,7 @@ force-soft-floats = [] unexpected_cfgs = { level = "warn", check-cfg = [ "cfg(arch_enabled)", "cfg(assert_no_panic)", + "cfg(f128_enabled)", + "cfg(f16_enabled)", "cfg(intrinsics_enabled)", ] } diff --git a/crates/libm-macros/Cargo.toml b/crates/libm-macros/Cargo.toml index c9defb1c5..9194232b2 100644 --- a/crates/libm-macros/Cargo.toml +++ b/crates/libm-macros/Cargo.toml @@ -12,3 +12,10 @@ heck = "0.5.0" proc-macro2 = "1.0.88" quote = "1.0.37" syn = { version = "2.0.79", features = ["full", "extra-traits", "visit-mut"] } + +[lints.rust] +# Values used during testing +unexpected_cfgs = { level = "warn", check-cfg = [ + 'cfg(f16_enabled)', + 'cfg(f128_enabled)', +] } diff --git a/crates/libm-macros/src/lib.rs b/crates/libm-macros/src/lib.rs index 1e7cd08b9..198af7f5c 100644 --- a/crates/libm-macros/src/lib.rs +++ b/crates/libm-macros/src/lib.rs @@ -12,6 +12,13 @@ use syn::visit_mut::VisitMut; use syn::{Ident, ItemEnum}; const ALL_FUNCTIONS: &[(Ty, Signature, Option, &[&str])] = &[ + ( + // `fn(f16) -> f16` + Ty::F16, + Signature { args: &[Ty::F16], returns: &[Ty::F16] }, + None, + &["fabsf16"], + ), ( // `fn(f32) -> f32` Ty::F32, @@ -36,6 +43,20 @@ const ALL_FUNCTIONS: &[(Ty, Signature, Option, &[&str])] = &[ "tgamma", "trunc", ], ), + ( + // `fn(f128) -> f128` + Ty::F128, + Signature { args: &[Ty::F128], returns: &[Ty::F128] }, + None, + &["fabsf128"], + ), + ( + // `(f16, f16) -> f16` + Ty::F16, + Signature { args: &[Ty::F16, Ty::F16], returns: &[Ty::F16] }, + None, + &["copysignf16"], + ), ( // `(f32, f32) -> f32` Ty::F32, @@ -72,6 +93,13 @@ const ALL_FUNCTIONS: &[(Ty, Signature, Option, &[&str])] = &[ "remainder", ], ), + ( + // `(f128, f128) -> f128` + Ty::F128, + Signature { args: &[Ty::F128, Ty::F128], returns: &[Ty::F128] }, + None, + &["copysignf128"], + ), ( // `(f32, f32, f32) -> f32` Ty::F32, @@ -190,7 +218,7 @@ const KNOWN_TYPES: &[&str] = &["FTy", "CFn", "CArgs", "CRet", "RustFn", "RustArg /// A type used in a function signature. #[allow(dead_code)] -#[derive(Debug, Clone, Copy)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] enum Ty { F16, F32, @@ -331,7 +359,7 @@ pub fn base_name_enum(attributes: pm::TokenStream, tokens: pm::TokenStream) -> p /// // The Rust version's return type (e.g. `(f32, f32)`) /// RustRet: $RustRet:ty, /// // Attributes for the current function, if any -/// attrs: [$($meta:meta)*] +/// attrs: [$($meta:meta),*], /// // Extra tokens passed directly (if any) /// extra: [$extra:ident], /// // Extra function-tokens passed directly (if any) @@ -349,6 +377,8 @@ pub fn base_name_enum(attributes: pm::TokenStream, tokens: pm::TokenStream) -> p /// 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. +/// // Note that `f16_enabled` and `f128_enabled` will always get emitted regardless of whether +/// // or not this is specified. /// attributes: [ /// #[meta1] /// #[meta2] @@ -507,16 +537,28 @@ fn expand(input: StructuredInput, fn_list: &[&FunctionInfo]) -> syn::Result { - let meta = attrs - .iter() - .filter(|map| map.names.contains(&fn_name)) - .flat_map(|map| &map.meta); - quote! { attrs: [ #( #meta )* ] } - } - None => pm2::TokenStream::new(), - }; + let mut meta_fields = Vec::new(); + if let Some(attrs) = &input.attributes { + let meta_iter = attrs + .iter() + .filter(|map| map.names.contains(&fn_name)) + .flat_map(|map| &map.meta) + .map(|v| v.into_token_stream()); + + meta_fields.extend(meta_iter); + } + + // Always emit f16 and f128 meta so this doesn't need to be repeated everywhere + if func.rust_sig.args.contains(&Ty::F16) || func.rust_sig.returns.contains(&Ty::F16) { + let ts = quote! { cfg(f16_enabled) }; + meta_fields.push(ts); + } + if func.rust_sig.args.contains(&Ty::F128) || func.rust_sig.returns.contains(&Ty::F128) { + let ts = quote! { cfg(f128_enabled) }; + meta_fields.push(ts); + } + + let meta_field = quote! { attrs: [ #( #meta_fields ),* ], }; // Prepare extra in an `extra: ...` field, running the replacer let extra_field = match input.extra.clone() { diff --git a/crates/libm-macros/tests/basic.rs b/crates/libm-macros/tests/basic.rs index 2eaba04f4..a973ccb0d 100644 --- a/crates/libm-macros/tests/basic.rs +++ b/crates/libm-macros/tests/basic.rs @@ -1,4 +1,6 @@ // `STATUS_DLL_NOT_FOUND` on i686 MinGW, not worth looking into. +#![feature(f16)] +#![feature(f128)] #![cfg(not(all(target_arch = "x86", target_os = "windows", target_env = "gnu")))] macro_rules! basic { @@ -11,7 +13,7 @@ macro_rules! basic { RustFn: $RustFn:ty, RustArgs: $RustArgs:ty, RustRet: $RustRet:ty, - attrs: [$($meta:meta)*] + attrs: [$($meta:meta),*], extra: [$($extra_tt:tt)*], fn_extra: $fn_extra:expr, ) => { @@ -60,6 +62,7 @@ mod test_basic { macro_rules! basic_no_extra { ( fn_name: $fn_name:ident, + attrs: [$($meta:meta),*], ) => { mod $fn_name {} }; @@ -85,6 +88,7 @@ macro_rules! specified_types { fn_name: $fn_name:ident, RustFn: $RustFn:ty, RustArgs: $RustArgs:ty, + attrs: [$($meta:meta),*], ) => { mod $fn_name { #[allow(unused)] diff --git a/crates/libm-test/Cargo.toml b/crates/libm-test/Cargo.toml index 4d75b25f8..90b3e1988 100644 --- a/crates/libm-test/Cargo.toml +++ b/crates/libm-test/Cargo.toml @@ -5,7 +5,10 @@ edition = "2021" publish = false [features] -default = [] +default = ["unstable-float"] + +# Propagated from libm because this affects which functions we test. +unstable-float = ["libm/unstable-float", "rug?/nightly-float"] # Generate tests which are random inputs and the outputs are calculated with # musl libc. @@ -44,3 +47,9 @@ criterion = { version = "0.5.1", default-features = false, features = ["cargo_be [[bench]] name = "random" harness = false + +[lints.rust] +# Values from the chared config.rs used by `libm` but not the test crate +unexpected_cfgs = { level = "warn", check-cfg = [ + 'cfg(feature, values("arch", "force-soft-floats", "unstable-intrinsics"))', +] } diff --git a/crates/libm-test/benches/random.rs b/crates/libm-test/benches/random.rs index b9c39334c..786d5d1a1 100644 --- a/crates/libm-test/benches/random.rs +++ b/crates/libm-test/benches/random.rs @@ -18,7 +18,8 @@ struct MuslExtra { macro_rules! musl_rand_benches { ( fn_name: $fn_name:ident, - fn_extra: $skip_on_i586:expr, + attrs: [$($meta:meta)*], + fn_extra: ($skip_on_i586:expr, $musl_fn:expr), ) => { paste::paste! { fn [< musl_bench_ $fn_name >](c: &mut Criterion) { @@ -26,7 +27,7 @@ macro_rules! musl_rand_benches { #[cfg(feature = "build-musl")] let musl_extra = MuslExtra { - musl_fn: Some(musl_math_sys::$fn_name as libm_test::OpCFn), + musl_fn: $musl_fn, skip_on_i586: $skip_on_i586 }; @@ -64,7 +65,10 @@ where break; } - let musl_res = input.call(musl_extra.musl_fn.unwrap()); + let Some(musl_fn) = musl_extra.musl_fn else { + continue; + }; + let musl_res = input.call(musl_fn); let crate_res = input.call(Op::ROUTINE); crate_res.validate(musl_res, input, &ctx).context(name).unwrap(); @@ -88,15 +92,16 @@ where // 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); - } - }) - }); + if let Some(musl_fn) = musl_extra.musl_fn { + group.bench_function("musl", |b| { + b.iter(|| { + let f = black_box(musl_fn); + for input in benchvec.iter().copied() { + input.call(f); + } + }) + }); + } } } @@ -104,15 +109,22 @@ libm_macros::for_each_function! { callback: musl_rand_benches, skip: [], fn_extra: match MACRO_FN_NAME { - // FIXME(correctness): wrong result on i586 - exp10 | exp10f | exp2 | exp2f => true, - _ => false + // We pass a tuple of `(skip_on_i586, musl_fn)`. By default we never skip (false) and + // we do pass a function, but there are a couple exceptions. + // FIXME(correctness): exp functions have the wrong result on i586 + exp10 | exp10f | exp2 | exp2f => ( + true, Some(musl_math_sys::MACRO_FN_NAME as ::CFn) + ), + // Musl does not provide `f16` and `f128` functions + copysignf16 | copysignf128 | fabsf16 | fabsf128 => (false, None), + _ => (false, Some(musl_math_sys::MACRO_FN_NAME as ::CFn)) } } macro_rules! run_callback { ( fn_name: $fn_name:ident, + attrs: [$($meta:meta)*], extra: [$criterion:ident], ) => { paste::paste! { diff --git a/crates/libm-test/build.rs b/crates/libm-test/build.rs index dc3126dbb..f2cd298ba 100644 --- a/crates/libm-test/build.rs +++ b/crates/libm-test/build.rs @@ -1,66 +1,16 @@ use std::fmt::Write; -use std::path::PathBuf; -use std::{env, fs}; +use std::fs; + +#[path = "../../configure.rs"] +mod configure; +use configure::Config; fn main() { let cfg = Config::from_env(); - emit_optimization_cfg(&cfg); - emit_cfg_shorthands(&cfg); list_all_tests(&cfg); -} - -#[allow(dead_code)] -struct Config { - manifest_dir: PathBuf, - out_dir: PathBuf, - opt_level: u8, - target_arch: String, - target_env: String, - target_family: Option, - target_os: String, - target_string: String, - target_vendor: String, - target_features: Vec, -} - -impl Config { - fn from_env() -> Self { - let target_features = env::var("CARGO_CFG_TARGET_FEATURE") - .map(|feats| feats.split(',').map(ToOwned::to_owned).collect()) - .unwrap_or_default(); - - Self { - manifest_dir: PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()), - out_dir: PathBuf::from(env::var("OUT_DIR").unwrap()), - opt_level: env::var("OPT_LEVEL").unwrap().parse().unwrap(), - target_arch: env::var("CARGO_CFG_TARGET_ARCH").unwrap(), - target_env: env::var("CARGO_CFG_TARGET_ENV").unwrap(), - target_family: env::var("CARGO_CFG_TARGET_FAMILY").ok(), - target_os: env::var("CARGO_CFG_TARGET_OS").unwrap(), - target_string: env::var("TARGET").unwrap(), - target_vendor: env::var("CARGO_CFG_TARGET_VENDOR").unwrap(), - target_features, - } - } -} -/// Some tests are extremely slow. Emit a config option based on optimization level. -fn emit_optimization_cfg(cfg: &Config) { - println!("cargo::rustc-check-cfg=cfg(optimizations_enabled)"); - - if cfg.opt_level >= 2 { - println!("cargo::rustc-cfg=optimizations_enabled"); - } -} - -/// Provide an alias for common longer config combinations. -fn emit_cfg_shorthands(cfg: &Config) { - println!("cargo::rustc-check-cfg=cfg(x86_no_sse)"); - if cfg.target_arch == "x86" && !cfg.target_features.iter().any(|f| f == "sse") { - // Shorthand to detect i586 targets - println!("cargo::rustc-cfg=x86_no_sse"); - } + configure::emit_test_config(&cfg); } /// Create a list of all source files in an array. This can be used for making sure that diff --git a/crates/libm-test/src/gen.rs b/crates/libm-test/src/gen.rs index 3e9eca37a..2c0fcb82c 100644 --- a/crates/libm-test/src/gen.rs +++ b/crates/libm-test/src/gen.rs @@ -6,11 +6,29 @@ pub mod random; /// Helper type to turn any reusable input into a generator. #[derive(Clone, Debug, Default)] pub struct CachedInput { + #[cfg(f16_enabled)] + pub inputs_f16: Vec<(f16, f16, f16)>, pub inputs_f32: Vec<(f32, f32, f32)>, pub inputs_f64: Vec<(f64, f64, f64)>, + #[cfg(f128_enabled)] + pub inputs_f128: Vec<(f128, f128, f128)>, pub inputs_i32: Vec<(i32, i32, i32)>, } +#[cfg(f16_enabled)] +impl GenerateInput<(f16,)> for CachedInput { + fn get_cases(&self) -> impl Iterator { + self.inputs_f16.iter().map(|f| (f.0,)) + } +} + +#[cfg(f16_enabled)] +impl GenerateInput<(f16, f16)> for CachedInput { + fn get_cases(&self) -> impl Iterator { + self.inputs_f16.iter().map(|f| (f.0, f.1)) + } +} + impl GenerateInput<(f32,)> for CachedInput { fn get_cases(&self) -> impl Iterator { self.inputs_f32.iter().map(|f| (f.0,)) @@ -70,3 +88,17 @@ impl GenerateInput<(f64, f64, f64)> for CachedInput { self.inputs_f64.iter().copied() } } + +#[cfg(f128_enabled)] +impl GenerateInput<(f128,)> for CachedInput { + fn get_cases(&self) -> impl Iterator { + self.inputs_f128.iter().map(|f| (f.0,)) + } +} + +#[cfg(f128_enabled)] +impl GenerateInput<(f128, f128)> for CachedInput { + fn get_cases(&self) -> impl Iterator { + self.inputs_f128.iter().map(|f| (f.0, f.1)) + } +} diff --git a/crates/libm-test/src/gen/random.rs b/crates/libm-test/src/gen/random.rs index 527cd1351..65ab285e8 100644 --- a/crates/libm-test/src/gen/random.rs +++ b/crates/libm-test/src/gen/random.rs @@ -42,8 +42,12 @@ static TEST_CASES_JN: LazyLock = LazyLock::new(|| { // These functions are extremely slow, limit them let ntests_jn = (NTESTS / 1000).max(80); cases.inputs_i32.truncate(ntests_jn); + #[cfg(f16_enabled)] + cases.inputs_f16.truncate(ntests_jn); cases.inputs_f32.truncate(ntests_jn); cases.inputs_f64.truncate(ntests_jn); + #[cfg(f128_enabled)] + cases.inputs_f128.truncate(ntests_jn); // It is easy to overflow the stack with these in debug mode let max_iterations = if cfg!(optimizations_enabled) && cfg!(target_pointer_width = "64") { @@ -68,6 +72,17 @@ fn make_test_cases(ntests: usize) -> CachedInput { // make sure we include some basic cases let mut inputs_i32 = vec![(0, 0, 0), (1, 1, 1), (-1, -1, -1)]; + #[cfg(f16_enabled)] + let mut inputs_f16 = vec![ + (0.0, 0.0, 0.0), + (f16::EPSILON, f16::EPSILON, f16::EPSILON), + (f16::INFINITY, f16::INFINITY, f16::INFINITY), + (f16::NEG_INFINITY, f16::NEG_INFINITY, f16::NEG_INFINITY), + (f16::MAX, f16::MAX, f16::MAX), + (f16::MIN, f16::MIN, f16::MIN), + (f16::MIN_POSITIVE, f16::MIN_POSITIVE, f16::MIN_POSITIVE), + (f16::NAN, f16::NAN, f16::NAN), + ]; let mut inputs_f32 = vec![ (0.0, 0.0, 0.0), (f32::EPSILON, f32::EPSILON, f32::EPSILON), @@ -88,11 +103,27 @@ fn make_test_cases(ntests: usize) -> CachedInput { (f64::MIN_POSITIVE, f64::MIN_POSITIVE, f64::MIN_POSITIVE), (f64::NAN, f64::NAN, f64::NAN), ]; + #[cfg(f128_enabled)] + let mut inputs_f128 = vec![ + (0.0, 0.0, 0.0), + (f128::EPSILON, f128::EPSILON, f128::EPSILON), + (f128::INFINITY, f128::INFINITY, f128::INFINITY), + (f128::NEG_INFINITY, f128::NEG_INFINITY, f128::NEG_INFINITY), + (f128::MAX, f128::MAX, f128::MAX), + (f128::MIN, f128::MIN, f128::MIN), + (f128::MIN_POSITIVE, f128::MIN_POSITIVE, f128::MIN_POSITIVE), + (f128::NAN, f128::NAN, f128::NAN), + ]; inputs_i32.extend((0..(ntests - inputs_i32.len())).map(|_| rng.gen::<(i32, i32, i32)>())); // Generate integers to get a full range of bitpatterns, then convert back to // floats. + #[cfg(f16_enabled)] + inputs_f16.extend((0..(ntests - inputs_f16.len())).map(|_| { + let ints = rng.gen::<(u16, u16, u16)>(); + (f16::from_bits(ints.0), f16::from_bits(ints.1), f16::from_bits(ints.2)) + })); inputs_f32.extend((0..(ntests - inputs_f32.len())).map(|_| { let ints = rng.gen::<(u32, u32, u32)>(); (f32::from_bits(ints.0), f32::from_bits(ints.1), f32::from_bits(ints.2)) @@ -101,8 +132,21 @@ fn make_test_cases(ntests: usize) -> CachedInput { let ints = rng.gen::<(u64, u64, u64)>(); (f64::from_bits(ints.0), f64::from_bits(ints.1), f64::from_bits(ints.2)) })); + #[cfg(f128_enabled)] + inputs_f128.extend((0..(ntests - inputs_f128.len())).map(|_| { + let ints = rng.gen::<(u128, u128, u128)>(); + (f128::from_bits(ints.0), f128::from_bits(ints.1), f128::from_bits(ints.2)) + })); - CachedInput { inputs_f32, inputs_f64, inputs_i32 } + CachedInput { + #[cfg(f16_enabled)] + inputs_f16, + inputs_f32, + inputs_f64, + #[cfg(f128_enabled)] + inputs_f128, + inputs_i32, + } } /// Create a test case iterator. diff --git a/crates/libm-test/src/lib.rs b/crates/libm-test/src/lib.rs index 17a06b3be..000ec828c 100644 --- a/crates/libm-test/src/lib.rs +++ b/crates/libm-test/src/lib.rs @@ -1,3 +1,6 @@ +#![cfg_attr(f128_enabled, feature(f128))] +#![cfg_attr(f16_enabled, feature(f16))] + pub mod gen; #[cfg(feature = "test-multiprecision")] pub mod mpfloat; diff --git a/crates/libm-test/src/mpfloat.rs b/crates/libm-test/src/mpfloat.rs index 507b077b3..9c69786b2 100644 --- a/crates/libm-test/src/mpfloat.rs +++ b/crates/libm-test/src/mpfloat.rs @@ -50,8 +50,10 @@ macro_rules! impl_mp_op { ( fn_name: $fn_name:ident, RustFn: fn($_fty:ty,) -> $_ret:ty, + attrs: [$($meta:meta),*], fn_extra: $fn_name_normalized:expr, ) => { + $(#[$meta])* paste::paste! { impl MpOp for crate::op::$fn_name::Routine { type MpTy = MpFloat; @@ -72,8 +74,10 @@ macro_rules! impl_mp_op { ( fn_name: $fn_name:ident, RustFn: fn($_fty:ty, $_fty2:ty,) -> $_ret:ty, + attrs: [$($meta:meta),*], fn_extra: $fn_name_normalized:expr, ) => { + $(#[$meta])* paste::paste! { impl MpOp for crate::op::$fn_name::Routine { type MpTy = (MpFloat, MpFloat); @@ -95,9 +99,11 @@ macro_rules! impl_mp_op { ( fn_name: $fn_name:ident, RustFn: fn($_fty:ty, $_fty2:ty, $_fty3:ty,) -> $_ret:ty, + attrs: [$($meta:meta),*], fn_extra: $fn_name_normalized:expr, ) => { paste::paste! { + $(#[$meta])* impl MpOp for crate::op::$fn_name::Routine { type MpTy = (MpFloat, MpFloat, MpFloat); @@ -128,6 +134,7 @@ libm_macros::for_each_function! { // Most of these need a manual implementation fabs, ceil, copysign, floor, rint, round, trunc, fabsf, ceilf, copysignf, floorf, rintf, roundf, truncf, + fabsf16, fabsf128, copysignf16, copysignf128, fmod, fmodf, frexp, frexpf, ilogb, ilogbf, jn, jnf, ldexp, ldexpf, lgamma_r, lgammaf_r, modf, modff, nextafter, nextafterf, pow,powf, remquo, remquof, scalbn, scalbnf, sincos, sincosf, @@ -150,12 +157,24 @@ libm_macros::for_each_function! { /// Implement unary functions that don't have a `_round` version macro_rules! impl_no_round { - // Unary matcher - ($($fn_name:ident, $rug_name:ident;)*) => { + ($($fn_name:ident, $rug_name:ident $(, @include $f16_ty:ty, $f128_ty:ty )?;)*) => { paste::paste! { // Implement for both f32 and f64 - $( impl_no_round!{ @inner_unary [< $fn_name f >], $rug_name } )* - $( impl_no_round!{ @inner_unary $fn_name, $rug_name } )* + $( + + impl_no_round!{ @inner_unary [< $fn_name f >], $rug_name } + impl_no_round!{ @inner_unary $fn_name, $rug_name } + + $( + // Possibly implement for `f16` and `f128`. We shouldn't need to match + // `f16_ty` and `f128_ty` (we know what they are...) but this gets us around + // the "repeat an expression containing no syntax variables" error. + #[cfg(f16_enabled)] + impl_no_round!{ @inner_unary [< $fn_name $f16_ty >], $rug_name } + #[cfg(f128_enabled)] + impl_no_round!{ @inner_unary [< $fn_name $f128_ty >], $rug_name } + )? + )* } }; @@ -177,7 +196,7 @@ macro_rules! impl_no_round { } impl_no_round! { - fabs, abs_mut; + fabs, abs_mut, @include f16, f128; ceil, ceil_mut; floor, floor_mut; rint, round_even_mut; // FIXME: respect rounding mode @@ -303,3 +322,37 @@ impl MpOp for crate::op::lgammaf_r::Routine { (ret, sign as i32) } } + +// Not all `f16` and `f128` functions exist yet so we can't easily use the macros. + +#[cfg(f16_enabled)] +impl MpOp for crate::op::copysignf16::Routine { + type MpTy = (MpFloat, MpFloat); + + fn new_mp() -> Self::MpTy { + (new_mpfloat::(), new_mpfloat::()) + } + + 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) + } +} + +#[cfg(f128_enabled)] +impl MpOp for crate::op::copysignf128::Routine { + type MpTy = (MpFloat, MpFloat); + + fn new_mp() -> Self::MpTy { + (new_mpfloat::(), new_mpfloat::()) + } + + 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) + } +} diff --git a/crates/libm-test/src/op.rs b/crates/libm-test/src/op.rs index bcea31c22..22b5d8602 100644 --- a/crates/libm-test/src/op.rs +++ b/crates/libm-test/src/op.rs @@ -92,8 +92,10 @@ macro_rules! do_thing { RustFn: $RustFn:ty, RustArgs: $RustArgs:ty, RustRet: $RustRet:ty, + attrs: [$($meta:meta),*], ) => { paste::paste! { + $(#[$meta])* pub mod $fn_name { use super::*; pub struct Routine; diff --git a/crates/libm-test/src/precision.rs b/crates/libm-test/src/precision.rs index c7f9d9e30..878c90f23 100644 --- a/crates/libm-test/src/precision.rs +++ b/crates/libm-test/src/precision.rs @@ -26,7 +26,7 @@ pub fn default_ulp(ctx: &CheckCtx) -> u32 { // Overrides that apply to either basis (_, Id::J0 | Id::J0f | Id::J1 | Id::J1f) => { // Results seem very target-dependent - if cfg!(target_arch = "x86_64") { 4000 } else { 800_000 } + if cfg!(target_arch = "x86_64") { 6000 } else { 800_000 } } (_, Id::Jn | Id::Jnf) => 1000, @@ -97,6 +97,19 @@ pub trait MaybeOverride { } } +#[cfg(f16_enabled)] +impl MaybeOverride<(f16, f16)> for SpecialCase { + fn check_float( + input: (f16, f16), + _actual: F, + expected: F, + _ulp: &mut u32, + ctx: &CheckCtx, + ) -> Option { + maybe_skip_binop_nan(input, expected, ctx) + } +} + impl MaybeOverride<(f32,)> for SpecialCase { fn check_float( input: (f32,), @@ -130,6 +143,12 @@ impl MaybeOverride<(f32,)> for SpecialCase { return XFAIL; } + if ctx.fn_name == "sinhf" && input.0.abs() > 80.0 && actual.is_nan() { + // we return some NaN that should be real values or infinite + // doesn't seem to happen on x86 + return XFAIL; + } + maybe_check_nan_bits(actual, expected, ctx) } @@ -350,7 +369,25 @@ fn bessel_prec_dropoff( None } +#[cfg(f128_enabled)] +impl MaybeOverride<(f128, f128)> for SpecialCase { + fn check_float( + input: (f128, f128), + _actual: F, + expected: F, + _ulp: &mut u32, + ctx: &CheckCtx, + ) -> Option { + maybe_skip_binop_nan(input, expected, ctx) + } +} + impl MaybeOverride<(f32, f32, f32)> for SpecialCase {} impl MaybeOverride<(f64, f64, f64)> for SpecialCase {} impl MaybeOverride<(f32, i32)> for SpecialCase {} impl MaybeOverride<(f64, i32)> for SpecialCase {} + +#[cfg(f16_enabled)] +impl MaybeOverride<(f16,)> for SpecialCase {} +#[cfg(f128_enabled)] +impl MaybeOverride<(f128,)> for SpecialCase {} diff --git a/crates/libm-test/src/test_traits.rs b/crates/libm-test/src/test_traits.rs index ca933bbda..32c73666f 100644 --- a/crates/libm-test/src/test_traits.rs +++ b/crates/libm-test/src/test_traits.rs @@ -346,6 +346,12 @@ where impl_float!(f32, f64); +#[cfg(f16_enabled)] +impl_float!(f16); + +#[cfg(f128_enabled)] +impl_float!(f128); + /* trait implementations for compound types */ /// Implement `CheckOutput` for combinations of types. diff --git a/crates/libm-test/tests/check_coverage.rs b/crates/libm-test/tests/check_coverage.rs index b7988660e..c35d2f7a9 100644 --- a/crates/libm-test/tests/check_coverage.rs +++ b/crates/libm-test/tests/check_coverage.rs @@ -22,6 +22,7 @@ const ALLOWED_SKIPS: &[&str] = &[ macro_rules! callback { ( fn_name: $name:ident, + attrs: [$($_meta:meta),*], extra: [$push_to:ident], ) => { $push_to.push(stringify!($name)); diff --git a/crates/libm-test/tests/compare_built_musl.rs b/crates/libm-test/tests/compare_built_musl.rs index 0022ee03c..8c0b7dd5e 100644 --- a/crates/libm-test/tests/compare_built_musl.rs +++ b/crates/libm-test/tests/compare_built_musl.rs @@ -15,7 +15,7 @@ use libm_test::{CheckBasis, CheckCtx, CheckOutput, GenerateInput, MathOp, TupleC macro_rules! musl_rand_tests { ( fn_name: $fn_name:ident, - attrs: [$($meta:meta)*] + attrs: [$($meta:meta),*], ) => { paste::paste! { #[test] @@ -45,6 +45,8 @@ where libm_macros::for_each_function! { callback: musl_rand_tests, + // Musl has no implementations for `f16` and `f128` (on all platforms) + skip: [copysignf16, copysignf128, fabsf16, fabsf128], attributes: [ #[cfg_attr(x86_no_sse, ignore)] // FIXME(correctness): wrong result on i586 [exp10, exp10f, exp2, exp2f, rint] diff --git a/crates/libm-test/tests/multiprecision.rs b/crates/libm-test/tests/multiprecision.rs index 0b41fba82..f7722897e 100644 --- a/crates/libm-test/tests/multiprecision.rs +++ b/crates/libm-test/tests/multiprecision.rs @@ -1,6 +1,8 @@ //! Test with "infinite precision" #![cfg(feature = "test-multiprecision")] +#![cfg_attr(f128_enabled, feature(f128))] +#![cfg_attr(f16_enabled, feature(f16))] use libm_test::gen::{CachedInput, random}; use libm_test::mpfloat::MpOp; @@ -10,11 +12,16 @@ use libm_test::{CheckBasis, CheckCtx, CheckOutput, GenerateInput, MathOp, TupleC macro_rules! mp_rand_tests { ( fn_name: $fn_name:ident, - attrs: [$($meta:meta)*] + attrs: [$($meta:meta),*], ) => { paste::paste! { #[test] $(#[$meta])* + // #[cfg_attr(all( + // optimizations_enabled, + // any(target_arch = "powerpc", target_arch = "powerpc64")), + // ignore = "stack overflow on PowerPC without optimizations" + // )] fn [< mp_random_ $fn_name >]() { test_one::(); } diff --git a/src/lib.rs b/src/lib.rs index 6bb06b5b8..327e3d6e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,8 @@ #![no_std] #![cfg_attr(intrinsics_enabled, allow(internal_features))] #![cfg_attr(intrinsics_enabled, feature(core_intrinsics))] +#![cfg_attr(f128_enabled, feature(f128))] +#![cfg_attr(f16_enabled, feature(f16))] #![allow(clippy::assign_op_pattern)] #![allow(clippy::deprecated_cfg_attr)] #![allow(clippy::eq_op)] diff --git a/src/libm_helper.rs b/src/libm_helper.rs index 52d0c4c2a..dfaff8a05 100644 --- a/src/libm_helper.rs +++ b/src/libm_helper.rs @@ -30,7 +30,7 @@ macro_rules! libm_helper { } }; - ({$($func:tt);*}) => { + ({$($func:tt;)*}) => { $( libm_helper! { $func } )* @@ -103,7 +103,7 @@ libm_helper! { (fn trunc(x: f32) -> (f32); => truncf); (fn y0(x: f32) -> (f32); => y0f); (fn y1(x: f32) -> (f32); => y1f); - (fn yn(n: i32, x: f32) -> (f32); => ynf) + (fn yn(n: i32, x: f32) -> (f32); => ynf); } } @@ -166,6 +166,24 @@ libm_helper! { (fn trunc(x: f64) -> (f64); => trunc); (fn y0(x: f64) -> (f64); => y0); (fn y1(x: f64) -> (f64); => y1); - (fn yn(n: i32, x: f64) -> (f64); => yn) + (fn yn(n: i32, x: f64) -> (f64); => yn); + } +} + +#[cfg(f16_enabled)] +libm_helper! { + f16, + funcs: { + (fn abs(x: f16) -> (f16); => fabsf16); + (fn copysign(x: f16, y: f16) -> (f16); => copysignf16); + } +} + +#[cfg(f128_enabled)] +libm_helper! { + f128, + funcs: { + (fn abs(x: f128) -> (f128); => fabsf128); + (fn copysign(x: f128, y: f128) -> (f128); => copysignf128); } } diff --git a/src/math/copysign.rs b/src/math/copysign.rs index 1f4a35a33..8cfb85133 100644 --- a/src/math/copysign.rs +++ b/src/math/copysign.rs @@ -4,9 +4,5 @@ /// first argument, `x`, and the sign of its second argument, `y`. #[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)] pub fn copysign(x: f64, y: f64) -> f64 { - let mut ux = x.to_bits(); - let uy = y.to_bits(); - ux &= (!0) >> 1; - ux |= uy & (1 << 63); - f64::from_bits(ux) + super::generic::copysign::copysign(x, y) } diff --git a/src/math/copysignf.rs b/src/math/copysignf.rs index 6c346e3a5..dfeda1742 100644 --- a/src/math/copysignf.rs +++ b/src/math/copysignf.rs @@ -4,9 +4,5 @@ /// first argument, `x`, and the sign of its second argument, `y`. #[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)] pub fn copysignf(x: f32, y: f32) -> f32 { - let mut ux = x.to_bits(); - let uy = y.to_bits(); - ux &= 0x7fffffff; - ux |= uy & 0x80000000; - f32::from_bits(ux) + super::generic::copysign::copysign(x, y) } diff --git a/src/math/copysignf128.rs b/src/math/copysignf128.rs new file mode 100644 index 000000000..58a8d9383 --- /dev/null +++ b/src/math/copysignf128.rs @@ -0,0 +1,8 @@ +/// Sign of Y, magnitude of X (f32) +/// +/// Constructs a number with the magnitude (absolute value) of its +/// first argument, `x`, and the sign of its second argument, `y`. +#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)] +pub fn copysignf128(x: f128, y: f128) -> f128 { + super::generic::copysign::copysign(x, y) +} diff --git a/src/math/copysignf16.rs b/src/math/copysignf16.rs new file mode 100644 index 000000000..6b4dcdd7d --- /dev/null +++ b/src/math/copysignf16.rs @@ -0,0 +1,8 @@ +/// Sign of Y, magnitude of X (f32) +/// +/// Constructs a number with the magnitude (absolute value) of its +/// first argument, `x`, and the sign of its second argument, `y`. +#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)] +pub fn copysignf16(x: f16, y: f16) -> f16 { + super::generic::copysign::copysign(x, y) +} diff --git a/src/math/fabs.rs b/src/math/fabs.rs index d083053e1..15fa742da 100644 --- a/src/math/fabs.rs +++ b/src/math/fabs.rs @@ -9,7 +9,7 @@ pub fn fabs(x: f64) -> f64 { args: x, } - f64::from_bits(x.to_bits() & (u64::MAX / 2)) + super::generic::abs::abs(x) } #[cfg(test)] diff --git a/src/math/fabsf.rs b/src/math/fabsf.rs index eabe87254..80d52f8c8 100644 --- a/src/math/fabsf.rs +++ b/src/math/fabsf.rs @@ -9,7 +9,7 @@ pub fn fabsf(x: f32) -> f32 { args: x, } - f32::from_bits(x.to_bits() & 0x7fffffff) + super::generic::abs::abs(x) } // PowerPC tests are failing on LLVM 13: https://github.com/rust-lang/rust/issues/88520 diff --git a/src/math/fabsf128.rs b/src/math/fabsf128.rs new file mode 100644 index 000000000..80f8cb6eb --- /dev/null +++ b/src/math/fabsf128.rs @@ -0,0 +1,5 @@ +/// Absolute value (magnitude) of a `f128` value. +#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)] +pub fn fabsf128(x: f128) -> f128 { + super::generic::abs::abs(x) +} diff --git a/src/math/fabsf16.rs b/src/math/fabsf16.rs new file mode 100644 index 000000000..e2cd4a4ee --- /dev/null +++ b/src/math/fabsf16.rs @@ -0,0 +1,5 @@ +/// Absolute value (magnitude) of a `f16` value. +#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)] +pub fn fabsf16(x: f16) -> f16 { + super::generic::abs::abs(x) +} diff --git a/src/math/generic/abs.rs b/src/math/generic/abs.rs new file mode 100644 index 000000000..98beacfcb --- /dev/null +++ b/src/math/generic/abs.rs @@ -0,0 +1,5 @@ +use super::super::Float; + +pub fn abs(x: F) -> F { + x.abs() +} diff --git a/src/math/generic/copysign.rs b/src/math/generic/copysign.rs new file mode 100644 index 000000000..e5301c07d --- /dev/null +++ b/src/math/generic/copysign.rs @@ -0,0 +1,9 @@ +use super::super::Float; + +pub fn copysign(x: F, y: F) -> F { + let mut ux = x.to_bits(); + let uy = y.to_bits(); + ux &= !F::SIGN_MASK; + ux |= uy & (F::SIGN_MASK); + F::from_bits(ux) +} diff --git a/src/math/generic/mod.rs b/src/math/generic/mod.rs new file mode 100644 index 000000000..f3872d3fb --- /dev/null +++ b/src/math/generic/mod.rs @@ -0,0 +1,2 @@ +pub mod abs; +pub mod copysign; diff --git a/src/math/mod.rs b/src/math/mod.rs index 3852c774e..8db8f1049 100644 --- a/src/math/mod.rs +++ b/src/math/mod.rs @@ -87,6 +87,7 @@ mod support; mod arch; mod expo2; mod fenv; +mod generic; mod k_cos; mod k_cosf; mod k_expo2; @@ -331,6 +332,26 @@ pub use self::tgammaf::tgammaf; pub use self::trunc::trunc; pub use self::truncf::truncf; +cfg_if! { + if #[cfg(f16_enabled)] { + mod copysignf16; + mod fabsf16; + + pub use self::fabsf16::fabsf16; + pub use self::copysignf16::copysignf16; + } +} + +cfg_if! { + if #[cfg(f128_enabled)] { + mod copysignf128; + mod fabsf128; + + pub use self::fabsf128::fabsf128; + pub use self::copysignf128::copysignf128; + } +} + #[inline] fn get_high_word(x: f64) -> u32 { (x.to_bits() >> 32) as u32 diff --git a/src/math/support/float_traits.rs b/src/math/support/float_traits.rs index 5808aeebc..7b3f6904b 100644 --- a/src/math/support/float_traits.rs +++ b/src/math/support/float_traits.rs @@ -219,5 +219,9 @@ macro_rules! float_impl { }; } +#[cfg(f16_enabled)] +float_impl!(f16, u16, i16, i8, 16, 10); float_impl!(f32, u32, i32, i16, 32, 23); float_impl!(f64, u64, i64, i16, 64, 52); +#[cfg(f128_enabled)] +float_impl!(f128, u128, i128, i16, 128, 112);