From 0fe2cf520ff7cbe751a08eb7859c2506f62a4a6e Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 3 Mar 2026 12:35:41 +0100 Subject: [PATCH] rename min/maxnum intrinsics to min/maximum_number and fix their LLVM lowering --- .../src/intrinsics/mod.rs | 16 ++-- compiler/rustc_codegen_cranelift/src/num.rs | 8 +- .../rustc_codegen_gcc/src/intrinsic/mod.rs | 10 --- compiler/rustc_codegen_llvm/src/intrinsic.rs | 36 +++++--- compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 1 + .../src/interpret/intrinsics.rs | 50 +++++------ .../src/interpret/intrinsics/simd.rs | 12 +-- .../rustc_hir_analysis/src/check/intrinsic.rs | 36 ++++---- .../rustc_llvm/llvm-wrapper/RustWrapper.cpp | 7 ++ compiler/rustc_span/src/symbol.rs | 16 ++-- library/core/src/intrinsics/mod.rs | 88 +++++++++++++++++-- library/core/src/num/f128.rs | 4 +- library/core/src/num/f16.rs | 4 +- library/core/src/num/f32.rs | 4 +- library/core/src/num/f64.rs | 4 +- library/coretests/tests/floats/mod.rs | 59 +++++++++++-- src/tools/miri/tests/pass/float.rs | 33 +++---- src/tools/miri/tests/pass/float_nan.rs | 6 ++ .../miri/tests/pass/intrinsics/intrinsics.rs | 19 ++-- tests/ui/float/minmax.rs | 22 +++++ tests/ui/simd/intrinsic/float-minmax-pass.rs | 2 + 21 files changed, 296 insertions(+), 141 deletions(-) create mode 100644 tests/ui/float/minmax.rs diff --git a/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs b/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs index ab9a11305baa3..08b73d504ee95 100644 --- a/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs +++ b/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs @@ -1267,7 +1267,7 @@ fn codegen_regular_intrinsic_call<'tcx>( ret.write_cvalue(fx, val); } - sym::minnumf16 => { + sym::minimum_number_nsz_f16 => { intrinsic_args!(fx, args => (a, b); intrinsic); let a = a.load_scalar(fx); let b = b.load_scalar(fx); @@ -1276,7 +1276,7 @@ fn codegen_regular_intrinsic_call<'tcx>( let val = CValue::by_val(val, fx.layout_of(fx.tcx.types.f16)); ret.write_cvalue(fx, val); } - sym::minnumf32 => { + sym::minimum_number_nsz_f32 => { intrinsic_args!(fx, args => (a, b); intrinsic); let a = a.load_scalar(fx); let b = b.load_scalar(fx); @@ -1285,7 +1285,7 @@ fn codegen_regular_intrinsic_call<'tcx>( let val = CValue::by_val(val, fx.layout_of(fx.tcx.types.f32)); ret.write_cvalue(fx, val); } - sym::minnumf64 => { + sym::minimum_number_nsz_f64 => { intrinsic_args!(fx, args => (a, b); intrinsic); let a = a.load_scalar(fx); let b = b.load_scalar(fx); @@ -1294,7 +1294,7 @@ fn codegen_regular_intrinsic_call<'tcx>( let val = CValue::by_val(val, fx.layout_of(fx.tcx.types.f64)); ret.write_cvalue(fx, val); } - sym::minnumf128 => { + sym::minimum_number_nsz_f128 => { intrinsic_args!(fx, args => (a, b); intrinsic); let a = a.load_scalar(fx); let b = b.load_scalar(fx); @@ -1303,7 +1303,7 @@ fn codegen_regular_intrinsic_call<'tcx>( let val = CValue::by_val(val, fx.layout_of(fx.tcx.types.f128)); ret.write_cvalue(fx, val); } - sym::maxnumf16 => { + sym::maximum_number_nsz_f16 => { intrinsic_args!(fx, args => (a, b); intrinsic); let a = a.load_scalar(fx); let b = b.load_scalar(fx); @@ -1312,7 +1312,7 @@ fn codegen_regular_intrinsic_call<'tcx>( let val = CValue::by_val(val, fx.layout_of(fx.tcx.types.f16)); ret.write_cvalue(fx, val); } - sym::maxnumf32 => { + sym::maximum_number_nsz_f32 => { intrinsic_args!(fx, args => (a, b); intrinsic); let a = a.load_scalar(fx); let b = b.load_scalar(fx); @@ -1321,7 +1321,7 @@ fn codegen_regular_intrinsic_call<'tcx>( let val = CValue::by_val(val, fx.layout_of(fx.tcx.types.f32)); ret.write_cvalue(fx, val); } - sym::maxnumf64 => { + sym::maximum_number_nsz_f64 => { intrinsic_args!(fx, args => (a, b); intrinsic); let a = a.load_scalar(fx); let b = b.load_scalar(fx); @@ -1330,7 +1330,7 @@ fn codegen_regular_intrinsic_call<'tcx>( let val = CValue::by_val(val, fx.layout_of(fx.tcx.types.f64)); ret.write_cvalue(fx, val); } - sym::maxnumf128 => { + sym::maximum_number_nsz_f128 => { intrinsic_args!(fx, args => (a, b); intrinsic); let a = a.load_scalar(fx); let b = b.load_scalar(fx); diff --git a/compiler/rustc_codegen_cranelift/src/num.rs b/compiler/rustc_codegen_cranelift/src/num.rs index 95d44dfb6d950..0459644e16aa7 100644 --- a/compiler/rustc_codegen_cranelift/src/num.rs +++ b/compiler/rustc_codegen_cranelift/src/num.rs @@ -498,10 +498,10 @@ fn codegen_ptr_binop<'tcx>( } } -// In Rust floating point min and max don't propagate NaN. In Cranelift they do however. -// For this reason it is necessary to use `a.is_nan() ? b : (a >= b ? b : a)` for `minnumf*` -// and `a.is_nan() ? b : (a <= b ? b : a)` for `maxnumf*`. NaN checks are done by comparing -// a float against itself. Only in case of NaN is it not equal to itself. +// In Rust floating point min and max don't propagate NaN (not even SNaN). In Cranelift they do +// however. For this reason it is necessary to use `a.is_nan() ? b : (a >= b ? b : a)` for +// `minnumf*` and `a.is_nan() ? b : (a <= b ? b : a)` for `maxnumf*`. NaN checks are done by +// comparing a float against itself. Only in case of NaN is it not equal to itself. pub(crate) fn codegen_float_min(fx: &mut FunctionCx<'_, '_, '_>, a: Value, b: Value) -> Value { // FIXME(bytecodealliance/wasmtime#8312): Replace with Cranelift `fcmp` once // `f16`/`f128` backend lowerings have been added to Cranelift. diff --git a/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs b/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs index fb1127ab4f48c..810b883c063c0 100644 --- a/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs +++ b/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs @@ -72,8 +72,6 @@ fn get_simple_intrinsic<'gcc, 'tcx>( sym::fmuladdf64 => "fma", // TODO: use gcc intrinsic analogous to llvm.fmuladd.f64 sym::fabsf32 => "fabsf", sym::fabsf64 => "fabs", - sym::minnumf32 => "fminf", - sym::minnumf64 => "fmin", sym::minimumf32 => "fminimumf", sym::minimumf64 => "fminimum", sym::minimumf128 => { @@ -92,8 +90,6 @@ fn get_simple_intrinsic<'gcc, 'tcx>( false, )); } - sym::maxnumf32 => "fmaxf", - sym::maxnumf64 => "fmax", sym::maximumf32 => "fmaximumf", sym::maximumf64 => "fmaximum", sym::maximumf128 => { @@ -236,8 +232,6 @@ fn get_simple_function_f128_2args<'gcc, 'tcx>( let f128_type = cx.type_f128(); let func_name = match name { - sym::maxnumf128 => "fmaxf128", - sym::minnumf128 => "fminf128", sym::copysignf128 => "copysignf128", _ => return None, }; @@ -266,8 +260,6 @@ fn f16_builtin<'gcc, 'tcx>( sym::fabsf16 => "fabsf", sym::floorf16 => "__builtin_floorf", sym::fmaf16 => "fmaf", - sym::maxnumf16 => "__builtin_fmaxf", - sym::minnumf16 => "__builtin_fminf", sym::powf16 => "__builtin_powf", sym::powif16 => { let func = cx.context.get_builtin_function("__builtin_powif"); @@ -333,8 +325,6 @@ impl<'a, 'gcc, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tc | sym::fabsf16 | sym::floorf16 | sym::fmaf16 - | sym::maxnumf16 - | sym::minnumf16 | sym::powf16 | sym::powif16 | sym::roundf16 diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index cf088ed509092..347b5c4933b03 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -113,11 +113,6 @@ fn call_simple_intrinsic<'ll, 'tcx>( sym::fabsf64 => ("llvm.fabs", &[bx.type_f64()]), sym::fabsf128 => ("llvm.fabs", &[bx.type_f128()]), - sym::minnumf16 => ("llvm.minnum", &[bx.type_f16()]), - sym::minnumf32 => ("llvm.minnum", &[bx.type_f32()]), - sym::minnumf64 => ("llvm.minnum", &[bx.type_f64()]), - sym::minnumf128 => ("llvm.minnum", &[bx.type_f128()]), - // FIXME: LLVM currently mis-compile those intrinsics, re-enable them // when llvm/llvm-project#{139380,139381,140445} are fixed. //sym::minimumf16 => ("llvm.minimum", &[bx.type_f16()]), @@ -125,11 +120,6 @@ fn call_simple_intrinsic<'ll, 'tcx>( //sym::minimumf64 => ("llvm.minimum", &[bx.type_f64()]), //sym::minimumf128 => ("llvm.minimum", &[cx.type_f128()]), // - sym::maxnumf16 => ("llvm.maxnum", &[bx.type_f16()]), - sym::maxnumf32 => ("llvm.maxnum", &[bx.type_f32()]), - sym::maxnumf64 => ("llvm.maxnum", &[bx.type_f64()]), - sym::maxnumf128 => ("llvm.maxnum", &[bx.type_f128()]), - // FIXME: LLVM currently mis-compile those intrinsics, re-enable them // when llvm/llvm-project#{139380,139381,140445} are fixed. //sym::maximumf16 => ("llvm.maximum", &[bx.type_f16()]), @@ -196,6 +186,32 @@ impl<'ll, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { let simple = call_simple_intrinsic(self, name, args); let llval = match name { _ if simple.is_some() => simple.unwrap(), + sym::minimum_number_nsz_f16 + | sym::minimum_number_nsz_f32 + | sym::minimum_number_nsz_f64 + | sym::minimum_number_nsz_f128 + | sym::maximum_number_nsz_f16 + | sym::maximum_number_nsz_f32 + | sym::maximum_number_nsz_f64 + | sym::maximum_number_nsz_f128 + // Need at least LLVM 22 for `min/maximumnum` to not crash LLVM. + if crate::llvm_util::get_version() >= (22, 0, 0) => + { + let intrinsic_name = if name.as_str().starts_with("min") { + "llvm.minimumnum" + } else { + "llvm.maximumnum" + }; + let call = self.call_intrinsic( + intrinsic_name, + &[args[0].layout.immediate_llvm_type(self.cx)], + &[args[0].immediate(), args[1].immediate()], + ); + // `nsz` on minimumnum/maximumnum is special: its only effect is to make + // signed-zero ordering non-deterministic. + unsafe { llvm::LLVMRustSetNoSignedZeros(call) }; + call + } sym::ptr_mask => { let ptr = args[0].immediate(); self.call_intrinsic( diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index f9af42494cada..77438472644fc 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -2045,6 +2045,7 @@ unsafe extern "C" { pub(crate) fn LLVMRustSetFastMath(Instr: &Value); pub(crate) fn LLVMRustSetAlgebraicMath(Instr: &Value); pub(crate) fn LLVMRustSetAllowReassoc(Instr: &Value); + pub(crate) fn LLVMRustSetNoSignedZeros(Instr: &Value); // Miscellaneous instructions pub(crate) fn LLVMRustBuildMemCpy<'a>( diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index fe06b0a6e0d81..e4765a1b19149 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -40,20 +40,20 @@ pub(crate) enum MinMax { /// In particular, `-0.0` is considered smaller than `+0.0` and /// if either input is NaN, the result is NaN. Minimum, - /// The IEEE-2008 `minNum` operation with the SNaN handling of the - /// IEEE-2019 `minimumNumber` operation - see `f32::min` etc. + /// The IEEE-2019 `minimumNumber` operation but with non-deterministic signed zero handling + /// (like in IEEE-2008 `minNum`) - see `f32::min` etc. /// In particular, if the inputs are `-0.0` and `+0.0`, the result is non-deterministic, /// and if one argument is NaN (quiet or signaling), the other one is returned. - MinimumNumber, + MinimumNumberNsz, /// The IEEE-2019 `maximum` operation - see `f32::maximum` etc. /// In particular, `-0.0` is considered smaller than `+0.0` and /// if either input is NaN, the result is NaN. Maximum, - /// The IEEE-2008 `maxNum` operation with the SNaN handling of the - /// IEEE-2019 `maximumNumber` operation - see `f32::max` etc. + /// The IEEE-2019 `maximumNumber` operation but with non-deterministic signed zero handling + /// (like in IEEE-2008 `maxNum`) - see `f32::max` etc. /// In particular, if the inputs are `-0.0` and `+0.0`, the result is non-deterministic, /// and if one argument is NaN (quiet or signaling), the other one is returned. - MaximumNumber, + MaximumNumberNsz, } /// Directly returns an `Allocation` containing an absolute path representation of the given type. @@ -542,17 +542,17 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { self.write_scalar(Scalar::from_target_usize(align.bytes(), self), dest)?; } - sym::minnumf16 => { - self.float_minmax_intrinsic::(args, MinMax::MinimumNumber, dest)? + sym::minimum_number_nsz_f16 => { + self.float_minmax_intrinsic::(args, MinMax::MinimumNumberNsz, dest)? } - sym::minnumf32 => { - self.float_minmax_intrinsic::(args, MinMax::MinimumNumber, dest)? + sym::minimum_number_nsz_f32 => { + self.float_minmax_intrinsic::(args, MinMax::MinimumNumberNsz, dest)? } - sym::minnumf64 => { - self.float_minmax_intrinsic::(args, MinMax::MinimumNumber, dest)? + sym::minimum_number_nsz_f64 => { + self.float_minmax_intrinsic::(args, MinMax::MinimumNumberNsz, dest)? } - sym::minnumf128 => { - self.float_minmax_intrinsic::(args, MinMax::MinimumNumber, dest)? + sym::minimum_number_nsz_f128 => { + self.float_minmax_intrinsic::(args, MinMax::MinimumNumberNsz, dest)? } sym::minimumf16 => self.float_minmax_intrinsic::(args, MinMax::Minimum, dest)?, @@ -564,17 +564,17 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } sym::minimumf128 => self.float_minmax_intrinsic::(args, MinMax::Minimum, dest)?, - sym::maxnumf16 => { - self.float_minmax_intrinsic::(args, MinMax::MaximumNumber, dest)? + sym::maximum_number_nsz_f16 => { + self.float_minmax_intrinsic::(args, MinMax::MaximumNumberNsz, dest)? } - sym::maxnumf32 => { - self.float_minmax_intrinsic::(args, MinMax::MaximumNumber, dest)? + sym::maximum_number_nsz_f32 => { + self.float_minmax_intrinsic::(args, MinMax::MaximumNumberNsz, dest)? } - sym::maxnumf64 => { - self.float_minmax_intrinsic::(args, MinMax::MaximumNumber, dest)? + sym::maximum_number_nsz_f64 => { + self.float_minmax_intrinsic::(args, MinMax::MaximumNumberNsz, dest)? } - sym::maxnumf128 => { - self.float_minmax_intrinsic::(args, MinMax::MaximumNumber, dest)? + sym::maximum_number_nsz_f128 => { + self.float_minmax_intrinsic::(args, MinMax::MaximumNumberNsz, dest)? } sym::maximumf16 => self.float_minmax_intrinsic::(args, MinMax::Maximum, dest)?, @@ -1051,16 +1051,16 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { { let a: F = a.to_float()?; let b: F = b.to_float()?; - let res = if matches!(op, MinMax::MinimumNumber | MinMax::MaximumNumber) && a == b { + let res = if matches!(op, MinMax::MinimumNumberNsz | MinMax::MaximumNumberNsz) && a == b { // They are definitely not NaN (those are never equal), but they could be `+0` and `-0`. // Let the machine decide which one to return. M::equal_float_min_max(self, a, b) } else { let result = match op { MinMax::Minimum => a.minimum(b), - MinMax::MinimumNumber => a.min(b), + MinMax::MinimumNumberNsz => a.min(b), MinMax::Maximum => a.maximum(b), - MinMax::MaximumNumber => a.max(b), + MinMax::MaximumNumberNsz => a.max(b), }; self.adjust_nan(result, &[a, b]) }; diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics/simd.rs b/compiler/rustc_const_eval/src/interpret/intrinsics/simd.rs index dc6841bb89dda..c47c512025c08 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics/simd.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics/simd.rs @@ -211,8 +211,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { sym::simd_le => Op::MirOp(BinOp::Le), sym::simd_gt => Op::MirOp(BinOp::Gt), sym::simd_ge => Op::MirOp(BinOp::Ge), - sym::simd_fmax => Op::FMinMax(MinMax::MaximumNumber), - sym::simd_fmin => Op::FMinMax(MinMax::MinimumNumber), + sym::simd_fmax => Op::FMinMax(MinMax::MaximumNumberNsz), + sym::simd_fmin => Op::FMinMax(MinMax::MinimumNumberNsz), sym::simd_saturating_add => Op::SaturatingOp(BinOp::Add), sym::simd_saturating_sub => Op::SaturatingOp(BinOp::Sub), sym::simd_arith_offset => Op::WrappingOffset, @@ -304,8 +304,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { sym::simd_reduce_xor => Op::MirOp(BinOp::BitXor), sym::simd_reduce_any => Op::MirOpBool(BinOp::BitOr), sym::simd_reduce_all => Op::MirOpBool(BinOp::BitAnd), - sym::simd_reduce_max => Op::MinMax(MinMax::MaximumNumber), - sym::simd_reduce_min => Op::MinMax(MinMax::MinimumNumber), + sym::simd_reduce_max => Op::MinMax(MinMax::MaximumNumberNsz), + sym::simd_reduce_min => Op::MinMax(MinMax::MinimumNumberNsz), _ => unreachable!(), }; @@ -329,8 +329,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } else { // Just boring integers, no NaNs to worry about. let mirop = match mmop { - MinMax::MinimumNumber | MinMax::Minimum => BinOp::Le, - MinMax::MaximumNumber | MinMax::Maximum => BinOp::Ge, + MinMax::MinimumNumberNsz | MinMax::Minimum => BinOp::Le, + MinMax::MaximumNumberNsz | MinMax::Maximum => BinOp::Ge, }; if self.binary_op(mirop, &res, &op)?.to_scalar().to_bool()? { res diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index a1c8c0150a66e..47420997a509a 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -147,22 +147,22 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi | sym::logf32 | sym::logf64 | sym::logf128 + | sym::maximum_number_nsz_f16 + | sym::maximum_number_nsz_f32 + | sym::maximum_number_nsz_f64 + | sym::maximum_number_nsz_f128 | sym::maximumf16 | sym::maximumf32 | sym::maximumf64 | sym::maximumf128 - | sym::maxnumf16 - | sym::maxnumf32 - | sym::maxnumf64 - | sym::maxnumf128 + | sym::minimum_number_nsz_f16 + | sym::minimum_number_nsz_f32 + | sym::minimum_number_nsz_f64 + | sym::minimum_number_nsz_f128 | sym::minimumf16 | sym::minimumf32 | sym::minimumf64 | sym::minimumf128 - | sym::minnumf16 - | sym::minnumf32 - | sym::minnumf64 - | sym::minnumf128 | sym::mul_with_overflow | sym::needs_drop | sym::offload @@ -468,20 +468,24 @@ pub(crate) fn check_intrinsic_type( sym::fabsf64 => (0, 0, vec![tcx.types.f64], tcx.types.f64), sym::fabsf128 => (0, 0, vec![tcx.types.f128], tcx.types.f128), - sym::minnumf16 => (0, 0, vec![tcx.types.f16, tcx.types.f16], tcx.types.f16), - sym::minnumf32 => (0, 0, vec![tcx.types.f32, tcx.types.f32], tcx.types.f32), - sym::minnumf64 => (0, 0, vec![tcx.types.f64, tcx.types.f64], tcx.types.f64), - sym::minnumf128 => (0, 0, vec![tcx.types.f128, tcx.types.f128], tcx.types.f128), + sym::minimum_number_nsz_f16 => (0, 0, vec![tcx.types.f16, tcx.types.f16], tcx.types.f16), + sym::minimum_number_nsz_f32 => (0, 0, vec![tcx.types.f32, tcx.types.f32], tcx.types.f32), + sym::minimum_number_nsz_f64 => (0, 0, vec![tcx.types.f64, tcx.types.f64], tcx.types.f64), + sym::minimum_number_nsz_f128 => { + (0, 0, vec![tcx.types.f128, tcx.types.f128], tcx.types.f128) + } sym::minimumf16 => (0, 0, vec![tcx.types.f16, tcx.types.f16], tcx.types.f16), sym::minimumf32 => (0, 0, vec![tcx.types.f32, tcx.types.f32], tcx.types.f32), sym::minimumf64 => (0, 0, vec![tcx.types.f64, tcx.types.f64], tcx.types.f64), sym::minimumf128 => (0, 0, vec![tcx.types.f128, tcx.types.f128], tcx.types.f128), - sym::maxnumf16 => (0, 0, vec![tcx.types.f16, tcx.types.f16], tcx.types.f16), - sym::maxnumf32 => (0, 0, vec![tcx.types.f32, tcx.types.f32], tcx.types.f32), - sym::maxnumf64 => (0, 0, vec![tcx.types.f64, tcx.types.f64], tcx.types.f64), - sym::maxnumf128 => (0, 0, vec![tcx.types.f128, tcx.types.f128], tcx.types.f128), + sym::maximum_number_nsz_f16 => (0, 0, vec![tcx.types.f16, tcx.types.f16], tcx.types.f16), + sym::maximum_number_nsz_f32 => (0, 0, vec![tcx.types.f32, tcx.types.f32], tcx.types.f32), + sym::maximum_number_nsz_f64 => (0, 0, vec![tcx.types.f64, tcx.types.f64], tcx.types.f64), + sym::maximum_number_nsz_f128 => { + (0, 0, vec![tcx.types.f128, tcx.types.f128], tcx.types.f128) + } sym::maximumf16 => (0, 0, vec![tcx.types.f16, tcx.types.f16], tcx.types.f16), sym::maximumf32 => (0, 0, vec![tcx.types.f32, tcx.types.f32], tcx.types.f32), diff --git a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp index eabc1c94f26e9..33b668d96c62a 100644 --- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp @@ -722,6 +722,13 @@ extern "C" void LLVMRustSetAllowReassoc(LLVMValueRef V) { } } +// Enable the NSZ flag on the given instruction. +extern "C" void LLVMRustSetNoSignedZeros(LLVMValueRef V) { + if (auto I = dyn_cast(unwrap(V))) { + I->setHasNoSignedZeros(true); + } +} + extern "C" uint64_t LLVMRustGetArrayNumElements(LLVMTypeRef Ty) { return unwrap(Ty)->getArrayNumElements(); } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index b7255ba67dfdb..6762378d035e6 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1206,14 +1206,14 @@ symbols! { masked, match_beginning_vert, match_default_bindings, + maximum_number_nsz_f16, + maximum_number_nsz_f32, + maximum_number_nsz_f64, + maximum_number_nsz_f128, maximumf16, maximumf32, maximumf64, maximumf128, - maxnumf16, - maxnumf32, - maxnumf64, - maxnumf128, may_dangle, may_unwind, maybe_uninit, @@ -1243,14 +1243,14 @@ symbols! { min_generic_const_args, min_specialization, min_type_alias_impl_trait, + minimum_number_nsz_f16, + minimum_number_nsz_f32, + minimum_number_nsz_f64, + minimum_number_nsz_f128, minimumf16, minimumf32, minimumf64, minimumf128, - minnumf16, - minnumf32, - minnumf64, - minnumf128, mips, mips32r6, mips64, diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index 7e9adc1e8d571..68e4f1c2aa787 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -2987,6 +2987,8 @@ pub const unsafe fn write_bytes(dst: *mut T, val: u8, count: usize); /// Returns the minimum of two `f16` values, ignoring NaN. /// +/// This behaves like IEEE 754-2019 minimumNumber, *except* that it does not order signed +/// zeros deterministically. In particular: /// If one of the arguments is NaN (quiet or signaling), then the other argument is returned. If /// both arguments are NaN, returns NaN. If the inputs compare equal (such as for the case of `+0.0` /// and `-0.0`), either input may be returned non-deterministically. @@ -2999,10 +3001,19 @@ pub const unsafe fn write_bytes(dst: *mut T, val: u8, count: usize); /// The stabilized version of this intrinsic is [`f16::min`]. #[rustc_nounwind] #[rustc_intrinsic] -pub const fn minnumf16(x: f16, y: f16) -> f16; +pub const fn minimum_number_nsz_f16(x: f16, y: f16) -> f16 { + if x.is_nan() || y <= x { + y + } else { + // Either y > x or y is a NaN. + x + } +} /// Returns the minimum of two `f32` values, ignoring NaN. /// +/// This behaves like IEEE 754-2019 minimumNumber, *except* that it does not order signed +/// zeros deterministically. In particular: /// If one of the arguments is NaN (quiet or signaling), then the other argument is returned. If /// both arguments are NaN, returns NaN. If the inputs compare equal (such as for the case of `+0.0` /// and `-0.0`), either input may be returned non-deterministically. @@ -3016,10 +3027,19 @@ pub const fn minnumf16(x: f16, y: f16) -> f16; #[rustc_nounwind] #[rustc_intrinsic_const_stable_indirect] #[rustc_intrinsic] -pub const fn minnumf32(x: f32, y: f32) -> f32; +pub const fn minimum_number_nsz_f32(x: f32, y: f32) -> f32 { + if x.is_nan() || y <= x { + y + } else { + // Either y > x or y is a NaN. + x + } +} /// Returns the minimum of two `f64` values, ignoring NaN. /// +/// This behaves like IEEE 754-2019 minimumNumber, *except* that it does not order signed +/// zeros deterministically. In particular: /// If one of the arguments is NaN (quiet or signaling), then the other argument is returned. If /// both arguments are NaN, returns NaN. If the inputs compare equal (such as for the case of `+0.0` /// and `-0.0`), either input may be returned non-deterministically. @@ -3033,10 +3053,19 @@ pub const fn minnumf32(x: f32, y: f32) -> f32; #[rustc_nounwind] #[rustc_intrinsic_const_stable_indirect] #[rustc_intrinsic] -pub const fn minnumf64(x: f64, y: f64) -> f64; +pub const fn minimum_number_nsz_f64(x: f64, y: f64) -> f64 { + if x.is_nan() || y <= x { + y + } else { + // Either y > x or y is a NaN. + x + } +} /// Returns the minimum of two `f128` values, ignoring NaN. /// +/// This behaves like IEEE 754-2019 minimumNumber, *except* that it does not order signed +/// zeros deterministically. In particular: /// If one of the arguments is NaN (quiet or signaling), then the other argument is returned. If /// both arguments are NaN, returns NaN. If the inputs compare equal (such as for the case of `+0.0` /// and `-0.0`), either input may be returned non-deterministically. @@ -3049,7 +3078,14 @@ pub const fn minnumf64(x: f64, y: f64) -> f64; /// The stabilized version of this intrinsic is [`f128::min`]. #[rustc_nounwind] #[rustc_intrinsic] -pub const fn minnumf128(x: f128, y: f128) -> f128; +pub const fn minimum_number_nsz_f128(x: f128, y: f128) -> f128 { + if x.is_nan() || y <= x { + y + } else { + // Either y > x or y is a NaN. + x + } +} /// Returns the minimum of two `f16` values, propagating NaN. /// @@ -3153,6 +3189,8 @@ pub const fn minimumf128(x: f128, y: f128) -> f128 { /// Returns the maximum of two `f16` values, ignoring NaN. /// +/// This behaves like IEEE 754-2019 maximumNumber, *except* that it does not order signed +/// zeros deterministically. In particular: /// If one of the arguments is NaN (quiet or signaling), then the other argument is returned. If /// both arguments are NaN, returns NaN. If the inputs compare equal (such as for the case of `+0.0` /// and `-0.0`), either input may be returned non-deterministically. @@ -3165,10 +3203,19 @@ pub const fn minimumf128(x: f128, y: f128) -> f128 { /// The stabilized version of this intrinsic is [`f16::max`]. #[rustc_nounwind] #[rustc_intrinsic] -pub const fn maxnumf16(x: f16, y: f16) -> f16; +pub const fn maximum_number_nsz_f16(x: f16, y: f16) -> f16 { + if x.is_nan() || y >= x { + y + } else { + // Either y < x or y is a NaN. + x + } +} /// Returns the maximum of two `f32` values, ignoring NaN. /// +/// This behaves like IEEE 754-2019 maximumNumber, *except* that it does not order signed +/// zeros deterministically. In particular: /// If one of the arguments is NaN (quiet or signaling), then the other argument is returned. If /// both arguments are NaN, returns NaN. If the inputs compare equal (such as for the case of `+0.0` /// and `-0.0`), either input may be returned non-deterministically. @@ -3182,10 +3229,19 @@ pub const fn maxnumf16(x: f16, y: f16) -> f16; #[rustc_nounwind] #[rustc_intrinsic_const_stable_indirect] #[rustc_intrinsic] -pub const fn maxnumf32(x: f32, y: f32) -> f32; +pub const fn maximum_number_nsz_f32(x: f32, y: f32) -> f32 { + if x.is_nan() || y >= x { + y + } else { + // Either y < x or y is a NaN. + x + } +} /// Returns the maximum of two `f64` values, ignoring NaN. /// +/// This behaves like IEEE 754-2019 maximumNumber, *except* that it does not order signed +/// zeros deterministically. In particular: /// If one of the arguments is NaN (quiet or signaling), then the other argument is returned. If /// both arguments are NaN, returns NaN. If the inputs compare equal (such as for the case of `+0.0` /// and `-0.0`), either input may be returned non-deterministically. @@ -3199,10 +3255,19 @@ pub const fn maxnumf32(x: f32, y: f32) -> f32; #[rustc_nounwind] #[rustc_intrinsic_const_stable_indirect] #[rustc_intrinsic] -pub const fn maxnumf64(x: f64, y: f64) -> f64; +pub const fn maximum_number_nsz_f64(x: f64, y: f64) -> f64 { + if x.is_nan() || y >= x { + y + } else { + // Either y < x or y is a NaN. + x + } +} /// Returns the maximum of two `f128` values, ignoring NaN. /// +/// This behaves like IEEE 754-2019 maximumNumber, *except* that it does not order signed +/// zeros deterministically. In particular: /// If one of the arguments is NaN (quiet or signaling), then the other argument is returned. If /// both arguments are NaN, returns NaN. If the inputs compare equal (such as for the case of `+0.0` /// and `-0.0`), either input may be returned non-deterministically. @@ -3215,7 +3280,14 @@ pub const fn maxnumf64(x: f64, y: f64) -> f64; /// The stabilized version of this intrinsic is [`f128::max`]. #[rustc_nounwind] #[rustc_intrinsic] -pub const fn maxnumf128(x: f128, y: f128) -> f128; +pub const fn maximum_number_nsz_f128(x: f128, y: f128) -> f128 { + if x.is_nan() || y >= x { + y + } else { + // Either y < x or y is a NaN. + x + } +} /// Returns the maximum of two `f16` values, propagating NaN. /// diff --git a/library/core/src/num/f128.rs b/library/core/src/num/f128.rs index 03bc5f20d7e94..68c87b48de94d 100644 --- a/library/core/src/num/f128.rs +++ b/library/core/src/num/f128.rs @@ -790,7 +790,7 @@ impl f128 { #[rustc_const_unstable(feature = "f128", issue = "116909")] #[must_use = "this returns the result of the comparison, without modifying either input"] pub const fn max(self, other: f128) -> f128 { - intrinsics::maxnumf128(self, other) + intrinsics::maximum_number_nsz_f128(self, other) } /// Returns the minimum of the two numbers, ignoring NaN. @@ -821,7 +821,7 @@ impl f128 { #[rustc_const_unstable(feature = "f128", issue = "116909")] #[must_use = "this returns the result of the comparison, without modifying either input"] pub const fn min(self, other: f128) -> f128 { - intrinsics::minnumf128(self, other) + intrinsics::minimum_number_nsz_f128(self, other) } /// Returns the maximum of the two numbers, propagating NaN. diff --git a/library/core/src/num/f16.rs b/library/core/src/num/f16.rs index ef937fccb47f3..f548de5a3d588 100644 --- a/library/core/src/num/f16.rs +++ b/library/core/src/num/f16.rs @@ -784,7 +784,7 @@ impl f16 { #[rustc_const_unstable(feature = "f16", issue = "116909")] #[must_use = "this returns the result of the comparison, without modifying either input"] pub const fn max(self, other: f16) -> f16 { - intrinsics::maxnumf16(self, other) + intrinsics::maximum_number_nsz_f16(self, other) } /// Returns the minimum of the two numbers, ignoring NaN. @@ -815,7 +815,7 @@ impl f16 { #[rustc_const_unstable(feature = "f16", issue = "116909")] #[must_use = "this returns the result of the comparison, without modifying either input"] pub const fn min(self, other: f16) -> f16 { - intrinsics::minnumf16(self, other) + intrinsics::minimum_number_nsz_f16(self, other) } /// Returns the maximum of the two numbers, propagating NaN. diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index aac81d48c1b45..14a44d7b7e371 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -989,7 +989,7 @@ impl f32 { #[rustc_const_stable(feature = "const_float_methods", since = "1.85.0")] #[inline] pub const fn max(self, other: f32) -> f32 { - intrinsics::maxnumf32(self, other) + intrinsics::maximum_number_nsz_f32(self, other) } /// Returns the minimum of the two numbers, ignoring NaN. @@ -1016,7 +1016,7 @@ impl f32 { #[rustc_const_stable(feature = "const_float_methods", since = "1.85.0")] #[inline] pub const fn min(self, other: f32) -> f32 { - intrinsics::minnumf32(self, other) + intrinsics::minimum_number_nsz_f32(self, other) } /// Returns the maximum of the two numbers, propagating NaN. diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index bacf429e77fab..be635d39cf88b 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -1007,7 +1007,7 @@ impl f64 { #[rustc_const_stable(feature = "const_float_methods", since = "1.85.0")] #[inline] pub const fn max(self, other: f64) -> f64 { - intrinsics::maxnumf64(self, other) + intrinsics::maximum_number_nsz_f64(self, other) } /// Returns the minimum of the two numbers, ignoring NaN. @@ -1034,7 +1034,7 @@ impl f64 { #[rustc_const_stable(feature = "const_float_methods", since = "1.85.0")] #[inline] pub const fn min(self, other: f64) -> f64 { - intrinsics::minnumf64(self, other) + intrinsics::minimum_number_nsz_f64(self, other) } /// Returns the maximum of the two numbers, propagating NaN. diff --git a/library/coretests/tests/floats/mod.rs b/library/coretests/tests/floats/mod.rs index b729cdf8458d7..34f07ac5045c6 100644 --- a/library/coretests/tests/floats/mod.rs +++ b/library/coretests/tests/floats/mod.rs @@ -1,3 +1,4 @@ +use std::hint::black_box; use std::num::FpCategory as Fp; use std::ops::{Add, Div, Mul, Rem, Sub}; @@ -32,7 +33,7 @@ trait TestableFloat: Sized { const LNGAMMA_APPROX_LOOSE: Self = Self::APPROX; const ZERO: Self; const ONE: Self; - + const SNAN: Self; const MIN_POSITIVE_NORMAL: Self; const MAX_SUBNORMAL: Self; /// Smallest number @@ -82,6 +83,9 @@ impl TestableFloat for f16 { const LNGAMMA_APPROX_LOOSE: Self = 1e-1; const ZERO: Self = 0.0; const ONE: Self = 1.0; + // We rely on NAN having an all-0 payload, so the signaling bit is the least significant + // non-0 bit, and that gets toggled by the "-1". + const SNAN: Self = Self::from_bits(Self::NAN.to_bits() - 1); const MIN_POSITIVE_NORMAL: Self = Self::MIN_POSITIVE; const MAX_SUBNORMAL: Self = Self::MIN_POSITIVE.next_down(); const TINY: Self = Self::from_bits(0x1); @@ -125,6 +129,9 @@ impl TestableFloat for f32 { const LNGAMMA_APPROX_LOOSE: Self = if cfg!(miri) { 1e-2 } else { 1e-4 }; const ZERO: Self = 0.0; const ONE: Self = 1.0; + // We rely on NAN having an all-0 payload, so the signaling bit is the least significant + // non-0 bit, and that gets toggled by the "-1". + const SNAN: Self = Self::from_bits(Self::NAN.to_bits() - 1); const MIN_POSITIVE_NORMAL: Self = Self::MIN_POSITIVE; const MAX_SUBNORMAL: Self = Self::MIN_POSITIVE.next_down(); const TINY: Self = Self::from_bits(0x1); @@ -153,6 +160,9 @@ impl TestableFloat for f64 { const LNGAMMA_APPROX_LOOSE: Self = 1e-4; const ZERO: Self = 0.0; const ONE: Self = 1.0; + // We rely on NAN having an all-0 payload, so the signaling bit is the least significant + // non-0 bit, and that gets toggled by the "-1". + const SNAN: Self = Self::from_bits(Self::NAN.to_bits() - 1); const MIN_POSITIVE_NORMAL: Self = Self::MIN_POSITIVE; const MAX_SUBNORMAL: Self = Self::MIN_POSITIVE.next_down(); const TINY: Self = Self::from_bits(0x1); @@ -191,6 +201,9 @@ impl TestableFloat for f128 { const LNGAMMA_APPROX_LOOSE: Self = 1e-10; const ZERO: Self = 0.0; const ONE: Self = 1.0; + // We rely on NAN having an all-0 payload, so the signaling bit is the least significant + // non-0 bit, and that gets toggled by the "-1". + const SNAN: Self = Self::from_bits(Self::NAN.to_bits() - 1); const MIN_POSITIVE_NORMAL: Self = Self::MIN_POSITIVE; const MAX_SUBNORMAL: Self = Self::MIN_POSITIVE.next_down(); const TINY: Self = Self::from_bits(0x1); @@ -392,9 +405,15 @@ macro_rules! float_test { $( $( #[$const_meta] )+ )? mod const_ { + // We cannot import `super::*` as that somehow makes `assert_biteq` ambiguous. + // That's probably a rustc bug (we explicitly import something for that name below + // which should take precedent). To work around this we have to manually re-import + // everything. #[allow(unused)] use super::TestableFloat; #[allow(unused)] + use std::hint::black_box; + #[allow(unused)] use std::num::FpCategory as Fp; #[allow(unused)] use std::ops::{Add, Div, Mul, Rem, Sub}; @@ -724,11 +743,22 @@ float_test! { assert_biteq!(flt(9.0).min(Float::NEG_INFINITY), Float::NEG_INFINITY); assert_biteq!(Float::NEG_INFINITY.min(-9.0), Float::NEG_INFINITY); assert_biteq!(flt(-9.0).min(Float::NEG_INFINITY), Float::NEG_INFINITY); - assert_biteq!(Float::NAN.min(9.0), 9.0); - assert_biteq!(Float::NAN.min(-9.0), -9.0); - assert_biteq!(flt(9.0).min(Float::NAN), 9.0); - assert_biteq!(flt(-9.0).min(Float::NAN), -9.0); + // We add black_box for the NAN tests as that used to be able to trigger miscompilations. + assert_biteq!(Float::NAN.min(black_box(9.0)), 9.0); + assert_biteq!(black_box(Float::NAN).min(-9.0), -9.0); + assert_biteq!(flt(9.0).min(black_box(Float::NAN)), 9.0); + assert_biteq!(black_box(flt(-9.0)).min(Float::NAN), -9.0); assert!(Float::NAN.min(Float::NAN).is_nan()); + // FIXME(llvm21): LLVM miscompiles the fallback impl on aarch64 and likely other targets + // (https://github.com/llvm/llvm-project/issues/176624). When we require LLVM 22, + // remove the ui test `tests/ui/float/minmax.rs` and unconditionally enable the test here. + if cfg!(miri) { + assert_biteq!(Float::SNAN.min(black_box(9.0)), 9.0); + assert_biteq!(black_box(Float::SNAN).min(-9.0), -9.0); + assert_biteq!(flt(9.0).min(black_box(Float::SNAN)), 9.0); + assert_biteq!(black_box(flt(-9.0)).min(Float::SNAN), -9.0); + } + assert!(Float::SNAN.min(Float::SNAN).is_nan()); } } @@ -755,11 +785,22 @@ float_test! { assert_biteq!(flt(9.0).max(Float::NEG_INFINITY), 9.0); assert_biteq!(Float::NEG_INFINITY.max(-9.0), -9.0); assert_biteq!(flt(-9.0).max(Float::NEG_INFINITY), -9.0); - assert_biteq!(Float::NAN.max(9.0), 9.0); - assert_biteq!(Float::NAN.max(-9.0), -9.0); - assert_biteq!(flt(9.0).max(Float::NAN), 9.0); - assert_biteq!(flt(-9.0).max(Float::NAN), -9.0); + // We add black_box for the NAN tests as that used to be able to trigger miscompilations. + assert_biteq!(Float::NAN.max(black_box(9.0)), 9.0); + assert_biteq!(black_box(Float::NAN).max(-9.0), -9.0); + assert_biteq!(flt(9.0).max(black_box(Float::NAN)), 9.0); + assert_biteq!(black_box(flt(-9.0)).max(Float::NAN), -9.0); assert!(Float::NAN.max(Float::NAN).is_nan()); + // FIXME(llvm21): LLVM miscompiles the fallback impl on aarch64 and likely other targets + // (https://github.com/llvm/llvm-project/issues/176624). When we require LLVM 22, + // remove the ui test `tests/ui/float/minmax.rs` and unconditionally enable the test here. + if cfg!(miri) { + assert_biteq!(Float::SNAN.max(black_box(9.0)), 9.0); + assert_biteq!(black_box(Float::SNAN).max(-9.0), -9.0); + assert_biteq!(flt(9.0).max(black_box(Float::SNAN)), 9.0); + assert_biteq!(black_box(flt(-9.0)).max(Float::SNAN), -9.0); + } + assert!(Float::SNAN.max(Float::SNAN).is_nan()); } } diff --git a/src/tools/miri/tests/pass/float.rs b/src/tools/miri/tests/pass/float.rs index 052c83d1aa313..184854774cc9e 100644 --- a/src/tools/miri/tests/pass/float.rs +++ b/src/tools/miri/tests/pass/float.rs @@ -8,14 +8,14 @@ #![allow(internal_features)] #![allow(unnecessary_transmutes)] -#[path = "../utils/mod.rs"] -mod utils; use std::any::type_name; use std::cmp::min; use std::fmt::{Debug, Display, LowerHex}; use std::hint::black_box; use std::{f32, f64}; +#[path = "../utils/mod.rs"] +mod utils; use utils::check_nondet; /// Compare the two floats, allowing for $ulp many ULPs of error. @@ -70,7 +70,6 @@ fn main() { test_fast(); test_algebraic(); test_fmuladd(); - test_min_max_nondet(); test_non_determinism(); } @@ -1425,19 +1424,13 @@ fn test_fmuladd() { test_operations_f64(1.1, 1.2, 1.3); } -/// `min` and `max` on equal arguments are non-deterministic. -fn test_min_max_nondet() { - check_nondet(|| f16::min(0.0, -0.0).is_sign_positive()); - check_nondet(|| f16::max(0.0, -0.0).is_sign_positive()); - check_nondet(|| f32::min(0.0, -0.0).is_sign_positive()); - check_nondet(|| f32::max(0.0, -0.0).is_sign_positive()); - check_nondet(|| f64::min(0.0, -0.0).is_sign_positive()); - check_nondet(|| f64::max(0.0, -0.0).is_sign_positive()); - check_nondet(|| f128::min(0.0, -0.0).is_sign_positive()); - check_nondet(|| f128::max(0.0, -0.0).is_sign_positive()); -} - fn test_non_determinism() { + if cfg!(force_intrinsic_fallback) { + // Skip this test when we use the fallback bodies, as that one is deterministic. + // (CI sets `--cfg force_intrinsic_fallback` together with `-Zmiri-force-intrinsic-fallback`.) + return; + } + use std::intrinsics::{ fadd_algebraic, fadd_fast, fdiv_algebraic, fdiv_fast, fmul_algebraic, fmul_fast, frem_algebraic, frem_fast, fsub_algebraic, fsub_fast, @@ -1541,6 +1534,16 @@ fn test_non_determinism() { test_operations_f64(19., 11.); test_operations_f128(25., 18.); + // min/max signed zero nondet + check_nondet(|| f16::min(0.0, -0.0).is_sign_positive()); + check_nondet(|| f16::max(0.0, -0.0).is_sign_positive()); + check_nondet(|| f32::min(0.0, -0.0).is_sign_positive()); + check_nondet(|| f32::max(0.0, -0.0).is_sign_positive()); + check_nondet(|| f64::min(0.0, -0.0).is_sign_positive()); + check_nondet(|| f64::max(0.0, -0.0).is_sign_positive()); + check_nondet(|| f128::min(0.0, -0.0).is_sign_positive()); + check_nondet(|| f128::max(0.0, -0.0).is_sign_positive()); + // SNaN^0 = (1 | NaN) check_nondet(|| f32::powf(F32_SNAN, 0.0).is_nan()); check_nondet(|| f64::powf(F64_SNAN, 0.0).is_nan()); diff --git a/src/tools/miri/tests/pass/float_nan.rs b/src/tools/miri/tests/pass/float_nan.rs index c07ffdf9740c4..6b39c2db3587a 100644 --- a/src/tools/miri/tests/pass/float_nan.rs +++ b/src/tools/miri/tests/pass/float_nan.rs @@ -541,6 +541,12 @@ fn test_simd() { } fn main() { + if cfg!(force_intrinsic_fallback) { + // Skip this test when we use the fallback bodies, as that one is deterministic. + // (CI sets `--cfg force_intrinsic_fallback` together with `-Zmiri-force-intrinsic-fallback`.) + return; + } + // Check our constants against std, just to be sure. // We add 1 since our numbers are the number of bits stored // to represent the value, and std has the precision of the value, diff --git a/src/tools/miri/tests/pass/intrinsics/intrinsics.rs b/src/tools/miri/tests/pass/intrinsics/intrinsics.rs index 913c3cde272d8..c6ccadf031888 100644 --- a/src/tools/miri/tests/pass/intrinsics/intrinsics.rs +++ b/src/tools/miri/tests/pass/intrinsics/intrinsics.rs @@ -5,6 +5,10 @@ use std::intrinsics; use std::mem::{discriminant, size_of, size_of_val, size_of_val_raw}; +#[path = "../../utils/mod.rs"] +mod utils; +use utils::check_nondet; + struct Bomb; impl Drop for Bomb { @@ -36,20 +40,7 @@ fn main() { // Skip this test when we use the fallback bodies, as that one is deterministic. // (CI sets `--cfg force_intrinsic_fallback` together with `-Zmiri-force-intrinsic-fallback`.) if !cfg!(force_intrinsic_fallback) { - let mut saw_true = false; - let mut saw_false = false; - - for _ in 0..50 { - if intrinsics::is_val_statically_known(0) { - saw_true = true; - } else { - saw_false = true; - } - } - assert!( - saw_true && saw_false, - "`is_val_statically_known` failed to return both true and false. Congrats, you won the lottery!" - ); + check_nondet(|| intrinsics::is_val_statically_known(0)); } intrinsics::forget(Bomb); diff --git a/tests/ui/float/minmax.rs b/tests/ui/float/minmax.rs new file mode 100644 index 0000000000000..e6bac901d7922 --- /dev/null +++ b/tests/ui/float/minmax.rs @@ -0,0 +1,22 @@ +//FIXME(llvm21) This should be a library test, but old LLVM miscompiles things so we can't just +// test this properly everywhere. Once we require LLVM 22, remove this test and enable the +// commented-out tests in `library/coretests/tests/floats/mod.rs` instead. +//@ min-llvm-version: 22 +//@ run-pass + +use std::hint::black_box; + +const SNAN32: f32 = f32::from_bits(f32::NAN.to_bits() - 1); +const SNAN64: f64 = f64::from_bits(f64::NAN.to_bits() - 1); + +fn main() { + assert_eq!(SNAN32.min(black_box(9.0)), 9.0f32); + assert_eq!(black_box(SNAN32).min(-9.0), -9.0f32); + assert_eq!((9.0f32).min(black_box(SNAN32)), 9.0f32); + assert_eq!(black_box(-9.0f32).min(SNAN32), -9.0f32); + + assert_eq!(SNAN64.min(black_box(9.0)), 9.0f64); + assert_eq!(black_box(SNAN64).min(-9.0), -9.0f64); + assert_eq!((9.0f64).min(black_box(SNAN64)), 9.0f64); + assert_eq!(black_box(-9.0f64).min(SNAN64), -9.0f64); +} diff --git a/tests/ui/simd/intrinsic/float-minmax-pass.rs b/tests/ui/simd/intrinsic/float-minmax-pass.rs index 4b6a35556ed57..d9dc291de63d4 100644 --- a/tests/ui/simd/intrinsic/float-minmax-pass.rs +++ b/tests/ui/simd/intrinsic/float-minmax-pass.rs @@ -21,6 +21,8 @@ const fn minmax() { let nan = f32::NAN; // MIPS hardware except MIPS R6 treats f32::NAN as SNAN. Clear the signaling bit. // See https://github.com/rust-lang/rust/issues/52746. + // The "-1" works because we rely on `NAN` to have an all-0 payload, so the signaling + // bit is the least significant non-zero bit. #[cfg(any(target_arch = "mips", target_arch = "mips64"))] let nan = f32::from_bits(f32::NAN.to_bits() - 1);