Auto merge of #3244 - RalfJung:nan-nondet, r=RalfJung
NaN non-determinism for intrinsics and libm functions
This commit is contained in:
commit
a63fd5ea6b
14 changed files with 488 additions and 197 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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" => {
|
||||
|
|
|
|||
|
|
@ -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()?)),
|
||||
})
|
||||
}
|
||||
|
|
|
|||
7
src/tools/miri/tests/fail/intrinsics/fast_math_result.rs
Normal file
7
src/tools/miri/tests/fail/intrinsics/fast_math_result.rs
Normal 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
|
||||
}
|
||||
}
|
||||
15
src/tools/miri/tests/fail/intrinsics/fast_math_result.stderr
Normal file
15
src/tools/miri/tests/fail/intrinsics/fast_math_result.stderr
Normal 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
|
||||
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue