Implement nondet behaviour and change/add tests.
This commit is contained in:
parent
fd479ca23c
commit
dcc342f449
4 changed files with 375 additions and 169 deletions
|
|
@ -13,7 +13,6 @@ use rustc_span::{Symbol, sym};
|
|||
use self::atomic::EvalContextExt as _;
|
||||
use self::helpers::{ToHost, ToSoft};
|
||||
use self::simd::EvalContextExt as _;
|
||||
use crate::math::apply_random_float_error_ulp;
|
||||
use crate::*;
|
||||
|
||||
/// Check that the number of args is what we expect.
|
||||
|
|
@ -362,13 +361,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
let f = this.read_scalar(f)?.to_f32()?;
|
||||
let i = this.read_scalar(i)?.to_i32()?;
|
||||
|
||||
let res = math::fixed_powi_float_value(this, f, i).unwrap_or_else(|| {
|
||||
let res = math::fixed_powi_value(this, f, i).unwrap_or_else(|| {
|
||||
// Using host floats (but it's fine, this operation does not have guaranteed precision).
|
||||
let res = f.to_host().powi(i).to_soft();
|
||||
|
||||
// Apply a relative error of 4ULP to introduce some non-determinism
|
||||
// simulating imprecise implementations and optimizations.
|
||||
apply_random_float_error_ulp(this, res, 4)
|
||||
math::apply_random_float_error_ulp(this, res, 4)
|
||||
});
|
||||
let res = this.adjust_nan(res, &[f]);
|
||||
this.write_scalar(res, dest)?;
|
||||
|
|
@ -378,7 +377,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
let f = this.read_scalar(f)?.to_f64()?;
|
||||
let i = this.read_scalar(i)?.to_i32()?;
|
||||
|
||||
let res = math::fixed_powi_float_value(this, f, i).unwrap_or_else(|| {
|
||||
let res = math::fixed_powi_value(this, f, i).unwrap_or_else(|| {
|
||||
// Using host floats (but it's fine, this operation does not have guaranteed precision).
|
||||
let res = f.to_host().powi(i).to_soft();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
use std::ops::Neg;
|
||||
use std::{f32, f64};
|
||||
|
||||
use rand::Rng as _;
|
||||
use rustc_apfloat::Float as _;
|
||||
use rustc_apfloat::ieee::{IeeeFloat, Semantics};
|
||||
use rustc_apfloat::ieee::{DoubleS, IeeeFloat, Semantics, SingleS};
|
||||
use rustc_middle::ty::{self, FloatTy, ScalarInt};
|
||||
|
||||
use crate::*;
|
||||
|
|
@ -107,29 +108,69 @@ pub(crate) fn apply_random_float_error_to_imm<'tcx>(
|
|||
interp_ok(ImmTy::from_scalar_int(res, val.layout))
|
||||
}
|
||||
|
||||
/// Given an floating-point operation and a floating-point value, clamps the result to the output
|
||||
/// range of the given operation.
|
||||
/// Given a floating-point operation and a floating-point value, clamps the result to the output
|
||||
/// range of the given operation according to the C standard, if any.
|
||||
pub(crate) fn clamp_float_value<S: Semantics>(
|
||||
intrinsic_name: &str,
|
||||
val: IeeeFloat<S>,
|
||||
) -> IeeeFloat<S> {
|
||||
) -> IeeeFloat<S>
|
||||
where
|
||||
IeeeFloat<S>: IeeeExt,
|
||||
{
|
||||
let zero = IeeeFloat::<S>::ZERO;
|
||||
let one = IeeeFloat::<S>::one();
|
||||
let two = IeeeFloat::<S>::two();
|
||||
let pi = IeeeFloat::<S>::pi();
|
||||
let pi_over_2 = (pi / two).value;
|
||||
|
||||
match intrinsic_name {
|
||||
// sin and cos: [-1, 1]
|
||||
"sinf32" | "cosf32" | "sinf64" | "cosf64" =>
|
||||
val.clamp(IeeeFloat::<S>::one().neg(), IeeeFloat::<S>::one()),
|
||||
// exp: [0, +INF]
|
||||
"expf32" | "exp2f32" | "expf64" | "exp2f64" =>
|
||||
IeeeFloat::<S>::maximum(val, IeeeFloat::<S>::ZERO),
|
||||
// sin, cos, tanh: [-1, 1]
|
||||
#[rustfmt::skip]
|
||||
| "sinf32"
|
||||
| "sinf64"
|
||||
| "cosf32"
|
||||
| "cosf64"
|
||||
| "tanhf"
|
||||
| "tanh"
|
||||
=> val.clamp(one.neg(), one),
|
||||
|
||||
// exp: [0, +INF)
|
||||
"expf32" | "exp2f32" | "expf64" | "exp2f64" => val.maximum(zero),
|
||||
|
||||
// cosh: [1, +INF)
|
||||
"coshf" | "cosh" => val.maximum(one),
|
||||
|
||||
// acos: [0, π]
|
||||
"acosf" | "acos" => val.clamp(zero, pi),
|
||||
|
||||
// asin: [-π, +π]
|
||||
"asinf" | "asin" => val.clamp(pi.neg(), pi),
|
||||
|
||||
// atan: (-π/2, +π/2)
|
||||
"atanf" | "atan" => val.clamp(pi_over_2.neg(), pi_over_2),
|
||||
|
||||
// erfc: (-1, 1)
|
||||
"erff" | "erf" => val.clamp(one.neg(), one),
|
||||
|
||||
// erfc: (0, 2)
|
||||
"erfcf" | "erfc" => val.clamp(zero, two),
|
||||
|
||||
// atan2(y, x): arctan(y/x) in [−π, +π]
|
||||
"atan2f" | "atan2" => val.clamp(pi.neg(), pi),
|
||||
|
||||
_ => val,
|
||||
}
|
||||
}
|
||||
|
||||
/// For the intrinsics:
|
||||
/// - sinf32, sinf64
|
||||
/// - cosf32, cosf64
|
||||
/// - sinf32, sinf64, sinhf, sinh
|
||||
/// - cosf32, cosf64, coshf, cosh
|
||||
/// - tanhf, tanh, atanf, atan, atan2f, atan2
|
||||
/// - expf32, expf64, exp2f32, exp2f64
|
||||
/// - logf32, logf64, log2f32, log2f64, log10f32, log10f64
|
||||
/// - powf32, powf64
|
||||
/// - erff, erf, erfcf, erfc
|
||||
/// - hypotf, hypot
|
||||
///
|
||||
/// # Return
|
||||
///
|
||||
|
|
@ -157,16 +198,68 @@ pub(crate) fn fixed_float_value<S: Semantics>(
|
|||
ecx: &mut MiriInterpCx<'_>,
|
||||
intrinsic_name: &str,
|
||||
args: &[IeeeFloat<S>],
|
||||
) -> Option<IeeeFloat<S>> {
|
||||
) -> Option<IeeeFloat<S>>
|
||||
where
|
||||
IeeeFloat<S>: IeeeExt,
|
||||
{
|
||||
let this = ecx.eval_context_mut();
|
||||
let one = IeeeFloat::<S>::one();
|
||||
let two = IeeeFloat::<S>::two();
|
||||
let three = IeeeFloat::<S>::three();
|
||||
let pi = IeeeFloat::<S>::pi();
|
||||
let pi_over_2 = (pi / two).value;
|
||||
let pi_over_4 = (pi_over_2 / two).value;
|
||||
|
||||
Some(match (intrinsic_name, args) {
|
||||
// cos(+- 0) = 1
|
||||
("cosf32" | "cosf64", [input]) if input.is_zero() => one,
|
||||
// cos(±0) and cosh(±0)= 1
|
||||
("cosf32" | "cosf64" | "coshf" | "cosh", [input]) if input.is_zero() => one,
|
||||
|
||||
// e^0 = 1
|
||||
("expf32" | "expf64" | "exp2f32" | "exp2f64", [input]) if input.is_zero() => one,
|
||||
|
||||
// tanh(±INF) = ±1
|
||||
("tanhf" | "tanh", [input]) if input.is_infinite() => one.copy_sign(*input),
|
||||
|
||||
// atan(±INF) = ±π/2
|
||||
("atanf" | "atan", [input]) if input.is_infinite() => pi_over_2.copy_sign(*input),
|
||||
|
||||
// erf(±INF) = ±1
|
||||
("erff" | "erf", [input]) if input.is_infinite() => one.copy_sign(*input),
|
||||
|
||||
// erfc(-INF) = 2
|
||||
("erfcf" | "erfc", [input]) if input.is_neg_infinity() => (one + one).value,
|
||||
|
||||
// hypot(x, ±0) = abs(x), if x is not a NaN.
|
||||
("_hypotf" | "hypotf" | "_hypot" | "hypot", [x, y]) if !x.is_nan() && y.is_zero() =>
|
||||
x.abs(),
|
||||
|
||||
// atan2(±0,−0) = ±π.
|
||||
// atan2(±0, y) = ±π for y < 0.
|
||||
// Must check for non NaN because `y.is_negative()` also applies to NaN.
|
||||
("atan2f" | "atan2", [x, y]) if (x.is_zero() && (y.is_negative() && !y.is_nan())) =>
|
||||
pi.copy_sign(*x),
|
||||
|
||||
// atan2(±x,−∞) = ±π for finite x > 0.
|
||||
("atan2f" | "atan2", [x, y])
|
||||
if (!x.is_zero() && !x.is_infinite()) && y.is_neg_infinity() =>
|
||||
pi.copy_sign(*x),
|
||||
|
||||
// atan2(x, ±0) = −π/2 for x < 0.
|
||||
// atan2(x, ±0) = π/2 for x > 0.
|
||||
("atan2f" | "atan2", [x, y]) if !x.is_zero() && y.is_zero() => pi_over_2.copy_sign(*x),
|
||||
|
||||
//atan2(±∞, −∞) = ±3π/4
|
||||
("atan2f" | "atan2", [x, y]) if x.is_infinite() && y.is_neg_infinity() =>
|
||||
(pi_over_4 * three).value.copy_sign(*x),
|
||||
|
||||
//atan2(±∞, +∞) = ±π/4
|
||||
("atan2f" | "atan2", [x, y]) if x.is_infinite() && y.is_pos_infinity() =>
|
||||
pi_over_4.copy_sign(*x),
|
||||
|
||||
// atan2(±∞, y) returns ±π/2 for finite y.
|
||||
("atan2f" | "atan2", [x, y]) if x.is_infinite() && (!y.is_infinite() && !y.is_nan()) =>
|
||||
pi_over_2.copy_sign(*x),
|
||||
|
||||
// (-1)^(±INF) = 1
|
||||
("powf32" | "powf64", [base, exp]) if *base == -one && exp.is_infinite() => one,
|
||||
|
||||
|
|
@ -196,25 +289,27 @@ pub(crate) fn fixed_float_value<S: Semantics>(
|
|||
|
||||
/// Returns `Some(output)` if `powi` (called `pown` in C) results in a fixed value specified in the
|
||||
/// C standard (specifically, C23 annex F.10.4.6) when doing `base^exp`. Otherwise, returns `None`.
|
||||
pub(crate) fn fixed_powi_float_value<S: Semantics>(
|
||||
pub(crate) fn fixed_powi_value<S: Semantics>(
|
||||
ecx: &mut MiriInterpCx<'_>,
|
||||
base: IeeeFloat<S>,
|
||||
exp: i32,
|
||||
) -> Option<IeeeFloat<S>> {
|
||||
let this = ecx.eval_context_mut();
|
||||
Some(match exp {
|
||||
) -> Option<IeeeFloat<S>>
|
||||
where
|
||||
IeeeFloat<S>: IeeeExt,
|
||||
{
|
||||
match exp {
|
||||
0 => {
|
||||
let one = IeeeFloat::<S>::one();
|
||||
let rng = this.machine.rng.get_mut();
|
||||
let return_nan = this.machine.float_nondet && rng.random() && base.is_signaling();
|
||||
let rng = ecx.machine.rng.get_mut();
|
||||
let return_nan = ecx.machine.float_nondet && rng.random() && base.is_signaling();
|
||||
// For SNaN treatment, we are consistent with `powf`above.
|
||||
// (We wouldn't have two, unlike powf all implementations seem to agree for powi,
|
||||
// but for now we are maximally conservative.)
|
||||
if return_nan { this.generate_nan(&[base]) } else { one }
|
||||
Some(if return_nan { ecx.generate_nan(&[base]) } else { one })
|
||||
}
|
||||
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn sqrt<S: rustc_apfloat::ieee::Semantics>(x: IeeeFloat<S>) -> IeeeFloat<S> {
|
||||
|
|
@ -299,19 +394,47 @@ pub(crate) fn sqrt<S: rustc_apfloat::ieee::Semantics>(x: IeeeFloat<S>) -> IeeeFl
|
|||
}
|
||||
}
|
||||
|
||||
/// Extend functionality of rustc_apfloat softfloats
|
||||
/// Extend functionality of `rustc_apfloat` softfloats for IEEE float types.
|
||||
pub trait IeeeExt: rustc_apfloat::Float {
|
||||
// Some values we use:
|
||||
|
||||
#[inline]
|
||||
fn one() -> Self {
|
||||
Self::from_u128(1).value
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn two() -> Self {
|
||||
Self::from_u128(2).value
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn three() -> Self {
|
||||
Self::from_u128(3).value
|
||||
}
|
||||
|
||||
fn pi() -> Self;
|
||||
|
||||
#[inline]
|
||||
fn clamp(self, min: Self, max: Self) -> Self {
|
||||
self.maximum(min).minimum(max)
|
||||
}
|
||||
}
|
||||
impl<S: rustc_apfloat::ieee::Semantics> IeeeExt for IeeeFloat<S> {}
|
||||
|
||||
macro_rules! impl_ieee_pi {
|
||||
($float_ty:ident, $semantic:ty) => {
|
||||
impl IeeeExt for IeeeFloat<$semantic> {
|
||||
#[inline]
|
||||
fn pi() -> Self {
|
||||
// We take the value from the standard library as the most reasonable source for an exact π here.
|
||||
Self::from_bits($float_ty::consts::PI.to_bits().into())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_ieee_pi!(f32, SingleS);
|
||||
impl_ieee_pi!(f64, DoubleS);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ use rustc_target::callconv::FnAbi;
|
|||
use self::helpers::{ToHost, ToSoft};
|
||||
use super::alloc::EvalContextExt as _;
|
||||
use super::backtrace::EvalContextExt as _;
|
||||
use crate::helpers::EvalContextExt as _;
|
||||
use crate::*;
|
||||
|
||||
/// Type of dynamic symbols (for `dlsym` et al)
|
||||
|
|
@ -826,33 +827,36 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
=> {
|
||||
let [f] = this.check_shim_sig_lenient(abi, CanonAbi::C , link_name, args)?;
|
||||
let f = this.read_scalar(f)?.to_f32()?;
|
||||
// Using host floats (but it's fine, these operations do not have guaranteed precision).
|
||||
let f_host = f.to_host();
|
||||
let res = match link_name.as_str() {
|
||||
"cbrtf" => f_host.cbrt(),
|
||||
"coshf" => f_host.cosh(),
|
||||
"sinhf" => f_host.sinh(),
|
||||
"tanf" => f_host.tan(),
|
||||
"tanhf" => f_host.tanh(),
|
||||
"acosf" => f_host.acos(),
|
||||
"asinf" => f_host.asin(),
|
||||
"atanf" => f_host.atan(),
|
||||
"log1pf" => f_host.ln_1p(),
|
||||
"expm1f" => f_host.exp_m1(),
|
||||
"tgammaf" => f_host.gamma(),
|
||||
"erff" => f_host.erf(),
|
||||
"erfcf" => f_host.erfc(),
|
||||
_ => bug!(),
|
||||
};
|
||||
let res = res.to_soft();
|
||||
// Apply a relative error of 16ULP to introduce some non-determinism
|
||||
// simulating imprecise implementations and optimizations.
|
||||
// FIXME: temporarily disabled as it breaks std tests.
|
||||
// let res = math::apply_random_float_error_ulp(
|
||||
// this,
|
||||
// res,
|
||||
// 4, // log2(16)
|
||||
// );
|
||||
|
||||
let res = math::fixed_float_value(this, link_name.as_str(), &[f]).unwrap_or_else(|| {
|
||||
// Using host floats (but it's fine, these operations do not have
|
||||
// guaranteed precision).
|
||||
let f_host = f.to_host();
|
||||
let res = match link_name.as_str() {
|
||||
"cbrtf" => f_host.cbrt(),
|
||||
"coshf" => f_host.cosh(),
|
||||
"sinhf" => f_host.sinh(),
|
||||
"tanf" => f_host.tan(),
|
||||
"tanhf" => f_host.tanh(),
|
||||
"acosf" => f_host.acos(),
|
||||
"asinf" => f_host.asin(),
|
||||
"atanf" => f_host.atan(),
|
||||
"log1pf" => f_host.ln_1p(),
|
||||
"expm1f" => f_host.exp_m1(),
|
||||
"tgammaf" => f_host.gamma(),
|
||||
"erff" => f_host.erf(),
|
||||
"erfcf" => f_host.erfc(),
|
||||
_ => bug!(),
|
||||
};
|
||||
let res = res.to_soft();
|
||||
// Apply a relative error of 4ULP to introduce some non-determinism
|
||||
// simulating imprecise implementations and optimizations.
|
||||
let res = math::apply_random_float_error_ulp(this, res, 4);
|
||||
|
||||
// Clamp the result to the guaranteed range of this function according to the C standard,
|
||||
// if any.
|
||||
math::clamp_float_value(link_name.as_str(), res)
|
||||
});
|
||||
let res = this.adjust_nan(res, &[f]);
|
||||
this.write_scalar(res, dest)?;
|
||||
}
|
||||
|
|
@ -865,24 +869,27 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
let [f1, f2] = this.check_shim_sig_lenient(abi, CanonAbi::C , link_name, args)?;
|
||||
let f1 = this.read_scalar(f1)?.to_f32()?;
|
||||
let f2 = this.read_scalar(f2)?.to_f32()?;
|
||||
// underscore case for windows, here and below
|
||||
// (see https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/floating-point-primitives?view=vs-2019)
|
||||
// Using host floats (but it's fine, these operations do not have guaranteed precision).
|
||||
let res = match link_name.as_str() {
|
||||
"_hypotf" | "hypotf" => f1.to_host().hypot(f2.to_host()).to_soft(),
|
||||
"atan2f" => f1.to_host().atan2(f2.to_host()).to_soft(),
|
||||
#[allow(deprecated)]
|
||||
"fdimf" => f1.to_host().abs_sub(f2.to_host()).to_soft(),
|
||||
_ => bug!(),
|
||||
};
|
||||
// Apply a relative error of 16ULP to introduce some non-determinism
|
||||
// simulating imprecise implementations and optimizations.
|
||||
// FIXME: temporarily disabled as it breaks std tests.
|
||||
// let res = math::apply_random_float_error_ulp(
|
||||
// this,
|
||||
// res,
|
||||
// 4, // log2(16)
|
||||
// );
|
||||
|
||||
let res = math::fixed_float_value(this, link_name.as_str(), &[f1, f2])
|
||||
.unwrap_or_else(|| {
|
||||
let res = match link_name.as_str() {
|
||||
// underscore case for windows, here and below
|
||||
// (see https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/floating-point-primitives?view=vs-2019)
|
||||
// Using host floats (but it's fine, these operations do not have guaranteed precision).
|
||||
"_hypotf" | "hypotf" => f1.to_host().hypot(f2.to_host()).to_soft(),
|
||||
"atan2f" => f1.to_host().atan2(f2.to_host()).to_soft(),
|
||||
#[allow(deprecated)]
|
||||
"fdimf" => f1.to_host().abs_sub(f2.to_host()).to_soft(),
|
||||
_ => bug!(),
|
||||
};
|
||||
// Apply a relative error of 4ULP to introduce some non-determinism
|
||||
// simulating imprecise implementations and optimizations.
|
||||
let res = math::apply_random_float_error_ulp(this, res, 4);
|
||||
|
||||
// Clamp the result to the guaranteed range of this function according to the C standard,
|
||||
// if any.
|
||||
math::clamp_float_value(link_name.as_str(), res)
|
||||
});
|
||||
let res = this.adjust_nan(res, &[f1, f2]);
|
||||
this.write_scalar(res, dest)?;
|
||||
}
|
||||
|
|
@ -903,33 +910,36 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
=> {
|
||||
let [f] = this.check_shim_sig_lenient(abi, CanonAbi::C , link_name, args)?;
|
||||
let f = this.read_scalar(f)?.to_f64()?;
|
||||
// Using host floats (but it's fine, these operations do not have guaranteed precision).
|
||||
let f_host = f.to_host();
|
||||
let res = match link_name.as_str() {
|
||||
"cbrt" => f_host.cbrt(),
|
||||
"cosh" => f_host.cosh(),
|
||||
"sinh" => f_host.sinh(),
|
||||
"tan" => f_host.tan(),
|
||||
"tanh" => f_host.tanh(),
|
||||
"acos" => f_host.acos(),
|
||||
"asin" => f_host.asin(),
|
||||
"atan" => f_host.atan(),
|
||||
"log1p" => f_host.ln_1p(),
|
||||
"expm1" => f_host.exp_m1(),
|
||||
"tgamma" => f_host.gamma(),
|
||||
"erf" => f_host.erf(),
|
||||
"erfc" => f_host.erfc(),
|
||||
_ => bug!(),
|
||||
};
|
||||
let res = res.to_soft();
|
||||
// Apply a relative error of 16ULP to introduce some non-determinism
|
||||
// simulating imprecise implementations and optimizations.
|
||||
// FIXME: temporarily disabled as it breaks std tests.
|
||||
// let res = math::apply_random_float_error_ulp(
|
||||
// this,
|
||||
// res.to_soft(),
|
||||
// 4, // log2(16)
|
||||
// );
|
||||
|
||||
let res = math::fixed_float_value(this, link_name.as_str(), &[f]).unwrap_or_else(|| {
|
||||
// Using host floats (but it's fine, these operations do not have
|
||||
// guaranteed precision).
|
||||
let f_host = f.to_host();
|
||||
let res = match link_name.as_str() {
|
||||
"cbrt" => f_host.cbrt(),
|
||||
"cosh" => f_host.cosh(),
|
||||
"sinh" => f_host.sinh(),
|
||||
"tan" => f_host.tan(),
|
||||
"tanh" => f_host.tanh(),
|
||||
"acos" => f_host.acos(),
|
||||
"asin" => f_host.asin(),
|
||||
"atan" => f_host.atan(),
|
||||
"log1p" => f_host.ln_1p(),
|
||||
"expm1" => f_host.exp_m1(),
|
||||
"tgamma" => f_host.gamma(),
|
||||
"erf" => f_host.erf(),
|
||||
"erfc" => f_host.erfc(),
|
||||
_ => bug!(),
|
||||
};
|
||||
let res = res.to_soft();
|
||||
// Apply a relative error of 4ULP to introduce some non-determinism
|
||||
// simulating imprecise implementations and optimizations.
|
||||
let res = math::apply_random_float_error_ulp(this, res, 4);
|
||||
|
||||
// Clamp the result to the guaranteed range of this function according to the C standard,
|
||||
// if any.
|
||||
math::clamp_float_value(link_name.as_str(), res)
|
||||
});
|
||||
let res = this.adjust_nan(res, &[f]);
|
||||
this.write_scalar(res, dest)?;
|
||||
}
|
||||
|
|
@ -942,24 +952,26 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
let [f1, f2] = this.check_shim_sig_lenient(abi, CanonAbi::C , link_name, args)?;
|
||||
let f1 = this.read_scalar(f1)?.to_f64()?;
|
||||
let f2 = this.read_scalar(f2)?.to_f64()?;
|
||||
// underscore case for windows, here and below
|
||||
// (see https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/floating-point-primitives?view=vs-2019)
|
||||
// Using host floats (but it's fine, these operations do not have guaranteed precision).
|
||||
let res = match link_name.as_str() {
|
||||
"_hypot" | "hypot" => f1.to_host().hypot(f2.to_host()).to_soft(),
|
||||
"atan2" => f1.to_host().atan2(f2.to_host()).to_soft(),
|
||||
#[allow(deprecated)]
|
||||
"fdim" => f1.to_host().abs_sub(f2.to_host()).to_soft(),
|
||||
_ => bug!(),
|
||||
};
|
||||
// Apply a relative error of 16ULP to introduce some non-determinism
|
||||
// simulating imprecise implementations and optimizations.
|
||||
// FIXME: temporarily disabled as it breaks std tests.
|
||||
// let res = math::apply_random_float_error_ulp(
|
||||
// this,
|
||||
// res,
|
||||
// 4, // log2(16)
|
||||
// );
|
||||
|
||||
let res = math::fixed_float_value(this, link_name.as_str(), &[f1, f2]).unwrap_or_else(|| {
|
||||
let res = match link_name.as_str() {
|
||||
// underscore case for windows, here and below
|
||||
// (see https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/floating-point-primitives?view=vs-2019)
|
||||
// Using host floats (but it's fine, these operations do not have guaranteed precision).
|
||||
"_hypot" | "hypot" => f1.to_host().hypot(f2.to_host()).to_soft(),
|
||||
"atan2" => f1.to_host().atan2(f2.to_host()).to_soft(),
|
||||
#[allow(deprecated)]
|
||||
"fdim" => f1.to_host().abs_sub(f2.to_host()).to_soft(),
|
||||
_ => bug!(),
|
||||
};
|
||||
// Apply a relative error of 4ULP to introduce some non-determinism
|
||||
// simulating imprecise implementations and optimizations.
|
||||
let res = math::apply_random_float_error_ulp(this, res, 4);
|
||||
|
||||
// Clamp the result to the guaranteed range of this function according to the C standard,
|
||||
// if any.
|
||||
math::clamp_float_value(link_name.as_str(), res)
|
||||
});
|
||||
let res = this.adjust_nan(res, &[f1, f2]);
|
||||
this.write_scalar(res, dest)?;
|
||||
}
|
||||
|
|
@ -985,11 +997,14 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
// Using host floats (but it's fine, these operations do not have guaranteed precision).
|
||||
let (res, sign) = x.to_host().ln_gamma();
|
||||
this.write_int(sign, &signp)?;
|
||||
|
||||
let res = res.to_soft();
|
||||
// Apply a relative error of 16ULP to introduce some non-determinism
|
||||
// Apply a relative error of 4ULP to introduce some non-determinism
|
||||
// simulating imprecise implementations and optimizations.
|
||||
// FIXME: temporarily disabled as it breaks std tests.
|
||||
// let res = math::apply_random_float_error_ulp(this, res, 4 /* log2(16) */);
|
||||
let res = math::apply_random_float_error_ulp(this, res, 4);
|
||||
// Clamp the result to the guaranteed range of this function according to the C standard,
|
||||
// if any.
|
||||
let res = math::clamp_float_value(link_name.as_str(), res);
|
||||
let res = this.adjust_nan(res, &[x]);
|
||||
this.write_scalar(res, dest)?;
|
||||
}
|
||||
|
|
@ -1001,11 +1016,14 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
// Using host floats (but it's fine, these operations do not have guaranteed precision).
|
||||
let (res, sign) = x.to_host().ln_gamma();
|
||||
this.write_int(sign, &signp)?;
|
||||
|
||||
let res = res.to_soft();
|
||||
// Apply a relative error of 16ULP to introduce some non-determinism
|
||||
// Apply a relative error of 4ULP to introduce some non-determinism
|
||||
// simulating imprecise implementations and optimizations.
|
||||
// FIXME: temporarily disabled as it breaks std tests.
|
||||
// let res = math::apply_random_float_error_ulp(this, res, 4 /* log2(16) */);
|
||||
let res = math::apply_random_float_error_ulp(this, res, 4);
|
||||
// Clamp the result to the guaranteed range of this function according to the C standard,
|
||||
// if any.
|
||||
let res = math::clamp_float_value(link_name.as_str(), res);
|
||||
let res = this.adjust_nan(res, &[x]);
|
||||
this.write_scalar(res, dest)?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1093,6 +1093,8 @@ pub fn libm() {
|
|||
|
||||
assert_approx_eq!(1f32.exp_m1(), f32::consts::E - 1.0);
|
||||
assert_approx_eq!(1f64.exp_m1(), f64::consts::E - 1.0);
|
||||
assert_approx_eq!(f32::NEG_INFINITY.exp_m1(), -1.0);
|
||||
assert_approx_eq!(f64::NEG_INFINITY.exp_m1(), -1.0);
|
||||
|
||||
assert_approx_eq!(10f32.exp2(), 1024f32);
|
||||
assert_approx_eq!(50f64.exp2(), 1125899906842624f64);
|
||||
|
|
@ -1128,6 +1130,7 @@ pub fn libm() {
|
|||
assert_eq!(ldexp(0.65f64, 3i32), 5.2f64);
|
||||
assert_eq!(ldexp(1.42, 0xFFFF), f64::INFINITY);
|
||||
assert_eq!(ldexp(1.42, -0xFFFF), 0f64);
|
||||
assert_eq!(ldexp(42.0, 0), 42.0);
|
||||
|
||||
// Trigonometric functions.
|
||||
|
||||
|
|
@ -1136,8 +1139,14 @@ pub fn libm() {
|
|||
assert_approx_eq!((f64::consts::PI / 2f64).sin(), 1f64);
|
||||
assert_approx_eq!(f32::consts::FRAC_PI_6.sin(), 0.5);
|
||||
assert_approx_eq!(f64::consts::FRAC_PI_6.sin(), 0.5);
|
||||
assert_approx_eq!(f32::consts::FRAC_PI_4.sin().asin(), f32::consts::FRAC_PI_4);
|
||||
assert_approx_eq!(f64::consts::FRAC_PI_4.sin().asin(), f64::consts::FRAC_PI_4);
|
||||
// Increase error tolerance from 12ULP to 16ULP because of the extra operation.
|
||||
assert_approx_eq!(f32::consts::FRAC_PI_4.sin().asin(), f32::consts::FRAC_PI_4, 16);
|
||||
assert_approx_eq!(f64::consts::FRAC_PI_4.sin().asin(), f64::consts::FRAC_PI_4, 16);
|
||||
assert_biteq(0.0f32.asin(), 0.0f32, "asin(+0) = +0");
|
||||
assert_biteq((-0.0f32).asin(), -0.0, "asin(-0) = -0");
|
||||
assert_biteq(0.0f64.asin(), 0.0, "asin(+0) = +0");
|
||||
assert_biteq((-0.0f64).asin(), -0.0, "asin(-0) = -0");
|
||||
|
||||
|
||||
assert_approx_eq!(1.0f32.sinh(), 1.1752012f32);
|
||||
assert_approx_eq!(1.0f64.sinh(), 1.1752011936438014f64);
|
||||
|
|
@ -1164,11 +1173,18 @@ pub fn libm() {
|
|||
assert_approx_eq!((f64::consts::PI * 2f64).cos(), 1f64);
|
||||
assert_approx_eq!(f32::consts::FRAC_PI_3.cos(), 0.5);
|
||||
assert_approx_eq!(f64::consts::FRAC_PI_3.cos(), 0.5);
|
||||
assert_approx_eq!(f32::consts::FRAC_PI_4.cos().acos(), f32::consts::FRAC_PI_4);
|
||||
assert_approx_eq!(f64::consts::FRAC_PI_4.cos().acos(), f64::consts::FRAC_PI_4);
|
||||
// Increase error tolerance from 12ULP to 16ULP because of the extra operation.
|
||||
assert_approx_eq!(f32::consts::FRAC_PI_4.cos().acos(), f32::consts::FRAC_PI_4, 16);
|
||||
assert_approx_eq!(f64::consts::FRAC_PI_4.cos().acos(), f64::consts::FRAC_PI_4, 16);
|
||||
assert_biteq(1.0f32.acos(), 0.0, "acos(1) = 0");
|
||||
assert_biteq(1.0f64.acos(), 0.0, "acos(1) = 0");
|
||||
|
||||
assert_approx_eq!(1.0f32.cosh(), 1.54308f32);
|
||||
assert_approx_eq!(1.0f64.cosh(), 1.5430806348152437f64);
|
||||
assert_eq!(0.0f32.cosh(), 1.0);
|
||||
assert_eq!(0.0f64.cosh(), 1.0);
|
||||
assert_eq!((-0.0f32).cosh(), 1.0);
|
||||
assert_eq!((-0.0f64).cosh(), 1.0);
|
||||
assert_approx_eq!(2.0f32.acosh(), 1.31695789692481670862504634730796844f32);
|
||||
assert_approx_eq!(3.0f64.acosh(), 1.76274717403908605046521864995958461f64);
|
||||
|
||||
|
|
@ -1178,6 +1194,47 @@ pub fn libm() {
|
|||
assert_approx_eq!(1.0_f64, 1.0_f64.tan().atan());
|
||||
assert_approx_eq!(1.0f32.atan2(2.0f32), 0.46364761f32);
|
||||
assert_approx_eq!(1.0f32.atan2(2.0f32), 0.46364761f32);
|
||||
// C standard defines a bunch of fixed outputs for atan2
|
||||
macro_rules! fixed_atan2_cases{
|
||||
($float_type:ident) => {{
|
||||
use std::$float_type::consts::{PI, FRAC_PI_2, FRAC_PI_4};
|
||||
use $float_type::{INFINITY, NEG_INFINITY};
|
||||
|
||||
// atan2(±0,−0) = ±π.
|
||||
assert_eq!($float_type::atan2(0.0, -0.0), PI, "atan2(0,−0) = π");
|
||||
assert_eq!($float_type::atan2(-0.0, -0.0), -PI, "atan2(-0,−0) = -π");
|
||||
|
||||
// atan2(±0, y) = ±π for y < 0.
|
||||
assert_eq!($float_type::atan2(0.0, -1.0), PI, "atan2(0, y) = π for y < 0.");
|
||||
assert_eq!($float_type::atan2(-0.0, -1.0), -PI, "atan2(-0, y) = -π for y < 0.");
|
||||
|
||||
// atan2(x, ±0) = −π/2 for x < 0.
|
||||
assert_eq!($float_type::atan2(-1.0, 0.0), -FRAC_PI_2, "atan2(x, 0) = −π/2 for x < 0");
|
||||
assert_eq!($float_type::atan2(-1.0, -0.0), -FRAC_PI_2, "atan2(x, -0) = −π/2 for x < 0");
|
||||
|
||||
// atan2(x, ±0) = π/2 for x > 0.
|
||||
assert_eq!($float_type::atan2(1.0, 0.0), FRAC_PI_2, "atan2(x, 0) = π/2 for x > 0.");
|
||||
assert_eq!($float_type::atan2(1.0, -0.0), FRAC_PI_2, "atan2(x, -0) = π/2 for x > 0.");
|
||||
|
||||
// atan2(±x,−∞) = ±π for finite x > 0.
|
||||
assert_eq!($float_type::atan2(1.0, NEG_INFINITY), PI, "atan2(x, −∞) = π for finite x > 0");
|
||||
assert_eq!($float_type::atan2(-1.0, NEG_INFINITY), -PI, "atan2(-x, −∞) = -π for finite x > 0");
|
||||
|
||||
// atan2(±∞, y) returns ±π/2 for finite y.
|
||||
assert_eq!($float_type::atan2(INFINITY, 1.0), FRAC_PI_2, "atan2(+∞, y) returns π/2 for finite y");
|
||||
assert_eq!($float_type::atan2(NEG_INFINITY, 1.0), -FRAC_PI_2, "atan2(-∞, y) returns -π/2 for finite y");
|
||||
|
||||
// atan2(±∞, −∞) = ±3π/4
|
||||
assert_eq!($float_type::atan2(INFINITY, NEG_INFINITY), 3.0 * FRAC_PI_4, "atan2(+∞, −∞) = 3π/4");
|
||||
assert_eq!($float_type::atan2(NEG_INFINITY, NEG_INFINITY), -3.0 * FRAC_PI_4, "atan2(-∞, −∞) = -3π/4");
|
||||
|
||||
// atan2(±∞, +∞) = ±π/4
|
||||
assert_eq!($float_type::atan2(INFINITY, INFINITY), FRAC_PI_4, "atan2(+∞, +∞) = π/4");
|
||||
assert_eq!($float_type::atan2(NEG_INFINITY, INFINITY), -FRAC_PI_4, "atan2(-∞, +∞) = -π/4");
|
||||
}}
|
||||
}
|
||||
fixed_atan2_cases!(f32);
|
||||
fixed_atan2_cases!(f64);
|
||||
|
||||
assert_approx_eq!(
|
||||
1.0f32.tanh(),
|
||||
|
|
@ -1187,6 +1244,11 @@ pub fn libm() {
|
|||
1.0f64.tanh(),
|
||||
(1.0 - f64::consts::E.powi(-2)) / (1.0 + f64::consts::E.powi(-2))
|
||||
);
|
||||
assert_eq!(f32::INFINITY.tanh(), 1.0);
|
||||
assert_eq!(f32::NEG_INFINITY.tanh(), -1.0);
|
||||
assert_eq!(f64::INFINITY.tanh(), 1.0);
|
||||
assert_eq!(f64::NEG_INFINITY.tanh(), -1.0);
|
||||
|
||||
assert_approx_eq!(0.5f32.atanh(), 0.54930614433405484569762261846126285f32);
|
||||
assert_approx_eq!(0.5f64.atanh(), 0.54930614433405484569762261846126285f64);
|
||||
|
||||
|
|
@ -1207,8 +1269,14 @@ pub fn libm() {
|
|||
|
||||
assert_approx_eq!(1.0f32.erf(), 0.84270079294971486934122063508260926f32);
|
||||
assert_approx_eq!(1.0f64.erf(), 0.84270079294971486934122063508260926f64);
|
||||
assert_eq!(f32::INFINITY.erf(), 1.0);
|
||||
assert_eq!(f64::INFINITY.erf(), 1.0);
|
||||
assert_approx_eq!(1.0f32.erfc(), 0.15729920705028513065877936491739074f32);
|
||||
assert_approx_eq!(1.0f64.erfc(), 0.15729920705028513065877936491739074f64);
|
||||
assert_eq!(f32::NEG_INFINITY.erfc(), 2.0);
|
||||
assert_eq!(f64::NEG_INFINITY.erfc(), 2.0);
|
||||
assert_eq!(f32::INFINITY.erfc(), 0.0);
|
||||
assert_eq!(f64::INFINITY.erfc(), 0.0);
|
||||
}
|
||||
|
||||
fn test_fast() {
|
||||
|
|
@ -1418,7 +1486,6 @@ fn test_non_determinism() {
|
|||
}
|
||||
pub fn test_operations_f32(a: f32, b: f32) {
|
||||
test_operations_f!(a, b);
|
||||
// FIXME: some are temporarily disabled as it breaks std tests.
|
||||
ensure_nondet(|| a.powf(b));
|
||||
ensure_nondet(|| a.powi(2));
|
||||
ensure_nondet(|| a.log(b));
|
||||
|
|
@ -1427,35 +1494,34 @@ fn test_non_determinism() {
|
|||
ensure_nondet(|| f32::consts::E.ln());
|
||||
ensure_nondet(|| 10f32.log10());
|
||||
ensure_nondet(|| 8f32.log2());
|
||||
// ensure_nondet(|| 1f32.ln_1p());
|
||||
// ensure_nondet(|| 27.0f32.cbrt());
|
||||
// ensure_nondet(|| 3.0f32.hypot(4.0f32));
|
||||
ensure_nondet(|| 1f32.ln_1p());
|
||||
ensure_nondet(|| 27.0f32.cbrt());
|
||||
ensure_nondet(|| 3.0f32.hypot(4.0f32));
|
||||
ensure_nondet(|| 1f32.sin());
|
||||
ensure_nondet(|| 1f32.cos());
|
||||
// On i686-pc-windows-msvc , these functions are implemented by calling the `f64` version,
|
||||
// which means the little rounding errors Miri introduces are discarded by the cast down to
|
||||
// `f32`. Just skip the test for them.
|
||||
// if !cfg!(all(target_os = "windows", target_env = "msvc", target_arch = "x86")) {
|
||||
// ensure_nondet(|| 1.0f32.tan());
|
||||
// ensure_nondet(|| 1.0f32.asin());
|
||||
// ensure_nondet(|| 5.0f32.acos());
|
||||
// ensure_nondet(|| 1.0f32.atan());
|
||||
// ensure_nondet(|| 1.0f32.atan2(2.0f32));
|
||||
// ensure_nondet(|| 1.0f32.sinh());
|
||||
// ensure_nondet(|| 1.0f32.cosh());
|
||||
// ensure_nondet(|| 1.0f32.tanh());
|
||||
// }
|
||||
// ensure_nondet(|| 1.0f32.asinh());
|
||||
// ensure_nondet(|| 2.0f32.acosh());
|
||||
// ensure_nondet(|| 0.5f32.atanh());
|
||||
// ensure_nondet(|| 5.0f32.gamma());
|
||||
// ensure_nondet(|| 5.0f32.ln_gamma());
|
||||
// ensure_nondet(|| 5.0f32.erf());
|
||||
// ensure_nondet(|| 5.0f32.erfc());
|
||||
if !cfg!(all(target_os = "windows", target_env = "msvc", target_arch = "x86")) {
|
||||
ensure_nondet(|| 1.0f32.tan());
|
||||
ensure_nondet(|| 1.0f32.asin());
|
||||
ensure_nondet(|| 5.0f32.acos());
|
||||
ensure_nondet(|| 1.0f32.atan());
|
||||
ensure_nondet(|| 1.0f32.atan2(2.0f32));
|
||||
ensure_nondet(|| 1.0f32.sinh());
|
||||
ensure_nondet(|| 1.0f32.cosh());
|
||||
ensure_nondet(|| 1.0f32.tanh());
|
||||
}
|
||||
ensure_nondet(|| 1.0f32.asinh());
|
||||
ensure_nondet(|| 2.0f32.acosh());
|
||||
ensure_nondet(|| 0.5f32.atanh());
|
||||
ensure_nondet(|| 5.0f32.gamma());
|
||||
ensure_nondet(|| 5.0f32.ln_gamma());
|
||||
ensure_nondet(|| 5.0f32.erf());
|
||||
ensure_nondet(|| 5.0f32.erfc());
|
||||
}
|
||||
pub fn test_operations_f64(a: f64, b: f64) {
|
||||
test_operations_f!(a, b);
|
||||
// FIXME: some are temporarily disabled as it breaks std tests.
|
||||
ensure_nondet(|| a.powf(b));
|
||||
ensure_nondet(|| a.powi(2));
|
||||
ensure_nondet(|| a.log(b));
|
||||
|
|
@ -1464,26 +1530,26 @@ fn test_non_determinism() {
|
|||
ensure_nondet(|| 3f64.ln());
|
||||
ensure_nondet(|| f64::consts::E.log10());
|
||||
ensure_nondet(|| f64::consts::E.log2());
|
||||
// ensure_nondet(|| 1f64.ln_1p());
|
||||
// ensure_nondet(|| 27.0f64.cbrt());
|
||||
// ensure_nondet(|| 3.0f64.hypot(4.0f64));
|
||||
ensure_nondet(|| 1f64.ln_1p());
|
||||
ensure_nondet(|| 27.0f64.cbrt());
|
||||
ensure_nondet(|| 3.0f64.hypot(4.0f64));
|
||||
ensure_nondet(|| 1f64.sin());
|
||||
ensure_nondet(|| 1f64.cos());
|
||||
// ensure_nondet(|| 1.0f64.tan());
|
||||
// ensure_nondet(|| 1.0f64.asin());
|
||||
// ensure_nondet(|| 5.0f64.acos());
|
||||
// ensure_nondet(|| 1.0f64.atan());
|
||||
// ensure_nondet(|| 1.0f64.atan2(2.0f64));
|
||||
// ensure_nondet(|| 1.0f64.sinh());
|
||||
// ensure_nondet(|| 1.0f64.cosh());
|
||||
// ensure_nondet(|| 1.0f64.tanh());
|
||||
// ensure_nondet(|| 1.0f64.asinh());
|
||||
// ensure_nondet(|| 3.0f64.acosh());
|
||||
// ensure_nondet(|| 0.5f64.atanh());
|
||||
// ensure_nondet(|| 5.0f64.gamma());
|
||||
// ensure_nondet(|| 5.0f64.ln_gamma());
|
||||
// ensure_nondet(|| 5.0f64.erf());
|
||||
// ensure_nondet(|| 5.0f64.erfc());
|
||||
ensure_nondet(|| 1.0f64.tan());
|
||||
ensure_nondet(|| 1.0f64.asin());
|
||||
ensure_nondet(|| 5.0f64.acos());
|
||||
ensure_nondet(|| 1.0f64.atan());
|
||||
ensure_nondet(|| 1.0f64.atan2(2.0f64));
|
||||
ensure_nondet(|| 1.0f64.sinh());
|
||||
ensure_nondet(|| 1.0f64.cosh());
|
||||
ensure_nondet(|| 1.0f64.tanh());
|
||||
ensure_nondet(|| 1.0f64.asinh());
|
||||
ensure_nondet(|| 3.0f64.acosh());
|
||||
ensure_nondet(|| 0.5f64.atanh());
|
||||
ensure_nondet(|| 5.0f64.gamma());
|
||||
ensure_nondet(|| 5.0f64.ln_gamma());
|
||||
ensure_nondet(|| 5.0f64.erf());
|
||||
ensure_nondet(|| 5.0f64.erfc());
|
||||
}
|
||||
pub fn test_operations_f128(a: f128, b: f128) {
|
||||
test_operations_f!(a, b);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue