float-to-float casts also have non-deterministic NaN results
This commit is contained in:
parent
615d738abe
commit
08deb0daed
6 changed files with 150 additions and 17 deletions
|
@ -311,6 +311,21 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||||
F: Float + Into<Scalar<M::Provenance>> + FloatConvert<Single> + FloatConvert<Double>,
|
F: Float + Into<Scalar<M::Provenance>> + FloatConvert<Single> + FloatConvert<Double>,
|
||||||
{
|
{
|
||||||
use rustc_type_ir::sty::TyKind::*;
|
use rustc_type_ir::sty::TyKind::*;
|
||||||
|
|
||||||
|
fn adjust_nan<
|
||||||
|
'mir,
|
||||||
|
'tcx: 'mir,
|
||||||
|
M: Machine<'mir, 'tcx>,
|
||||||
|
F1: rustc_apfloat::Float + FloatConvert<F2>,
|
||||||
|
F2: rustc_apfloat::Float,
|
||||||
|
>(
|
||||||
|
ecx: &InterpCx<'mir, 'tcx, M>,
|
||||||
|
f1: F1,
|
||||||
|
f2: F2,
|
||||||
|
) -> F2 {
|
||||||
|
if f2.is_nan() { M::generate_nan(ecx, &[f1]) } else { f2 }
|
||||||
|
}
|
||||||
|
|
||||||
match *dest_ty.kind() {
|
match *dest_ty.kind() {
|
||||||
// float -> uint
|
// float -> uint
|
||||||
Uint(t) => {
|
Uint(t) => {
|
||||||
|
@ -330,9 +345,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||||
Scalar::from_int(v, size)
|
Scalar::from_int(v, size)
|
||||||
}
|
}
|
||||||
// float -> f32
|
// float -> f32
|
||||||
Float(FloatTy::F32) => Scalar::from_f32(f.convert(&mut false).value),
|
Float(FloatTy::F32) => {
|
||||||
|
Scalar::from_f32(adjust_nan(self, f, f.convert(&mut false).value))
|
||||||
|
}
|
||||||
// float -> f64
|
// float -> f64
|
||||||
Float(FloatTy::F64) => Scalar::from_f64(f.convert(&mut false).value),
|
Float(FloatTy::F64) => {
|
||||||
|
Scalar::from_f64(adjust_nan(self, f, f.convert(&mut false).value))
|
||||||
|
}
|
||||||
// That's it.
|
// That's it.
|
||||||
_ => span_bug!(self.cur_span(), "invalid float to {} cast", dest_ty),
|
_ => span_bug!(self.cur_span(), "invalid float to {} cast", dest_ty),
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use std::borrow::{Borrow, Cow};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
||||||
use rustc_apfloat::Float;
|
use rustc_apfloat::{Float, FloatConvert};
|
||||||
use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece};
|
use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece};
|
||||||
use rustc_middle::mir;
|
use rustc_middle::mir;
|
||||||
use rustc_middle::ty::layout::TyAndLayout;
|
use rustc_middle::ty::layout::TyAndLayout;
|
||||||
|
@ -243,9 +243,12 @@ pub trait Machine<'mir, 'tcx: 'mir>: Sized {
|
||||||
|
|
||||||
/// Generate the NaN returned by a float operation, given the list of inputs.
|
/// Generate the NaN returned by a float operation, given the list of inputs.
|
||||||
/// (This is all inputs, not just NaN inputs!)
|
/// (This is all inputs, not just NaN inputs!)
|
||||||
fn generate_nan<F: Float>(_ecx: &InterpCx<'mir, 'tcx, Self>, _inputs: &[F]) -> F {
|
fn generate_nan<F1: Float + FloatConvert<F2>, F2: Float>(
|
||||||
|
_ecx: &InterpCx<'mir, 'tcx, Self>,
|
||||||
|
_inputs: &[F1],
|
||||||
|
) -> F2 {
|
||||||
// By default we always return the preferred NaN.
|
// By default we always return the preferred NaN.
|
||||||
F::NAN
|
F2::NAN
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called before writing the specified `local` of the `frame`.
|
/// Called before writing the specified `local` of the `frame`.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use rustc_apfloat::Float;
|
use rustc_apfloat::{Float, FloatConvert};
|
||||||
use rustc_middle::mir;
|
use rustc_middle::mir;
|
||||||
use rustc_middle::mir::interpret::{InterpResult, Scalar};
|
use rustc_middle::mir::interpret::{InterpResult, Scalar};
|
||||||
use rustc_middle::ty::layout::TyAndLayout;
|
use rustc_middle::ty::layout::TyAndLayout;
|
||||||
|
@ -104,7 +104,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||||
(ImmTy::from_bool(res, *self.tcx), false)
|
(ImmTy::from_bool(res, *self.tcx), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn binary_float_op<F: Float + Into<Scalar<M::Provenance>>>(
|
fn binary_float_op<F: Float + FloatConvert<F> + Into<Scalar<M::Provenance>>>(
|
||||||
&self,
|
&self,
|
||||||
bin_op: mir::BinOp,
|
bin_op: mir::BinOp,
|
||||||
layout: TyAndLayout<'tcx>,
|
layout: TyAndLayout<'tcx>,
|
||||||
|
|
|
@ -1002,7 +1002,10 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn generate_nan<F: rustc_apfloat::Float>(ecx: &InterpCx<'mir, 'tcx, Self>, inputs: &[F]) -> F {
|
fn generate_nan<F1: rustc_apfloat::Float + rustc_apfloat::FloatConvert<F2>, F2: rustc_apfloat::Float>(
|
||||||
|
ecx: &InterpCx<'mir, 'tcx, Self>,
|
||||||
|
inputs: &[F1],
|
||||||
|
) -> F2 {
|
||||||
ecx.generate_nan(inputs)
|
ecx.generate_nan(inputs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::iter;
|
||||||
use log::trace;
|
use log::trace;
|
||||||
|
|
||||||
use rand::{seq::IteratorRandom, Rng};
|
use rand::{seq::IteratorRandom, Rng};
|
||||||
use rustc_apfloat::Float;
|
use rustc_apfloat::{Float, FloatConvert};
|
||||||
use rustc_middle::mir;
|
use rustc_middle::mir;
|
||||||
use rustc_target::abi::Size;
|
use rustc_target::abi::Size;
|
||||||
|
|
||||||
|
@ -78,17 +78,35 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_nan<F: Float>(&self, inputs: &[F]) -> F {
|
fn generate_nan<F1: Float + FloatConvert<F2>, F2: Float>(&self, inputs: &[F1]) -> F2 {
|
||||||
|
/// Make the given NaN a signaling NaN.
|
||||||
|
/// Returns `None` if this would not result in a NaN.
|
||||||
|
fn make_signaling<F: Float>(f: F) -> Option<F> {
|
||||||
|
// The quiet/signaling bit is the leftmost bit in the mantissa.
|
||||||
|
// That's position `PRECISION-1`, since `PRECISION` includes the fixed leading 1 bit,
|
||||||
|
// and then we subtract 1 more since this is 0-indexed.
|
||||||
|
let quiet_bit_mask = 1 << (F::PRECISION - 2);
|
||||||
|
// Unset the bit. Double-check that this wasn't the last bit set in the payload.
|
||||||
|
// (which would turn the NaN into an infinity).
|
||||||
|
let f = F::from_bits(f.to_bits() & !quiet_bit_mask);
|
||||||
|
if f.is_nan() { Some(f) } else { None }
|
||||||
|
}
|
||||||
|
|
||||||
let this = self.eval_context_ref();
|
let this = self.eval_context_ref();
|
||||||
let mut rand = this.machine.rng.borrow_mut();
|
let mut rand = this.machine.rng.borrow_mut();
|
||||||
// Assemble an iterator of possible NaNs: preferred, unchanged propagation, quieting propagation.
|
// Assemble an iterator of possible NaNs: preferred, quieting propagation, unchanged propagation.
|
||||||
let preferred_nan = F::qnan(Some(0));
|
// On some targets there are more possibilities; for now we just generate those options that
|
||||||
|
// are possible everywhere.
|
||||||
|
let preferred_nan = F2::qnan(Some(0));
|
||||||
let nans = iter::once(preferred_nan)
|
let nans = iter::once(preferred_nan)
|
||||||
.chain(inputs.iter().filter(|f| f.is_nan()).copied())
|
.chain(inputs.iter().filter(|f| f.is_nan()).map(|&f| {
|
||||||
.chain(inputs.iter().filter(|f| f.is_signaling()).map(|f| {
|
// Regular apfloat cast is quieting.
|
||||||
// Make it quiet, by setting the bit. We assume that `preferred_nan`
|
f.convert(&mut false).value
|
||||||
// only has bits set that all quiet NaNs need to have set.
|
}))
|
||||||
F::from_bits(f.to_bits() | preferred_nan.to_bits())
|
.chain(inputs.iter().filter(|f| f.is_signaling()).filter_map(|&f| {
|
||||||
|
let f: F2 = f.convert(&mut false).value;
|
||||||
|
// We have to de-quiet this again for unchanged propagation.
|
||||||
|
make_signaling(f)
|
||||||
}));
|
}));
|
||||||
// Pick one of the NaNs.
|
// Pick one of the NaNs.
|
||||||
let nan = nans.choose(&mut *rand).unwrap();
|
let nan = nans.choose(&mut *rand).unwrap();
|
||||||
|
|
|
@ -311,6 +311,95 @@ fn test_f64() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_casts() {
|
||||||
|
let all1_payload_32 = u32_ones(22);
|
||||||
|
let all1_payload_64 = u64_ones(51);
|
||||||
|
let left1_payload_64 = (all1_payload_32 as u64) << (51 - 22);
|
||||||
|
|
||||||
|
// 64-to-32
|
||||||
|
check_all_outcomes(
|
||||||
|
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|
||||||
|
|| F32::from(F64::nan(Pos, Quiet, 0).as_f64() as f32),
|
||||||
|
);
|
||||||
|
// The preferred payload is always a possibility.
|
||||||
|
check_all_outcomes(
|
||||||
|
HashSet::from_iter([
|
||||||
|
F32::nan(Pos, Quiet, 0),
|
||||||
|
F32::nan(Neg, Quiet, 0),
|
||||||
|
F32::nan(Pos, Quiet, all1_payload_32),
|
||||||
|
F32::nan(Neg, Quiet, all1_payload_32),
|
||||||
|
]),
|
||||||
|
|| F32::from(F64::nan(Pos, Quiet, all1_payload_64).as_f64() as f32),
|
||||||
|
);
|
||||||
|
// If the input is signaling, then the output *may* also be signaling.
|
||||||
|
check_all_outcomes(
|
||||||
|
HashSet::from_iter([
|
||||||
|
F32::nan(Pos, Quiet, 0),
|
||||||
|
F32::nan(Neg, Quiet, 0),
|
||||||
|
F32::nan(Pos, Quiet, all1_payload_32),
|
||||||
|
F32::nan(Neg, Quiet, all1_payload_32),
|
||||||
|
F32::nan(Pos, Signaling, all1_payload_32),
|
||||||
|
F32::nan(Neg, Signaling, all1_payload_32),
|
||||||
|
]),
|
||||||
|
|| F32::from(F64::nan(Pos, Signaling, all1_payload_64).as_f64() as f32),
|
||||||
|
);
|
||||||
|
// Check that the low bits are gone (not the high bits).
|
||||||
|
check_all_outcomes(
|
||||||
|
HashSet::from_iter([
|
||||||
|
F32::nan(Pos, Quiet, 0),
|
||||||
|
F32::nan(Neg, Quiet, 0),
|
||||||
|
]),
|
||||||
|
|| F32::from(F64::nan(Pos, Quiet, 1).as_f64() as f32),
|
||||||
|
);
|
||||||
|
check_all_outcomes(
|
||||||
|
HashSet::from_iter([
|
||||||
|
F32::nan(Pos, Quiet, 0),
|
||||||
|
F32::nan(Neg, Quiet, 0),
|
||||||
|
F32::nan(Pos, Quiet, 1),
|
||||||
|
F32::nan(Neg, Quiet, 1),
|
||||||
|
]),
|
||||||
|
|| F32::from(F64::nan(Pos, Quiet, 1 << (51-22)).as_f64() as f32),
|
||||||
|
);
|
||||||
|
check_all_outcomes(
|
||||||
|
HashSet::from_iter([
|
||||||
|
F32::nan(Pos, Quiet, 0),
|
||||||
|
F32::nan(Neg, Quiet, 0),
|
||||||
|
// The `1` payload becomes `0`, and the `0` payload cannot be signaling,
|
||||||
|
// so these are the only options.
|
||||||
|
]),
|
||||||
|
|| F32::from(F64::nan(Pos, Signaling, 1).as_f64() as f32),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 32-to-64
|
||||||
|
check_all_outcomes(
|
||||||
|
HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
|
||||||
|
|| F64::from(F32::nan(Pos, Quiet, 0).as_f32() as f64),
|
||||||
|
);
|
||||||
|
// The preferred payload is always a possibility.
|
||||||
|
// Also checks that 0s are added on the right.
|
||||||
|
check_all_outcomes(
|
||||||
|
HashSet::from_iter([
|
||||||
|
F64::nan(Pos, Quiet, 0),
|
||||||
|
F64::nan(Neg, Quiet, 0),
|
||||||
|
F64::nan(Pos, Quiet, left1_payload_64),
|
||||||
|
F64::nan(Neg, Quiet, left1_payload_64),
|
||||||
|
]),
|
||||||
|
|| F64::from(F32::nan(Pos, Quiet, all1_payload_32).as_f32() as f64),
|
||||||
|
);
|
||||||
|
// If the input is signaling, then the output *may* also be signaling.
|
||||||
|
check_all_outcomes(
|
||||||
|
HashSet::from_iter([
|
||||||
|
F64::nan(Pos, Quiet, 0),
|
||||||
|
F64::nan(Neg, Quiet, 0),
|
||||||
|
F64::nan(Pos, Quiet, left1_payload_64),
|
||||||
|
F64::nan(Neg, Quiet, left1_payload_64),
|
||||||
|
F64::nan(Pos, Signaling, left1_payload_64),
|
||||||
|
F64::nan(Neg, Signaling, left1_payload_64),
|
||||||
|
]),
|
||||||
|
|| F64::from(F32::nan(Pos, Signaling, all1_payload_32).as_f32() as f64),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// Check our constants against std, just to be sure.
|
// Check our constants against std, just to be sure.
|
||||||
// We add 1 since our numbers are the number of bits stored
|
// We add 1 since our numbers are the number of bits stored
|
||||||
|
@ -321,4 +410,5 @@ fn main() {
|
||||||
|
|
||||||
test_f32();
|
test_f32();
|
||||||
test_f64();
|
test_f64();
|
||||||
|
test_casts();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue