From dbf8a4ebe5106a28c7c95dc87b38ce716d6950c4 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 31 Oct 2024 23:53:03 -0500 Subject: [PATCH] Replace `libm_test::{Float, Int}` with `libm::{Float, Int}` This involves moving some things from full generic implementations (e.g. `impl SomeTrait for F { /* ... */ }` to generic functions and macros to implement traits that call them, due to orphan rule violations after `Float` became a not-in-crate trait. `Hex` was moved to `test_traits` so we can eliminate `num_traits`. --- .../libm/crates/libm-test/Cargo.toml | 2 +- .../libm/crates/libm-test/src/lib.rs | 5 +- .../libm/crates/libm-test/src/num_traits.rs | 214 --------------- .../libm/crates/libm-test/src/test_traits.rs | 259 +++++++++++++----- 4 files changed, 198 insertions(+), 282 deletions(-) delete mode 100644 library/compiler-builtins/libm/crates/libm-test/src/num_traits.rs diff --git a/library/compiler-builtins/libm/crates/libm-test/Cargo.toml b/library/compiler-builtins/libm/crates/libm-test/Cargo.toml index fedf745ed7f4..3587b44e6a47 100644 --- a/library/compiler-builtins/libm/crates/libm-test/Cargo.toml +++ b/library/compiler-builtins/libm/crates/libm-test/Cargo.toml @@ -24,7 +24,7 @@ short-benchmarks = [] [dependencies] anyhow = "1.0.90" az = { version = "1.2.1", optional = true } -libm = { path = "../.." } +libm = { path = "../..", features = ["unstable-test-support"] } libm-macros = { path = "../libm-macros" } musl-math-sys = { path = "../musl-math-sys", optional = true } paste = "1.0.15" 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 6c7a3f5ece28..56a872779d6c 100644 --- a/library/compiler-builtins/libm/crates/libm-test/src/lib.rs +++ b/library/compiler-builtins/libm/crates/libm-test/src/lib.rs @@ -1,13 +1,12 @@ pub mod gen; #[cfg(feature = "test-multiprecision")] pub mod mpfloat; -mod num_traits; mod precision; mod test_traits; -pub use num_traits::{Float, Hex, Int}; +pub use libm::support::{Float, Int}; pub use precision::{MaybeOverride, SpecialCase, multiprec_allowed_ulp, musl_allowed_ulp}; -pub use test_traits::{CheckBasis, CheckCtx, CheckOutput, GenerateInput, TupleCall}; +pub use test_traits::{CheckBasis, CheckCtx, CheckOutput, GenerateInput, Hex, TupleCall}; /// Result type for tests is usually from `anyhow`. Most times there is no success value to /// propagate. diff --git a/library/compiler-builtins/libm/crates/libm-test/src/num_traits.rs b/library/compiler-builtins/libm/crates/libm-test/src/num_traits.rs deleted file mode 100644 index e16f4e4dca45..000000000000 --- a/library/compiler-builtins/libm/crates/libm-test/src/num_traits.rs +++ /dev/null @@ -1,214 +0,0 @@ -use std::fmt; - -use crate::{MaybeOverride, SpecialCase, TestResult}; - -/// Common types and methods for floating point numbers. -pub trait Float: Copy + fmt::Display + fmt::Debug + PartialEq { - type Int: Int; - type SignedInt: Int + Int; - - const ZERO: Self; - const ONE: Self; - - /// The bitwidth of the float type - const BITS: u32; - - /// The bitwidth of the significand - const SIGNIFICAND_BITS: u32; - - /// The bitwidth of the exponent - const EXPONENT_BITS: u32 = Self::BITS - Self::SIGNIFICAND_BITS - 1; - - fn is_nan(self) -> bool; - fn is_infinite(self) -> bool; - fn to_bits(self) -> Self::Int; - fn from_bits(bits: Self::Int) -> Self; - fn signum(self) -> Self; -} - -macro_rules! impl_float { - ($($fty:ty, $ui:ty, $si:ty, $significand_bits:expr;)+) => { - $( - impl Float for $fty { - type Int = $ui; - type SignedInt = $si; - - const ZERO: Self = 0.0; - const ONE: Self = 1.0; - - const BITS: u32 = <$ui>::BITS; - const SIGNIFICAND_BITS: u32 = $significand_bits; - - fn is_nan(self) -> bool { - self.is_nan() - } - fn is_infinite(self) -> bool { - self.is_infinite() - } - fn to_bits(self) -> Self::Int { - self.to_bits() - } - fn from_bits(bits: Self::Int) -> Self { - Self::from_bits(bits) - } - fn signum(self) -> Self { - self.signum() - } - } - - impl Hex for $fty { - fn hex(self) -> String { - self.to_bits().hex() - } - } - )+ - } -} - -impl_float!( - f32, u32, i32, 23; - f64, u64, i64, 52; -); - -/// Common types and methods for integers. -pub trait Int: Copy + fmt::Display + fmt::Debug + PartialEq { - type OtherSign: Int; - type Unsigned: Int; - const BITS: u32; - const SIGNED: bool; - - fn signed(self) -> ::OtherSign; - fn unsigned(self) -> Self::Unsigned; - fn checked_sub(self, other: Self) -> Option; - fn abs(self) -> Self; -} - -macro_rules! impl_int { - ($($ui:ty, $si:ty ;)+) => { - $( - impl Int for $ui { - type OtherSign = $si; - type Unsigned = Self; - const BITS: u32 = <$ui>::BITS; - const SIGNED: bool = false; - fn signed(self) -> Self::OtherSign { - self as $si - } - fn unsigned(self) -> Self { - self - } - fn checked_sub(self, other: Self) -> Option { - self.checked_sub(other) - } - fn abs(self) -> Self { - unimplemented!() - } - } - - impl Int for $si { - type OtherSign = $ui; - type Unsigned = $ui; - const BITS: u32 = <$ui>::BITS; - const SIGNED: bool = true; - fn signed(self) -> Self { - self - } - fn unsigned(self) -> $ui { - self as $ui - } - fn checked_sub(self, other: Self) -> Option { - self.checked_sub(other) - } - fn abs(self) -> Self { - self.abs() - } - } - - impl_int!(@for_both $si); - impl_int!(@for_both $ui); - - )+ - }; - - (@for_both $ty:ty) => { - impl Hex for $ty { - fn hex(self) -> String { - format!("{self:#0width$x}", width = ((Self::BITS / 4) + 2) as usize) - } - } - - impl $crate::CheckOutput for $ty - where - Input: Hex + fmt::Debug, - SpecialCase: MaybeOverride, - { - fn validate<'a>( - self, - expected: Self, - input: Input, - ctx: &$crate::CheckCtx, - ) -> TestResult { - if let Some(res) = SpecialCase::check_int(input, self, expected, ctx) { - return res; - } - - anyhow::ensure!( - self == expected, - "\ - \n input: {input:?} {ibits}\ - \n expected: {expected:<22?} {expbits}\ - \n actual: {self:<22?} {actbits}\ - ", - actbits = self.hex(), - expbits = expected.hex(), - ibits = input.hex(), - ); - - Ok(()) - } - } - } -} - -impl_int!( - u32, i32; - u64, i64; -); - -/// A helper trait to print something as hex with the correct number of nibbles, e.g. a `u32` -/// will always print with `0x` followed by 8 digits. -/// -/// This is only used for printing errors so allocating is okay. -pub trait Hex: Copy { - fn hex(self) -> String; -} - -impl Hex for (T1,) -where - T1: Hex, -{ - fn hex(self) -> String { - format!("({},)", self.0.hex()) - } -} - -impl Hex for (T1, T2) -where - T1: Hex, - T2: Hex, -{ - fn hex(self) -> String { - format!("({}, {})", self.0.hex(), self.1.hex()) - } -} - -impl Hex for (T1, T2, T3) -where - T1: Hex, - T2: Hex, - T3: Hex, -{ - fn hex(self) -> String { - format!("({}, {}, {})", self.0.hex(), self.1.hex(), self.2.hex()) - } -} 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 34e15e0b2863..67df83fb4921 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 @@ -11,21 +11,7 @@ use std::fmt; use anyhow::{Context, bail, ensure}; -use crate::{Float, Hex, Int, MaybeOverride, SpecialCase, TestResult}; - -/// Implement this on types that can generate a sequence of tuples for test input. -pub trait GenerateInput { - fn get_cases(&self) -> impl Iterator; -} - -/// Trait for calling a function with a tuple as arguments. -/// -/// Implemented on the tuple with the function signature as the generic (so we can use the same -/// tuple for multiple signatures). -pub trait TupleCall: fmt::Debug { - type Output; - fn call(self, f: Func) -> Self::Output; -} +use crate::{Float, Int, MaybeOverride, SpecialCase, TestResult}; /// Context passed to [`CheckOutput`]. #[derive(Clone, Debug, PartialEq, Eq)] @@ -56,14 +42,38 @@ pub enum CheckBasis { Mpfr, } +/// Implement this on types that can generate a sequence of tuples for test input. +pub trait GenerateInput { + fn get_cases(&self) -> impl Iterator; +} + +/// Trait for calling a function with a tuple as arguments. +/// +/// Implemented on the tuple with the function signature as the generic (so we can use the same +/// tuple for multiple signatures). +pub trait TupleCall: fmt::Debug { + type Output; + fn call(self, f: Func) -> Self::Output; +} + /// A trait to implement on any output type so we can verify it in a generic way. pub trait CheckOutput: Sized { /// Validate `self` (actual) and `expected` are the same. /// /// `input` is only used here for error messages. - fn validate<'a>(self, expected: Self, input: Input, ctx: &CheckCtx) -> TestResult; + fn validate(self, expected: Self, input: Input, ctx: &CheckCtx) -> TestResult; } +/// A helper trait to print something as hex with the correct number of nibbles, e.g. a `u32` +/// will always print with `0x` followed by 8 digits. +/// +/// This is only used for printing errors so allocating is okay. +pub trait Hex: Copy { + fn hex(self) -> String; +} + +/* implement `TupleCall` */ + impl TupleCall R> for (T1,) where T1: fmt::Debug, @@ -143,72 +153,193 @@ where } } -// Implement for floats -impl CheckOutput for F +/* implement `Hex` */ + +impl Hex for (T1,) +where + T1: Hex, +{ + fn hex(self) -> String { + format!("({},)", self.0.hex()) + } +} + +impl Hex for (T1, T2) +where + T1: Hex, + T2: Hex, +{ + fn hex(self) -> String { + format!("({}, {})", self.0.hex(), self.1.hex()) + } +} + +impl Hex for (T1, T2, T3) +where + T1: Hex, + T2: Hex, + T3: Hex, +{ + fn hex(self) -> String { + format!("({}, {}, {})", self.0.hex(), self.1.hex(), self.2.hex()) + } +} + +/* trait implementations for ints */ + +macro_rules! impl_int { + ($($ty:ty),*) => { + $( + impl Hex for $ty { + fn hex(self) -> String { + format!("{self:#0width$x}", width = ((Self::BITS / 4) + 2) as usize) + } + } + + impl $crate::CheckOutput for $ty + where + Input: Hex + fmt::Debug, + SpecialCase: MaybeOverride, + { + fn validate<'a>( + self, + expected: Self, + input: Input, + ctx: &$crate::CheckCtx, + ) -> TestResult { + validate_int(self, expected, input, ctx) + } + } + )* + }; +} + +fn validate_int<'a, I, Input>(actual: I, expected: I, input: Input, ctx: &CheckCtx) -> TestResult +where + I: Int + Hex, + Input: Hex + fmt::Debug, + SpecialCase: MaybeOverride, +{ + if let Some(res) = SpecialCase::check_int(input, actual, expected, ctx) { + return res; + } + + anyhow::ensure!( + actual == expected, + "\ + \n input: {input:?} {ibits}\ + \n expected: {expected:<22?} {expbits}\ + \n actual: {actual:<22?} {actbits}\ + ", + actbits = actual.hex(), + expbits = expected.hex(), + ibits = input.hex(), + ); + + Ok(()) +} + +impl_int!(u32, i32, u64, i64); + +/* trait implementations for floats */ + +macro_rules! impl_float { + ($($ty:ty),*) => { + $( + impl Hex for $ty { + fn hex(self) -> String { + format!( + "{:#0width$x}", + self.to_bits(), + width = ((Self::BITS / 4) + 2) as usize + ) + } + } + + impl $crate::CheckOutput for $ty + where + Input: Hex + fmt::Debug, + SpecialCase: MaybeOverride, + { + fn validate<'a>( + self, + expected: Self, + input: Input, + ctx: &$crate::CheckCtx, + ) -> TestResult { + validate_float(self, expected, input, ctx) + } + } + )* + }; +} + +fn validate_float<'a, F, Input>(actual: F, expected: F, input: Input, ctx: &CheckCtx) -> TestResult where F: Float + Hex, Input: Hex + fmt::Debug, u32: TryFrom, SpecialCase: MaybeOverride, { - fn validate<'a>(self, expected: Self, input: Input, ctx: &CheckCtx) -> TestResult { - // Create a wrapper function so we only need to `.with_context` once. - let inner = || -> TestResult { - let mut allowed_ulp = ctx.ulp; + // Create a wrapper function so we only need to `.with_context` once. + let inner = || -> TestResult { + let mut allowed_ulp = ctx.ulp; - // If the tested function requires a nonstandard test, run it here. - if let Some(res) = - SpecialCase::check_float(input, self, expected, &mut allowed_ulp, ctx) - { - return res; - } + // If the tested function requires a nonstandard test, run it here. + if let Some(res) = SpecialCase::check_float(input, actual, expected, &mut allowed_ulp, ctx) + { + return res; + } - // Check when both are NaNs - if self.is_nan() && expected.is_nan() { - // By default, NaNs have nothing special to check. - return Ok(()); - } else if self.is_nan() || expected.is_nan() { - // Check when only one is a NaN - bail!("real value != NaN") - } + // Check when both are NaNs + if actual.is_nan() && expected.is_nan() { + // By default, NaNs have nothing special to check. + return Ok(()); + } else if actual.is_nan() || expected.is_nan() { + // Check when only one is a NaN + bail!("real value != NaN") + } - // Make sure that the signs are the same before checing ULP to avoid wraparound - let act_sig = self.signum(); - let exp_sig = expected.signum(); - ensure!(act_sig == exp_sig, "mismatched signs {act_sig} {exp_sig}"); + // Make sure that the signs are the same before checing ULP to avoid wraparound + let act_sig = actual.signum(); + let exp_sig = expected.signum(); + ensure!(act_sig == exp_sig, "mismatched signs {act_sig} {exp_sig}"); - if self.is_infinite() ^ expected.is_infinite() { - bail!("mismatched infinities"); - } + if actual.is_infinite() ^ expected.is_infinite() { + bail!("mismatched infinities"); + } - let act_bits = self.to_bits().signed(); - let exp_bits = expected.to_bits().signed(); + let act_bits = actual.to_bits().signed(); + let exp_bits = expected.to_bits().signed(); - let ulp_diff = act_bits.checked_sub(exp_bits).unwrap().abs(); + let ulp_diff = act_bits.checked_sub(exp_bits).unwrap().abs(); - let ulp_u32 = u32::try_from(ulp_diff) - .map_err(|e| anyhow::anyhow!("{e:?}: ulp of {ulp_diff} exceeds u32::MAX"))?; + let ulp_u32 = u32::try_from(ulp_diff) + .map_err(|e| anyhow::anyhow!("{e:?}: ulp of {ulp_diff} exceeds u32::MAX"))?; - ensure!(ulp_u32 <= allowed_ulp, "ulp {ulp_diff} > {allowed_ulp}",); + ensure!(ulp_u32 <= allowed_ulp, "ulp {ulp_diff} > {allowed_ulp}",); - Ok(()) - }; + Ok(()) + }; - inner().with_context(|| { - format!( - "\ - \n input: {input:?} {ibits}\ - \n expected: {expected:<22?} {expbits}\ - \n actual: {self:<22?} {actbits}\ - ", - actbits = self.hex(), - expbits = expected.hex(), - ibits = input.hex(), - ) - }) - } + inner().with_context(|| { + format!( + "\ + \n input: {input:?} {ibits}\ + \n expected: {expected:<22?} {expbits}\ + \n actual: {actual:<22?} {actbits}\ + ", + actbits = actual.hex(), + expbits = expected.hex(), + ibits = input.hex(), + ) + }) } +impl_float!(f32, f64); + +/* trait implementations for compound types */ + /// Implement `CheckOutput` for combinations of types. macro_rules! impl_tuples { ($(($a:ty, $b:ty);)*) => {