Skip to content

Commit 4c1fbd3

Browse files
lewingCopilot
andcommitted
[mono][llvm] Recognize MathF.Abs/Log and Single/Double.MinNumber/MaxNumber as intrinsics
Mono's LLVM backend has end-to-end plumbing for OP_FMA, OP_FCOPYSIGN, OP_SQRTF, etc., but a handful of obvious BCL entry points fall through to the C# implementations: * `MathF.Abs(float)` and `MathF.Log(float)` are not recognized in the MathF block of intrinsics.c, even though MathF.Sqrt/Sin/Cos/Exp/Log2/ Log10/Floor/Ceiling/Truncate all are. (Math.Abs(float) is recognized via the Math (float) fallback, but Math.Log(float) doesn't exist — only MathF.Log does.) Adding INTRINS_LOGF + OP_LOGF closes the gap; MathF.Abs reuses the existing OP_ABSF / INTRINS_ABSF. * `Single.MinNumber/MaxNumber` and `Double.MinNumber/MaxNumber` (forwarders for INumber<TSelf>.{Min,Max}Number) are IEEE 754-2008 numNum semantics: when exactly one argument is NaN, return the non-NaN; when both are NaN, return NaN. These map exactly to llvm.minnum / llvm.maxnum, which on AArch64 lower to a single fminnm/fmaxnm instruction. Without recognition the C# bodies inline to a fcmp+select chain — semantically correct, but several instructions on every target. Mono had no recognition for the Single/Double primitive classes previously; this adds a focused block that only handles MinNumber and MaxNumber. (Min/Max forward to Math/MathF and are caught by the existing recognition there.) Adds: * OP_LOGF / OP_FMINNUM / OP_FMAXNUM / OP_RMINNUM / OP_RMAXNUM in mini-ops.h. * INTRINS_LOGF / INTRINS_MINNUM / INTRINS_MINNUMF / INTRINS_MAXNUM / INTRINS_MAXNUMF in llvm-intrinsics.h. * Case handlers in mini-llvm.c (OP_LOGF reuses the existing scalar-1-arg pattern; the four num/numF ops share a single block that selects the correct intrinsic by opcode). * Recognition in intrinsics.c — MathF.Abs/MathF.Log added to the existing MathF (float) block; MinNumber/MaxNumber added in a new Single/Double block placed after the Math block. Validation: on the PR dotnet#129299 branch (emsdk 5.0.6 / LLVM 23) stacked with the Min/Max NaN fix from PR dotnet#129593, full AOT + LLVM run of System.Runtime.Tests on iossimulator-arm64: HalfTests 1442/1442 pass HalfTests_GenericMath 356/356 pass SingleTests 1334/1334 pass DoubleTests 1559/1559 pass (no regression vs the dotnet#129593-only baseline; covers MinNumber/ MaxNumber on Single/Double) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 4e663eb commit 4c1fbd3

4 files changed

Lines changed: 81 additions & 1 deletion

File tree

src/mono/mono/mini/intrinsics.c

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,18 @@ llvm_emit_inst_for_method (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSign
102102
if (in_corlib && !strcmp (m_class_get_name (cmethod->klass), "MathF") && cfg->r4fp) {
103103
// (float)
104104
if (fsig->param_count == 1 && fsig->params [0]->type == MONO_TYPE_R4) {
105-
if (!strcmp (cmethod->name, "Ceiling")) {
105+
if (!strcmp (cmethod->name, "Abs")) {
106+
opcode = OP_ABSF;
107+
} else if (!strcmp (cmethod->name, "Ceiling")) {
106108
opcode = OP_CEILF;
107109
} else if (!strcmp (cmethod->name, "Cos")) {
108110
opcode = OP_COSF;
109111
} else if (!strcmp (cmethod->name, "Exp")) {
110112
opcode = OP_EXPF;
111113
} else if (!strcmp (cmethod->name, "Floor")) {
112114
opcode = OP_FLOORF;
115+
} else if (!strcmp (cmethod->name, "Log")) {
116+
opcode = OP_LOGF;
113117
} else if (!strcmp (cmethod->name, "Log2")) {
114118
opcode = OP_LOG2F;
115119
} else if (!strcmp (cmethod->name, "Log10")) {
@@ -285,6 +289,32 @@ llvm_emit_inst_for_method (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSign
285289
}
286290
}
287291

292+
if (in_corlib && (!strcmp (m_class_get_name (cmethod->klass), "Single") || !strcmp (m_class_get_name (cmethod->klass), "Double"))) {
293+
// Recognize the IEEE 754-2008 numNum/maxNum helpers on Single/Double. These
294+
// live on the primitive types (forwarded from INumber<TSelf>), not on
295+
// Math/MathF. Lower them to llvm.minnum/llvm.maxnum (NaN-suppressing),
296+
// which matches the spec ("if either is NaN, return the non-NaN; if both
297+
// are NaN, return NaN") and on AArch64 maps to a single fminnm/fmaxnm.
298+
opcode = 0;
299+
if (fsig->param_count == 2 &&
300+
fsig->params [0]->type == fsig->params [1]->type &&
301+
(fsig->params [0]->type == MONO_TYPE_R4 || fsig->params [0]->type == MONO_TYPE_R8)) {
302+
gboolean is_r4 = fsig->params [0]->type == MONO_TYPE_R4;
303+
if (!strcmp (cmethod->name, "MaxNumber"))
304+
opcode = is_r4 ? OP_RMAXNUM : OP_FMAXNUM;
305+
else if (!strcmp (cmethod->name, "MinNumber"))
306+
opcode = is_r4 ? OP_RMINNUM : OP_FMINNUM;
307+
}
308+
if (opcode) {
309+
MONO_INST_NEW (cfg, ins, opcode);
310+
ins->type = STACK_R8;
311+
ins->dreg = mono_alloc_dreg (cfg, (MonoStackType)ins->type);
312+
ins->sreg1 = args [0]->dreg;
313+
ins->sreg2 = args [1]->dreg;
314+
MONO_ADD_INS (cfg->cbb, ins);
315+
}
316+
}
317+
288318
if (in_corlib && !strcmp (m_class_get_name (cmethod->klass), "SpanHelpers")) {
289319
if (!strcmp (cmethod->name, "Memmove") && fsig->param_count == 3 && m_type_is_byref (fsig->params [0]) && m_type_is_byref (fsig->params [1]) && !cmethod->is_inflated) {
290320
MonoBasicBlock *end_bb;

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ INTRINS_OVR(POW, pow, Generic, LLVMDoubleType ())
8888
INTRINS_OVR(EXP, exp, Generic, LLVMDoubleType ())
8989
INTRINS_OVR(EXPF, exp, Generic, LLVMFloatType ())
9090
INTRINS_OVR(LOG, log, Generic, LLVMDoubleType ())
91+
INTRINS_OVR(LOGF, log, Generic, LLVMFloatType ())
9192
INTRINS_OVR(LOG2, log2, Generic, LLVMDoubleType ())
9293
INTRINS_OVR(LOG2F, log2, Generic, LLVMFloatType ())
9394
INTRINS_OVR(LOG10, log10, Generic, LLVMDoubleType ())
@@ -96,6 +97,18 @@ INTRINS_OVR(TRUNC, trunc, Generic, LLVMDoubleType ())
9697
INTRINS_OVR(TRUNCF, trunc, Generic, LLVMFloatType ())
9798
INTRINS_OVR(COPYSIGN, copysign, Generic, LLVMDoubleType ())
9899
INTRINS_OVR(COPYSIGNF, copysign, Generic, LLVMFloatType ())
100+
/*
101+
* IEEE 754-2008 minNum/maxNum (NaN-suppressing). When exactly one operand
102+
* is NaN they return the other; when both are NaN they return NaN. This is
103+
* what Math.MinNumber/MaxNumber (and the MathF.MinNumber/MaxNumber forwarders)
104+
* are documented to do, and on AArch64 these lower to single fminnm/fmaxnm
105+
* instructions. Use llvm.minimum/maximum (see above) for the NaN-propagating
106+
* Math.Min/Math.Max instead.
107+
*/
108+
INTRINS_OVR(MINNUM, minnum, Generic, LLVMDoubleType ())
109+
INTRINS_OVR(MINNUMF, minnum, Generic, LLVMFloatType ())
110+
INTRINS_OVR(MAXNUM, maxnum, Generic, LLVMDoubleType ())
111+
INTRINS_OVR(MAXNUMF, maxnum, Generic, LLVMFloatType ())
99112
INTRINS_OVR(EXPECT_I8, expect, Generic, LLVMInt8Type ())
100113
INTRINS_OVR(EXPECT_I1, expect, Generic, LLVMInt1Type ())
101114
INTRINS_OVR(CTPOP_I32, ctpop, Generic, LLVMInt32Type ())

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7494,6 +7494,13 @@ MONO_RESTORE_WARNING
74947494
values [ins->dreg] = call_intrins (ctx, INTRINS_LOG, args, dname);
74957495
break;
74967496
}
7497+
case OP_LOGF: {
7498+
LLVMValueRef args [1];
7499+
7500+
args [0] = convert (ctx, lhs, LLVMFloatType ());
7501+
values [ins->dreg] = call_intrins (ctx, INTRINS_LOGF, args, dname);
7502+
break;
7503+
}
74977504
case OP_TRUNC: {
74987505
LLVMValueRef args [1];
74997506

@@ -7703,6 +7710,31 @@ MONO_RESTORE_WARNING
77037710
break;
77047711
}
77057712

7713+
case OP_FMINNUM:
7714+
case OP_FMAXNUM:
7715+
case OP_RMINNUM:
7716+
case OP_RMAXNUM: {
7717+
/*
7718+
* IEEE 754-2008 minNum/maxNum (NaN-suppressing). Maps directly to
7719+
* llvm.minnum/maxnum, which is what Math.MinNumber/MaxNumber (and the
7720+
* MathF.MinNumber/MaxNumber forwarders) specify. On AArch64 this
7721+
* lowers to a single fminnm/fmaxnm instruction.
7722+
*/
7723+
gboolean is_r4 = ins->opcode == OP_RMINNUM || ins->opcode == OP_RMAXNUM;
7724+
LLVMTypeRef t = is_r4 ? LLVMFloatType () : LLVMDoubleType ();
7725+
LLVMValueRef args [2] = { convert (ctx, lhs, t), convert (ctx, rhs, t) };
7726+
IntrinsicId iid;
7727+
switch (ins->opcode) {
7728+
case OP_FMAXNUM: iid = INTRINS_MAXNUM; break;
7729+
case OP_FMINNUM: iid = INTRINS_MINNUM; break;
7730+
case OP_RMAXNUM: iid = INTRINS_MAXNUMF; break;
7731+
case OP_RMINNUM: iid = INTRINS_MINNUMF; break;
7732+
default: g_assert_not_reached (); break;
7733+
}
7734+
values [ins->dreg] = call_intrins (ctx, iid, args, dname);
7735+
break;
7736+
}
7737+
77067738
/*
77077739
* See the ARM64 comment in mono/utils/atomic.h for an explanation of why this
77087740
* hack is necessary (for now).

src/mono/mono/mini/mini-ops.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,9 +684,13 @@ MINI_OP(OP_LMIN, "long_min", LREG, LREG, LREG)
684684
MINI_OP(OP_LMAX, "long_max", LREG, LREG, LREG)
685685
MINI_OP(OP_RMAX, "rmax", FREG, FREG, FREG)
686686
MINI_OP(OP_RMIN, "rmin", FREG, FREG, FREG)
687+
MINI_OP(OP_RMAXNUM, "rmaxnum", FREG, FREG, FREG)
688+
MINI_OP(OP_RMINNUM, "rminnum", FREG, FREG, FREG)
687689
MINI_OP(OP_RPOW, "rpow", FREG, FREG, FREG)
688690
MINI_OP(OP_FMAX, "fmax", FREG, FREG, FREG)
689691
MINI_OP(OP_FMIN, "fmin", FREG, FREG, FREG)
692+
MINI_OP(OP_FMAXNUM, "fmaxnum", FREG, FREG, FREG)
693+
MINI_OP(OP_FMINNUM, "fminnum", FREG, FREG, FREG)
690694
MINI_OP(OP_FPOW, "fpow", FREG, FREG, FREG)
691695
MINI_OP(OP_RCOPYSIGN,"rcopysign", FREG, FREG, FREG)
692696
MINI_OP(OP_FCOPYSIGN,"fcopysign", FREG, FREG, FREG)
@@ -740,6 +744,7 @@ MINI_OP(OP_LOG2, "log2", FREG, FREG, NONE)
740744
MINI_OP(OP_LOG2F, "log2f", FREG, FREG, NONE)
741745
MINI_OP(OP_LOG10, "log10", FREG, FREG, NONE)
742746
MINI_OP(OP_LOG10F, "log10f", FREG, FREG, NONE)
747+
MINI_OP(OP_LOGF, "logf", FREG, FREG, NONE)
743748
MINI_OP(OP_TRUNC, "trunc", FREG, FREG, NONE)
744749
MINI_OP(OP_TRUNCF, "truncf", FREG, FREG, NONE)
745750
MINI_OP(OP_ABSF, "absf", FREG, FREG, NONE)

0 commit comments

Comments
 (0)