|
1 | 1 | use std::ops::Neg;
|
| 2 | +use std::{f16, f32, f64, f128}; |
2 | 3 |
|
3 | 4 | use rand::Rng as _;
|
4 | 5 | use rustc_apfloat::Float as _;
|
5 |
| -use rustc_apfloat::ieee::{IeeeFloat, Semantics}; |
| 6 | +use rustc_apfloat::ieee::{DoubleS, HalfS, IeeeFloat, QuadS, Semantics, SingleS}; |
6 | 7 | use rustc_middle::ty::{self, FloatTy, ScalarInt};
|
7 | 8 |
|
8 | 9 | use crate::*;
|
@@ -52,52 +53,95 @@ pub(crate) fn apply_random_float_error_ulp<F: rustc_apfloat::Float>(
|
52 | 53 | apply_random_float_error(ecx, val, err_scale)
|
53 | 54 | }
|
54 | 55 |
|
55 |
| -/// Applies a random 16ULP floating point error to `val` and returns the new value. |
| 56 | +/// Applies a random ULP floating point error to `val` and returns the new value. |
| 57 | +/// So if you want an X ULP error, `ulp_exponent` should be log2(X). |
| 58 | +/// |
56 | 59 | /// Will fail if `val` is not a floating point number.
|
57 | 60 | pub(crate) fn apply_random_float_error_to_imm<'tcx>(
|
58 | 61 | ecx: &mut MiriInterpCx<'tcx>,
|
59 | 62 | val: ImmTy<'tcx>,
|
60 | 63 | ulp_exponent: u32,
|
61 | 64 | ) -> InterpResult<'tcx, ImmTy<'tcx>> {
|
| 65 | + let this = ecx.eval_context_mut(); |
62 | 66 | let scalar = val.to_scalar_int()?;
|
63 | 67 | let res: ScalarInt = match val.layout.ty.kind() {
|
64 | 68 | ty::Float(FloatTy::F16) =>
|
65 |
| - apply_random_float_error_ulp(ecx, scalar.to_f16(), ulp_exponent).into(), |
| 69 | + apply_random_float_error_ulp(this, scalar.to_f16(), ulp_exponent).into(), |
66 | 70 | ty::Float(FloatTy::F32) =>
|
67 |
| - apply_random_float_error_ulp(ecx, scalar.to_f32(), ulp_exponent).into(), |
| 71 | + apply_random_float_error_ulp(this, scalar.to_f32(), ulp_exponent).into(), |
68 | 72 | ty::Float(FloatTy::F64) =>
|
69 |
| - apply_random_float_error_ulp(ecx, scalar.to_f64(), ulp_exponent).into(), |
| 73 | + apply_random_float_error_ulp(this, scalar.to_f64(), ulp_exponent).into(), |
70 | 74 | ty::Float(FloatTy::F128) =>
|
71 |
| - apply_random_float_error_ulp(ecx, scalar.to_f128(), ulp_exponent).into(), |
| 75 | + apply_random_float_error_ulp(this, scalar.to_f128(), ulp_exponent).into(), |
72 | 76 | _ => bug!("intrinsic called with non-float input type"),
|
73 | 77 | };
|
74 | 78 |
|
75 | 79 | interp_ok(ImmTy::from_scalar_int(res, val.layout))
|
76 | 80 | }
|
77 | 81 |
|
78 |
| -/// Given an floating-point operation and a floating-point value, clamps the result to the output |
79 |
| -/// range of the given operation. |
| 82 | +/// Given a floating-point operation and a floating-point value, clamps the result to the output |
| 83 | +/// range of the given operation according to the C standard, if any. |
80 | 84 | pub(crate) fn clamp_float_value<S: Semantics>(
|
81 | 85 | intrinsic_name: &str,
|
82 | 86 | val: IeeeFloat<S>,
|
83 |
| -) -> IeeeFloat<S> { |
| 87 | +) -> IeeeFloat<S> |
| 88 | +where |
| 89 | + IeeeFloat<S>: IeeeExt, |
| 90 | +{ |
| 91 | + let zero = IeeeFloat::<S>::ZERO; |
| 92 | + let one = IeeeFloat::<S>::one(); |
| 93 | + let two = IeeeFloat::<S>::two(); |
| 94 | + let pi = IeeeFloat::<S>::pi(); |
| 95 | + let pi_over_2 = (pi / two).value; |
| 96 | + |
84 | 97 | match intrinsic_name {
|
85 |
| - // sin and cos: [-1, 1] |
86 |
| - "sinf32" | "cosf32" | "sinf64" | "cosf64" => |
87 |
| - val.clamp(IeeeFloat::<S>::one().neg(), IeeeFloat::<S>::one()), |
88 |
| - // exp: [0, +INF] |
89 |
| - "expf32" | "exp2f32" | "expf64" | "exp2f64" => |
90 |
| - IeeeFloat::<S>::maximum(val, IeeeFloat::<S>::ZERO), |
| 98 | + // sin, cos, tanh: [-1, 1] |
| 99 | + #[rustfmt::skip] |
| 100 | + | "sinf32" |
| 101 | + | "sinf64" |
| 102 | + | "cosf32" |
| 103 | + | "cosf64" |
| 104 | + | "tanhf" |
| 105 | + | "tanh" |
| 106 | + => val.clamp(one.neg(), one), |
| 107 | + |
| 108 | + // exp: [0, +INF) |
| 109 | + "expf32" | "exp2f32" | "expf64" | "exp2f64" => val.maximum(zero), |
| 110 | + |
| 111 | + // cosh: [1, +INF) |
| 112 | + "coshf" | "cosh" => val.maximum(one), |
| 113 | + |
| 114 | + // acos: [0, π] |
| 115 | + "acosf" | "acos" => val.clamp(zero, pi), |
| 116 | + |
| 117 | + // asin: [-π, +π] |
| 118 | + "asinf" | "asin" => val.clamp(pi.neg(), pi), |
| 119 | + |
| 120 | + // atan: (-π/2, +π/2) |
| 121 | + "atanf" | "atan" => val.clamp(pi_over_2.neg(), pi_over_2), |
| 122 | + |
| 123 | + // erfc: (-1, 1) |
| 124 | + "erff" | "erf" => val.clamp(one.neg(), one), |
| 125 | + |
| 126 | + // erfc: (0, 2) |
| 127 | + "erfcf" | "erfc" => val.clamp(zero, two), |
| 128 | + |
| 129 | + // atan2(y, x): arctan(y/x) in [−π, +π] |
| 130 | + "atan2f" | "atan2" => val.clamp(pi.neg(), pi), |
| 131 | + |
91 | 132 | _ => val,
|
92 | 133 | }
|
93 | 134 | }
|
94 | 135 |
|
95 | 136 | /// For the intrinsics:
|
96 |
| -/// - sinf32, sinf64 |
97 |
| -/// - cosf32, cosf64 |
| 137 | +/// - sinf32, sinf64, sinhf, sinh |
| 138 | +/// - cosf32, cosf64, coshf, cosh |
| 139 | +/// - tanhf, tanh, atanf, atan, atan2f, atan2 |
98 | 140 | /// - expf32, expf64, exp2f32, exp2f64
|
99 | 141 | /// - logf32, logf64, log2f32, log2f64, log10f32, log10f64
|
100 | 142 | /// - powf32, powf64
|
| 143 | +/// - erff, erf, erfcf, erfc |
| 144 | +/// - hypotf, hypot |
101 | 145 | ///
|
102 | 146 | /// # Return
|
103 | 147 | ///
|
@@ -125,16 +169,68 @@ pub(crate) fn fixed_float_value<S: Semantics>(
|
125 | 169 | ecx: &mut MiriInterpCx<'_>,
|
126 | 170 | intrinsic_name: &str,
|
127 | 171 | args: &[IeeeFloat<S>],
|
128 |
| -) -> Option<IeeeFloat<S>> { |
| 172 | +) -> Option<IeeeFloat<S>> |
| 173 | +where |
| 174 | + IeeeFloat<S>: IeeeExt, |
| 175 | +{ |
129 | 176 | let this = ecx.eval_context_mut();
|
130 | 177 | let one = IeeeFloat::<S>::one();
|
| 178 | + let two = IeeeFloat::<S>::two(); |
| 179 | + let three = IeeeFloat::<S>::three(); |
| 180 | + let pi = IeeeFloat::<S>::pi(); |
| 181 | + let pi_over_2 = (pi / two).value; |
| 182 | + let pi_over_4 = (pi_over_2 / two).value; |
| 183 | + |
131 | 184 | Some(match (intrinsic_name, args) {
|
132 |
| - // cos(+- 0) = 1 |
133 |
| - ("cosf32" | "cosf64", [input]) if input.is_zero() => one, |
| 185 | + // cos(±0) and cosh(±0)= 1 |
| 186 | + ("cosf32" | "cosf64" | "coshf" | "cosh", [input]) if input.is_zero() => one, |
134 | 187 |
|
135 | 188 | // e^0 = 1
|
136 | 189 | ("expf32" | "expf64" | "exp2f32" | "exp2f64", [input]) if input.is_zero() => one,
|
137 | 190 |
|
| 191 | + // tanh(±INF) = ±1 |
| 192 | + ("tanhf" | "tanh", [input]) if input.is_infinite() => one.copy_sign(*input), |
| 193 | + |
| 194 | + // atan(±INF) = ±π/2 |
| 195 | + ("atanf" | "atan", [input]) if input.is_infinite() => pi_over_2.copy_sign(*input), |
| 196 | + |
| 197 | + // erf(±INF) = ±1 |
| 198 | + ("erff" | "erf", [input]) if input.is_infinite() => one.copy_sign(*input), |
| 199 | + |
| 200 | + // erfc(-INF) = 2 |
| 201 | + ("erfcf" | "erfc", [input]) if input.is_neg_infinity() => (one + one).value, |
| 202 | + |
| 203 | + // hypot(x, ±0) = abs(x), if x is not a NaN. |
| 204 | + ("_hypotf" | "hypotf" | "_hypot" | "hypot", [x, y]) if !x.is_nan() && y.is_zero() => |
| 205 | + x.abs(), |
| 206 | + |
| 207 | + // atan2(±0,−0) = ±π. |
| 208 | + // atan2(±0, y) = ±π for y < 0. |
| 209 | + // Must check for non NaN because `y.is_negative()` also applies to NaN. |
| 210 | + ("atan2f" | "atan2", [x, y]) if (x.is_zero() && (y.is_negative() && !y.is_nan())) => |
| 211 | + pi.copy_sign(*x), |
| 212 | + |
| 213 | + // atan2(±x,−∞) = ±π for finite x > 0. |
| 214 | + ("atan2f" | "atan2", [x, y]) |
| 215 | + if (!x.is_zero() && !x.is_infinite()) && y.is_neg_infinity() => |
| 216 | + pi.copy_sign(*x), |
| 217 | + |
| 218 | + // atan2(x, ±0) = −π/2 for x < 0. |
| 219 | + // atan2(x, ±0) = π/2 for x > 0. |
| 220 | + ("atan2f" | "atan2", [x, y]) if !x.is_zero() && y.is_zero() => pi_over_2.copy_sign(*x), |
| 221 | + |
| 222 | + //atan2(±∞, −∞) = ±3π/4 |
| 223 | + ("atan2f" | "atan2", [x, y]) if x.is_infinite() && y.is_neg_infinity() => |
| 224 | + (pi_over_4 * three).value.copy_sign(*x), |
| 225 | + |
| 226 | + //atan2(±∞, +∞) = ±π/4 |
| 227 | + ("atan2f" | "atan2", [x, y]) if x.is_infinite() && y.is_pos_infinity() => |
| 228 | + pi_over_4.copy_sign(*x), |
| 229 | + |
| 230 | + // atan2(±∞, y) returns ±π/2 for finite y. |
| 231 | + ("atan2f" | "atan2", [x, y]) if x.is_infinite() && (!y.is_infinite() && !y.is_nan()) => |
| 232 | + pi_over_2.copy_sign(*x), |
| 233 | + |
138 | 234 | // (-1)^(±INF) = 1
|
139 | 235 | ("powf32" | "powf64", [base, exp]) if *base == -one && exp.is_infinite() => one,
|
140 | 236 |
|
@@ -164,25 +260,27 @@ pub(crate) fn fixed_float_value<S: Semantics>(
|
164 | 260 |
|
165 | 261 | /// Returns `Some(output)` if `powi` (called `pown` in C) results in a fixed value specified in the
|
166 | 262 | /// C standard (specifically, C23 annex F.10.4.6) when doing `base^exp`. Otherwise, returns `None`.
|
167 |
| -pub(crate) fn fixed_powi_float_value<S: Semantics>( |
| 263 | +pub(crate) fn fixed_powi_value<S: Semantics>( |
168 | 264 | ecx: &mut MiriInterpCx<'_>,
|
169 | 265 | base: IeeeFloat<S>,
|
170 | 266 | exp: i32,
|
171 |
| -) -> Option<IeeeFloat<S>> { |
172 |
| - let this = ecx.eval_context_mut(); |
173 |
| - Some(match exp { |
| 267 | +) -> Option<IeeeFloat<S>> |
| 268 | +where |
| 269 | + IeeeFloat<S>: IeeeExt, |
| 270 | +{ |
| 271 | + match exp { |
174 | 272 | 0 => {
|
175 | 273 | let one = IeeeFloat::<S>::one();
|
176 |
| - let rng = this.machine.rng.get_mut(); |
177 |
| - let return_nan = this.machine.float_nondet && rng.random() && base.is_signaling(); |
| 274 | + let rng = ecx.machine.rng.get_mut(); |
| 275 | + let return_nan = ecx.machine.float_nondet && rng.random() && base.is_signaling(); |
178 | 276 | // For SNaN treatment, we are consistent with `powf`above.
|
179 | 277 | // (We wouldn't have two, unlike powf all implementations seem to agree for powi,
|
180 | 278 | // but for now we are maximally conservative.)
|
181 |
| - if return_nan { this.generate_nan(&[base]) } else { one } |
| 279 | + Some(if return_nan { ecx.generate_nan(&[base]) } else { one }) |
182 | 280 | }
|
183 | 281 |
|
184 | 282 | _ => return None,
|
185 |
| - }) |
| 283 | + } |
186 | 284 | }
|
187 | 285 |
|
188 | 286 | pub(crate) fn sqrt<S: rustc_apfloat::ieee::Semantics>(x: IeeeFloat<S>) -> IeeeFloat<S> {
|
@@ -267,19 +365,49 @@ pub(crate) fn sqrt<S: rustc_apfloat::ieee::Semantics>(x: IeeeFloat<S>) -> IeeeFl
|
267 | 365 | }
|
268 | 366 | }
|
269 | 367 |
|
270 |
| -/// Extend functionality of rustc_apfloat softfloats |
| 368 | +/// Extend functionality of `rustc_apfloat` softfloats for IEEE float types. |
271 | 369 | pub trait IeeeExt: rustc_apfloat::Float {
|
| 370 | + // Some values we use: |
| 371 | + |
272 | 372 | #[inline]
|
273 | 373 | fn one() -> Self {
|
274 | 374 | Self::from_u128(1).value
|
275 | 375 | }
|
276 | 376 |
|
| 377 | + #[inline] |
| 378 | + fn two() -> Self { |
| 379 | + Self::from_u128(2).value |
| 380 | + } |
| 381 | + |
| 382 | + #[inline] |
| 383 | + fn three() -> Self { |
| 384 | + Self::from_u128(3).value |
| 385 | + } |
| 386 | + |
| 387 | + fn pi() -> Self; |
| 388 | + |
277 | 389 | #[inline]
|
278 | 390 | fn clamp(self, min: Self, max: Self) -> Self {
|
279 | 391 | self.maximum(min).minimum(max)
|
280 | 392 | }
|
281 | 393 | }
|
282 |
| -impl<S: rustc_apfloat::ieee::Semantics> IeeeExt for IeeeFloat<S> {} |
| 394 | + |
| 395 | +macro_rules! impl_ieee_pi { |
| 396 | + ($float_ty:ident, $semantic:ty) => { |
| 397 | + impl IeeeExt for IeeeFloat<$semantic> { |
| 398 | + #[inline] |
| 399 | + fn pi() -> Self { |
| 400 | + // We take the value from the standard library as the most reasonable source for an exact π here. |
| 401 | + Self::from_bits($float_ty::consts::PI.to_bits() as _) |
| 402 | + } |
| 403 | + } |
| 404 | + }; |
| 405 | +} |
| 406 | + |
| 407 | +impl_ieee_pi!(f16, HalfS); |
| 408 | +impl_ieee_pi!(f32, SingleS); |
| 409 | +impl_ieee_pi!(f64, DoubleS); |
| 410 | +impl_ieee_pi!(f128, QuadS); |
283 | 411 |
|
284 | 412 | #[cfg(test)]
|
285 | 413 | mod tests {
|
|
0 commit comments