Skip to content

Commit e059457

Browse files
committed
add float_nan test for powf
plus various minor tweaks
1 parent 60a4828 commit e059457

File tree

3 files changed

+52
-26
lines changed

3 files changed

+52
-26
lines changed

src/tools/miri/src/intrinsics/mod.rs

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ use self::atomic::EvalContextExt as _;
1717
use self::helpers::{ToHost, ToSoft, check_intrinsic_arg_count};
1818
use self::simd::EvalContextExt as _;
1919
use crate::math::{IeeeExt, apply_random_float_error_ulp};
20-
use crate::operator::EvalContextExt as _;
2120
use crate::*;
2221

2322
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
@@ -535,49 +534,50 @@ fn fixed_float_value<S: Semantics>(
535534
// (-1)^(±INF) = 1
536535
("powf32" | "powf64", [base, exp]) if *base == -one && exp.is_infinite() => one,
537536

538-
// 1^y = 1 for any y, even a NaN, *but* not a SNaN
537+
// 1^y = 1 for any y, even a NaN
539538
("powf32" | "powf64", [base, exp]) if *base == one => {
540539
let rng = ecx.machine.rng.get_mut();
541-
let return_nan = ecx.machine.float_nondet && rng.random() && exp.is_signaling();
540+
// SNaN exponents get special treatment: they might return 1, or a NaN.
541+
let return_nan = exp.is_signaling() && ecx.machine.float_nondet && rng.random();
542542
// Handle both the musl and glibc cases non-deterministically.
543543
if return_nan { ecx.generate_nan(args) } else { one }
544544
}
545545

546-
// x^(±0) = 1 for any x, even a NaN, *but* not a SNaN
546+
// x^(±0) = 1 for any x, even a NaN
547547
("powf32" | "powf64", [base, exp]) if exp.is_zero() => {
548548
let rng = ecx.machine.rng.get_mut();
549-
let return_nan = ecx.machine.float_nondet && rng.random() && base.is_signaling();
549+
// SNaN bases get special treatment: they might return 1, or a NaN.
550+
let return_nan = base.is_signaling() && ecx.machine.float_nondet && rng.random();
550551
// Handle both the musl and glibc cases non-deterministically.
551552
if return_nan { ecx.generate_nan(args) } else { one }
552553
}
553554

554-
// There are a lot of cases for fixed outputs according to the C Standard, but these are mainly INF or zero
555-
// which are not affected by the applied error.
555+
// There are a lot of cases for fixed outputs according to the C Standard, but these are
556+
// mainly INF or zero which are not affected by the applied error.
556557
_ => return None,
557558
})
558559
}
559560

560-
/// Returns `Some(output)` if `powi` (called `pown` in C) results in a fixed value specified in the C standard
561-
/// (specifically, C23 annex F.10.4.6) when doing `base^exp`. Otherwise, returns `None`.
562-
// TODO: I'm not sure what I should document here about pown(1, SNaN) since musl and glibc do the same and the C standard is explicit here.
561+
/// Returns `Some(output)` if `powi` (called `pown` in C) results in a fixed value specified in the
562+
/// C standard (specifically, C23 annex F.10.4.6) when doing `base^exp`. Otherwise, returns `None`.
563563
fn fixed_powi_float_value<S: Semantics>(
564564
ecx: &mut MiriInterpCx<'_>,
565565
base: IeeeFloat<S>,
566566
exp: i32,
567567
) -> Option<IeeeFloat<S>> {
568-
match exp {
568+
Some(match exp {
569569
0 => {
570570
let one = IeeeFloat::<S>::one();
571571
let rng = ecx.machine.rng.get_mut();
572572
let return_nan = ecx.machine.float_nondet && rng.random() && base.is_signaling();
573-
Some(
574-
// Handle both the musl and glibc powf cases non-deterministically.
575-
if return_nan { ecx.generate_nan(&[base]) } else { one },
576-
)
573+
// For SNaN treatment, we are consistent with `powf`above.
574+
// (We wouldn't have two, unlike powf all implementations seem to agree for powi,
575+
// but for now we are maximally conservative.)
576+
if return_nan { ecx.generate_nan(&[base]) } else { one }
577577
}
578578

579-
_ => None,
580-
}
579+
_ => return None,
580+
})
581581
}
582582

583583
/// Given an floating-point operation and a floating-point value, clamps the result to the output

src/tools/miri/tests/pass/float.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1491,16 +1491,16 @@ fn test_non_determinism() {
14911491
test_operations_f128(25., 18.);
14921492

14931493

1494-
// x^(SNaN) = (1 | NaN)
1495-
ensure_nondet(|| f32::powf(SNAN_F32, 0.0));
1496-
ensure_nondet(|| f64::powf(SNAN_F64, 0.0));
1494+
// SNaN^0 = (1 | NaN)
1495+
ensure_nondet(|| f32::powf(SNAN_F32, 0.0).is_nan());
1496+
ensure_nondet(|| f64::powf(SNAN_F64, 0.0).is_nan());
14971497

1498-
// 1^(SNaN) = (1 | NaN)
1499-
ensure_nondet(|| f32::powf(1.0, SNAN_F32));
1500-
ensure_nondet(|| f64::powf(1.0, SNAN_F64));
1498+
// 1^SNaN = (1 | NaN)
1499+
ensure_nondet(|| f32::powf(1.0, SNAN_F32).is_nan());
1500+
ensure_nondet(|| f64::powf(1.0, SNAN_F64).is_nan());
15011501

15021502
// same as powf (keep it consistent):
1503-
// x^(SNaN) = (1 | NaN)
1504-
ensure_nondet(|| f32::powi(SNAN_F32, 0));
1505-
ensure_nondet(|| f64::powi(SNAN_F64, 0));
1503+
// x^SNaN = (1 | NaN)
1504+
ensure_nondet(|| f32::powi(SNAN_F32, 0).is_nan());
1505+
ensure_nondet(|| f64::powi(SNAN_F64, 0).is_nan());
15061506
}

src/tools/miri/tests/pass/float_nan.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ fn test_f32() {
260260

261261
// Intrinsics
262262
let nan = F32::nan(Neg, Quiet, 0).as_f32();
263+
let snan = F32::nan(Neg, Signaling, 1).as_f32();
263264
check_all_outcomes(
264265
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
265266
|| F32::from(f32::min(nan, nan)),
@@ -313,6 +314,18 @@ fn test_f32() {
313314
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
314315
|| F32::from(nan.ln_gamma().0),
315316
);
317+
check_all_outcomes(
318+
HashSet::from_iter([
319+
F32::from(1.0),
320+
F32::nan(Pos, Quiet, 0),
321+
F32::nan(Neg, Quiet, 0),
322+
F32::nan(Pos, Quiet, 1),
323+
F32::nan(Neg, Quiet, 1),
324+
F32::nan(Pos, Signaling, 1),
325+
F32::nan(Neg, Signaling, 1),
326+
]),
327+
|| F32::from(snan.powf(0.0)),
328+
);
316329
}
317330

318331
fn test_f64() {
@@ -376,6 +389,7 @@ fn test_f64() {
376389

377390
// Intrinsics
378391
let nan = F64::nan(Neg, Quiet, 0).as_f64();
392+
let snan = F64::nan(Neg, Signaling, 1).as_f64();
379393
check_all_outcomes(
380394
HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
381395
|| F64::from(f64::min(nan, nan)),
@@ -433,6 +447,18 @@ fn test_f64() {
433447
HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
434448
|| F64::from(nan.ln_gamma().0),
435449
);
450+
check_all_outcomes(
451+
HashSet::from_iter([
452+
F64::from(1.0),
453+
F64::nan(Pos, Quiet, 0),
454+
F64::nan(Neg, Quiet, 0),
455+
F64::nan(Pos, Quiet, 1),
456+
F64::nan(Neg, Quiet, 1),
457+
F64::nan(Pos, Signaling, 1),
458+
F64::nan(Neg, Signaling, 1),
459+
]),
460+
|| F64::from(snan.powf(0.0)),
461+
);
436462
}
437463

438464
fn test_casts() {

0 commit comments

Comments
 (0)