blob: f9ff4c84c129d39aa0ae712ba5a54d97c13e866c [file] [log] [blame]
use std::ops::Neg;
use std::{f32, f64};
use rand::Rng as _;
use rustc_apfloat::Float;
use rustc_apfloat::ieee::{DoubleS, IeeeFloat, Semantics, SingleS};
use rustc_middle::ty::{self, FloatTy, ScalarInt};
use crate::*;
/// Disturbes a floating-point result by a relative error in the range (-2^scale, 2^scale).
pub(crate) fn apply_random_float_error<F: rustc_apfloat::Float>(
ecx: &mut crate::MiriInterpCx<'_>,
val: F,
err_scale: i32,
) -> F {
if !ecx.machine.float_nondet
|| matches!(ecx.machine.float_rounding_error, FloatRoundingErrorMode::None)
// relative errors don't do anything to zeros... avoid messing up the sign
|| val.is_zero()
// The logic below makes no sense if the input is already non-finite.
|| !val.is_finite()
{
return val;
}
let rng = ecx.machine.rng.get_mut();
// Generate a random integer in the range [0, 2^PREC).
// (When read as binary, the position of the first `1` determines the exponent,
// and the remaining bits fill the mantissa. `PREC` is one plus the size of the mantissa,
// so this all works out.)
let r = F::from_u128(match ecx.machine.float_rounding_error {
FloatRoundingErrorMode::Random => rng.random_range(0..(1 << F::PRECISION)),
FloatRoundingErrorMode::Max => (1 << F::PRECISION) - 1, // force max error
FloatRoundingErrorMode::None => unreachable!(),
})
.value;
// Multiply this with 2^(scale - PREC). The result is between 0 and
// 2^PREC * 2^(scale - PREC) = 2^scale.
let err = r.scalbn(err_scale.strict_sub(F::PRECISION.try_into().unwrap()));
// give it a random sign
let err = if rng.random() { -err } else { err };
// Compute `val*(1+err)`, distributed out as `val + val*err` to avoid the imprecise addition
// error being amplified by multiplication.
(val + (val * err).value).value
}
/// Applies an error of `[-N, +N]` ULP to the given value.
pub(crate) fn apply_random_float_error_ulp<F: rustc_apfloat::Float>(
ecx: &mut crate::MiriInterpCx<'_>,
val: F,
max_error: u32,
) -> F {
// We could try to be clever and reuse `apply_random_float_error`, but that is hard to get right
// (see <https://github.com/rust-lang/miri/pull/4558#discussion_r2316838085> for why) so we
// implement the logic directly instead.
if !ecx.machine.float_nondet
|| matches!(ecx.machine.float_rounding_error, FloatRoundingErrorMode::None)
// FIXME: also disturb zeros? That requires a lot more cases in `fixed_float_value`
// and might make the std test suite quite unhappy.
|| val.is_zero()
// The logic below makes no sense if the input is already non-finite.
|| !val.is_finite()
{
return val;
}
let rng = ecx.machine.rng.get_mut();
let max_error = i64::from(max_error);
let error = match ecx.machine.float_rounding_error {
FloatRoundingErrorMode::Random => rng.random_range(-max_error..=max_error),
FloatRoundingErrorMode::Max =>
if rng.random() {
max_error
} else {
-max_error
},
FloatRoundingErrorMode::None => unreachable!(),
};
// If upwards ULP and downwards ULP differ, we take the average.
let ulp = (((val.next_up().value - val).value + (val - val.next_down().value).value).value
/ F::from_u128(2).value)
.value;
// Shift the value by N times the ULP
(val + (ulp * F::from_i128(error.into()).value).value).value
}
/// Applies an error of `[-N, +N]` ULP to the given value.
/// Will fail if `val` is not a floating point number.
pub(crate) fn apply_random_float_error_to_imm<'tcx>(
ecx: &mut MiriInterpCx<'tcx>,
val: ImmTy<'tcx>,
max_error: u32,
) -> InterpResult<'tcx, ImmTy<'tcx>> {
let scalar = val.to_scalar_int()?;
let res: ScalarInt = match val.layout.ty.kind() {
ty::Float(FloatTy::F16) =>
apply_random_float_error_ulp(ecx, scalar.to_f16(), max_error).into(),
ty::Float(FloatTy::F32) =>
apply_random_float_error_ulp(ecx, scalar.to_f32(), max_error).into(),
ty::Float(FloatTy::F64) =>
apply_random_float_error_ulp(ecx, scalar.to_f64(), max_error).into(),
ty::Float(FloatTy::F128) =>
apply_random_float_error_ulp(ecx, scalar.to_f128(), max_error).into(),
_ => bug!("intrinsic called with non-float input type"),
};
interp_ok(ImmTy::from_scalar_int(res, val.layout))
}
/// 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>
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, 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, 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
///
/// Returns `Some(output)` if the `intrinsic` results in a defined fixed `output` specified in the C standard
/// (specifically, C23 annex F.10) when given `args` as arguments. Outputs that are unaffected by a relative error
/// (such as INF and zero) are not handled here, they are assumed to be handled by the underlying
/// implementation. Returns `None` if no specific value is guaranteed.
///
/// # Note
///
/// For `powf*` operations of the form:
///
/// - `(SNaN)^(±0)`
/// - `1^(SNaN)`
///
/// The result is implementation-defined:
/// - musl returns for both `1.0`
/// - glibc returns for both `NaN`
///
/// This discrepancy exists because SNaN handling is not consistently defined across platforms,
/// and the C standard leaves behavior for SNaNs unspecified.
///
/// Miri chooses to adhere to both implementations and returns either one of them non-deterministically.
pub(crate) fn fixed_float_value<S: Semantics>(
ecx: &mut MiriInterpCx<'_>,
intrinsic_name: &str,
args: &[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;
// Remove `f32`/`f64` suffix, if any.
let name = intrinsic_name
.strip_suffix("f32")
.or_else(|| intrinsic_name.strip_suffix("f64"))
.unwrap_or(intrinsic_name);
// Also strip trailing `f` (indicates "float"), with an exception for "erf" to avoid
// removing that `f`.
let name = if name == "erf" { name } else { name.strip_suffix("f").unwrap_or(name) };
Some(match (name, args) {
// cos(±0) and cosh(±0)= 1
("cos" | "cosh", [input]) if input.is_zero() => one,
// e^0 = 1
("exp" | "exp2", [input]) if input.is_zero() => one,
// tanh(±INF) = ±1
("tanh", [input]) if input.is_infinite() => one.copy_sign(*input),
// atan(±INF) = ±π/2
("atan", [input]) if input.is_infinite() => pi_over_2.copy_sign(*input),
// erf(±INF) = ±1
("erf", [input]) if input.is_infinite() => one.copy_sign(*input),
// erfc(-INF) = 2
("erfc", [input]) if input.is_neg_infinity() => (one + one).value,
// hypot(x, ±0) = abs(x), if x is not a NaN.
// `_hypot` is the Windows name for this.
("_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.
("atan2", [x, y]) if (x.is_zero() && (y.is_negative() && !y.is_nan())) => pi.copy_sign(*x),
// atan2(±x,−∞) = ±π for finite x > 0.
("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.
("atan2", [x, y]) if !x.is_zero() && y.is_zero() => pi_over_2.copy_sign(*x),
//atan2(±∞, −∞) = ±3π/4
("atan2", [x, y]) if x.is_infinite() && y.is_neg_infinity() =>
(pi_over_4 * three).value.copy_sign(*x),
//atan2(±∞, +∞) = ±π/4
("atan2", [x, y]) if x.is_infinite() && y.is_pos_infinity() => pi_over_4.copy_sign(*x),
// atan2(±∞, y) returns ±π/2 for finite y.
("atan2", [x, y]) if x.is_infinite() && (!y.is_infinite() && !y.is_nan()) =>
pi_over_2.copy_sign(*x),
// (-1)^(±INF) = 1
("pow", [base, exp]) if *base == -one && exp.is_infinite() => one,
// 1^y = 1 for any y, even a NaN
("pow", [base, exp]) if *base == one => {
let rng = this.machine.rng.get_mut();
// SNaN exponents get special treatment: they might return 1, or a NaN.
// This is non-deterministic because LLVM can treat SNaN as QNaN, and because
// implementation behavior differs between glibc and musl.
let return_nan = exp.is_signaling() && this.machine.float_nondet && rng.random();
if return_nan { this.generate_nan(args) } else { one }
}
// x^(±0) = 1 for any x, even a NaN
("pow", [base, exp]) if exp.is_zero() => {
let rng = this.machine.rng.get_mut();
// SNaN bases get special treatment: they might return 1, or a NaN.
// This is non-deterministic because LLVM can treat SNaN as QNaN, and because
// implementation behavior differs between glibc and musl.
let return_nan = base.is_signaling() && this.machine.float_nondet && rng.random();
if return_nan { this.generate_nan(args) } else { one }
}
// There are a lot of cases for fixed outputs according to the C Standard, but these are
// mainly INF or zero which are not affected by the applied error.
_ => return None,
})
}
/// 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_value<S: Semantics>(
ecx: &mut MiriInterpCx<'_>,
base: IeeeFloat<S>,
exp: i32,
) -> Option<IeeeFloat<S>>
where
IeeeFloat<S>: IeeeExt,
{
match exp {
0 => {
let one = IeeeFloat::<S>::one();
let rng = ecx.machine.rng.get_mut();
// SNaN bases get special treatment: they might return 1, or a NaN.
// This is non-deterministic because LLVM can treat SNaN as QNaN.
let return_nan = base.is_signaling() && ecx.machine.float_nondet && rng.random();
Some(if return_nan { ecx.generate_nan(&[base]) } else { one })
}
_ => None,
}
}
pub(crate) fn sqrt<F: Float>(x: F) -> F {
match x.category() {
// preserve zero sign
rustc_apfloat::Category::Zero => x,
// propagate NaN
rustc_apfloat::Category::NaN => x,
// sqrt of negative number is NaN
_ if x.is_negative() => F::NAN,
// sqrt(∞) = ∞
rustc_apfloat::Category::Infinity => F::INFINITY,
rustc_apfloat::Category::Normal => {
// Floating point precision, excluding the integer bit
let prec = i32::try_from(F::PRECISION).unwrap() - 1;
// x = 2^(exp - prec) * mant
// where mant is an integer with prec+1 bits
// mant is a u128, which should be large enough for the largest prec (112 for f128)
let mut exp = x.ilogb();
let mut mant = x.scalbn(prec - exp).to_u128(128).value;
if exp % 2 != 0 {
// Make exponent even, so it can be divided by 2
exp -= 1;
mant <<= 1;
}
// Bit-by-bit (base-2 digit-by-digit) sqrt of mant.
// mant is treated here as a fixed point number with prec fractional bits.
// mant will be shifted left by one bit to have an extra fractional bit, which
// will be used to determine the rounding direction.
// res is the truncated sqrt of mant, where one bit is added at each iteration.
let mut res = 0u128;
// rem is the remainder with the current res
// rem_i = 2^i * ((mant<<1) - res_i^2)
// starting with res = 0, rem = mant<<1
let mut rem = mant << 1;
// s_i = 2*res_i
let mut s = 0u128;
// d is used to iterate over bits, from high to low (d_i = 2^(-i))
let mut d = 1u128 << (prec + 1);
// For iteration j=i+1, we need to find largest b_j = 0 or 1 such that
// (res_i + b_j * 2^(-j))^2 <= mant<<1
// Expanding (a + b)^2 = a^2 + b^2 + 2*a*b:
// res_i^2 + (b_j * 2^(-j))^2 + 2 * res_i * b_j * 2^(-j) <= mant<<1
// And rearranging the terms:
// b_j^2 * 2^(-j) + 2 * res_i * b_j <= 2^j * (mant<<1 - res_i^2)
// b_j^2 * 2^(-j) + 2 * res_i * b_j <= rem_i
while d != 0 {
// Probe b_j^2 * 2^(-j) + 2 * res_i * b_j <= rem_i with b_j = 1:
// t = 2*res_i + 2^(-j)
let t = s + d;
if rem >= t {
// b_j should be 1, so make res_j = res_i + 2^(-j) and adjust rem
res += d;
s += d + d;
rem -= t;
}
// Adjust rem for next iteration
rem <<= 1;
// Shift iterator
d >>= 1;
}
// Remove extra fractional bit from result, rounding to nearest.
// If the last bit is 0, then the nearest neighbor is definitely the lower one.
// If the last bit is 1, it sounds like this may either be a tie (if there's
// infinitely many 0s after this 1), or the nearest neighbor is the upper one.
// However, since square roots are either exact or irrational, and an exact root
// would lead to the last "extra" bit being 0, we can exclude a tie in this case.
// We therefore always round up if the last bit is 1. When the last bit is 0,
// adding 1 will not do anything since the shift will discard it.
res = (res + 1) >> 1;
// Build resulting value with res as mantissa and exp/2 as exponent
F::from_u128(res).value.scalbn(exp / 2 - prec)
}
}
}
/// 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)
}
}
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 {
use rustc_apfloat::ieee::{DoubleS, HalfS, IeeeFloat, QuadS, SingleS};
use super::sqrt;
#[test]
fn test_sqrt() {
#[track_caller]
fn test<S: rustc_apfloat::ieee::Semantics>(x: &str, expected: &str) {
let x: IeeeFloat<S> = x.parse().unwrap();
let expected: IeeeFloat<S> = expected.parse().unwrap();
let result = sqrt(x);
assert_eq!(result, expected);
}
fn exact_tests<S: rustc_apfloat::ieee::Semantics>() {
test::<S>("0", "0");
test::<S>("1", "1");
test::<S>("1.5625", "1.25");
test::<S>("2.25", "1.5");
test::<S>("4", "2");
test::<S>("5.0625", "2.25");
test::<S>("9", "3");
test::<S>("16", "4");
test::<S>("25", "5");
test::<S>("36", "6");
test::<S>("49", "7");
test::<S>("64", "8");
test::<S>("81", "9");
test::<S>("100", "10");
test::<S>("0.5625", "0.75");
test::<S>("0.25", "0.5");
test::<S>("0.0625", "0.25");
test::<S>("0.00390625", "0.0625");
}
exact_tests::<HalfS>();
exact_tests::<SingleS>();
exact_tests::<DoubleS>();
exact_tests::<QuadS>();
test::<SingleS>("2", "1.4142135");
test::<DoubleS>("2", "1.4142135623730951");
test::<SingleS>("1.1", "1.0488088");
test::<DoubleS>("1.1", "1.0488088481701516");
test::<SingleS>("2.2", "1.4832398");
test::<DoubleS>("2.2", "1.4832396974191326");
test::<SingleS>("1.22101e-40", "1.10499205e-20");
test::<DoubleS>("1.22101e-310", "1.1049932126488395e-155");
test::<SingleS>("3.4028235e38", "1.8446743e19");
test::<DoubleS>("1.7976931348623157e308", "1.3407807929942596e154");
}
}