Skip to content

Commit 6f5483e

Browse files
committed
float-to-float casts also have non-deterministic NaN results
1 parent 191e81f commit 6f5483e

File tree

3 files changed

+121
-10
lines changed

3 files changed

+121
-10
lines changed

src/machine.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1002,7 +1002,10 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
10021002
}
10031003

10041004
#[inline(always)]
1005-
fn generate_nan<F: rustc_apfloat::Float>(ecx: &InterpCx<'mir, 'tcx, Self>, inputs: &[F]) -> F {
1005+
fn generate_nan<F1: rustc_apfloat::Float + rustc_apfloat::FloatConvert<F2>, F2: rustc_apfloat::Float>(
1006+
ecx: &InterpCx<'mir, 'tcx, Self>,
1007+
inputs: &[F1],
1008+
) -> F2 {
10061009
ecx.generate_nan(inputs)
10071010
}
10081011

src/operator.rs

+27-9
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::iter;
33
use log::trace;
44

55
use rand::{seq::IteratorRandom, Rng};
6-
use rustc_apfloat::Float;
6+
use rustc_apfloat::{Float, FloatConvert};
77
use rustc_middle::mir;
88
use rustc_target::abi::Size;
99

@@ -78,17 +78,35 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
7878
})
7979
}
8080

81-
fn generate_nan<F: Float>(&self, inputs: &[F]) -> F {
81+
fn generate_nan<F1: Float + FloatConvert<F2>, F2: Float>(&self, inputs: &[F1]) -> F2 {
82+
/// Make the given NaN a signaling NaN.
83+
/// Returns `None` if this would not result in a NaN.
84+
fn make_signaling<F: Float>(f: F) -> Option<F> {
85+
// The quiet/signaling bit is the leftmost bit in the mantissa.
86+
// That's position `PRECISION-1`, since `PRECISION` includes the fixed leading 1 bit,
87+
// and then we subtract 1 more since this is 0-indexed.
88+
let quiet_bit_mask = 1 << (F::PRECISION - 2);
89+
// Unset the bit. Double-check that this wasn't the last bit set in the payload.
90+
// (which would turn the NaN into an infinity).
91+
let f = F::from_bits(f.to_bits() & !quiet_bit_mask);
92+
if f.is_nan() { Some(f) } else { None }
93+
}
94+
8295
let this = self.eval_context_ref();
8396
let mut rand = this.machine.rng.borrow_mut();
84-
// Assemble an iterator of possible NaNs: preferred, unchanged propagation, quieting propagation.
85-
let preferred_nan = F::qnan(Some(0));
97+
// Assemble an iterator of possible NaNs: preferred, quieting propagation, unchanged propagation.
98+
// On some targets there are more possibilities; for now we just generate those options that
99+
// are possible everywhere.
100+
let preferred_nan = F2::qnan(Some(0));
86101
let nans = iter::once(preferred_nan)
87-
.chain(inputs.iter().filter(|f| f.is_nan()).copied())
88-
.chain(inputs.iter().filter(|f| f.is_signaling()).map(|f| {
89-
// Make it quiet, by setting the bit. We assume that `preferred_nan`
90-
// only has bits set that all quiet NaNs need to have set.
91-
F::from_bits(f.to_bits() | preferred_nan.to_bits())
102+
.chain(inputs.iter().filter(|f| f.is_nan()).map(|&f| {
103+
// Regular apfloat cast is quieting.
104+
f.convert(&mut false).value
105+
}))
106+
.chain(inputs.iter().filter(|f| f.is_signaling()).filter_map(|&f| {
107+
let f: F2 = f.convert(&mut false).value;
108+
// We have to de-quiet this again for unchanged propagation.
109+
make_signaling(f)
92110
}));
93111
// Pick one of the NaNs.
94112
let nan = nans.choose(&mut *rand).unwrap();

tests/pass/float_nan.rs

+90
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,95 @@ fn test_f64() {
311311
);
312312
}
313313

314+
fn test_casts() {
315+
let all1_payload_32 = u32_ones(22);
316+
let all1_payload_64 = u64_ones(51);
317+
let left1_payload_64 = (all1_payload_32 as u64) << (51 - 22);
318+
319+
// 64-to-32
320+
check_all_outcomes(
321+
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
322+
|| F32::from(F64::nan(Pos, Quiet, 0).as_f64() as f32),
323+
);
324+
// The preferred payload is always a possibility.
325+
check_all_outcomes(
326+
HashSet::from_iter([
327+
F32::nan(Pos, Quiet, 0),
328+
F32::nan(Neg, Quiet, 0),
329+
F32::nan(Pos, Quiet, all1_payload_32),
330+
F32::nan(Neg, Quiet, all1_payload_32),
331+
]),
332+
|| F32::from(F64::nan(Pos, Quiet, all1_payload_64).as_f64() as f32),
333+
);
334+
// If the input is signaling, then the output *may* also be signaling.
335+
check_all_outcomes(
336+
HashSet::from_iter([
337+
F32::nan(Pos, Quiet, 0),
338+
F32::nan(Neg, Quiet, 0),
339+
F32::nan(Pos, Quiet, all1_payload_32),
340+
F32::nan(Neg, Quiet, all1_payload_32),
341+
F32::nan(Pos, Signaling, all1_payload_32),
342+
F32::nan(Neg, Signaling, all1_payload_32),
343+
]),
344+
|| F32::from(F64::nan(Pos, Signaling, all1_payload_64).as_f64() as f32),
345+
);
346+
// Check that the low bits are gone (not the high bits).
347+
check_all_outcomes(
348+
HashSet::from_iter([
349+
F32::nan(Pos, Quiet, 0),
350+
F32::nan(Neg, Quiet, 0),
351+
]),
352+
|| F32::from(F64::nan(Pos, Quiet, 1).as_f64() as f32),
353+
);
354+
check_all_outcomes(
355+
HashSet::from_iter([
356+
F32::nan(Pos, Quiet, 0),
357+
F32::nan(Neg, Quiet, 0),
358+
F32::nan(Pos, Quiet, 1),
359+
F32::nan(Neg, Quiet, 1),
360+
]),
361+
|| F32::from(F64::nan(Pos, Quiet, 1 << (51-22)).as_f64() as f32),
362+
);
363+
check_all_outcomes(
364+
HashSet::from_iter([
365+
F32::nan(Pos, Quiet, 0),
366+
F32::nan(Neg, Quiet, 0),
367+
// The `1` payload becomes `0`, and the `0` payload cannot be signaling,
368+
// so these are the only options.
369+
]),
370+
|| F32::from(F64::nan(Pos, Signaling, 1).as_f64() as f32),
371+
);
372+
373+
// 32-to-64
374+
check_all_outcomes(
375+
HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
376+
|| F64::from(F32::nan(Pos, Quiet, 0).as_f32() as f64),
377+
);
378+
// The preferred payload is always a possibility.
379+
// Also checks that 0s are added on the right.
380+
check_all_outcomes(
381+
HashSet::from_iter([
382+
F64::nan(Pos, Quiet, 0),
383+
F64::nan(Neg, Quiet, 0),
384+
F64::nan(Pos, Quiet, left1_payload_64),
385+
F64::nan(Neg, Quiet, left1_payload_64),
386+
]),
387+
|| F64::from(F32::nan(Pos, Quiet, all1_payload_32).as_f32() as f64),
388+
);
389+
// If the input is signaling, then the output *may* also be signaling.
390+
check_all_outcomes(
391+
HashSet::from_iter([
392+
F64::nan(Pos, Quiet, 0),
393+
F64::nan(Neg, Quiet, 0),
394+
F64::nan(Pos, Quiet, left1_payload_64),
395+
F64::nan(Neg, Quiet, left1_payload_64),
396+
F64::nan(Pos, Signaling, left1_payload_64),
397+
F64::nan(Neg, Signaling, left1_payload_64),
398+
]),
399+
|| F64::from(F32::nan(Pos, Signaling, all1_payload_32).as_f32() as f64),
400+
);
401+
}
402+
314403
fn main() {
315404
// Check our constants against std, just to be sure.
316405
// We add 1 since our numbers are the number of bits stored
@@ -321,4 +410,5 @@ fn main() {
321410

322411
test_f32();
323412
test_f64();
413+
test_casts();
324414
}

0 commit comments

Comments
 (0)