diff --git a/Cargo.lock b/Cargo.lock index 05d1c2d1eb..46dd79a075 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -80,6 +80,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.6.0" @@ -349,6 +355,15 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +[[package]] +name = "fpmath" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f617488244e3bd5aa86da93efaf4072579a301832ca5a9c7e3493563b509a43d" +dependencies = [ + "rustc_apfloat", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -482,7 +497,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags", + "bitflags 2.6.0", "libc", ] @@ -556,6 +571,7 @@ dependencies = [ "colored", "ctrlc", "directories", + "fpmath", "getrandom", "jemalloc-sys", "libc", @@ -577,7 +593,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags", + "bitflags 2.6.0", "cfg-if", "cfg_aliases", "libc", @@ -798,7 +814,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ - "bitflags", + "bitflags 2.6.0", ] [[package]] @@ -853,6 +869,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_apfloat" +version = "0.2.1+llvm-462a31f5a5ab" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "886d94c63c812a8037c4faca2607453a0fa4cf82f734665266876b022244543f" +dependencies = [ + "bitflags 1.3.2", + "smallvec", +] + [[package]] name = "rustc_version" version = "0.4.0" @@ -880,7 +906,7 @@ version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", diff --git a/Cargo.toml b/Cargo.toml index cb02914fd9..94af1b3478 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ ctrlc = "3.2.5" chrono = { version = "0.4.38", default-features = false } chrono-tz = "0.10" directories = "5" +fpmath = { version = "0.1.1", features = ["soft-float"] } # Copied from `compiler/rustc/Cargo.toml`. # But only for some targets, it fails for others. Rustc configures this in its CI, but we can't diff --git a/src/intrinsics/mod.rs b/src/intrinsics/mod.rs index 665dd7c441..fed26921aa 100644 --- a/src/intrinsics/mod.rs +++ b/src/intrinsics/mod.rs @@ -202,21 +202,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { => { let [f] = check_arg_count(args)?; let f = this.read_scalar(f)?.to_f32()?; - // Using host floats (but it's fine, these operations do not have guaranteed precision). - let f_host = f.to_host(); - let res = match intrinsic_name { - "sinf32" => f_host.sin(), - "cosf32" => f_host.cos(), - "sqrtf32" => f_host.sqrt(), // FIXME Using host floats, this should use full-precision soft-floats - "expf32" => f_host.exp(), - "exp2f32" => f_host.exp2(), - "logf32" => f_host.ln(), - "log10f32" => f_host.log10(), - "log2f32" => f_host.log2(), + let op = match intrinsic_name { + "sinf32" => math::UnaryOp::Sin, + "cosf32" => math::UnaryOp::Cos, + "sqrtf32" => math::UnaryOp::Sqrt, + "expf32" => math::UnaryOp::Exp, + "exp2f32" => math::UnaryOp::Exp2, + "logf32" => math::UnaryOp::Ln, + "log10f32" => math::UnaryOp::Log10, + "log2f32" => math::UnaryOp::Log2, _ => bug!(), }; - let res = res.to_soft(); - let res = this.adjust_nan(res, &[f]); + let res = math::unary_op(this, op, f); this.write_scalar(res, dest)?; } #[rustfmt::skip] @@ -231,21 +228,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { => { let [f] = check_arg_count(args)?; let f = this.read_scalar(f)?.to_f64()?; - // Using host floats (but it's fine, these operations do not have guaranteed precision). - let f_host = f.to_host(); - let res = match intrinsic_name { - "sinf64" => f_host.sin(), - "cosf64" => f_host.cos(), - "sqrtf64" => f_host.sqrt(), // FIXME Using host floats, this should use full-precision soft-floats - "expf64" => f_host.exp(), - "exp2f64" => f_host.exp2(), - "logf64" => f_host.ln(), - "log10f64" => f_host.log10(), - "log2f64" => f_host.log2(), + let op = match intrinsic_name { + "sinf64" => math::UnaryOp::Sin, + "cosf64" => math::UnaryOp::Cos, + "sqrtf64" => math::UnaryOp::Sqrt, + "expf64" => math::UnaryOp::Exp, + "exp2f64" => math::UnaryOp::Exp2, + "logf64" => math::UnaryOp::Ln, + "log10f64" => math::UnaryOp::Log10, + "log2f64" => math::UnaryOp::Log2, _ => bug!(), }; - let res = res.to_soft(); - let res = this.adjust_nan(res, &[f]); + let res = math::unary_op(this, op, f); this.write_scalar(res, dest)?; } @@ -299,18 +293,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let [f1, f2] = check_arg_count(args)?; let f1 = this.read_scalar(f1)?.to_f32()?; let f2 = this.read_scalar(f2)?.to_f32()?; - // Using host floats (but it's fine, this operation does not have guaranteed precision). - let res = f1.to_host().powf(f2.to_host()).to_soft(); - let res = this.adjust_nan(res, &[f1, f2]); + let res = math::binary_op(this, math::BinaryOp::Powf, 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()?; - // Using host floats (but it's fine, this operation does not have guaranteed precision). - let res = f1.to_host().powf(f2.to_host()).to_soft(); - let res = this.adjust_nan(res, &[f1, f2]); + let res = math::binary_op(this, math::BinaryOp::Powf, f1, f2); this.write_scalar(res, dest)?; } @@ -318,18 +308,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let [f, i] = check_arg_count(args)?; let f = this.read_scalar(f)?.to_f32()?; let i = this.read_scalar(i)?.to_i32()?; - // Using host floats (but it's fine, this operation does not have guaranteed precision). - let res = f.to_host().powi(i).to_soft(); - let res = this.adjust_nan(res, &[f]); + let res = math::powi(this, f, i); this.write_scalar(res, dest)?; } "powif64" => { let [f, i] = check_arg_count(args)?; let f = this.read_scalar(f)?.to_f64()?; let i = this.read_scalar(i)?.to_i32()?; - // Using host floats (but it's fine, this operation does not have guaranteed precision). - let res = f.to_host().powi(i).to_soft(); - let res = this.adjust_nan(res, &[f]); + let res = math::powi(this, f, i); this.write_scalar(res, dest)?; } diff --git a/src/intrinsics/simd.rs b/src/intrinsics/simd.rs index de293495e8..22737e31bd 100644 --- a/src/intrinsics/simd.rs +++ b/src/intrinsics/simd.rs @@ -56,12 +56,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { assert_eq!(dest_len, op_len); #[derive(Copy, Clone)] - enum Op<'a> { + enum Op { MirOp(mir::UnOp), Abs, Round(rustc_apfloat::Round), Numeric(Symbol), - HostOp(&'a str), + Math(math::UnaryOp), } let which = match intrinsic_name { "neg" => Op::MirOp(mir::UnOp::Neg), @@ -75,7 +75,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "cttz" => Op::Numeric(sym::cttz), "bswap" => Op::Numeric(sym::bswap), "bitreverse" => Op::Numeric(sym::bitreverse), - _ => Op::HostOp(intrinsic_name), + "fsqrt" => Op::Math(math::UnaryOp::Sqrt), + "fsin" => Op::Math(math::UnaryOp::Sin), + "fcos" => Op::Math(math::UnaryOp::Cos), + "fexp" => Op::Math(math::UnaryOp::Exp), + "fexp2" => Op::Math(math::UnaryOp::Exp2), + "flog" => Op::Math(math::UnaryOp::Ln), + "flog2" => Op::Math(math::UnaryOp::Log2), + "flog10" => Op::Math(math::UnaryOp::Log10), + _ => bug!(), }; for i in 0..dest_len { @@ -100,47 +108,20 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { FloatTy::F128 => unimplemented!("f16_f128"), } } - Op::HostOp(host_op) => { + Op::Math(math_op) => { let ty::Float(float_ty) = op.layout.ty.kind() else { span_bug!(this.cur_span(), "{} operand is not a float", intrinsic_name) }; - // Using host floats (but it's fine, these operations do not have guaranteed precision). match float_ty { FloatTy::F16 => unimplemented!("f16_f128"), FloatTy::F32 => { let f = op.to_scalar().to_f32()?; - let f_host = f.to_host(); - let res = match host_op { - "fsqrt" => f_host.sqrt(), // FIXME Using host floats, this should use full-precision soft-floats - "fsin" => f_host.sin(), - "fcos" => f_host.cos(), - "fexp" => f_host.exp(), - "fexp2" => f_host.exp2(), - "flog" => f_host.ln(), - "flog2" => f_host.log2(), - "flog10" => f_host.log10(), - _ => bug!(), - }; - let res = res.to_soft(); - let res = this.adjust_nan(res, &[f]); + let res = math::unary_op(this, math_op, f); Scalar::from(res) } FloatTy::F64 => { let f = op.to_scalar().to_f64()?; - let f_host = f.to_host(); - let res = match host_op { - "fsqrt" => f_host.sqrt(), - "fsin" => f_host.sin(), - "fcos" => f_host.cos(), - "fexp" => f_host.exp(), - "fexp2" => f_host.exp2(), - "flog" => f_host.ln(), - "flog2" => f_host.log2(), - "flog10" => f_host.log10(), - _ => bug!(), - }; - let res = res.to_soft(); - let res = this.adjust_nan(res, &[f]); + let res = math::unary_op(this, math_op, f); Scalar::from(res) } FloatTy::F128 => unimplemented!("f16_f128"), diff --git a/src/lib.rs b/src/lib.rs index 330147c8f1..8fa5a86c7a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,6 +85,7 @@ mod eval; mod helpers; mod intrinsics; mod machine; +mod math; mod mono_hash_map; mod operator; mod provenance_gc; diff --git a/src/math.rs b/src/math.rs new file mode 100644 index 0000000000..f437c2797b --- /dev/null +++ b/src/math.rs @@ -0,0 +1,173 @@ +use rand::Rng as _; +use rand::distributions::Distribution as _; +use rustc_apfloat::Float as _; + +use crate::operator::EvalContextExt as _; + +pub(crate) trait FpMath: rustc_apfloat::Float + rustc_apfloat::FloatConvert { + type M: fpmath::FloatMath; + + fn into_fp_math(self) -> Self::M; + fn from_fp_math(value: Self::M) -> Self; +} + +impl FpMath for rustc_apfloat::ieee::Single { + type M = fpmath::SoftF32; + + fn into_fp_math(self) -> Self::M { + fpmath::SoftF32::from_bits(self.to_bits().try_into().unwrap()) + } + + fn from_fp_math(value: Self::M) -> Self { + rustc_apfloat::ieee::Single::from_bits(value.to_bits().into()) + } +} + +impl FpMath for rustc_apfloat::ieee::Double { + type M = fpmath::SoftF64; + + fn into_fp_math(self) -> Self::M { + fpmath::SoftF64::from_bits(self.to_bits().try_into().unwrap()) + } + + fn from_fp_math(value: Self::M) -> Self { + rustc_apfloat::ieee::Double::from_bits(value.to_bits().into()) + } +} + +/// Disturbes a floating-point result by a relative error on the order of (-2^scale, 2^scale). +pub(crate) fn apply_random_float_error( + this: &mut crate::MiriInterpCx<'_>, + val: F, + err_scale: i32, +) -> F { + let rng = this.machine.rng.get_mut(); + // Generate a random integer in the range [0, 2^PREC). + let dist = rand::distributions::Uniform::new(0, 1 << F::PRECISION); + let err = F::from_u128(dist.sample(rng)) + .value + .scalbn(err_scale.strict_sub(F::PRECISION.try_into().unwrap())); + // give it a random sign + let err = if rng.gen::() { -err } else { err }; + // multiple the value with (1+err) + (val * (F::from_u128(1).value + err).value).value +} + +fn disturb_result(this: &mut crate::MiriInterpCx<'_>, val: F) -> F { + apply_random_float_error(this, val, 4 - i32::try_from(F::PRECISION).unwrap()) +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub(crate) enum UnaryOp { + Sqrt, + Cbrt, + Exp, + Expm1, + Exp2, + Ln, + Ln1p, + Log2, + Log10, + Sin, + Cos, + Tan, + Asin, + Acos, + Atan, + Sinh, + Cosh, + Tanh, + Gamma, +} + +pub(crate) fn unary_op(this: &mut crate::MiriInterpCx<'_>, op: UnaryOp, x: F) -> F { + let fp_x = x.into_fp_math(); + let res = match op { + UnaryOp::Sqrt => fpmath::sqrt(fp_x), + UnaryOp::Cbrt => fpmath::cbrt(fp_x), + UnaryOp::Exp => fpmath::exp(fp_x), + UnaryOp::Expm1 => fpmath::exp_m1(fp_x), + UnaryOp::Exp2 => fpmath::exp2(fp_x), + UnaryOp::Ln => fpmath::log(fp_x), + UnaryOp::Ln1p => fpmath::log_1p(fp_x), + UnaryOp::Log2 => fpmath::log2(fp_x), + UnaryOp::Log10 => fpmath::log10(fp_x), + UnaryOp::Sin => fpmath::sin(fp_x), + UnaryOp::Cos => fpmath::cos(fp_x), + UnaryOp::Tan => fpmath::tan(fp_x), + UnaryOp::Asin => fpmath::asin(fp_x), + UnaryOp::Acos => fpmath::acos(fp_x), + UnaryOp::Atan => fpmath::atan(fp_x), + UnaryOp::Sinh => fpmath::sinh(fp_x), + UnaryOp::Cosh => fpmath::cosh(fp_x), + UnaryOp::Tanh => fpmath::tanh(fp_x), + UnaryOp::Gamma => fpmath::tgamma(fp_x), + }; + let res = F::from_fp_math(res); + // Only sqrt has guaranteed precision. + let res = if op != UnaryOp::Sqrt { disturb_result(this, res) } else { res }; + this.adjust_nan(res, &[x]) +} + +pub(crate) fn sqrt(x: F) -> F { + F::from_fp_math(fpmath::sqrt(x.into_fp_math())) +} + +pub(crate) fn ln_gamma(this: &mut crate::MiriInterpCx<'_>, x: F) -> (F, i32) { + let (res, sign) = fpmath::lgamma(x.into_fp_math()); + let res = F::from_fp_math(res); + // Precision is not guaranteed. + let res = disturb_result(this, res); + let res = this.adjust_nan(res, &[x]); + (res, sign.into()) +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub(crate) enum BinaryOp { + Hypot, + Powf, + Atan2, + Fdim, +} + +pub(crate) fn binary_op>( + this: &mut crate::MiriInterpCx<'_>, + op: BinaryOp, + x: F, + y: F, +) -> F { + let fp_x = x.into_fp_math(); + let fp_y = y.into_fp_math(); + let res = match op { + BinaryOp::Hypot => F::from_fp_math(fpmath::hypot(fp_x, fp_y)), + BinaryOp::Powf => F::from_fp_math(fpmath::pow(fp_x, fp_y)), + BinaryOp::Atan2 => F::from_fp_math(fpmath::atan2(fp_x, fp_y)), + BinaryOp::Fdim => { + // If `x` or `y` is NaN, the result is NaN. + let diff = (x - y).value; + if diff < F::ZERO { F::ZERO } else { diff } + } + }; + // Precision is not guaranteed. + let res = if op == BinaryOp::Powf { + // Special case exact 1^nan = 1 + // I'm not sure how to fix float_nan.rs test otherwise. + if x == F::from_u128(1).value && y.is_nan() { res } else { disturb_result(this, res) } + } else { + disturb_result(this, res) + }; + this.adjust_nan(res, &[x, y]) +} + +pub(crate) fn powi>( + this: &mut crate::MiriInterpCx<'_>, + x: F, + y: i32, +) -> F { + let fp_x = x.into_fp_math(); + let res = fpmath::powi(fp_x, y); + let res = F::from_fp_math(res); + // Precision is not guaranteed. + let res = disturb_result(this, res); + this.adjust_nan(res, &[x]) +} diff --git a/src/shims/foreign_items.rs b/src/shims/foreign_items.rs index 7fce5b6330..8530940d75 100644 --- a/src/shims/foreign_items.rs +++ b/src/shims/foreign_items.rs @@ -13,7 +13,6 @@ use rustc_span::Symbol; use rustc_target::abi::{Align, AlignFromBytesError, Size}; use rustc_target::spec::abi::Abi; -use self::helpers::{ToHost, ToSoft}; use super::alloc::EvalContextExt as _; use super::backtrace::EvalContextExt as _; use crate::*; @@ -760,24 +759,21 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { => { let [f] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let f = this.read_scalar(f)?.to_f32()?; - // Using host floats (but it's fine, these operations do not have guaranteed precision). - let f_host = f.to_host(); - let res = match link_name.as_str() { - "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(), + let op = match link_name.as_str() { + "cbrtf" => math::UnaryOp::Cbrt, + "coshf" => math::UnaryOp::Cosh, + "sinhf" => math::UnaryOp::Sinh, + "tanf" => math::UnaryOp::Tan, + "tanhf" => math::UnaryOp::Tanh, + "acosf" => math::UnaryOp::Acos, + "asinf" => math::UnaryOp::Asin, + "atanf" => math::UnaryOp::Atan, + "log1pf" => math::UnaryOp::Ln1p, + "expm1f" => math::UnaryOp::Expm1, + "tgammaf" => math::UnaryOp::Gamma, _ => bug!(), }; - let res = res.to_soft(); - let res = this.adjust_nan(res, &[f]); + let res = math::unary_op(this, op, f); this.write_scalar(res, dest)?; } #[rustfmt::skip] @@ -791,15 +787,13 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { 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) - // Using host floats (but it's fine, these operations do not have guaranteed precision). - let res = match link_name.as_str() { - "_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.to_host().abs_sub(f2.to_host()).to_soft(), + let op = match link_name.as_str() { + "_hypotf" | "hypotf" => math::BinaryOp::Hypot, + "atan2f" => math::BinaryOp::Atan2, + "fdimf" => math::BinaryOp::Fdim, _ => bug!(), }; - let res = this.adjust_nan(res, &[f1, f2]); + let res = math::binary_op(this, op, f1, f2); this.write_scalar(res, dest)?; } #[rustfmt::skip] @@ -817,24 +811,21 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { => { let [f] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let f = this.read_scalar(f)?.to_f64()?; - // Using host floats (but it's fine, these operations do not have guaranteed precision). - let f_host = f.to_host(); - let res = match link_name.as_str() { - "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(), + let op = match link_name.as_str() { + "cbrt" => math::UnaryOp::Cbrt, + "cosh" => math::UnaryOp::Cosh, + "sinh" => math::UnaryOp::Sinh, + "tan" => math::UnaryOp::Tan, + "tanh" => math::UnaryOp::Tanh, + "acos" => math::UnaryOp::Acos, + "asin" => math::UnaryOp::Asin, + "atan" => math::UnaryOp::Atan, + "log1p" => math::UnaryOp::Ln1p, + "expm1" => math::UnaryOp::Expm1, + "tgamma" => math::UnaryOp::Gamma, _ => bug!(), }; - let res = res.to_soft(); - let res = this.adjust_nan(res, &[f]); + let res = math::unary_op(this, op, f); this.write_scalar(res, dest)?; } #[rustfmt::skip] @@ -848,15 +839,13 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { 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) - // Using host floats (but it's fine, these operations do not have guaranteed precision). - let res = match link_name.as_str() { - "_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.to_host().abs_sub(f2.to_host()).to_soft(), + let op = match link_name.as_str() { + "_hypot" | "hypot" => math::BinaryOp::Hypot, + "atan2" => math::BinaryOp::Atan2, + "fdim" => math::BinaryOp::Fdim, _ => bug!(), }; - let res = this.adjust_nan(res, &[f1, f2]); + let res = math::binary_op(this, op, f1, f2); this.write_scalar(res, dest)?; } #[rustfmt::skip] @@ -878,10 +867,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { let x = this.read_scalar(x)?.to_f32()?; let signp = this.deref_pointer(signp)?; - // Using host floats (but it's fine, these operations do not have guaranteed precision). - let (res, sign) = x.to_host().ln_gamma(); + let (res, sign) = math::ln_gamma(this, x); this.write_int(sign, &signp)?; - let res = this.adjust_nan(res.to_soft(), &[x]); this.write_scalar(res, dest)?; } "lgamma_r" => { @@ -889,10 +876,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { let x = this.read_scalar(x)?.to_f64()?; let signp = this.deref_pointer(signp)?; - // Using host floats (but it's fine, these operations do not have guaranteed precision). - let (res, sign) = x.to_host().ln_gamma(); + let (res, sign) = math::ln_gamma(this, x); this.write_int(sign, &signp)?; - let res = this.adjust_nan(res.to_soft(), &[x]); this.write_scalar(res, dest)?; } diff --git a/src/shims/x86/mod.rs b/src/shims/x86/mod.rs index 9339d301ae..0cd2cff129 100644 --- a/src/shims/x86/mod.rs +++ b/src/shims/x86/mod.rs @@ -1,4 +1,3 @@ -use rand::Rng as _; use rustc_apfloat::Float; use rustc_apfloat::ieee::Single; use rustc_middle::ty::Ty; @@ -398,38 +397,20 @@ fn unary_op_f32<'tcx>( let div = (Single::from_u128(1).value / op).value; // Apply a relative error with a magnitude on the order of 2^-12 to simulate the // inaccuracy of RCP. - let res = apply_random_float_error(this, div, -12); + let res = math::apply_random_float_error(this, div, -12); interp_ok(Scalar::from_f32(res)) } FloatUnaryOp::Rsqrt => { - let op = op.to_scalar().to_u32()?; - // FIXME using host floats - let sqrt = Single::from_bits(f32::from_bits(op).sqrt().to_bits().into()); - let rsqrt = (Single::from_u128(1).value / sqrt).value; + let op = op.to_scalar().to_f32()?; + let rsqrt = (Single::from_u128(1).value / math::sqrt(op)).value; // Apply a relative error with a magnitude on the order of 2^-12 to simulate the // inaccuracy of RSQRT. - let res = apply_random_float_error(this, rsqrt, -12); + let res = math::apply_random_float_error(this, rsqrt, -12); interp_ok(Scalar::from_f32(res)) } } } -/// Disturbes a floating-point result by a relative error on the order of (-2^scale, 2^scale). -#[allow(clippy::arithmetic_side_effects)] // floating point arithmetic cannot panic -fn apply_random_float_error( - this: &mut crate::MiriInterpCx<'_>, - val: F, - err_scale: i32, -) -> F { - let rng = this.machine.rng.get_mut(); - // generates rand(0, 2^64) * 2^(scale - 64) = rand(0, 1) * 2^scale - let err = F::from_u128(rng.gen::().into()).value.scalbn(err_scale.strict_sub(64)); - // give it a random sign - let err = if rng.gen::() { -err } else { err }; - // multiple the value with (1+err) - (val * (F::from_u128(1).value + err).value).value -} - /// Performs `which` operation on the first component of `op` and copies /// the other components. The result is stored in `dest`. fn unary_op_ss<'tcx>( diff --git a/tests/pass/float.rs b/tests/pass/float.rs index 6ab18a5345..5907d371bc 100644 --- a/tests/pass/float.rs +++ b/tests/pass/float.rs @@ -12,10 +12,23 @@ use std::fmt::{Debug, Display, LowerHex}; use std::hint::black_box; use std::{f32, f64}; +trait MathPrec { + const MAX_ERR: Self; +} + +impl MathPrec for f32 { + const MAX_ERR: Self = 1.0e-5; +} + +impl MathPrec for f64 { + const MAX_ERR: Self = 1.0e-14; +} + macro_rules! assert_approx_eq { ($a:expr, $b:expr) => {{ let (a, b) = (&$a, &$b); - assert!((*a - *b).abs() < 1.0e-6, "{} is not approximately equal to {}", *a, *b); + let err = if *b == 0.0 { (*a - *b).abs() } else { ((*a - *b) / *b).abs() }; + assert!(err < MathPrec::MAX_ERR, "{a} is not approximately equal to {b}"); }}; } @@ -870,8 +883,8 @@ pub fn libm() { unsafe { ldexp(a, b) } } - assert_approx_eq!(64f32.sqrt(), 8f32); - assert_approx_eq!(64f64.sqrt(), 8f64); + assert_eq!(64f32.sqrt(), 8f32); + assert_eq!(64f64.sqrt(), 8f64); assert!((-5.0_f32).sqrt().is_nan()); assert!((-5.0_f64).sqrt().is_nan()); @@ -928,7 +941,7 @@ pub fn libm() { assert_approx_eq!(f64::consts::FRAC_PI_4.sin().asin(), f64::consts::FRAC_PI_4); assert_approx_eq!(1.0f32.sinh(), 1.1752012f32); - assert_approx_eq!(1.0f64.sinh(), 1.1752012f64); + assert_approx_eq!(1.0f64.sinh(), 1.1752011936438014f64); assert_approx_eq!(2.0f32.asinh(), 1.443635475178810342493276740273105f32); assert_approx_eq!((-2.0f64).asinh(), -1.443635475178810342493276740273105f64); @@ -940,12 +953,12 @@ pub fn libm() { assert_approx_eq!(f64::consts::FRAC_PI_4.cos().acos(), f64::consts::FRAC_PI_4); assert_approx_eq!(1.0f32.cosh(), 1.54308f32); - assert_approx_eq!(1.0f64.cosh(), 1.54308f64); + assert_approx_eq!(1.0f64.cosh(), 1.5430806348152437f64); assert_approx_eq!(2.0f32.acosh(), 1.31695789692481670862504634730796844f32); assert_approx_eq!(3.0f64.acosh(), 1.76274717403908605046521864995958461f64); assert_approx_eq!(1.0f32.tan(), 1.557408f32); - assert_approx_eq!(1.0f64.tan(), 1.557408f64); + assert_approx_eq!(1.0f64.tan(), 1.5574077246549023f64); assert_approx_eq!(1.0_f32, 1.0_f32.tan().atan()); assert_approx_eq!(1.0_f64, 1.0_f64.tan().atan()); assert_approx_eq!(1.0f32.atan2(2.0f32), 0.46364761f32);