Everything in C
This commit is contained in:
31
Cargo.toml
31
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"]
|
||||
|
||||
48
build.rs
48
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")
|
||||
}
|
||||
|
||||
126
src/lib.rs
126
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<cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
<$base_ty>::partial_cmp(&self.freeze_raw(), &other.freeze_raw())
|
||||
}
|
||||
|
||||
// TODO specialize a MaybePoison<bool> 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 {
|
||||
|
||||
226
src/math/mod.rs
226
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 [<add_ $base_ty>](a: $fast_ty, b: $fast_ty) -> $fast_ty;
|
||||
fn [<sub_ $base_ty>](a: $fast_ty, b: $fast_ty) -> $fast_ty;
|
||||
fn [<mul_ $base_ty>](a: $fast_ty, b: $fast_ty) -> $fast_ty;
|
||||
fn [<div_ $base_ty>](a: $fast_ty, b: $fast_ty) -> $fast_ty;
|
||||
fn [<rem_ $base_ty>](a: $fast_ty, b: $fast_ty) -> $fast_ty;
|
||||
|
||||
fn [<neg_ $base_ty>](a: $fast_ty) -> $fast_ty;
|
||||
|
||||
fn [<min_ $base_ty>](a: $fast_ty, b: $fast_ty) -> $fast_ty;
|
||||
fn [<max_ $base_ty>](a: $fast_ty, b: $fast_ty) -> $fast_ty;
|
||||
fn [<clamp_ $base_ty>](a: $fast_ty, min: $fast_ty, max: $fast_ty) -> $fast_ty;
|
||||
fn [<powi_ $base_ty>](a: $fast_ty, b: i32) -> $fast_ty;
|
||||
}
|
||||
|
||||
fn [<sqrt_ $base_ty>](a: $fast_ty) -> $fast_ty;
|
||||
#[link(name = "poison_unsafe")]
|
||||
extern "C" {
|
||||
fn [<rem_ $base_ty>](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 { [<clamp_ $base_ty>](self, min, max) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn powi(self, n: i32) -> Self {
|
||||
unsafe { [<powi_ $base_ty>](self, n) }
|
||||
}
|
||||
}
|
||||
|
||||
impl_fast_ops! {
|
||||
@@ -166,7 +194,6 @@ macro_rules! impl_extern_math {
|
||||
Sub, sub, [<sub_ $base_ty>],
|
||||
Mul, mul, [<mul_ $base_ty>],
|
||||
Div, div, [<div_ $base_ty>],
|
||||
Rem, rem, [<rem_ $base_ty>],
|
||||
}
|
||||
|
||||
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 { [<max_ $base_ty>](self, other) }
|
||||
impl Rem <$fast_ty> for $fast_ty {
|
||||
type Output = $fast_ty;
|
||||
|
||||
#[inline(always)]
|
||||
fn rem(self, other: $fast_ty) -> Self::Output {
|
||||
unsafe { [<rem_ $base_ty>](self.freeze_raw(), other.freeze_raw()) }
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn min(self, other: Self) -> Self {
|
||||
unsafe { [<min_ $base_ty>](self, other) }
|
||||
impl Rem <$base_ty> for $fast_ty {
|
||||
type Output = $fast_ty;
|
||||
|
||||
#[inline(always)]
|
||||
fn rem(self, other: $base_ty) -> Self::Output {
|
||||
unsafe { [<rem_ $base_ty>](self.freeze_raw(), other) }
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sqrt(self) -> Self {
|
||||
unsafe { [<sqrt_ $base_ty>](self) }
|
||||
impl Rem <$fast_ty> for $base_ty {
|
||||
type Output = $fast_ty;
|
||||
|
||||
#[inline(always)]
|
||||
fn rem(self, other: $fast_ty) -> Self::Output {
|
||||
unsafe { [<rem_ $base_ty>](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 }
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
#include <stdbool.h>
|
||||
/*
|
||||
* 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 <math.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
85
src/math/poison_unsafe.c
Normal file
85
src/math/poison_unsafe.c
Normal file
@@ -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 <math.h>
|
||||
|
||||
#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)
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -20,40 +20,10 @@ impl<T> MaybePoison<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user