rename min/maxnum intrinsics to min/maximum_number and fix their LLVM lowering

This commit is contained in:
Ralf Jung
2026-03-03 12:35:41 +01:00
parent 79d2026ae8
commit c7220f423b
21 changed files with 290 additions and 141 deletions
@@ -1266,7 +1266,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);
@@ -1275,7 +1275,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);
@@ -1284,7 +1284,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);
@@ -1293,7 +1293,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);
@@ -1302,7 +1302,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);
@@ -1311,7 +1311,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);
@@ -1320,7 +1320,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);
@@ -1329,7 +1329,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);
+4 -4
View File
@@ -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.
@@ -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 @@ fn codegen_intrinsic_call(
| sym::fabsf16
| sym::floorf16
| sym::fmaf16
| sym::maxnumf16
| sym::minnumf16
| sym::powf16
| sym::powif16
| sym::roundf16
+26 -10
View File
@@ -112,11 +112,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()]),
@@ -124,11 +119,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()]),
@@ -195,6 +185,32 @@ fn codegen_intrinsic_call(
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(
@@ -2045,6 +2045,7 @@ pub(crate) fn LLVMRustAddCallSiteAttributes<'a>(
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>(
@@ -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.
@@ -526,17 +526,17 @@ pub fn eval_intrinsic(
self.write_scalar(Scalar::from_target_usize(align.bytes(), self), dest)?;
}
sym::minnumf16 => {
self.float_minmax_intrinsic::<Half>(args, MinMax::MinimumNumber, dest)?
sym::minimum_number_nsz_f16 => {
self.float_minmax_intrinsic::<Half>(args, MinMax::MinimumNumberNsz, dest)?
}
sym::minnumf32 => {
self.float_minmax_intrinsic::<Single>(args, MinMax::MinimumNumber, dest)?
sym::minimum_number_nsz_f32 => {
self.float_minmax_intrinsic::<Single>(args, MinMax::MinimumNumberNsz, dest)?
}
sym::minnumf64 => {
self.float_minmax_intrinsic::<Double>(args, MinMax::MinimumNumber, dest)?
sym::minimum_number_nsz_f64 => {
self.float_minmax_intrinsic::<Double>(args, MinMax::MinimumNumberNsz, dest)?
}
sym::minnumf128 => {
self.float_minmax_intrinsic::<Quad>(args, MinMax::MinimumNumber, dest)?
sym::minimum_number_nsz_f128 => {
self.float_minmax_intrinsic::<Quad>(args, MinMax::MinimumNumberNsz, dest)?
}
sym::minimumf16 => self.float_minmax_intrinsic::<Half>(args, MinMax::Minimum, dest)?,
@@ -548,17 +548,17 @@ pub fn eval_intrinsic(
}
sym::minimumf128 => self.float_minmax_intrinsic::<Quad>(args, MinMax::Minimum, dest)?,
sym::maxnumf16 => {
self.float_minmax_intrinsic::<Half>(args, MinMax::MaximumNumber, dest)?
sym::maximum_number_nsz_f16 => {
self.float_minmax_intrinsic::<Half>(args, MinMax::MaximumNumberNsz, dest)?
}
sym::maxnumf32 => {
self.float_minmax_intrinsic::<Single>(args, MinMax::MaximumNumber, dest)?
sym::maximum_number_nsz_f32 => {
self.float_minmax_intrinsic::<Single>(args, MinMax::MaximumNumberNsz, dest)?
}
sym::maxnumf64 => {
self.float_minmax_intrinsic::<Double>(args, MinMax::MaximumNumber, dest)?
sym::maximum_number_nsz_f64 => {
self.float_minmax_intrinsic::<Double>(args, MinMax::MaximumNumberNsz, dest)?
}
sym::maxnumf128 => {
self.float_minmax_intrinsic::<Quad>(args, MinMax::MaximumNumber, dest)?
sym::maximum_number_nsz_f128 => {
self.float_minmax_intrinsic::<Quad>(args, MinMax::MaximumNumberNsz, dest)?
}
sym::maximumf16 => self.float_minmax_intrinsic::<Half>(args, MinMax::Maximum, dest)?,
@@ -1031,16 +1031,16 @@ fn float_minmax<F>(
{
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])
};
@@ -211,8 +211,8 @@ enum Op {
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 @@ enum Op {
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 @@ enum Op {
} 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
@@ -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),
@@ -712,6 +712,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<Instruction>(unwrap<Value>(V))) {
I->setHasNoSignedZeros(true);
}
}
extern "C" uint64_t LLVMRustGetArrayNumElements(LLVMTypeRef Ty) {
return unwrap(Ty)->getArrayNumElements();
}
+8 -8
View File
@@ -1209,14 +1209,14 @@
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_dangling,
@@ -1247,14 +1247,14 @@
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,
+80 -8
View File
@@ -2987,6 +2987,8 @@ pub const fn aggregate_raw_ptr<P: bounds::BuiltinDeref, D, M>(data: D, meta: M)
/// 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 fn aggregate_raw_ptr<P: bounds::BuiltinDeref, D, M>(data: D, meta: M)
/// 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 aggregate_raw_ptr<P: bounds::BuiltinDeref, D, M>(data: D, meta: M)
#[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 aggregate_raw_ptr<P: bounds::BuiltinDeref, D, M>(data: D, meta: M)
#[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 aggregate_raw_ptr<P: bounds::BuiltinDeref, D, M>(data: D, meta: M)
/// 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 minimumf128(x: f128, y: f128) -> f128 {
#[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 minimumf128(x: f128, y: f128) -> f128 {
#[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 minimumf128(x: f128, y: f128) -> f128 {
/// 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.
///
+2 -2
View File
@@ -790,7 +790,7 @@ pub const fn to_radians(self) -> 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 @@ pub const fn max(self, other: f128) -> 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.
+2 -2
View File
@@ -784,7 +784,7 @@ pub const fn to_radians(self) -> 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 @@ pub const fn max(self, other: f16) -> 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.
+2 -2
View File
@@ -990,7 +990,7 @@ pub const fn to_radians(self) -> 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.
@@ -1017,7 +1017,7 @@ pub const fn max(self, other: f32) -> 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.
+2 -2
View File
@@ -1008,7 +1008,7 @@ pub const fn to_radians(self) -> 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.
@@ -1035,7 +1035,7 @@ pub const fn max(self, other: f64) -> 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.
+44 -9
View File
@@ -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);
@@ -668,11 +681,22 @@ const fn flt (x: Float) -> Float { x }
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());
}
}
@@ -699,11 +723,22 @@ const fn flt (x: Float) -> Float { x }
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());
}
}
+18 -15
View File
@@ -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_operations_f64(a: f64, b: f64, c: f64) {
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_operations_f128(a: f128, b: f128) {
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());
+6
View File
@@ -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,
@@ -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);
+22
View File
@@ -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);
}
@@ -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);