diff --git a/Cargo.toml b/Cargo.toml index f17d09e..13aa9ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,5 +14,9 @@ harness = false num-traits = { version = "0.2", optional = true } [dev-dependencies] -criterion = "0.3" +criterion = { version = "0.3", features = ["html_reports"] } rand = "0.8" + +[profile.test] +# run tests at high optimization to exercise typical codegen +opt-level = 3 diff --git a/src/lib.rs b/src/lib.rs index 585fd12..03dcd8e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,11 @@ #![doc = include_str!("../README.md")] #![feature(core_intrinsics)] // intrinsics for the fast math #![feature(asm)] // asm used to emulate freeze -use core::{cmp, fmt, intrinsics::fadd_fast, ops}; +use core::{ + cmp, fmt, + intrinsics::{fadd_fast, fdiv_fast, fmul_fast, frem_fast, fsub_fast}, + ops::{Add, Div, Mul, Neg, Rem, Sub}, +}; mod poison; use poison::MaybePoison; @@ -113,27 +117,131 @@ pub fn ff32(f: f32) -> FF32 { FF32::new(f) } -impl ops::Add for FF32 { +impl Neg for FF32 { type Output = Self; #[inline(always)] - fn add(self, other: FF32) -> Self { + fn neg(self) -> Self::Output { // Safety: // // - dereferencing the pointers is safe because every bit pattern is valid in float // primitives - // - encountering poison operands is safe because LLVM's fast add documents not producing - // UB on any inputs; it may produce poison on inf/nan (or if the sum is inf/nan), but these - // are then wrapped in the MaybePoison to control propagation - ff32(unsafe { - fadd_fast( - *self.0.maybe_poison().as_ptr(), - *other.0.maybe_poison().as_ptr(), - ) - }) + // - encountering poison is safe because LLVM's negate instruction documents + // not producing UB on any inputs. The value is also immediately wrapped, so + // poison propagation is controlled + let val = unsafe { *self.0.maybe_poison().as_ptr() }; + FF32::new(-val) } } +impl Neg for &FF32 { + type Output = ::Output; + + #[inline] + fn neg(self) -> Self::Output { + -(*self) + } +} + +impl fmt::Debug for FF32 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&self.freeze_f32(), f) + } +} + +impl fmt::Display for FF32 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.freeze_f32(), f) + } +} + +macro_rules! impl_refs { + ($lhs:ident, $rhs:ident, $op_trait:ident, $op_fn:ident) => { + impl $op_trait<$rhs> for &$lhs { + type Output = <$lhs as $op_trait<$rhs>>::Output; + + #[inline] + fn $op_fn(self, other: $rhs) -> Self::Output { + (*self).$op_fn(other) + } + } + impl $op_trait<&$rhs> for $lhs { + type Output = <$lhs as $op_trait<$rhs>>::Output; + + #[inline] + fn $op_fn(self, other: &$rhs) -> Self::Output { + self.$op_fn(*other) + } + } + impl $op_trait<&$rhs> for &$lhs { + type Output = <$lhs as $op_trait<$rhs>>::Output; + + #[inline] + fn $op_fn(self, other: &$rhs) -> Self::Output { + (*self).$op_fn(*other) + } + } + }; +} + +macro_rules! impl_fast_ops { + ($fast_ty:ident, $base_ty: ident: $($op_trait:ident, $op_fn:ident, $op_impl:ident,)*) => { + $( + impl $op_trait <$fast_ty> for $fast_ty { + type Output = $fast_ty; + + #[inline(always)] + fn $op_fn(self, other: $fast_ty) -> Self::Output { + // Safety: + // + // - dereferencing the pointers is safe because every bit pattern is valid in float + // primitives + // - encountering poison operands is safe because LLVM's fast ops documents not producing + // UB on any inputs; it may produce poison on inf/nan (or if the sum is inf/nan), but these + // are then wrapped in the MaybePoison to control propagation + <$fast_ty>::new(unsafe { + $op_impl( + *self.0.maybe_poison().as_ptr(), + *other.0.maybe_poison().as_ptr(), + ) + }) + } + } + + impl $op_trait <$base_ty> for $fast_ty { + type Output = $fast_ty; + + #[inline(always)] + fn $op_fn(self, other: $base_ty) -> Self::Output { + self.$op_fn(<$fast_ty>::new(other)) + } + } + + impl $op_trait <$fast_ty> for $base_ty { + type Output = $fast_ty; + + #[inline(always)] + fn $op_fn(self, other: $fast_ty) -> Self::Output { + <$fast_ty>::new(self).$op_fn(other) + } + } + + impl_refs! { $fast_ty, $fast_ty, $op_trait, $op_fn } + impl_refs! { $fast_ty, $base_ty, $op_trait, $op_fn } + impl_refs! { $base_ty, $fast_ty, $op_trait, $op_fn } + )* + }; +} + +impl_fast_ops! { + FF32, f32: + Add, add, fadd_fast, + Sub, sub, fsub_fast, + Mul, mul, fmul_fast, + Div, div, fdiv_fast, + Rem, rem, frem_fast, +} + // Branching on poison values is UB, so any operation that makes a bool is protected by freezing // the operands. This includes [Partial]Eq and [Partial]Ord. // @@ -152,6 +260,26 @@ impl PartialEq for FF32 { } } +impl PartialEq for FF32 { + #[inline] + fn eq(&self, other: &f32) -> bool { + let this = self.freeze_f32(); + let that = *other; + + this == that + } +} + +impl PartialEq for f32 { + #[inline] + fn eq(&self, other: &FF32) -> bool { + let this = *self; + let that = other.freeze_f32(); + + this == that + } +} + impl Eq for FF32 {} impl PartialOrd for FF32 {