Everything in C
This commit is contained in:
31
Cargo.toml
31
Cargo.toml
@@ -18,28 +18,19 @@ name = "math"
|
|||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = [
|
default = ["num-traits"]
|
||||||
"num-traits",
|
|
||||||
"finite-math-only",
|
|
||||||
"associative-math",
|
|
||||||
"reciprocal-math",
|
|
||||||
"no-signed-zeros",
|
|
||||||
"no-trapping-math",
|
|
||||||
"fp-contract-fast",
|
|
||||||
"approx-func",
|
|
||||||
]
|
|
||||||
|
|
||||||
# default fast-math features
|
# disable-able fast-math features
|
||||||
finite-math-only = []
|
no-finite-math-only = []
|
||||||
associative-math = []
|
no-associative-math = []
|
||||||
reciprocal-math = []
|
no-reciprocal-math = []
|
||||||
no-signed-zeros = []
|
signed-zeros = []
|
||||||
no-trapping-math = []
|
trapping-math = []
|
||||||
fp-contract-fast = []
|
fp-contract-on = []
|
||||||
approx-func = []
|
no-approx-func = []
|
||||||
|
math-errno = []
|
||||||
|
|
||||||
# non-default fast-math-like features
|
# TODO denormal-fp-math? can have cpu-wide consequences
|
||||||
denormal-fp-math-preserve-sign = []
|
|
||||||
|
|
||||||
# optional trait implementations
|
# optional trait implementations
|
||||||
nalgebra-v021 = ["num-traits", "nalgebra_v021", "simba_v01", "approx_v03"]
|
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.compiler("clang");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
builder.warnings_into_errors(true);
|
||||||
builder.flag("-flto=thin");
|
builder.flag("-flto=thin");
|
||||||
|
|
||||||
build_ll(builder.clone());
|
build_ll(builder.clone());
|
||||||
@@ -21,32 +22,49 @@ fn build_ll(mut builder: cc::Build) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn build_c(mut builder: cc::Build) {
|
fn build_c(mut builder: cc::Build) {
|
||||||
builder.flag("-O3");
|
builder.opt_level(3);
|
||||||
|
|
||||||
#[cfg(feature = "finite-math-only")]
|
#[cfg(not(feature = "no-associative-math"))]
|
||||||
builder.flag("-ffinite-math-only");
|
|
||||||
|
|
||||||
#[cfg(feature = "associative-math")]
|
|
||||||
builder.flag("-fassociative-math");
|
builder.flag("-fassociative-math");
|
||||||
|
|
||||||
#[cfg(feature = "reciprocal-math")]
|
#[cfg(not(feature = "no-reciprocal-math"))]
|
||||||
builder.flag("-freciprocal-math");
|
builder.flag("-freciprocal-math");
|
||||||
|
|
||||||
#[cfg(feature = "no-signed-zeros")]
|
#[cfg(not(feature = "signed-zeros"))]
|
||||||
builder.flag("-fno-signed-zeros");
|
builder.flag("-fno-signed-zeros");
|
||||||
|
|
||||||
#[cfg(feature = "no-trapping-math")]
|
#[cfg(not(feature = "trapping-math"))]
|
||||||
builder.flag("-fno-trapping-math");
|
builder.flag("-fno-trapping-math");
|
||||||
|
|
||||||
#[cfg(feature = "fp-contract-fast")]
|
#[cfg(not(feature = "fp-contract-on"))]
|
||||||
builder.flag("-ffp-contract=fast");
|
builder.flag("-ffp-contract=fast");
|
||||||
|
|
||||||
// TODO figure out if this works
|
// -fapprox-func isn't currently available in the driver, but it is in clang itself
|
||||||
//#[cfg(feature = "approx-func")]
|
// https://reviews.llvm.org/D106191
|
||||||
//builder.flag("-Xclang -fapprox-func");
|
#[cfg(not(feature = "no-approx-func"))]
|
||||||
|
builder.flag("-Xclang").flag("-fapprox-func");
|
||||||
|
|
||||||
#[cfg(feature = "denormal-fp-math-preserve-sign")]
|
#[cfg(not(feature = "math-errno"))]
|
||||||
builder.flag("-fdenormal-fp-math=preserve-sign");
|
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,
|
// 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.
|
// 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
|
// FIXME more docs
|
||||||
// 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.
|
|
||||||
//
|
//
|
||||||
// Prior art and references
|
// Prior art and references
|
||||||
//
|
//
|
||||||
// https://github.com/rust-lang/rust/issues/21690
|
// https://github.com/rust-lang/rust/issues/21690
|
||||||
// Task for general purpose fast-math in rust lang. Discussions about the right approach
|
// 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
|
// 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
|
// uses types because it's the only option available in user space, and gets good optimizations
|
||||||
// good optimizations useful in practice
|
// useful in practice
|
||||||
//
|
//
|
||||||
// https://docs.rs/fast-floats/0.2.0/fast_floats/index.html
|
// 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,
|
// A crate that wraps fast intrinsics in types. Intrinsics only apply to basic ops, and they didn't
|
||||||
// leaving constructors unsafe
|
// address poison propagation, leaving constructors unsafe
|
||||||
//
|
//
|
||||||
// https://llvm.org/docs/LangRef.html#fast-math-flags
|
// https://llvm.org/docs/LangRef.html#fast-math-flags
|
||||||
// LLVM's documentation on fast-math
|
// LLVM's documentation on fast-math
|
||||||
@@ -227,8 +217,17 @@ macro_rules! impls {
|
|||||||
const ONE: $fast_ty = <$fast_ty>::new(1.0);
|
const ONE: $fast_ty = <$fast_ty>::new(1.0);
|
||||||
const ZERO: $fast_ty = <$fast_ty>::new(0.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 = "Create a new `"]
|
||||||
#[doc= stringify!($fast_ty)]
|
#[doc = stringify!($fast_ty)]
|
||||||
#[doc = "` instance from the given float value."]
|
#[doc = "` instance from the given float value."]
|
||||||
///
|
///
|
||||||
/// The given value **MUST NOT** be infinite or NaN, and any operations involving this value must
|
/// 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 = "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."]
|
#[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
|
/// Note that this check is **not sufficient** to avoid all unspecified outputs, because an
|
||||||
@@ -261,57 +260,12 @@ macro_rules! impls {
|
|||||||
self.0.freeze()
|
self.0.freeze()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO migrate these to native implementations to freeze less and fast-math more
|
|
||||||
forward_freeze_self! {
|
forward_freeze_self! {
|
||||||
$fast_ty, $base_ty
|
$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 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 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_degrees(self) -> Self;
|
||||||
pub fn to_radians(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]
|
#[inline]
|
||||||
@@ -347,14 +301,40 @@ macro_rules! impls {
|
|||||||
self.classify() == FpCategory::Subnormal
|
self.classify() == FpCategory::Subnormal
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The smallest finite value
|
#[inline]
|
||||||
pub const MIN: $fast_ty = <$fast_ty>::new($base_ty::MIN);
|
pub fn hypot(self, other: Self) -> Self {
|
||||||
|
(self * self + other * other).sqrt()
|
||||||
|
}
|
||||||
|
|
||||||
/// The smallest positive value
|
#[inline]
|
||||||
pub const MIN_POSITIVE: $fast_ty = <$fast_ty>::new($base_ty::MIN_POSITIVE);
|
pub fn signum(self) -> Self {
|
||||||
|
Self::ONE.copysign(self)
|
||||||
|
}
|
||||||
|
|
||||||
/// The largest finite value
|
#[inline]
|
||||||
pub const MAX: $fast_ty = <$fast_ty>::new($base_ty::MAX);
|
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! {
|
impl_fmt! {
|
||||||
@@ -417,17 +397,12 @@ macro_rules! impls {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eq for $fast_ty {}
|
|
||||||
|
|
||||||
impl PartialOrd<$fast_ty> for $fast_ty {
|
impl PartialOrd<$fast_ty> for $fast_ty {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn partial_cmp(&self, other: &$fast_ty) -> Option<cmp::Ordering> {
|
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)]
|
#[inline(always)]
|
||||||
fn lt(&self, other: &$fast_ty) -> bool {
|
fn lt(&self, other: &$fast_ty) -> bool {
|
||||||
self.freeze_raw() < other.freeze_raw()
|
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 {
|
impl Ord for $fast_ty {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn cmp(&self, other: &$fast_ty) -> cmp::Ordering {
|
fn cmp(&self, other: &$fast_ty) -> cmp::Ordering {
|
||||||
|
|||||||
232
src/math/mod.rs
232
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 core::ops::{Add, Div, Mul, Neg, Rem, Sub};
|
||||||
use paste::paste;
|
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 {
|
macro_rules! impl_binary_refs {
|
||||||
($lhs:ident, $rhs:ident, $op_trait:ident, $op_fn:ident) => {
|
($lhs:ident, $rhs:ident, $op_trait:ident, $op_fn:ident) => {
|
||||||
impl $op_trait<$rhs> for &$lhs {
|
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 {
|
macro_rules! impl_extern_math {
|
||||||
($fast_ty:ident, $base_ty:ident) => {
|
($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! {
|
paste! {
|
||||||
|
#[link(name = "poison_safe")]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
fn [<add_ $base_ty>](a: $fast_ty, b: $fast_ty) -> $fast_ty;
|
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 [<sub_ $base_ty>](a: $fast_ty, b: $fast_ty) -> $fast_ty;
|
||||||
fn [<mul_ $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 [<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 [<neg_ $base_ty>](a: $fast_ty) -> $fast_ty;
|
||||||
|
|
||||||
fn [<min_ $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 [<max_ $base_ty>](a: $fast_ty, b: $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! {
|
impl_fast_ops! {
|
||||||
@@ -166,7 +194,6 @@ macro_rules! impl_extern_math {
|
|||||||
Sub, sub, [<sub_ $base_ty>],
|
Sub, sub, [<sub_ $base_ty>],
|
||||||
Mul, mul, [<mul_ $base_ty>],
|
Mul, mul, [<mul_ $base_ty>],
|
||||||
Div, div, [<div_ $base_ty>],
|
Div, div, [<div_ $base_ty>],
|
||||||
Rem, rem, [<rem_ $base_ty>],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Neg for $fast_ty {
|
impl Neg for $fast_ty {
|
||||||
@@ -187,28 +214,39 @@ macro_rules! impl_extern_math {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl $fast_ty {
|
impl Rem <$fast_ty> for $fast_ty {
|
||||||
#[inline]
|
type Output = $fast_ty;
|
||||||
pub fn max(self, other: Self) -> Self {
|
|
||||||
unsafe { [<max_ $base_ty>](self, other) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline(always)]
|
||||||
pub fn min(self, other: Self) -> Self {
|
fn rem(self, other: $fast_ty) -> Self::Output {
|
||||||
unsafe { [<min_ $base_ty>](self, other) }
|
unsafe { [<rem_ $base_ty>](self.freeze_raw(), other.freeze_raw()) }
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn sqrt(self) -> Self {
|
|
||||||
unsafe { [<sqrt_ $base_ty>](self) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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! { FF32, f32 }
|
||||||
impl_extern_math! { FF64, f64 }
|
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>
|
#include <math.h>
|
||||||
|
|
||||||
#define IMPL_OPERATIONS(C_TYPE, RUST_TYPE) \
|
#define IMPL_OPERATIONS(C_TYPE, RUST_TYPE) \
|
||||||
@@ -25,31 +33,6 @@
|
|||||||
__attribute__((always_inline)) \
|
__attribute__((always_inline)) \
|
||||||
C_TYPE neg_ ## RUST_TYPE(C_TYPE a) { \
|
C_TYPE neg_ ## RUST_TYPE(C_TYPE a) { \
|
||||||
return -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) \
|
#define IMPL_UNARY_FUNCTION(C_TYPE, RUST_TYPE, FN_NAME, FN_IMPL) \
|
||||||
@@ -67,16 +50,49 @@
|
|||||||
IMPL_OPERATIONS(float, f32)
|
IMPL_OPERATIONS(float, f32)
|
||||||
IMPL_OPERATIONS(double, f64)
|
IMPL_OPERATIONS(double, f64)
|
||||||
|
|
||||||
// FIXME sqrt is not poison safe on some targets
|
IMPL_UNARY_FUNCTION(float, f32, abs, fabsf)
|
||||||
IMPL_UNARY_FUNCTION(float, f32, sqrt, sqrtf)
|
IMPL_UNARY_FUNCTION(double, f64, abs, fabs)
|
||||||
IMPL_UNARY_FUNCTION(double, f64, sqrt, sqrt)
|
|
||||||
|
|
||||||
// FIXME mod is not poison safe, though LLVM frem is
|
IMPL_BINARY_FUNCTION(float, f32, copysign, copysignf)
|
||||||
IMPL_BINARY_FUNCTION(float, f32, rem, fmodf)
|
IMPL_BINARY_FUNCTION(double, f64, copysign, copysign)
|
||||||
IMPL_BINARY_FUNCTION(double, f64, rem, fmod)
|
|
||||||
|
|
||||||
IMPL_BINARY_FUNCTION(float, f32, max, fmaxf)
|
IMPL_BINARY_FUNCTION(float, f32, max, fmaxf)
|
||||||
IMPL_BINARY_FUNCTION(double, f64, max, fmax)
|
IMPL_BINARY_FUNCTION(double, f64, max, fmax)
|
||||||
|
|
||||||
IMPL_BINARY_FUNCTION(float, f32, min, fminf)
|
IMPL_BINARY_FUNCTION(float, f32, min, fminf)
|
||||||
IMPL_BINARY_FUNCTION(double, f64, min, fmin)
|
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
|
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 }
|
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 {
|
macro_rules! impl_freeze {
|
||||||
($($raw_ty:ty, $fn_name:ident;)*) => {
|
($($raw_ty:ty, $fn_name:ident;)*) => {
|
||||||
$(
|
$(
|
||||||
|
#[link(name = "freeze")]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
fn $fn_name(val: MaybePoison<$raw_ty>) -> $raw_ty;
|
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! {
|
impl_freeze! {
|
||||||
f32, freeze_f32;
|
f32, freeze_f32;
|
||||||
f64, freeze_f64;
|
f64, freeze_f64;
|
||||||
//u32, freeze_i32;
|
|
||||||
//u64, freeze_i64;
|
|
||||||
//bool, freeze_i1;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user