From 63647ad28d0c7e20d1f1163e6543a7a50b84fbab Mon Sep 17 00:00:00 2001 From: Renar Narubin Date: Thu, 16 Dec 2021 17:43:52 -0800 Subject: [PATCH] Everything in C --- Cargo.toml | 31 ++-- build.rs | 48 ++++-- src/lib.rs | 126 +++++++--------- src/math/mod.rs | 232 +++++++++++++++++------------ src/math/{math.c => poison_safe.c} | 80 ++++++---- src/math/poison_unsafe.c | 85 +++++++++++ src/poison/freeze.ll | 15 -- src/poison/mod.rs | 36 +---- 8 files changed, 365 insertions(+), 288 deletions(-) rename src/math/{math.c => poison_safe.c} (58%) create mode 100644 src/math/poison_unsafe.c diff --git a/Cargo.toml b/Cargo.toml index f95f9c9..cf2d9ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,28 +18,19 @@ name = "math" harness = false [features] -default = [ - "num-traits", - "finite-math-only", - "associative-math", - "reciprocal-math", - "no-signed-zeros", - "no-trapping-math", - "fp-contract-fast", - "approx-func", -] +default = ["num-traits"] -# default fast-math features -finite-math-only = [] -associative-math = [] -reciprocal-math = [] -no-signed-zeros = [] -no-trapping-math = [] -fp-contract-fast = [] -approx-func = [] +# disable-able fast-math features +no-finite-math-only = [] +no-associative-math = [] +no-reciprocal-math = [] +signed-zeros = [] +trapping-math = [] +fp-contract-on = [] +no-approx-func = [] +math-errno = [] -# non-default fast-math-like features -denormal-fp-math-preserve-sign = [] +# TODO denormal-fp-math? can have cpu-wide consequences # optional trait implementations nalgebra-v021 = ["num-traits", "nalgebra_v021", "simba_v01", "approx_v03"] diff --git a/build.rs b/build.rs index 04eee83..c2fb8c3 100644 --- a/build.rs +++ b/build.rs @@ -6,6 +6,7 @@ fn main() { builder.compiler("clang"); } + builder.warnings_into_errors(true); builder.flag("-flto=thin"); build_ll(builder.clone()); @@ -21,32 +22,49 @@ fn build_ll(mut builder: cc::Build) { } fn build_c(mut builder: cc::Build) { - builder.flag("-O3"); + builder.opt_level(3); - #[cfg(feature = "finite-math-only")] - builder.flag("-ffinite-math-only"); - - #[cfg(feature = "associative-math")] + #[cfg(not(feature = "no-associative-math"))] builder.flag("-fassociative-math"); - #[cfg(feature = "reciprocal-math")] + #[cfg(not(feature = "no-reciprocal-math"))] builder.flag("-freciprocal-math"); - #[cfg(feature = "no-signed-zeros")] + #[cfg(not(feature = "signed-zeros"))] builder.flag("-fno-signed-zeros"); - #[cfg(feature = "no-trapping-math")] + #[cfg(not(feature = "trapping-math"))] builder.flag("-fno-trapping-math"); - #[cfg(feature = "fp-contract-fast")] + #[cfg(not(feature = "fp-contract-on"))] builder.flag("-ffp-contract=fast"); - // TODO figure out if this works - //#[cfg(feature = "approx-func")] - //builder.flag("-Xclang -fapprox-func"); + // -fapprox-func isn't currently available in the driver, but it is in clang itself + // https://reviews.llvm.org/D106191 + #[cfg(not(feature = "no-approx-func"))] + builder.flag("-Xclang").flag("-fapprox-func"); - #[cfg(feature = "denormal-fp-math-preserve-sign")] - builder.flag("-fdenormal-fp-math=preserve-sign"); + #[cfg(not(feature = "math-errno"))] + builder.flag("-fno-math-errno"); - builder.file("src/math/math.c").compile("math") + // poison_unsafe must be compiled without finite-math-only + // see its docs for details + poison_unsafe(builder.clone()); + + #[cfg(not(feature = "no-finite-math-only"))] + builder.flag("-ffinite-math-only"); + + poison_safe(builder); +} + +fn poison_unsafe(mut builder: cc::Build) { + builder + .file("src/math/poison_unsafe.c") + .compile("poison_unsafe") +} + +fn poison_safe(mut builder: cc::Build) { + builder + .file("src/math/poison_safe.c") + .compile("poison_safe") } diff --git a/src/lib.rs b/src/lib.rs index 17b5381..8737bc0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,29 +64,19 @@ impl std::error::Error for InvalidValueError {} // values have a relatively consistent behavior (stuff like transitivity), defined cases for UB, // and importantly can be limited in scope by freezing to a fixed value. // -// This library handles poison by limiting its reach to only the pure arithmetic operations on the -// wrapper float types. Any arbitrary FF32 is considered possibly invalid (containing +-inf or NaN) -// because it's not feasible to track validity (without running all operations in parallel with -// unfast-math and thus negating any possible improvement). Float add/sub/mul/div/rem are permitted -// on the possibly poison values (as documented by LLVM), producing transitively poison results, -// then wrapped in FF32. Any other operations require the value to be not-poison in order to be -// not-UB: anything like comparison/printing/conversion/casting/etc is done on frozen copies of -// the data. Originating values that were valid will pass through the arithmetic and freezing -// exactly as they are; invalid values will become poison through the arithmetic and then be frozen -// to some unspecified value. The user may encounter garbage in such a case, but not in a way that -// triggers UB. +// FIXME more docs // // Prior art and references // // https://github.com/rust-lang/rust/issues/21690 // Task for general purpose fast-math in rust lang. Discussions about the right approach // and generalizability, including whether it should be type-based or annotation based. fast_fp -// uses types wrapping intrinsics because it's the only option available in user space, and gets -// good optimizations useful in practice +// uses types because it's the only option available in user space, and gets good optimizations +// useful in practice // // https://docs.rs/fast-floats/0.2.0/fast_floats/index.html -// Another crate that wraps fast intrinsics in types. They didn't address poison propagation, -// leaving constructors unsafe +// A crate that wraps fast intrinsics in types. Intrinsics only apply to basic ops, and they didn't +// address poison propagation, leaving constructors unsafe // // https://llvm.org/docs/LangRef.html#fast-math-flags // LLVM's documentation on fast-math @@ -227,8 +217,17 @@ macro_rules! impls { const ONE: $fast_ty = <$fast_ty>::new(1.0); const ZERO: $fast_ty = <$fast_ty>::new(0.0); + /// The smallest finite value + pub const MIN: $fast_ty = <$fast_ty>::new($base_ty::MIN); + + /// The smallest positive value + pub const MIN_POSITIVE: $fast_ty = <$fast_ty>::new($base_ty::MIN_POSITIVE); + + /// The largest finite value + pub const MAX: $fast_ty = <$fast_ty>::new($base_ty::MAX); + #[doc = "Create a new `"] - #[doc= stringify!($fast_ty)] + #[doc = stringify!($fast_ty)] #[doc = "` instance from the given float value."] /// /// The given value **MUST NOT** be infinite or NaN, and any operations involving this value must @@ -239,7 +238,7 @@ macro_rules! impls { } #[doc = "Create a new `"] - #[doc= stringify!($fast_ty)] + #[doc = stringify!($fast_ty)] #[doc = "` instance from the given float value, returning an error if the value is infinite or NaN."] /// /// Note that this check is **not sufficient** to avoid all unspecified outputs, because an @@ -261,57 +260,12 @@ macro_rules! impls { self.0.freeze() } - // TODO migrate these to native implementations to freeze less and fast-math more forward_freeze_self! { $fast_ty, $base_ty - pub fn acos(self) -> Self; - pub fn acosh(self) -> Self; - pub fn asin(self) -> Self; - pub fn asinh(self) -> Self; - pub fn atan(self) -> Self; - pub fn atan2(self, other: Self) -> Self; - pub fn atanh(self) -> Self; - pub fn cbrt(self) -> Self; - pub fn ceil(self) -> Self; - pub fn clamp(self, min: Self, max: Self) -> Self; - pub fn cos(self) -> Self; - pub fn cosh(self) -> Self; pub fn div_euclid(self, rhs: Self) -> Self; - pub fn exp(self) -> Self; - pub fn exp2(self) -> Self; - pub fn exp_m1(self) -> Self; - pub fn floor(self) -> Self; - pub fn fract(self) -> Self; - pub fn ln(self) -> Self; - pub fn ln_1p(self) -> Self; - pub fn log(self, base: Self) -> Self; - pub fn log10(self) -> Self; - pub fn log2(self) -> Self; - //pub fn max(self, other: Self) -> Self; - //pub fn min(self, other: Self) -> Self; - pub fn mul_add(self, a: Self, b: Self) -> Self; - pub fn powf(self, n: Self) -> Self; pub fn rem_euclid(self, rhs: Self) -> Self; - pub fn round(self) -> Self; - pub fn sin(self) -> Self; - pub fn sinh(self) -> Self; - //pub fn sqrt(self) -> Self; - pub fn tan(self) -> Self; - pub fn tanh(self) -> Self; pub fn to_degrees(self) -> Self; pub fn to_radians(self) -> Self; - pub fn trunc(self) -> Self; - } - - #[inline] - pub fn powi(self, n: i32) -> Self { - <$fast_ty>::new(self.freeze_raw().powi(n)) - } - - #[inline] - pub fn sin_cos(self) -> (Self, Self) { - let (sin, cos) = self.freeze_raw().sin_cos(); - (<$fast_ty>::new(sin), <$fast_ty>::new(cos)) } #[inline] @@ -347,14 +301,40 @@ macro_rules! impls { self.classify() == FpCategory::Subnormal } - /// The smallest finite value - pub const MIN: $fast_ty = <$fast_ty>::new($base_ty::MIN); + #[inline] + pub fn hypot(self, other: Self) -> Self { + (self * self + other * other).sqrt() + } - /// The smallest positive value - pub const MIN_POSITIVE: $fast_ty = <$fast_ty>::new($base_ty::MIN_POSITIVE); + #[inline] + pub fn signum(self) -> Self { + Self::ONE.copysign(self) + } - /// The largest finite value - pub const MAX: $fast_ty = <$fast_ty>::new($base_ty::MAX); + #[inline] + pub fn recip(self) -> Self { + Self::ONE / self + } + + #[inline] + pub fn fract(self) -> Self { + self - self.trunc() + } + + #[inline] + pub fn log(self, base: Self) -> Self { + self.ln() / base.ln() + } + + #[inline] + pub fn mul_add(self, mul: Self, add: Self) -> Self { + self * mul + add + } + + #[inline] + pub fn sin_cos(self) -> (Self, Self) { + (self.sin(), self.cos()) + } } impl_fmt! { @@ -417,17 +397,12 @@ macro_rules! impls { } } - impl Eq for $fast_ty {} - impl PartialOrd<$fast_ty> for $fast_ty { #[inline(always)] fn partial_cmp(&self, other: &$fast_ty) -> Option { - Some(self.cmp(other)) + <$base_ty>::partial_cmp(&self.freeze_raw(), &other.freeze_raw()) } - // TODO specialize a MaybePoison with `x & 0b1`? - // then comparisons can freeze only once on output instead of twice on input - #[inline(always)] fn lt(&self, other: &$fast_ty) -> bool { self.freeze_raw() < other.freeze_raw() @@ -449,6 +424,9 @@ macro_rules! impls { } } + // FIXME feature conditional Eq/Ord + impl Eq for $fast_ty {} + impl Ord for $fast_ty { #[inline(always)] fn cmp(&self, other: &$fast_ty) -> cmp::Ordering { diff --git a/src/math/mod.rs b/src/math/mod.rs index 2a1dda1..e044059 100644 --- a/src/math/mod.rs +++ b/src/math/mod.rs @@ -1,81 +1,7 @@ -use crate::{poison::MaybePoison, FF32, FF64}; +use crate::{FF32, FF64}; use core::ops::{Add, Div, Mul, Neg, Rem, Sub}; use paste::paste; -impl FF32 { - const SIGN_BIT: u32 = 0x8000_0000; - const UNSIGNED_MASK: u32 = 0x7fff_ffff; -} - -impl FF64 { - const SIGN_BIT: u64 = 0x8000_0000_0000_0000; - const UNSIGNED_MASK: u64 = 0x7fff_ffff_ffff_ffff; -} - -macro_rules! impl_generic_math { - ($fast_ty:ident, $base_ty:ident, $base_int:ident) => { - impl $fast_ty { - #[inline] - fn to_bits(self) -> MaybePoison<$base_int> { - // Safety: - // - // - `to_bits` should be valid for any input bits - // - poison propagation is controlled with MaybePoison - MaybePoison::new(unsafe { <$base_ty>::to_bits(self.0.maybe_poison()) }) - } - - #[inline] - fn from_bits(bits: MaybePoison<$base_int>) -> Self { - // Safety: - // - // - `from_bits` should be valid for any input bits - // - poison propagation is controlled with MaybePoison - Self(MaybePoison::new(unsafe { - <$base_ty>::from_bits(bits.maybe_poison()) - })) - } - - #[inline] - pub fn abs(self) -> Self { - let bits = self.to_bits(); - <$fast_ty>::from_bits(MaybePoison::new(unsafe { - bits.maybe_poison() & Self::UNSIGNED_MASK - })) - } - - #[inline] - pub fn copysign(self, other: Self) -> Self { - let this = self.to_bits(); - let that = other.to_bits(); - - // Safety: - // - // - & of poison is safe because & does not produce UB for any input values - // - poison propagation is handled by wrapping in maybe poison - <$fast_ty>::from_bits(MaybePoison::new(unsafe { - (this.maybe_poison() & Self::UNSIGNED_MASK) - | (that.maybe_poison() & Self::SIGN_BIT) - })) - } - - #[inline] - pub fn hypot(self, other: Self) -> Self { - (self * self + other * other).sqrt() - } - - #[inline] - pub fn signum(self) -> Self { - Self::ONE.copysign(self) - } - - #[inline] - pub fn recip(self) -> Self { - Self::ONE / self - } - } - }; -} - macro_rules! impl_binary_refs { ($lhs:ident, $rhs:ident, $op_trait:ident, $op_fn:ident) => { impl $op_trait<$rhs> for &$lhs { @@ -142,22 +68,124 @@ macro_rules! impl_fast_ops { }; } +macro_rules! poison_safe_fns { + ($fast_ty:ident, $base_ty:ident: + $(fn $fn:ident(self $(, $arg:ident : Self)*) -> Self;)*) => { + paste! { + $( + #[link(name = "poison_safe")] + extern "C" { + // functions in the poison_safe lib can accept poison args. + // because the fast types are (transitively) repr(transparent) over the + // primitive type, we can pass them directly over FFI + fn [<$fn _ $base_ty>](a: $fast_ty $(, $arg: $fast_ty)*) -> $fast_ty; + } + + impl $fast_ty { + #[inline] + pub fn $fn(self $(, $arg: Self)*) -> Self { + unsafe { [<$fn _ $base_ty>](self $(, $arg)*) } + } + } + )* + } + } +} + +macro_rules! poison_unsafe_fns { + ($fast_ty:ident, $base_ty:ident: + $(fn $fn:ident(self $(, $arg:ident : Self)*) -> Self;)*) => { + paste! { + $( + #[link(name = "poison_unsafe")] + extern "C" { + // functions in the poison_unsafe lib must have their arguments frozen, which + // is best expressed as accepting the base type instead of the fast type + fn [<$fn _ $base_ty>](a: $base_ty $(, $arg: $base_ty)*) -> $fast_ty; + } + + impl $fast_ty { + #[inline] + pub fn $fn(self $(, $arg: Self)*) -> Self { + unsafe { [<$fn _ $base_ty>](self.freeze_raw() $(, $arg.freeze_raw())*) } + } + } + )* + } + } +} + macro_rules! impl_extern_math { ($fast_ty:ident, $base_ty:ident) => { + poison_safe_fns! { + $fast_ty, $base_ty: + fn abs(self) -> Self; + fn copysign(self, other: Self) -> Self; + fn max(self, other: Self) -> Self; + fn min(self, other: Self) -> Self; + } + + poison_unsafe_fns! { + $fast_ty, $base_ty: + fn acos(self) -> Self; + fn acosh(self) -> Self; + fn asin(self) -> Self; + fn asinh(self) -> Self; + fn atan(self) -> Self; + fn atan2(self, other: Self) -> Self; + fn atanh(self) -> Self; + fn cbrt(self) -> Self; + fn ceil(self) -> Self; + fn cos(self) -> Self; + fn cosh(self) -> Self; + fn exp(self) -> Self; + fn exp2(self) -> Self; + fn floor(self) -> Self; + fn exp_m1(self) -> Self; + fn ln(self) -> Self; + fn ln_1p(self) -> Self; + fn log2(self) -> Self; + fn log10(self) -> Self; + fn powf(self, n: Self) -> Self; + fn round(self) -> Self; + fn sin(self) -> Self; + fn sinh(self) -> Self; + fn sqrt(self) -> Self; + fn tan(self) -> Self; + fn tanh(self) -> Self; + fn trunc(self) -> Self; + } + paste! { + #[link(name = "poison_safe")] extern "C" { fn [](a: $fast_ty, b: $fast_ty) -> $fast_ty; fn [](a: $fast_ty, b: $fast_ty) -> $fast_ty; fn [](a: $fast_ty, b: $fast_ty) -> $fast_ty; fn [](a: $fast_ty, b: $fast_ty) -> $fast_ty; - fn [](a: $fast_ty, b: $fast_ty) -> $fast_ty; - fn [](a: $fast_ty) -> $fast_ty; - fn [](a: $fast_ty, b: $fast_ty) -> $fast_ty; - fn [](a: $fast_ty, b: $fast_ty) -> $fast_ty; + fn [](a: $fast_ty, min: $fast_ty, max: $fast_ty) -> $fast_ty; + fn [](a: $fast_ty, b: i32) -> $fast_ty; + } - fn [](a: $fast_ty) -> $fast_ty; + #[link(name = "poison_unsafe")] + extern "C" { + fn [](a: $base_ty, b: $base_ty) -> $fast_ty; + } + + // a few functions are special cases and aren't defined in submacros + impl $fast_ty { + #[inline] + pub fn clamp(self, min: Self, max: Self) -> Self { + assert!(min <= max); + unsafe { [](self, min, max) } + } + + #[inline] + pub fn powi(self, n: i32) -> Self { + unsafe { [](self, n) } + } } impl_fast_ops! { @@ -166,7 +194,6 @@ macro_rules! impl_extern_math { Sub, sub, [], Mul, mul, [], Div, div, [], - Rem, rem, [], } impl Neg for $fast_ty { @@ -187,28 +214,39 @@ macro_rules! impl_extern_math { } } - impl $fast_ty { - #[inline] - pub fn max(self, other: Self) -> Self { - unsafe { [](self, other) } - } + impl Rem <$fast_ty> for $fast_ty { + type Output = $fast_ty; - #[inline] - pub fn min(self, other: Self) -> Self { - unsafe { [](self, other) } - } - - #[inline] - pub fn sqrt(self) -> Self { - unsafe { [](self) } + #[inline(always)] + fn rem(self, other: $fast_ty) -> Self::Output { + unsafe { [](self.freeze_raw(), other.freeze_raw()) } } } + + impl Rem <$base_ty> for $fast_ty { + type Output = $fast_ty; + + #[inline(always)] + fn rem(self, other: $base_ty) -> Self::Output { + unsafe { [](self.freeze_raw(), other) } + } + } + + impl Rem <$fast_ty> for $base_ty { + type Output = $fast_ty; + + #[inline(always)] + fn rem(self, other: $fast_ty) -> Self::Output { + unsafe { [](self, other.freeze_raw()) } + } + } + + impl_binary_refs! { $fast_ty, $fast_ty, Rem, rem } + impl_binary_refs! { $fast_ty, $base_ty, Rem, rem } + impl_binary_refs! { $base_ty, $fast_ty, Rem, rem } } }; } -impl_generic_math! { FF32, f32, u32 } -impl_generic_math! { FF64, f64, u64 } - impl_extern_math! { FF32, f32 } impl_extern_math! { FF64, f64 } diff --git a/src/math/math.c b/src/math/poison_safe.c similarity index 58% rename from src/math/math.c rename to src/math/poison_safe.c index 7e035c0..ce6e3d5 100644 --- a/src/math/math.c +++ b/src/math/poison_safe.c @@ -1,4 +1,12 @@ -#include +/* + * The functions in this file are ones which can safely accept poison values in + * their input arguments without triggering any UB[1]. Because they can accept + * poison values, any fast-math optimizations are valid for this file, and rust + * code can still safely call it without precautions like freezing. + * + * [1]: https://llvm.org/docs/LangRef.html#poison-values + */ + #include #define IMPL_OPERATIONS(C_TYPE, RUST_TYPE) \ @@ -25,31 +33,6 @@ __attribute__((always_inline)) \ C_TYPE neg_ ## RUST_TYPE(C_TYPE a) { \ return -a; \ - } \ - \ - __attribute__((always_inline)) \ - bool eq_ ## RUST_TYPE(C_TYPE a, C_TYPE b) { \ - return a == b; \ - } \ - \ - __attribute__((always_inline)) \ - bool lt_ ## RUST_TYPE(C_TYPE a, C_TYPE b) { \ - return a < b; \ - } \ - \ - __attribute__((always_inline)) \ - bool le_ ## RUST_TYPE(C_TYPE a, C_TYPE b) { \ - return a <= b; \ - } \ - \ - __attribute__((always_inline)) \ - bool gt_ ## RUST_TYPE(C_TYPE a, C_TYPE b) { \ - return a > b; \ - } \ - \ - __attribute__((always_inline)) \ - bool ge_ ## RUST_TYPE(C_TYPE a, C_TYPE b) { \ - return a >= b; \ } \ #define IMPL_UNARY_FUNCTION(C_TYPE, RUST_TYPE, FN_NAME, FN_IMPL) \ @@ -67,16 +50,49 @@ IMPL_OPERATIONS(float, f32) IMPL_OPERATIONS(double, f64) -// FIXME sqrt is not poison safe on some targets -IMPL_UNARY_FUNCTION(float, f32, sqrt, sqrtf) -IMPL_UNARY_FUNCTION(double, f64, sqrt, sqrt) +IMPL_UNARY_FUNCTION(float, f32, abs, fabsf) +IMPL_UNARY_FUNCTION(double, f64, abs, fabs) -// FIXME mod is not poison safe, though LLVM frem is -IMPL_BINARY_FUNCTION(float, f32, rem, fmodf) -IMPL_BINARY_FUNCTION(double, f64, rem, fmod) +IMPL_BINARY_FUNCTION(float, f32, copysign, copysignf) +IMPL_BINARY_FUNCTION(double, f64, copysign, copysign) IMPL_BINARY_FUNCTION(float, f32, max, fmaxf) IMPL_BINARY_FUNCTION(double, f64, max, fmax) IMPL_BINARY_FUNCTION(float, f32, min, fminf) IMPL_BINARY_FUNCTION(double, f64, min, fmin) + +__attribute__((always_inline)) +float powi_f32(float a, int b) { + return __builtin_powif(a, b); +} + +__attribute__((always_inline)) +double powi_f64(double a, int b) { + return __builtin_powi(a, b); +} + +__attribute__((always_inline)) +float clamp_f32(float a, float min, float max) { + // under -O3 these comparisons are compiled to selects which, unlike + // branches, propagate poison without UB + if(a < min) { + a = min; + } + if(a > max) { + a = max; + } + return a; +} + +__attribute__((always_inline)) +double clamp_f64(double a, double min, double max) { + if(a < min) { + a = min; + } + if(a > max) { + a = max; + } + return a; +} + diff --git a/src/math/poison_unsafe.c b/src/math/poison_unsafe.c new file mode 100644 index 0000000..578fad1 --- /dev/null +++ b/src/math/poison_unsafe.c @@ -0,0 +1,85 @@ +/* + * The functions in this file are ones which *cannot* safely accept poison + * values in their input arguments without potentially triggering UB[1]. To + * make them safe to call from rust, two steps must be taken: + * + * 1. Arguments passed to these functions must be frozen before the call, so + * that poison values do not enter the function. + * + * 2. fast-math flags which potentially produce poison values must be disabled + * when compiling this file, so that poison values cannot be generated within + * them. Currently, this applies only to `finite-math-only`: NaN and +/-inf + * must be honored, because the `finite-math-only` flag is the only fast-math + * flag that can produce LLVM poison as of this writing. + * + * These two constraints potentially prevent some optimizations. However this + * seems like the best compromise between safety and performance, to allow an + * ergonomic rust interface without labelling many methods `unsafe` and + * requiring the users to police their input values. + * + * [1]: https://llvm.org/docs/LangRef.html#poison-values + */ + +#include + +#define IMPL_UNARY_FUNCTION(C_TYPE, RUST_TYPE, FN_NAME, FN_IMPL) \ + __attribute__((always_inline)) \ + C_TYPE FN_NAME ## _ ## RUST_TYPE(C_TYPE a) { \ + return FN_IMPL(a); \ + } \ + +#define IMPL_UNARY(DOUBLE_FN) \ + IMPL_UNARY_FUNCTION(double, f64, DOUBLE_FN, DOUBLE_FN) \ + IMPL_UNARY_FUNCTION(float, f32, DOUBLE_FN, DOUBLE_FN ## f) \ + +#define IMPL_BINARY_FUNCTION(C_TYPE, RUST_TYPE, FN_NAME, FN_IMPL) \ + __attribute__((always_inline)) \ + C_TYPE FN_NAME ## _ ## RUST_TYPE(C_TYPE a, C_TYPE b) { \ + return FN_IMPL(a, b); \ + } \ + +#define IMPL_BINARY(DOUBLE_FN) \ + IMPL_BINARY_FUNCTION(double, f64, DOUBLE_FN, DOUBLE_FN) \ + IMPL_BINARY_FUNCTION(float, f32, DOUBLE_FN, DOUBLE_FN ## f) \ + +IMPL_UNARY(acos) +IMPL_UNARY(acosh) +IMPL_UNARY(asin) +IMPL_UNARY(asinh) +IMPL_UNARY(atan) +IMPL_BINARY(atan2) +IMPL_UNARY(atanh) +IMPL_UNARY(cbrt) +IMPL_UNARY(ceil) +IMPL_UNARY(cos) +IMPL_UNARY(cosh) +IMPL_UNARY(exp) +IMPL_UNARY(exp2) +IMPL_UNARY(floor) + +IMPL_UNARY_FUNCTION(double, f64, exp_m1, expm1) +IMPL_UNARY_FUNCTION(float, f32, exp_m1, expm1f) + +IMPL_BINARY_FUNCTION(double, f64, rem, fmod) +IMPL_BINARY_FUNCTION(float, f32, rem, fmodf) + +IMPL_UNARY_FUNCTION(double, f64, ln, log) +IMPL_UNARY_FUNCTION(float, f32, ln, logf) + +IMPL_UNARY_FUNCTION(double, f64, ln_1p, log1p) +IMPL_UNARY_FUNCTION(float, f32, ln_1p, log1pf) + +IMPL_UNARY(log2) +IMPL_UNARY(log10) + +IMPL_BINARY_FUNCTION(double, f64, powf, pow) +IMPL_BINARY_FUNCTION(float, f32, powf, powf) + +IMPL_UNARY(round) +IMPL_UNARY(sin) +IMPL_UNARY(sinh) +IMPL_UNARY(sqrt) +IMPL_UNARY(tan) +IMPL_UNARY(tanh) +IMPL_UNARY(trunc) + diff --git a/src/poison/freeze.ll b/src/poison/freeze.ll index 0281a44..bfa9a4c 100644 --- a/src/poison/freeze.ll +++ b/src/poison/freeze.ll @@ -8,19 +8,4 @@ define double @freeze_f64(double %a) unnamed_addr #0 { ret double %b } -define i1 @freeze_i1(i1 %a) unnamed_addr #0 { - %b = freeze i1 %a - ret i1 %b -} - -define i32 @freeze_i32(i32 %a) unnamed_addr #0 { - %b = freeze i32 %a - ret i32 %b -} - -define i64 @freeze_i64(i64 %a) unnamed_addr #0 { - %b = freeze i64 %a - ret i64 %b -} - attributes #0 = { alwaysinline nofree norecurse willreturn nosync nounwind readnone } diff --git a/src/poison/mod.rs b/src/poison/mod.rs index 4531084..2691e59 100644 --- a/src/poison/mod.rs +++ b/src/poison/mod.rs @@ -20,40 +20,10 @@ impl MaybePoison { } } -/// A macro to implement poison handling *only* on types which are valid for every bit pattern -macro_rules! impl_maybe_poison { - ($($raw_ty:ty),*) => { - $( - impl MaybePoison<$raw_ty> { - /// Get the (possibly poison) value from this instance. - /// - /// The compiler may relax poison values to undefined values. That means, among other - /// consequences, that calls to this function from copies of the same value could manifest - /// different return values. Poison values are also transitive: an instruction that depends on - /// a poison value, produces a poison value itself. - /// - /// Propogation of poison values can be stopped using [`freeze`](MaybePoison::freeze) - /// - /// # Safety - /// - /// It is UB to use a poison value as an operand to an instruction where _any_ of the operand's - /// values trigger UB. This includes, for example, use as the divisor in integer division, or - /// as the condition of a branch. - /// - /// See more examples and explanations in the [LLVM - /// documentation](https://llvm.org/docs/LangRef.html#poisonvalues) - #[inline(always)] - pub(crate) unsafe fn maybe_poison(self) -> $raw_ty { - *self.0.as_ptr() - } - } - )* - } -} - macro_rules! impl_freeze { ($($raw_ty:ty, $fn_name:ident;)*) => { $( + #[link(name = "freeze")] extern "C" { fn $fn_name(val: MaybePoison<$raw_ty>) -> $raw_ty; } @@ -68,11 +38,7 @@ macro_rules! impl_freeze { } } -impl_maybe_poison! { f32, f64, u32, u64 } impl_freeze! { f32, freeze_f32; f64, freeze_f64; - //u32, freeze_i32; - //u64, freeze_i64; - //bool, freeze_i1; }