Skip to content

Commit f5a2d69

Browse files
authored
[mono][llvm] Use llvm.minimum/maximum for scalar Math.Min/Max float ops (#129593)
1 parent 95aa263 commit f5a2d69

4 files changed

Lines changed: 50 additions & 18 deletions

File tree

src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Numerics/BFloat16Tests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,7 @@ public static IEnumerable<object[]> ExplicitConversion_FromSingle_TestData()
538538

539539
[MemberData(nameof(ExplicitConversion_FromSingle_TestData))]
540540
[Theory]
541+
[ActiveIssue("https://github.com/dotnet/runtime/issues/103347", TestPlatforms.Browser)]
541542
public static void ExplicitConversion_FromSingle(float f, BFloat16 expected) // Check the underlying bits for verifying NaNs
542543
{
543544
BFloat16 b16 = (BFloat16)f;
@@ -623,6 +624,7 @@ public static IEnumerable<object[]> ExplicitConversion_FromDouble_TestData()
623624

624625
[MemberData(nameof(ExplicitConversion_FromDouble_TestData))]
625626
[Theory]
627+
[ActiveIssue("https://github.com/dotnet/runtime/issues/103347", TestPlatforms.Browser)]
626628
public static void ExplicitConversion_FromDouble(double d, BFloat16 expected) // Check the underlying bits for verifying NaNs
627629
{
628630
BFloat16 b16 = (BFloat16)d;

src/mono/mono/mini/intrinsics.c

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -199,10 +199,12 @@ llvm_emit_inst_for_method (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSign
199199
}
200200
// (double, double)
201201
if (fsig->param_count == 2 && fsig->params [0]->type == MONO_TYPE_R8 && fsig->params [1]->type == MONO_TYPE_R8) {
202-
// Max and Min can only be optimized in fast math mode
203-
if (!strcmp (cmethod->name, "Max") && mono_use_fast_math) {
202+
// Max/Min use llvm.maximum/minimum (NaN-propagating); see mini-llvm.c.
203+
// Matches IEEE 754-2019 minimum/maximum which is what Math.Min/Math.Max
204+
// (and the MathF.Min/MathF.Max forwarders) document.
205+
if (!strcmp (cmethod->name, "Max")) {
204206
opcode = OP_FMAX;
205-
} else if (!strcmp (cmethod->name, "Min") && mono_use_fast_math) {
207+
} else if (!strcmp (cmethod->name, "Min")) {
206208
opcode = OP_FMIN;
207209
} else if (!strcmp (cmethod->name, "Pow")) {
208210
opcode = OP_FPOW;
@@ -226,9 +228,9 @@ llvm_emit_inst_for_method (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSign
226228
}
227229
// (float, float)
228230
if (fsig->param_count == 2 && fsig->params [0]->type == MONO_TYPE_R4 && fsig->params [1]->type == MONO_TYPE_R4) {
229-
if (!strcmp (cmethod->name, "Max") && mono_use_fast_math) {
231+
if (!strcmp (cmethod->name, "Max")) {
230232
opcode = OP_RMAX;
231-
} else if (!strcmp (cmethod->name, "Min") && mono_use_fast_math) {
233+
} else if (!strcmp (cmethod->name, "Min")) {
232234
opcode = OP_RMIN;
233235
} else if (!strcmp (cmethod->name, "Pow")) {
234236
opcode = OP_RPOW;

src/mono/mono/mini/llvm-intrinsics.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,19 @@ INTRINS_OVR(CEIL, ceil, Generic, LLVMDoubleType ())
6464
INTRINS_OVR(CEILF, ceil, Generic, LLVMFloatType ())
6565
INTRINS_OVR(FMA, fma, Generic, LLVMDoubleType ())
6666
INTRINS_OVR(FMAF, fma, Generic, LLVMFloatType ())
67+
/*
68+
* IEEE 754-2019 minimum/maximum (NaN-propagating). Used for scalar OP_FMIN/OP_FMAX/
69+
* OP_RMIN/OP_RMAX. We avoid the older fcmp+select lowering because (a) it has
70+
* asymmetric NaN semantics (Min(NaN,x)=NaN but Min(x,NaN)=x), violating
71+
* Math.Min/Math.Max (and the MathF.Min/MathF.Max forwarders) spec, and (b) on
72+
* AArch64 the backend folds it to fminnm/fmaxnm (IEEE 754-2008 minNum/maxNum),
73+
* which suppresses NaN entirely and miscompiles the System.Half software
74+
* conversion path under LLVM 23.
75+
*/
76+
INTRINS_OVR(MINIMUM, minimum, Generic, LLVMDoubleType ())
77+
INTRINS_OVR(MINIMUMF, minimum, Generic, LLVMFloatType ())
78+
INTRINS_OVR(MAXIMUM, maximum, Generic, LLVMDoubleType ())
79+
INTRINS_OVR(MAXIMUMF, maximum, Generic, LLVMFloatType ())
6780
/* This isn't an intrinsic, instead llvm seems to special case it by name */
6881
INTRINS_OVR(FABS, fabs, Generic, LLVMDoubleType ())
6982
INTRINS_OVR(ABSF, fabs, Generic, LLVMFloatType ())

src/mono/mono/mini/mini-llvm.c

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7531,11 +7531,7 @@ MONO_RESTORE_WARNING
75317531
case OP_IMIN_UN:
75327532
case OP_LMIN_UN:
75337533
case OP_IMAX_UN:
7534-
case OP_LMAX_UN:
7535-
case OP_FMIN:
7536-
case OP_FMAX:
7537-
case OP_RMIN:
7538-
case OP_RMAX: {
7534+
case OP_LMAX_UN: {
75397535
LLVMValueRef v;
75407536

75417537
lhs = convert (ctx, lhs, regtype_to_llvm_type (spec [MONO_INST_DEST]));
@@ -7558,14 +7554,6 @@ MONO_RESTORE_WARNING
75587554
case OP_LMAX_UN:
75597555
v = LLVMBuildICmp (builder, LLVMIntUGE, lhs, rhs, "");
75607556
break;
7561-
case OP_FMAX:
7562-
case OP_RMAX:
7563-
v = LLVMBuildFCmp (builder, LLVMRealUGE, lhs, rhs, "");
7564-
break;
7565-
case OP_FMIN:
7566-
case OP_RMIN:
7567-
v = LLVMBuildFCmp (builder, LLVMRealULE, lhs, rhs, "");
7568-
break;
75697557
default:
75707558
g_assert_not_reached ();
75717559
break;
@@ -7574,6 +7562,33 @@ MONO_RESTORE_WARNING
75747562
break;
75757563
}
75767564

7565+
case OP_FMIN:
7566+
case OP_FMAX:
7567+
case OP_RMIN:
7568+
case OP_RMAX: {
7569+
/*
7570+
* Use llvm.minimum/maximum (IEEE 754-2019, NaN-propagating) so the
7571+
* Math.Min/Math.Max semantics ("if either argument is NaN, NaN is
7572+
* returned"), as forwarded by MathF.Min/MathF.Max, are honored
7573+
* symmetrically. The old fcmp+select lowering was both asymmetric for
7574+
* NaN and got folded into AArch64 fminnm/fmaxnm by the backend, which
7575+
* discards NaN entirely. See llvm-intrinsics.h.
7576+
*/
7577+
gboolean is_r4 = ins->opcode == OP_RMIN || ins->opcode == OP_RMAX;
7578+
LLVMTypeRef t = is_r4 ? LLVMFloatType () : LLVMDoubleType ();
7579+
LLVMValueRef args [2] = { convert (ctx, lhs, t), convert (ctx, rhs, t) };
7580+
IntrinsicId iid;
7581+
switch (ins->opcode) {
7582+
case OP_FMAX: iid = INTRINS_MAXIMUM; break;
7583+
case OP_FMIN: iid = INTRINS_MINIMUM; break;
7584+
case OP_RMAX: iid = INTRINS_MAXIMUMF; break;
7585+
case OP_RMIN: iid = INTRINS_MINIMUMF; break;
7586+
default: g_assert_not_reached (); break;
7587+
}
7588+
values [ins->dreg] = call_intrins (ctx, iid, args, dname);
7589+
break;
7590+
}
7591+
75777592
/*
75787593
* See the ARM64 comment in mono/utils/atomic.h for an explanation of why this
75797594
* hack is necessary (for now).

0 commit comments

Comments
 (0)