diff --git a/src/mono/mono/mini/intrinsics.c b/src/mono/mono/mini/intrinsics.c index 4b8c9020084783..a9b62e8d36e3dd 100644 --- a/src/mono/mono/mini/intrinsics.c +++ b/src/mono/mono/mini/intrinsics.c @@ -102,7 +102,9 @@ llvm_emit_inst_for_method (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSign if (in_corlib && !strcmp (m_class_get_name (cmethod->klass), "MathF") && cfg->r4fp) { // (float) if (fsig->param_count == 1 && fsig->params [0]->type == MONO_TYPE_R4) { - if (!strcmp (cmethod->name, "Ceiling")) { + if (!strcmp (cmethod->name, "Abs")) { + opcode = OP_ABSF; + } else if (!strcmp (cmethod->name, "Ceiling")) { opcode = OP_CEILF; } else if (!strcmp (cmethod->name, "Cos")) { opcode = OP_COSF; @@ -110,6 +112,8 @@ llvm_emit_inst_for_method (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSign opcode = OP_EXPF; } else if (!strcmp (cmethod->name, "Floor")) { opcode = OP_FLOORF; + } else if (!strcmp (cmethod->name, "Log")) { + opcode = OP_LOGF; } else if (!strcmp (cmethod->name, "Log2")) { opcode = OP_LOG2F; } else if (!strcmp (cmethod->name, "Log10")) { @@ -285,6 +289,46 @@ llvm_emit_inst_for_method (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSign } } + if (in_corlib && (!strcmp (m_class_get_name (cmethod->klass), "Single") || !strcmp (m_class_get_name (cmethod->klass), "Double"))) { + // Recognize a handful of helpers on the primitive `Single` / `Double` types + // (forwarded from INumberBase / INumber). These don't have + // corresponding APIs on Math/MathF -- e.g. there is no `Math.MinNumber` -- + // so the existing Math/MathF block above doesn't catch them. + // + // * MinNumber / MaxNumber: IEEE 754-2008 minNum / maxNum (NaN-suppressing). + // Lower to llvm.minnum / llvm.maxnum, which on AArch64 maps to a single + // fminnm / fmaxnm instruction; matches the BCL spec "if either is NaN + // return the non-NaN; if both are NaN return NaN". + // * Abs: BCL forwarder to MathF.Abs / Math.Abs. Today this usually inlines + // into the Math/MathF recognition above, but adding direct recognition + // keeps the lowering working even if the JIT inliner declines. + opcode = 0; + if (fsig->param_count == 1 && + (fsig->params [0]->type == MONO_TYPE_R4 || fsig->params [0]->type == MONO_TYPE_R8)) { + gboolean is_r4 = fsig->params [0]->type == MONO_TYPE_R4; + if (!strcmp (cmethod->name, "Abs")) + opcode = is_r4 ? OP_ABSF : OP_ABS; + } + if (fsig->param_count == 2 && + fsig->params [0]->type == fsig->params [1]->type && + (fsig->params [0]->type == MONO_TYPE_R4 || fsig->params [0]->type == MONO_TYPE_R8)) { + gboolean is_r4 = fsig->params [0]->type == MONO_TYPE_R4; + if (!strcmp (cmethod->name, "MaxNumber")) + opcode = is_r4 ? OP_RMAXNUM : OP_FMAXNUM; + else if (!strcmp (cmethod->name, "MinNumber")) + opcode = is_r4 ? OP_RMINNUM : OP_FMINNUM; + } + if (opcode) { + MONO_INST_NEW (cfg, ins, opcode); + ins->type = STACK_R8; + ins->dreg = mono_alloc_dreg (cfg, (MonoStackType)ins->type); + ins->sreg1 = args [0]->dreg; + if (fsig->param_count > 1) + ins->sreg2 = args [1]->dreg; + MONO_ADD_INS (cfg->cbb, ins); + } + } + if (in_corlib && !strcmp (m_class_get_name (cmethod->klass), "SpanHelpers")) { 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) { MonoBasicBlock *end_bb; diff --git a/src/mono/mono/mini/llvm-intrinsics.h b/src/mono/mono/mini/llvm-intrinsics.h index a4f73dc8c823ad..0b00d40b1e2485 100644 --- a/src/mono/mono/mini/llvm-intrinsics.h +++ b/src/mono/mono/mini/llvm-intrinsics.h @@ -88,6 +88,7 @@ INTRINS_OVR(POW, pow, Generic, LLVMDoubleType ()) INTRINS_OVR(EXP, exp, Generic, LLVMDoubleType ()) INTRINS_OVR(EXPF, exp, Generic, LLVMFloatType ()) INTRINS_OVR(LOG, log, Generic, LLVMDoubleType ()) +INTRINS_OVR(LOGF, log, Generic, LLVMFloatType ()) INTRINS_OVR(LOG2, log2, Generic, LLVMDoubleType ()) INTRINS_OVR(LOG2F, log2, Generic, LLVMFloatType ()) INTRINS_OVR(LOG10, log10, Generic, LLVMDoubleType ()) @@ -96,6 +97,19 @@ INTRINS_OVR(TRUNC, trunc, Generic, LLVMDoubleType ()) INTRINS_OVR(TRUNCF, trunc, Generic, LLVMFloatType ()) INTRINS_OVR(COPYSIGN, copysign, Generic, LLVMDoubleType ()) INTRINS_OVR(COPYSIGNF, copysign, Generic, LLVMFloatType ()) + /* + * IEEE 754-2008 minNum/maxNum (NaN-suppressing). When exactly one operand + * is NaN they return the other; when both are NaN they return NaN. This is + * what `float.MinNumber` / `double.MinNumber` (and the Max variants, + * surfaced via INumber on the primitive Single/Double/Half types) + * are documented to do, and on AArch64 these lower to single fminnm/fmaxnm + * instructions. Use llvm.minimum/maximum (see above) for the NaN-propagating + * Math.Min/Math.Max instead. + */ +INTRINS_OVR(MINNUM, minnum, Generic, LLVMDoubleType ()) +INTRINS_OVR(MINNUMF, minnum, Generic, LLVMFloatType ()) +INTRINS_OVR(MAXNUM, maxnum, Generic, LLVMDoubleType ()) +INTRINS_OVR(MAXNUMF, maxnum, Generic, LLVMFloatType ()) INTRINS_OVR(EXPECT_I8, expect, Generic, LLVMInt8Type ()) INTRINS_OVR(EXPECT_I1, expect, Generic, LLVMInt1Type ()) INTRINS_OVR(CTPOP_I32, ctpop, Generic, LLVMInt32Type ()) diff --git a/src/mono/mono/mini/mini-llvm.c b/src/mono/mono/mini/mini-llvm.c index e68ce5ba8274b4..9e33b23de7c1c4 100644 --- a/src/mono/mono/mini/mini-llvm.c +++ b/src/mono/mono/mini/mini-llvm.c @@ -7494,6 +7494,13 @@ MONO_RESTORE_WARNING values [ins->dreg] = call_intrins (ctx, INTRINS_LOG, args, dname); break; } + case OP_LOGF: { + LLVMValueRef args [1]; + + args [0] = convert (ctx, lhs, LLVMFloatType ()); + values [ins->dreg] = call_intrins (ctx, INTRINS_LOGF, args, dname); + break; + } case OP_TRUNC: { LLVMValueRef args [1]; @@ -7703,6 +7710,32 @@ MONO_RESTORE_WARNING break; } + case OP_FMINNUM: + case OP_FMAXNUM: + case OP_RMINNUM: + case OP_RMAXNUM: { + /* + * IEEE 754-2008 minNum/maxNum (NaN-suppressing). Maps directly to + * llvm.minnum/maxnum, which is what `float.MinNumber` / + * `double.MinNumber` (and the Max variants, surfaced via + * INumber on the primitive Single/Double/Half types) specify. + * On AArch64 this lowers to a single fminnm/fmaxnm instruction. + */ + gboolean is_r4 = ins->opcode == OP_RMINNUM || ins->opcode == OP_RMAXNUM; + LLVMTypeRef t = is_r4 ? LLVMFloatType () : LLVMDoubleType (); + LLVMValueRef args [2] = { convert (ctx, lhs, t), convert (ctx, rhs, t) }; + IntrinsicId iid; + switch (ins->opcode) { + case OP_FMAXNUM: iid = INTRINS_MAXNUM; break; + case OP_FMINNUM: iid = INTRINS_MINNUM; break; + case OP_RMAXNUM: iid = INTRINS_MAXNUMF; break; + case OP_RMINNUM: iid = INTRINS_MINNUMF; break; + default: g_assert_not_reached (); break; + } + values [ins->dreg] = call_intrins (ctx, iid, args, dname); + break; + } + /* * See the ARM64 comment in mono/utils/atomic.h for an explanation of why this * hack is necessary (for now). diff --git a/src/mono/mono/mini/mini-ops.h b/src/mono/mono/mini/mini-ops.h index 1971c7286ec86c..84238042808ead 100644 --- a/src/mono/mono/mini/mini-ops.h +++ b/src/mono/mono/mini/mini-ops.h @@ -684,9 +684,13 @@ MINI_OP(OP_LMIN, "long_min", LREG, LREG, LREG) MINI_OP(OP_LMAX, "long_max", LREG, LREG, LREG) MINI_OP(OP_RMAX, "rmax", FREG, FREG, FREG) MINI_OP(OP_RMIN, "rmin", FREG, FREG, FREG) +MINI_OP(OP_RMAXNUM, "rmaxnum", FREG, FREG, FREG) +MINI_OP(OP_RMINNUM, "rminnum", FREG, FREG, FREG) MINI_OP(OP_RPOW, "rpow", FREG, FREG, FREG) MINI_OP(OP_FMAX, "fmax", FREG, FREG, FREG) MINI_OP(OP_FMIN, "fmin", FREG, FREG, FREG) +MINI_OP(OP_FMAXNUM, "fmaxnum", FREG, FREG, FREG) +MINI_OP(OP_FMINNUM, "fminnum", FREG, FREG, FREG) MINI_OP(OP_FPOW, "fpow", FREG, FREG, FREG) MINI_OP(OP_RCOPYSIGN,"rcopysign", FREG, FREG, FREG) MINI_OP(OP_FCOPYSIGN,"fcopysign", FREG, FREG, FREG) @@ -740,6 +744,7 @@ MINI_OP(OP_LOG2, "log2", FREG, FREG, NONE) MINI_OP(OP_LOG2F, "log2f", FREG, FREG, NONE) MINI_OP(OP_LOG10, "log10", FREG, FREG, NONE) MINI_OP(OP_LOG10F, "log10f", FREG, FREG, NONE) +MINI_OP(OP_LOGF, "logf", FREG, FREG, NONE) MINI_OP(OP_TRUNC, "trunc", FREG, FREG, NONE) MINI_OP(OP_TRUNCF, "truncf", FREG, FREG, NONE) MINI_OP(OP_ABSF, "absf", FREG, FREG, NONE)