Skip to content

Commit 69c9687

Browse files
Implement nondet behaviour and change/add tests.
1 parent 83d4ddb commit 69c9687

File tree

4 files changed

+375
-169
lines changed

4 files changed

+375
-169
lines changed

src/intrinsics/mod.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ use rustc_span::{Symbol, sym};
1313
use self::atomic::EvalContextExt as _;
1414
use self::helpers::{ToHost, ToSoft};
1515
use self::simd::EvalContextExt as _;
16-
use crate::math::apply_random_float_error_ulp;
1716
use crate::*;
1817

1918
/// Check that the number of args is what we expect.
@@ -362,13 +361,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
362361
let f = this.read_scalar(f)?.to_f32()?;
363362
let i = this.read_scalar(i)?.to_i32()?;
364363

365-
let res = math::fixed_powi_float_value(this, f, i).unwrap_or_else(|| {
364+
let res = math::fixed_powi_value(this, f, i).unwrap_or_else(|| {
366365
// Using host floats (but it's fine, this operation does not have guaranteed precision).
367366
let res = f.to_host().powi(i).to_soft();
368367

369368
// Apply a relative error of 4ULP to introduce some non-determinism
370369
// simulating imprecise implementations and optimizations.
371-
apply_random_float_error_ulp(this, res, 4)
370+
math::apply_random_float_error_ulp(this, res, 4)
372371
});
373372
let res = this.adjust_nan(res, &[f]);
374373
this.write_scalar(res, dest)?;
@@ -378,7 +377,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
378377
let f = this.read_scalar(f)?.to_f64()?;
379378
let i = this.read_scalar(i)?.to_i32()?;
380379

381-
let res = math::fixed_powi_float_value(this, f, i).unwrap_or_else(|| {
380+
let res = math::fixed_powi_value(this, f, i).unwrap_or_else(|| {
382381
// Using host floats (but it's fine, this operation does not have guaranteed precision).
383382
let res = f.to_host().powi(i).to_soft();
384383

src/math.rs

Lines changed: 148 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use std::ops::Neg;
2+
use std::{f32, f64};
23

34
use rand::Rng as _;
45
use rustc_apfloat::Float as _;
5-
use rustc_apfloat::ieee::{IeeeFloat, Semantics};
6+
use rustc_apfloat::ieee::{DoubleS, IeeeFloat, Semantics, SingleS};
67
use rustc_middle::ty::{self, FloatTy, ScalarInt};
78

89
use crate::*;
@@ -107,29 +108,69 @@ pub(crate) fn apply_random_float_error_to_imm<'tcx>(
107108
interp_ok(ImmTy::from_scalar_int(res, val.layout))
108109
}
109110

110-
/// Given an floating-point operation and a floating-point value, clamps the result to the output
111-
/// range of the given operation.
111+
/// Given a floating-point operation and a floating-point value, clamps the result to the output
112+
/// range of the given operation according to the C standard, if any.
112113
pub(crate) fn clamp_float_value<S: Semantics>(
113114
intrinsic_name: &str,
114115
val: IeeeFloat<S>,
115-
) -> IeeeFloat<S> {
116+
) -> IeeeFloat<S>
117+
where
118+
IeeeFloat<S>: IeeeExt,
119+
{
120+
let zero = IeeeFloat::<S>::ZERO;
121+
let one = IeeeFloat::<S>::one();
122+
let two = IeeeFloat::<S>::two();
123+
let pi = IeeeFloat::<S>::pi();
124+
let pi_over_2 = (pi / two).value;
125+
116126
match intrinsic_name {
117-
// sin and cos: [-1, 1]
118-
"sinf32" | "cosf32" | "sinf64" | "cosf64" =>
119-
val.clamp(IeeeFloat::<S>::one().neg(), IeeeFloat::<S>::one()),
120-
// exp: [0, +INF]
121-
"expf32" | "exp2f32" | "expf64" | "exp2f64" =>
122-
IeeeFloat::<S>::maximum(val, IeeeFloat::<S>::ZERO),
127+
// sin, cos, tanh: [-1, 1]
128+
#[rustfmt::skip]
129+
| "sinf32"
130+
| "sinf64"
131+
| "cosf32"
132+
| "cosf64"
133+
| "tanhf"
134+
| "tanh"
135+
=> val.clamp(one.neg(), one),
136+
137+
// exp: [0, +INF)
138+
"expf32" | "exp2f32" | "expf64" | "exp2f64" => val.maximum(zero),
139+
140+
// cosh: [1, +INF)
141+
"coshf" | "cosh" => val.maximum(one),
142+
143+
// acos: [0, π]
144+
"acosf" | "acos" => val.clamp(zero, pi),
145+
146+
// asin: [-π, +π]
147+
"asinf" | "asin" => val.clamp(pi.neg(), pi),
148+
149+
// atan: (-π/2, +π/2)
150+
"atanf" | "atan" => val.clamp(pi_over_2.neg(), pi_over_2),
151+
152+
// erfc: (-1, 1)
153+
"erff" | "erf" => val.clamp(one.neg(), one),
154+
155+
// erfc: (0, 2)
156+
"erfcf" | "erfc" => val.clamp(zero, two),
157+
158+
// atan2(y, x): arctan(y/x) in [−π, +π]
159+
"atan2f" | "atan2" => val.clamp(pi.neg(), pi),
160+
123161
_ => val,
124162
}
125163
}
126164

127165
/// For the intrinsics:
128-
/// - sinf32, sinf64
129-
/// - cosf32, cosf64
166+
/// - sinf32, sinf64, sinhf, sinh
167+
/// - cosf32, cosf64, coshf, cosh
168+
/// - tanhf, tanh, atanf, atan, atan2f, atan2
130169
/// - expf32, expf64, exp2f32, exp2f64
131170
/// - logf32, logf64, log2f32, log2f64, log10f32, log10f64
132171
/// - powf32, powf64
172+
/// - erff, erf, erfcf, erfc
173+
/// - hypotf, hypot
133174
///
134175
/// # Return
135176
///
@@ -157,16 +198,68 @@ pub(crate) fn fixed_float_value<S: Semantics>(
157198
ecx: &mut MiriInterpCx<'_>,
158199
intrinsic_name: &str,
159200
args: &[IeeeFloat<S>],
160-
) -> Option<IeeeFloat<S>> {
201+
) -> Option<IeeeFloat<S>>
202+
where
203+
IeeeFloat<S>: IeeeExt,
204+
{
161205
let this = ecx.eval_context_mut();
162206
let one = IeeeFloat::<S>::one();
207+
let two = IeeeFloat::<S>::two();
208+
let three = IeeeFloat::<S>::three();
209+
let pi = IeeeFloat::<S>::pi();
210+
let pi_over_2 = (pi / two).value;
211+
let pi_over_4 = (pi_over_2 / two).value;
212+
163213
Some(match (intrinsic_name, args) {
164-
// cos(+- 0) = 1
165-
("cosf32" | "cosf64", [input]) if input.is_zero() => one,
214+
// cos(±0) and cosh(±0)= 1
215+
("cosf32" | "cosf64" | "coshf" | "cosh", [input]) if input.is_zero() => one,
166216

167217
// e^0 = 1
168218
("expf32" | "expf64" | "exp2f32" | "exp2f64", [input]) if input.is_zero() => one,
169219

220+
// tanh(±INF) = ±1
221+
("tanhf" | "tanh", [input]) if input.is_infinite() => one.copy_sign(*input),
222+
223+
// atan(±INF) = ±π/2
224+
("atanf" | "atan", [input]) if input.is_infinite() => pi_over_2.copy_sign(*input),
225+
226+
// erf(±INF) = ±1
227+
("erff" | "erf", [input]) if input.is_infinite() => one.copy_sign(*input),
228+
229+
// erfc(-INF) = 2
230+
("erfcf" | "erfc", [input]) if input.is_neg_infinity() => (one + one).value,
231+
232+
// hypot(x, ±0) = abs(x), if x is not a NaN.
233+
("_hypotf" | "hypotf" | "_hypot" | "hypot", [x, y]) if !x.is_nan() && y.is_zero() =>
234+
x.abs(),
235+
236+
// atan2(±0,−0) = ±π.
237+
// atan2(±0, y) = ±π for y < 0.
238+
// Must check for non NaN because `y.is_negative()` also applies to NaN.
239+
("atan2f" | "atan2", [x, y]) if (x.is_zero() && (y.is_negative() && !y.is_nan())) =>
240+
pi.copy_sign(*x),
241+
242+
// atan2(±x,−∞) = ±π for finite x > 0.
243+
("atan2f" | "atan2", [x, y])
244+
if (!x.is_zero() && !x.is_infinite()) && y.is_neg_infinity() =>
245+
pi.copy_sign(*x),
246+
247+
// atan2(x, ±0) = −π/2 for x < 0.
248+
// atan2(x, ±0) = π/2 for x > 0.
249+
("atan2f" | "atan2", [x, y]) if !x.is_zero() && y.is_zero() => pi_over_2.copy_sign(*x),
250+
251+
//atan2(±∞, −∞) = ±3π/4
252+
("atan2f" | "atan2", [x, y]) if x.is_infinite() && y.is_neg_infinity() =>
253+
(pi_over_4 * three).value.copy_sign(*x),
254+
255+
//atan2(±∞, +∞) = ±π/4
256+
("atan2f" | "atan2", [x, y]) if x.is_infinite() && y.is_pos_infinity() =>
257+
pi_over_4.copy_sign(*x),
258+
259+
// atan2(±∞, y) returns ±π/2 for finite y.
260+
("atan2f" | "atan2", [x, y]) if x.is_infinite() && (!y.is_infinite() && !y.is_nan()) =>
261+
pi_over_2.copy_sign(*x),
262+
170263
// (-1)^(±INF) = 1
171264
("powf32" | "powf64", [base, exp]) if *base == -one && exp.is_infinite() => one,
172265

@@ -196,25 +289,27 @@ pub(crate) fn fixed_float_value<S: Semantics>(
196289

197290
/// Returns `Some(output)` if `powi` (called `pown` in C) results in a fixed value specified in the
198291
/// C standard (specifically, C23 annex F.10.4.6) when doing `base^exp`. Otherwise, returns `None`.
199-
pub(crate) fn fixed_powi_float_value<S: Semantics>(
292+
pub(crate) fn fixed_powi_value<S: Semantics>(
200293
ecx: &mut MiriInterpCx<'_>,
201294
base: IeeeFloat<S>,
202295
exp: i32,
203-
) -> Option<IeeeFloat<S>> {
204-
let this = ecx.eval_context_mut();
205-
Some(match exp {
296+
) -> Option<IeeeFloat<S>>
297+
where
298+
IeeeFloat<S>: IeeeExt,
299+
{
300+
match exp {
206301
0 => {
207302
let one = IeeeFloat::<S>::one();
208-
let rng = this.machine.rng.get_mut();
209-
let return_nan = this.machine.float_nondet && rng.random() && base.is_signaling();
303+
let rng = ecx.machine.rng.get_mut();
304+
let return_nan = ecx.machine.float_nondet && rng.random() && base.is_signaling();
210305
// For SNaN treatment, we are consistent with `powf`above.
211306
// (We wouldn't have two, unlike powf all implementations seem to agree for powi,
212307
// but for now we are maximally conservative.)
213-
if return_nan { this.generate_nan(&[base]) } else { one }
308+
Some(if return_nan { ecx.generate_nan(&[base]) } else { one })
214309
}
215310

216311
_ => return None,
217-
})
312+
}
218313
}
219314

220315
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
299394
}
300395
}
301396

302-
/// Extend functionality of rustc_apfloat softfloats
397+
/// Extend functionality of `rustc_apfloat` softfloats for IEEE float types.
303398
pub trait IeeeExt: rustc_apfloat::Float {
399+
// Some values we use:
400+
304401
#[inline]
305402
fn one() -> Self {
306403
Self::from_u128(1).value
307404
}
308405

406+
#[inline]
407+
fn two() -> Self {
408+
Self::from_u128(2).value
409+
}
410+
411+
#[inline]
412+
fn three() -> Self {
413+
Self::from_u128(3).value
414+
}
415+
416+
fn pi() -> Self;
417+
309418
#[inline]
310419
fn clamp(self, min: Self, max: Self) -> Self {
311420
self.maximum(min).minimum(max)
312421
}
313422
}
314-
impl<S: rustc_apfloat::ieee::Semantics> IeeeExt for IeeeFloat<S> {}
423+
424+
macro_rules! impl_ieee_pi {
425+
($float_ty:ident, $semantic:ty) => {
426+
impl IeeeExt for IeeeFloat<$semantic> {
427+
#[inline]
428+
fn pi() -> Self {
429+
// We take the value from the standard library as the most reasonable source for an exact π here.
430+
Self::from_bits($float_ty::consts::PI.to_bits().into())
431+
}
432+
}
433+
};
434+
}
435+
436+
impl_ieee_pi!(f32, SingleS);
437+
impl_ieee_pi!(f64, DoubleS);
315438

316439
#[cfg(test)]
317440
mod tests {

0 commit comments

Comments
 (0)