Auto merge of #3244 - RalfJung:nan-nondet, r=RalfJung

NaN non-determinism for intrinsics and libm functions
This commit is contained in:
bors 2023-12-28 11:57:27 +00:00
commit a63fd5ea6b
14 changed files with 488 additions and 197 deletions

View file

@ -6,6 +6,7 @@ use std::time::Duration;
use log::trace;
use rustc_apfloat::ieee::{Double, Single};
use rustc_apfloat::Float;
use rustc_hir::def::{DefKind, Namespace};
use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX};
use rustc_index::IndexVec;
@ -117,6 +118,50 @@ fn try_resolve_did(tcx: TyCtxt<'_>, path: &[&str], namespace: Option<Namespace>)
}
}
/// Convert a softfloat type to its corresponding hostfloat type.
pub trait ToHost {
type HostFloat;
fn to_host(self) -> Self::HostFloat;
}
/// Convert a hostfloat type to its corresponding softfloat type.
pub trait ToSoft {
type SoftFloat;
fn to_soft(self) -> Self::SoftFloat;
}
impl ToHost for rustc_apfloat::ieee::Double {
type HostFloat = f64;
fn to_host(self) -> Self::HostFloat {
f64::from_bits(self.to_bits().try_into().unwrap())
}
}
impl ToSoft for f64 {
type SoftFloat = rustc_apfloat::ieee::Double;
fn to_soft(self) -> Self::SoftFloat {
Float::from_bits(self.to_bits().into())
}
}
impl ToHost for rustc_apfloat::ieee::Single {
type HostFloat = f32;
fn to_host(self) -> Self::HostFloat {
f32::from_bits(self.to_bits().try_into().unwrap())
}
}
impl ToSoft for f32 {
type SoftFloat = rustc_apfloat::ieee::Single;
fn to_soft(self) -> Self::SoftFloat {
Float::from_bits(self.to_bits().into())
}
}
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
/// Checks if the given crate/module exists.

View file

@ -118,4 +118,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
nan
}
}
fn adjust_nan<F1: Float + FloatConvert<F2>, F2: Float>(&self, f: F2, inputs: &[F1]) -> F2 {
if f.is_nan() { self.generate_nan(inputs) } else { f }
}
}

View file

@ -23,6 +23,7 @@ use rustc_target::{
use super::backtrace::EvalContextExt as _;
use crate::*;
use helpers::{ToHost, ToSoft};
/// Type of dynamic symbols (for `dlsym` et al)
#[derive(Debug, Copy, Clone)]
@ -886,23 +887,26 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
| "tgammaf"
=> {
let [f] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let f = this.read_scalar(f)?.to_f32()?;
// FIXME: Using host floats.
let f = f32::from_bits(this.read_scalar(f)?.to_u32()?);
let f_host = f.to_host();
let res = match link_name.as_str() {
"cbrtf" => f.cbrt(),
"coshf" => f.cosh(),
"sinhf" => f.sinh(),
"tanf" => f.tan(),
"tanhf" => f.tanh(),
"acosf" => f.acos(),
"asinf" => f.asin(),
"atanf" => f.atan(),
"log1pf" => f.ln_1p(),
"expm1f" => f.exp_m1(),
"tgammaf" => f.gamma(),
"cbrtf" => f_host.cbrt(),
"coshf" => f_host.cosh(),
"sinhf" => f_host.sinh(),
"tanf" => f_host.tan(),
"tanhf" => f_host.tanh(),
"acosf" => f_host.acos(),
"asinf" => f_host.asin(),
"atanf" => f_host.atan(),
"log1pf" => f_host.ln_1p(),
"expm1f" => f_host.exp_m1(),
"tgammaf" => f_host.gamma(),
_ => bug!(),
};
this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?;
let res = res.to_soft();
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
#[rustfmt::skip]
| "_hypotf"
@ -911,19 +915,20 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
| "fdimf"
=> {
let [f1, f2] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let f1 = this.read_scalar(f1)?.to_f32()?;
let f2 = this.read_scalar(f2)?.to_f32()?;
// underscore case for windows, here and below
// (see https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/floating-point-primitives?view=vs-2019)
// FIXME: Using host floats.
let f1 = f32::from_bits(this.read_scalar(f1)?.to_u32()?);
let f2 = f32::from_bits(this.read_scalar(f2)?.to_u32()?);
let res = match link_name.as_str() {
"_hypotf" | "hypotf" => f1.hypot(f2),
"atan2f" => f1.atan2(f2),
"_hypotf" | "hypotf" => f1.to_host().hypot(f2.to_host()).to_soft(),
"atan2f" => f1.to_host().atan2(f2.to_host()).to_soft(),
#[allow(deprecated)]
"fdimf" => f1.abs_sub(f2),
"fdimf" => f1.to_host().abs_sub(f2.to_host()).to_soft(),
_ => bug!(),
};
this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?;
let res = this.adjust_nan(res, &[f1, f2]);
this.write_scalar(res, dest)?;
}
#[rustfmt::skip]
| "cbrt"
@ -939,23 +944,26 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
| "tgamma"
=> {
let [f] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let f = this.read_scalar(f)?.to_f64()?;
// FIXME: Using host floats.
let f = f64::from_bits(this.read_scalar(f)?.to_u64()?);
let f_host = f.to_host();
let res = match link_name.as_str() {
"cbrt" => f.cbrt(),
"cosh" => f.cosh(),
"sinh" => f.sinh(),
"tan" => f.tan(),
"tanh" => f.tanh(),
"acos" => f.acos(),
"asin" => f.asin(),
"atan" => f.atan(),
"log1p" => f.ln_1p(),
"expm1" => f.exp_m1(),
"tgamma" => f.gamma(),
"cbrt" => f_host.cbrt(),
"cosh" => f_host.cosh(),
"sinh" => f_host.sinh(),
"tan" => f_host.tan(),
"tanh" => f_host.tanh(),
"acos" => f_host.acos(),
"asin" => f_host.asin(),
"atan" => f_host.atan(),
"log1p" => f_host.ln_1p(),
"expm1" => f_host.exp_m1(),
"tgamma" => f_host.gamma(),
_ => bug!(),
};
this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?;
let res = res.to_soft();
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
#[rustfmt::skip]
| "_hypot"
@ -964,17 +972,20 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
| "fdim"
=> {
let [f1, f2] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let f1 = this.read_scalar(f1)?.to_f64()?;
let f2 = this.read_scalar(f2)?.to_f64()?;
// underscore case for windows, here and below
// (see https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/floating-point-primitives?view=vs-2019)
// FIXME: Using host floats.
let f1 = f64::from_bits(this.read_scalar(f1)?.to_u64()?);
let f2 = f64::from_bits(this.read_scalar(f2)?.to_u64()?);
let res = match link_name.as_str() {
"_hypot" | "hypot" => f1.hypot(f2),
"atan2" => f1.atan2(f2),
"_hypot" | "hypot" => f1.to_host().hypot(f2.to_host()).to_soft(),
"atan2" => f1.to_host().atan2(f2.to_host()).to_soft(),
#[allow(deprecated)]
"fdim" => f1.abs_sub(f2),
"fdim" => f1.to_host().abs_sub(f2.to_host()).to_soft(),
_ => bug!(),
};
this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?;
let res = this.adjust_nan(res, &[f1, f2]);
this.write_scalar(res, dest)?;
}
#[rustfmt::skip]
| "_ldexp"
@ -987,27 +998,30 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let exp = this.read_scalar(exp)?.to_i32()?;
let res = x.scalbn(exp);
this.write_scalar(Scalar::from_f64(res), dest)?;
let res = this.adjust_nan(res, &[x]);
this.write_scalar(res, dest)?;
}
"lgammaf_r" => {
let [x, signp] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
// FIXME: Using host floats.
let x = f32::from_bits(this.read_scalar(x)?.to_u32()?);
let x = this.read_scalar(x)?.to_f32()?;
let signp = this.deref_pointer(signp)?;
let (res, sign) = x.ln_gamma();
// FIXME: Using host floats.
let (res, sign) = x.to_host().ln_gamma();
this.write_int(sign, &signp)?;
this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?;
let res = this.adjust_nan(res.to_soft(), &[x]);
this.write_scalar(res, dest)?;
}
"lgamma_r" => {
let [x, signp] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
// FIXME: Using host floats.
let x = f64::from_bits(this.read_scalar(x)?.to_u64()?);
let x = this.read_scalar(x)?.to_f64()?;
let signp = this.deref_pointer(signp)?;
let (res, sign) = x.ln_gamma();
// FIXME: Using host floats.
let (res, sign) = x.to_host().ln_gamma();
this.write_int(sign, &signp)?;
this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?;
let res = this.adjust_nan(res.to_soft(), &[x]);
this.write_scalar(res, dest)?;
}
// LLVM intrinsics

View file

@ -15,7 +15,7 @@ use rustc_target::abi::Size;
use crate::*;
use atomic::EvalContextExt as _;
use helpers::check_arg_count;
use helpers::{check_arg_count, ToHost, ToSoft};
use simd::EvalContextExt as _;
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
@ -146,12 +146,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let [f] = check_arg_count(args)?;
let f = this.read_scalar(f)?.to_f32()?;
// Can be implemented in soft-floats.
// This is a "bitwise" operation, so there's no NaN non-determinism.
this.write_scalar(Scalar::from_f32(f.abs()), dest)?;
}
"fabsf64" => {
let [f] = check_arg_count(args)?;
let f = this.read_scalar(f)?.to_f64()?;
// Can be implemented in soft-floats.
// This is a "bitwise" operation, so there's no NaN non-determinism.
this.write_scalar(Scalar::from_f64(f.abs()), dest)?;
}
#[rustfmt::skip]
@ -170,25 +172,28 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
| "rintf32"
=> {
let [f] = check_arg_count(args)?;
let f = this.read_scalar(f)?.to_f32()?;
// FIXME: Using host floats.
let f = f32::from_bits(this.read_scalar(f)?.to_u32()?);
let f = match intrinsic_name {
"sinf32" => f.sin(),
"cosf32" => f.cos(),
"sqrtf32" => f.sqrt(),
"expf32" => f.exp(),
"exp2f32" => f.exp2(),
"logf32" => f.ln(),
"log10f32" => f.log10(),
"log2f32" => f.log2(),
"floorf32" => f.floor(),
"ceilf32" => f.ceil(),
"truncf32" => f.trunc(),
"roundf32" => f.round(),
"rintf32" => f.round_ties_even(),
let f_host = f.to_host();
let res = match intrinsic_name {
"sinf32" => f_host.sin(),
"cosf32" => f_host.cos(),
"sqrtf32" => f_host.sqrt(),
"expf32" => f_host.exp(),
"exp2f32" => f_host.exp2(),
"logf32" => f_host.ln(),
"log10f32" => f_host.log10(),
"log2f32" => f_host.log2(),
"floorf32" => f_host.floor(),
"ceilf32" => f_host.ceil(),
"truncf32" => f_host.trunc(),
"roundf32" => f_host.round(),
"rintf32" => f_host.round_ties_even(),
_ => bug!(),
};
this.write_scalar(Scalar::from_u32(f.to_bits()), dest)?;
let res = res.to_soft();
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
#[rustfmt::skip]
@ -207,25 +212,28 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
| "rintf64"
=> {
let [f] = check_arg_count(args)?;
let f = this.read_scalar(f)?.to_f64()?;
// FIXME: Using host floats.
let f = f64::from_bits(this.read_scalar(f)?.to_u64()?);
let f = match intrinsic_name {
"sinf64" => f.sin(),
"cosf64" => f.cos(),
"sqrtf64" => f.sqrt(),
"expf64" => f.exp(),
"exp2f64" => f.exp2(),
"logf64" => f.ln(),
"log10f64" => f.log10(),
"log2f64" => f.log2(),
"floorf64" => f.floor(),
"ceilf64" => f.ceil(),
"truncf64" => f.trunc(),
"roundf64" => f.round(),
"rintf64" => f.round_ties_even(),
let f_host = f.to_host();
let res = match intrinsic_name {
"sinf64" => f_host.sin(),
"cosf64" => f_host.cos(),
"sqrtf64" => f_host.sqrt(),
"expf64" => f_host.exp(),
"exp2f64" => f_host.exp2(),
"logf64" => f_host.ln(),
"log10f64" => f_host.log10(),
"log2f64" => f_host.log2(),
"floorf64" => f_host.floor(),
"ceilf64" => f_host.ceil(),
"truncf64" => f_host.trunc(),
"roundf64" => f_host.round(),
"rintf64" => f_host.round_ties_even(),
_ => bug!(),
};
this.write_scalar(Scalar::from_u64(f.to_bits()), dest)?;
let res = res.to_soft();
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
#[rustfmt::skip]
@ -268,7 +276,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
),
_ => {}
}
this.binop_ignore_overflow(op, &a, &b, dest)?;
let res = this.wrapping_binary_op(op, &a, &b)?;
if !float_finite(&res)? {
throw_ub_format!("`{intrinsic_name}` intrinsic produced non-finite value as result");
}
// This cannot be a NaN so we also don't have to apply any non-determinism.
// (Also, `wrapping_binary_op` already called `generate_nan` if needed.)
this.write_immediate(*res, dest)?;
}
#[rustfmt::skip]
@ -280,9 +294,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let a = this.read_scalar(a)?.to_f32()?;
let b = this.read_scalar(b)?.to_f32()?;
let res = match intrinsic_name {
"minnumf32" => a.min(b),
"maxnumf32" => a.max(b),
"copysignf32" => a.copy_sign(b),
"minnumf32" => this.adjust_nan(a.min(b), &[a, b]),
"maxnumf32" => this.adjust_nan(a.max(b), &[a, b]),
"copysignf32" => a.copy_sign(b), // bitwise, no NaN adjustments
_ => bug!(),
};
this.write_scalar(Scalar::from_f32(res), dest)?;
@ -297,68 +311,74 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let a = this.read_scalar(a)?.to_f64()?;
let b = this.read_scalar(b)?.to_f64()?;
let res = match intrinsic_name {
"minnumf64" => a.min(b),
"maxnumf64" => a.max(b),
"copysignf64" => a.copy_sign(b),
"minnumf64" => this.adjust_nan(a.min(b), &[a, b]),
"maxnumf64" => this.adjust_nan(a.max(b), &[a, b]),
"copysignf64" => a.copy_sign(b), // bitwise, no NaN adjustments
_ => bug!(),
};
this.write_scalar(Scalar::from_f64(res), dest)?;
}
"powf32" => {
let [f, f2] = check_arg_count(args)?;
// FIXME: Using host floats.
let f = f32::from_bits(this.read_scalar(f)?.to_u32()?);
let f2 = f32::from_bits(this.read_scalar(f2)?.to_u32()?);
let res = f.powf(f2);
this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?;
}
"powf64" => {
let [f, f2] = check_arg_count(args)?;
// FIXME: Using host floats.
let f = f64::from_bits(this.read_scalar(f)?.to_u64()?);
let f2 = f64::from_bits(this.read_scalar(f2)?.to_u64()?);
let res = f.powf(f2);
this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?;
}
"fmaf32" => {
let [a, b, c] = check_arg_count(args)?;
let a = this.read_scalar(a)?.to_f32()?;
let b = this.read_scalar(b)?.to_f32()?;
let c = this.read_scalar(c)?.to_f32()?;
// FIXME: Using host floats, to work around https://github.com/rust-lang/rustc_apfloat/issues/11
let a = f32::from_bits(this.read_scalar(a)?.to_u32()?);
let b = f32::from_bits(this.read_scalar(b)?.to_u32()?);
let c = f32::from_bits(this.read_scalar(c)?.to_u32()?);
let res = a.mul_add(b, c);
this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?;
let res = a.to_host().mul_add(b.to_host(), c.to_host()).to_soft();
let res = this.adjust_nan(res, &[a, b, c]);
this.write_scalar(res, dest)?;
}
"fmaf64" => {
let [a, b, c] = check_arg_count(args)?;
let a = this.read_scalar(a)?.to_f64()?;
let b = this.read_scalar(b)?.to_f64()?;
let c = this.read_scalar(c)?.to_f64()?;
// FIXME: Using host floats, to work around https://github.com/rust-lang/rustc_apfloat/issues/11
let a = f64::from_bits(this.read_scalar(a)?.to_u64()?);
let b = f64::from_bits(this.read_scalar(b)?.to_u64()?);
let c = f64::from_bits(this.read_scalar(c)?.to_u64()?);
let res = a.mul_add(b, c);
this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?;
let res = a.to_host().mul_add(b.to_host(), c.to_host()).to_soft();
let res = this.adjust_nan(res, &[a, b, c]);
this.write_scalar(res, dest)?;
}
"powf32" => {
let [f1, f2] = check_arg_count(args)?;
let f1 = this.read_scalar(f1)?.to_f32()?;
let f2 = this.read_scalar(f2)?.to_f32()?;
// FIXME: Using host floats.
let res = f1.to_host().powf(f2.to_host()).to_soft();
let res = this.adjust_nan(res, &[f1, f2]);
this.write_scalar(res, dest)?;
}
"powf64" => {
let [f1, f2] = check_arg_count(args)?;
let f1 = this.read_scalar(f1)?.to_f64()?;
let f2 = this.read_scalar(f2)?.to_f64()?;
// FIXME: Using host floats.
let res = f1.to_host().powf(f2.to_host()).to_soft();
let res = this.adjust_nan(res, &[f1, f2]);
this.write_scalar(res, dest)?;
}
"powif32" => {
let [f, i] = check_arg_count(args)?;
// FIXME: Using host floats.
let f = f32::from_bits(this.read_scalar(f)?.to_u32()?);
let f = this.read_scalar(f)?.to_f32()?;
let i = this.read_scalar(i)?.to_i32()?;
let res = f.powi(i);
this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?;
// FIXME: Using host floats.
let res = f.to_host().powi(i).to_soft();
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
"powif64" => {
let [f, i] = check_arg_count(args)?;
// FIXME: Using host floats.
let f = f64::from_bits(this.read_scalar(f)?.to_u64()?);
let f = this.read_scalar(f)?.to_f64()?;
let i = this.read_scalar(i)?.to_i32()?;
let res = f.powi(i);
this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?;
// FIXME: Using host floats.
let res = f.to_host().powi(i).to_soft();
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
"float_to_int_unchecked" => {

View file

@ -5,10 +5,17 @@ use rustc_span::{sym, Symbol};
use rustc_target::abi::{Endian, HasDataLayout};
use crate::helpers::{
bool_to_simd_element, check_arg_count, round_to_next_multiple_of, simd_element_to_bool,
bool_to_simd_element, check_arg_count, round_to_next_multiple_of, simd_element_to_bool, ToHost,
ToSoft,
};
use crate::*;
#[derive(Copy, Clone)]
pub(crate) enum MinMax {
Min,
Max,
}
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
/// Calls the simd intrinsic `intrinsic`; the `simd_` prefix has already been removed.
@ -67,13 +74,17 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let op = this.read_immediate(&this.project_index(&op, i)?)?;
let dest = this.project_index(&dest, i)?;
let val = match which {
Op::MirOp(mir_op) => this.wrapping_unary_op(mir_op, &op)?.to_scalar(),
Op::MirOp(mir_op) => {
// This already does NaN adjustments
this.wrapping_unary_op(mir_op, &op)?.to_scalar()
}
Op::Abs => {
// Works for f32 and f64.
let ty::Float(float_ty) = op.layout.ty.kind() else {
span_bug!(this.cur_span(), "{} operand is not a float", intrinsic_name)
};
let op = op.to_scalar();
// "Bitwise" operation, no NaN adjustments
match float_ty {
FloatTy::F32 => Scalar::from_f32(op.to_f32()?.abs()),
FloatTy::F64 => Scalar::from_f64(op.to_f64()?.abs()),
@ -86,14 +97,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
// FIXME using host floats
match float_ty {
FloatTy::F32 => {
let f = f32::from_bits(op.to_scalar().to_u32()?);
let res = f.sqrt();
Scalar::from_u32(res.to_bits())
let f = op.to_scalar().to_f32()?;
let res = f.to_host().sqrt().to_soft();
let res = this.adjust_nan(res, &[f]);
Scalar::from(res)
}
FloatTy::F64 => {
let f = f64::from_bits(op.to_scalar().to_u64()?);
let res = f.sqrt();
Scalar::from_u64(res.to_bits())
let f = op.to_scalar().to_f64()?;
let res = f.to_host().sqrt().to_soft();
let res = this.adjust_nan(res, &[f]);
Scalar::from(res)
}
}
}
@ -105,11 +118,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
FloatTy::F32 => {
let f = op.to_scalar().to_f32()?;
let res = f.round_to_integral(rounding).value;
let res = this.adjust_nan(res, &[f]);
Scalar::from_f32(res)
}
FloatTy::F64 => {
let f = op.to_scalar().to_f64()?;
let res = f.round_to_integral(rounding).value;
let res = this.adjust_nan(res, &[f]);
Scalar::from_f64(res)
}
}
@ -157,8 +172,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
enum Op {
MirOp(BinOp),
SaturatingOp(BinOp),
FMax,
FMin,
FMinMax(MinMax),
WrappingOffset,
}
let which = match intrinsic_name {
@ -178,8 +192,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
"le" => Op::MirOp(BinOp::Le),
"gt" => Op::MirOp(BinOp::Gt),
"ge" => Op::MirOp(BinOp::Ge),
"fmax" => Op::FMax,
"fmin" => Op::FMin,
"fmax" => Op::FMinMax(MinMax::Max),
"fmin" => Op::FMinMax(MinMax::Min),
"saturating_add" => Op::SaturatingOp(BinOp::Add),
"saturating_sub" => Op::SaturatingOp(BinOp::Sub),
"arith_offset" => Op::WrappingOffset,
@ -192,6 +206,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let dest = this.project_index(&dest, i)?;
let val = match which {
Op::MirOp(mir_op) => {
// This does NaN adjustments.
let (val, overflowed) = this.overflowing_binary_op(mir_op, &left, &right)?;
if matches!(mir_op, BinOp::Shl | BinOp::Shr) {
// Shifts have extra UB as SIMD operations that the MIR binop does not have.
@ -225,11 +240,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let offset_ptr = ptr.wrapping_signed_offset(offset_bytes, this);
Scalar::from_maybe_pointer(offset_ptr, this)
}
Op::FMax => {
fmax_op(&left, &right)?
}
Op::FMin => {
fmin_op(&left, &right)?
Op::FMinMax(op) => {
this.fminmax_op(op, &left, &right)?
}
};
this.write_scalar(val, &dest)?;
@ -259,18 +271,20 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
};
let val = match float_ty {
FloatTy::F32 => {
let a = f32::from_bits(a.to_u32()?);
let b = f32::from_bits(b.to_u32()?);
let c = f32::from_bits(c.to_u32()?);
let res = a.mul_add(b, c);
Scalar::from_u32(res.to_bits())
let a = a.to_f32()?;
let b = b.to_f32()?;
let c = c.to_f32()?;
let res = a.to_host().mul_add(b.to_host(), c.to_host()).to_soft();
let res = this.adjust_nan(res, &[a, b, c]);
Scalar::from(res)
}
FloatTy::F64 => {
let a = f64::from_bits(a.to_u64()?);
let b = f64::from_bits(b.to_u64()?);
let c = f64::from_bits(c.to_u64()?);
let res = a.mul_add(b, c);
Scalar::from_u64(res.to_bits())
let a = a.to_f64()?;
let b = b.to_f64()?;
let c = c.to_f64()?;
let res = a.to_host().mul_add(b.to_host(), c.to_host()).to_soft();
let res = this.adjust_nan(res, &[a, b, c]);
Scalar::from(res)
}
};
this.write_scalar(val, &dest)?;
@ -295,8 +309,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
enum Op {
MirOp(BinOp),
MirOpBool(BinOp),
Max,
Min,
MinMax(MinMax),
}
let which = match intrinsic_name {
"reduce_and" => Op::MirOp(BinOp::BitAnd),
@ -304,8 +317,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
"reduce_xor" => Op::MirOp(BinOp::BitXor),
"reduce_any" => Op::MirOpBool(BinOp::BitOr),
"reduce_all" => Op::MirOpBool(BinOp::BitAnd),
"reduce_max" => Op::Max,
"reduce_min" => Op::Min,
"reduce_max" => Op::MinMax(MinMax::Max),
"reduce_min" => Op::MinMax(MinMax::Min),
_ => unreachable!(),
};
@ -325,24 +338,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let op = imm_from_bool(simd_element_to_bool(op)?);
this.wrapping_binary_op(mir_op, &res, &op)?
}
Op::Max => {
Op::MinMax(mmop) => {
if matches!(res.layout.ty.kind(), ty::Float(_)) {
ImmTy::from_scalar(fmax_op(&res, &op)?, res.layout)
ImmTy::from_scalar(this.fminmax_op(mmop, &res, &op)?, res.layout)
} else {
// Just boring integers, so NaNs to worry about
if this.wrapping_binary_op(BinOp::Ge, &res, &op)?.to_scalar().to_bool()? {
res
} else {
op
}
}
}
Op::Min => {
if matches!(res.layout.ty.kind(), ty::Float(_)) {
ImmTy::from_scalar(fmin_op(&res, &op)?, res.layout)
} else {
// Just boring integers, so NaNs to worry about
if this.wrapping_binary_op(BinOp::Le, &res, &op)?.to_scalar().to_bool()? {
let mirop = match mmop {
MinMax::Min => BinOp::Le,
MinMax::Max => BinOp::Ge,
};
if this.wrapping_binary_op(mirop, &res, &op)?.to_scalar().to_bool()? {
res
} else {
op
@ -709,6 +714,43 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
}
Ok(())
}
fn fminmax_op(
&self,
op: MinMax,
left: &ImmTy<'tcx, Provenance>,
right: &ImmTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_ref();
assert_eq!(left.layout.ty, right.layout.ty);
let ty::Float(float_ty) = left.layout.ty.kind() else {
bug!("fmax operand is not a float")
};
let left = left.to_scalar();
let right = right.to_scalar();
Ok(match float_ty {
FloatTy::F32 => {
let left = left.to_f32()?;
let right = right.to_f32()?;
let res = match op {
MinMax::Min => left.min(right),
MinMax::Max => left.max(right),
};
let res = this.adjust_nan(res, &[left, right]);
Scalar::from_f32(res)
}
FloatTy::F64 => {
let left = left.to_f64()?;
let right = right.to_f64()?;
let res = match op {
MinMax::Min => left.min(right),
MinMax::Max => left.max(right),
};
let res = this.adjust_nan(res, &[left, right]);
Scalar::from_f64(res)
}
})
}
}
fn simd_bitmask_index(idx: u32, vec_len: u32, endianness: Endian) -> u32 {
@ -719,31 +761,3 @@ fn simd_bitmask_index(idx: u32, vec_len: u32, endianness: Endian) -> u32 {
Endian::Big => vec_len - 1 - idx, // reverse order of bits
}
}
fn fmax_op<'tcx>(
left: &ImmTy<'tcx, Provenance>,
right: &ImmTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
assert_eq!(left.layout.ty, right.layout.ty);
let ty::Float(float_ty) = left.layout.ty.kind() else { bug!("fmax operand is not a float") };
let left = left.to_scalar();
let right = right.to_scalar();
Ok(match float_ty {
FloatTy::F32 => Scalar::from_f32(left.to_f32()?.max(right.to_f32()?)),
FloatTy::F64 => Scalar::from_f64(left.to_f64()?.max(right.to_f64()?)),
})
}
fn fmin_op<'tcx>(
left: &ImmTy<'tcx, Provenance>,
right: &ImmTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
assert_eq!(left.layout.ty, right.layout.ty);
let ty::Float(float_ty) = left.layout.ty.kind() else { bug!("fmin operand is not a float") };
let left = left.to_scalar();
let right = right.to_scalar();
Ok(match float_ty {
FloatTy::F32 => Scalar::from_f32(left.to_f32()?.min(right.to_f32()?)),
FloatTy::F64 => Scalar::from_f64(left.to_f64()?.min(right.to_f64()?)),
})
}

View file

@ -0,0 +1,7 @@
#![feature(core_intrinsics)]
fn main() {
unsafe {
let _x: f32 = core::intrinsics::fdiv_fast(1.0, 0.0); //~ ERROR: `fdiv_fast` intrinsic produced non-finite value as result
}
}

View file

@ -0,0 +1,15 @@
error: Undefined Behavior: `fdiv_fast` intrinsic produced non-finite value as result
--> $DIR/fast_math_result.rs:LL:CC
|
LL | let _x: f32 = core::intrinsics::fdiv_fast(1.0, 0.0);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `fdiv_fast` intrinsic produced non-finite value as result
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE:
= note: inside `main` at $DIR/fast_math_result.rs:LL:CC
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error

View file

@ -1,8 +1,16 @@
#![feature(float_gamma, portable_simd, core_intrinsics, platform_intrinsics)]
use std::collections::HashSet;
use std::fmt;
use std::hash::Hash;
use std::hint::black_box;
fn ldexp(a: f64, b: i32) -> f64 {
extern "C" {
fn ldexp(x: f64, n: i32) -> f64;
}
unsafe { ldexp(a, b) }
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Sign {
Neg = 1,
@ -249,6 +257,58 @@ fn test_f32() {
check_all_outcomes(HashSet::from_iter([F32::nan(Neg, Signaling, all1_payload)]), || {
F32::from(-all1_snan)
});
// Intrinsics
let nan = F32::nan(Neg, Quiet, 0).as_f32();
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(f32::min(nan, nan)),
);
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(nan.sin()),
);
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::nan(Pos, Quiet, 2),
F32::nan(Neg, Quiet, 2),
F32::nan(Pos, Quiet, all1_payload),
F32::nan(Neg, Quiet, all1_payload),
F32::nan(Pos, Signaling, all1_payload),
F32::nan(Neg, Signaling, all1_payload),
]),
|| F32::from(just1.mul_add(F32::nan(Neg, Quiet, 2).as_f32(), all1_snan)),
);
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(nan.powf(nan)),
);
check_all_outcomes(
HashSet::from_iter([1.0f32.into()]),
|| F32::from(1.0f32.powf(nan)), // special `pow` rule
);
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(nan.powi(1)),
);
// libm functions
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(nan.sinh()),
);
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(nan.atan2(nan)),
);
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(nan.ln_gamma().0),
);
}
fn test_f64() {
@ -309,6 +369,62 @@ fn test_f64() {
]),
|| F64::from(just1 % all1_snan),
);
// Intrinsics
let nan = F64::nan(Neg, Quiet, 0).as_f64();
check_all_outcomes(
HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
|| F64::from(f64::min(nan, nan)),
);
check_all_outcomes(
HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
|| F64::from(nan.sin()),
);
check_all_outcomes(
HashSet::from_iter([
F64::nan(Pos, Quiet, 0),
F64::nan(Neg, Quiet, 0),
F64::nan(Pos, Quiet, 1),
F64::nan(Neg, Quiet, 1),
F64::nan(Pos, Quiet, 2),
F64::nan(Neg, Quiet, 2),
F64::nan(Pos, Quiet, all1_payload),
F64::nan(Neg, Quiet, all1_payload),
F64::nan(Pos, Signaling, all1_payload),
F64::nan(Neg, Signaling, all1_payload),
]),
|| F64::from(just1.mul_add(F64::nan(Neg, Quiet, 2).as_f64(), all1_snan)),
);
check_all_outcomes(
HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
|| F64::from(nan.powf(nan)),
);
check_all_outcomes(
HashSet::from_iter([1.0f64.into()]),
|| F64::from(1.0f64.powf(nan)), // special `pow` rule
);
check_all_outcomes(
HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
|| F64::from(nan.powi(1)),
);
// libm functions
check_all_outcomes(
HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
|| F64::from(nan.sinh()),
);
check_all_outcomes(
HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
|| F64::from(nan.atan2(nan)),
);
check_all_outcomes(
HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
|| F64::from(ldexp(nan, 1)),
);
check_all_outcomes(
HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
|| F64::from(nan.ln_gamma().0),
);
}
fn test_casts() {
@ -397,6 +513,61 @@ fn test_casts() {
);
}
fn test_simd() {
use std::intrinsics::simd::*;
use std::simd::*;
extern "platform-intrinsic" {
fn simd_fsqrt<T>(x: T) -> T;
fn simd_ceil<T>(x: T) -> T;
fn simd_fma<T>(x: T, y: T, z: T) -> T;
}
let nan = F32::nan(Neg, Quiet, 0).as_f32();
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(unsafe { simd_div(f32x4::splat(0.0), f32x4::splat(0.0)) }[0]),
);
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(unsafe { simd_fmin(f32x4::splat(nan), f32x4::splat(nan)) }[0]),
);
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(unsafe { simd_fmax(f32x4::splat(nan), f32x4::splat(nan)) }[0]),
);
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| {
F32::from(
unsafe { simd_fma(f32x4::splat(nan), f32x4::splat(nan), f32x4::splat(nan)) }[0],
)
},
);
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(unsafe { simd_reduce_add_ordered::<_, f32>(f32x4::splat(nan), nan) }),
);
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(unsafe { simd_reduce_max::<_, f32>(f32x4::splat(nan)) }),
);
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(unsafe { simd_fsqrt(f32x4::splat(nan)) }[0]),
);
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(unsafe { simd_ceil(f32x4::splat(nan)) }[0]),
);
// Casts
check_all_outcomes(
HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
|| F64::from(unsafe { simd_cast::<f32x4, f64x4>(f32x4::splat(nan)) }[0]),
);
}
fn main() {
// Check our constants against std, just to be sure.
// We add 1 since our numbers are the number of bits stored
@ -408,4 +579,5 @@ fn main() {
test_f32();
test_f64();
test_casts();
test_simd();
}