Skip to content

Commit f391acb

Browse files
Implement nondet behaviour and change/add tests.
1 parent 5a5027a commit f391acb

File tree

5 files changed

+394
-174
lines changed

5 files changed

+394
-174
lines changed

src/tools/miri/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, check_intrinsic_arg_count};
1515
use self::simd::EvalContextExt as _;
16-
use crate::math::apply_random_float_error_ulp;
1716
use crate::*;
1817

1918
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
@@ -348,13 +347,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
348347
let f = this.read_scalar(f)?.to_f32()?;
349348
let i = this.read_scalar(i)?.to_i32()?;
350349

351-
let res = math::fixed_powi_float_value(this, f, i).unwrap_or_else(|| {
350+
let res = math::fixed_powi_value(this, f, i).unwrap_or_else(|| {
352351
// Using host floats (but it's fine, this operation does not have guaranteed precision).
353352
let res = f.to_host().powi(i).to_soft();
354353

355354
// Apply a relative error of 4ULP to introduce some non-determinism
356355
// simulating imprecise implementations and optimizations.
357-
apply_random_float_error_ulp(
356+
math::apply_random_float_error_ulp(
358357
this, res, 2, // log2(4)
359358
)
360359
});
@@ -366,7 +365,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
366365
let f = this.read_scalar(f)?.to_f64()?;
367366
let i = this.read_scalar(i)?.to_i32()?;
368367

369-
let res = math::fixed_powi_float_value(this, f, i).unwrap_or_else(|| {
368+
let res = math::fixed_powi_value(this, f, i).unwrap_or_else(|| {
370369
// Using host floats (but it's fine, this operation does not have guaranteed precision).
371370
let res = f.to_host().powi(i).to_soft();
372371

src/tools/miri/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
#![feature(derive_coerce_pointee)]
1717
#![feature(arbitrary_self_types)]
1818
#![feature(iter_advance_by)]
19+
#![feature(f16)]
20+
#![feature(f128)]
1921
// Configure clippy and other lints
2022
#![allow(
2123
clippy::collapsible_else_if,

src/tools/miri/src/math.rs

Lines changed: 158 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use std::ops::Neg;
2+
use std::{f16, f32, f64, f128};
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, HalfS, IeeeFloat, QuadS, Semantics, SingleS};
67
use rustc_middle::ty::{self, FloatTy, ScalarInt};
78

89
use crate::*;
@@ -52,52 +53,95 @@ pub(crate) fn apply_random_float_error_ulp<F: rustc_apfloat::Float>(
5253
apply_random_float_error(ecx, val, err_scale)
5354
}
5455

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+
///
5659
/// Will fail if `val` is not a floating point number.
5760
pub(crate) fn apply_random_float_error_to_imm<'tcx>(
5861
ecx: &mut MiriInterpCx<'tcx>,
5962
val: ImmTy<'tcx>,
6063
ulp_exponent: u32,
6164
) -> InterpResult<'tcx, ImmTy<'tcx>> {
65+
let this = ecx.eval_context_mut();
6266
let scalar = val.to_scalar_int()?;
6367
let res: ScalarInt = match val.layout.ty.kind() {
6468
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(),
6670
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(),
6872
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(),
7074
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(),
7276
_ => bug!("intrinsic called with non-float input type"),
7377
};
7478

7579
interp_ok(ImmTy::from_scalar_int(res, val.layout))
7680
}
7781

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.
8084
pub(crate) fn clamp_float_value<S: Semantics>(
8185
intrinsic_name: &str,
8286
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+
8497
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+
91132
_ => val,
92133
}
93134
}
94135

95136
/// 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
98140
/// - expf32, expf64, exp2f32, exp2f64
99141
/// - logf32, logf64, log2f32, log2f64, log10f32, log10f64
100142
/// - powf32, powf64
143+
/// - erff, erf, erfcf, erfc
144+
/// - hypotf, hypot
101145
///
102146
/// # Return
103147
///
@@ -125,16 +169,68 @@ pub(crate) fn fixed_float_value<S: Semantics>(
125169
ecx: &mut MiriInterpCx<'_>,
126170
intrinsic_name: &str,
127171
args: &[IeeeFloat<S>],
128-
) -> Option<IeeeFloat<S>> {
172+
) -> Option<IeeeFloat<S>>
173+
where
174+
IeeeFloat<S>: IeeeExt,
175+
{
129176
let this = ecx.eval_context_mut();
130177
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+
131184
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,
134187

135188
// e^0 = 1
136189
("expf32" | "expf64" | "exp2f32" | "exp2f64", [input]) if input.is_zero() => one,
137190

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+
138234
// (-1)^(±INF) = 1
139235
("powf32" | "powf64", [base, exp]) if *base == -one && exp.is_infinite() => one,
140236

@@ -164,25 +260,27 @@ pub(crate) fn fixed_float_value<S: Semantics>(
164260

165261
/// Returns `Some(output)` if `powi` (called `pown` in C) results in a fixed value specified in the
166262
/// 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>(
168264
ecx: &mut MiriInterpCx<'_>,
169265
base: IeeeFloat<S>,
170266
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 {
174272
0 => {
175273
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();
178276
// For SNaN treatment, we are consistent with `powf`above.
179277
// (We wouldn't have two, unlike powf all implementations seem to agree for powi,
180278
// 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 })
182280
}
183281

184282
_ => return None,
185-
})
283+
}
186284
}
187285

188286
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
267365
}
268366
}
269367

270-
/// Extend functionality of rustc_apfloat softfloats
368+
/// Extend functionality of `rustc_apfloat` softfloats for IEEE float types.
271369
pub trait IeeeExt: rustc_apfloat::Float {
370+
// Some values we use:
371+
272372
#[inline]
273373
fn one() -> Self {
274374
Self::from_u128(1).value
275375
}
276376

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+
277389
#[inline]
278390
fn clamp(self, min: Self, max: Self) -> Self {
279391
self.maximum(min).minimum(max)
280392
}
281393
}
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);
283411

284412
#[cfg(test)]
285413
mod tests {

0 commit comments

Comments
 (0)